using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using System.Threading.Tasks;
using static BrainFailProductions.PolyFewRuntime.PolyfewRuntime;
#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// Asynchronous importer and loader
/// </summary>
namespace BrainFailProductions.PolyFew.AsImpL
{

    /// <summary>
    /// Abstract loader to be used as a base class for specific loaders.
    /// </summary>
    public abstract class Loader : MonoBehaviour
    {
        /// <summary>
        /// Total loading progress, for all the models currently loading.
        /// </summary>
        public static LoadingProgress totalProgress = new LoadingProgress();

        /// <summary>
        /// Options to define how the model will be loaded and imported.
        /// </summary>
        public ImportOptions buildOptions;


        public ReferencedNumeric<float> individualProgress = new ReferencedNumeric<float>(0);


#if UNITY_EDITOR
        /// <summary>
        /// Alternative texture path: if not null textures will be loaded from here.
        /// </summary>
        public string altTexPath = null;
#endif

        // raw subdivision in percentages of the loading phases (empirically computed loading a large sample OBJ file)
        // TODO: refine or change this method
        protected static float LOAD_PHASE_PERC = 8f;
        protected static float TEXTURE_PHASE_PERC = 1f;
        protected static float MATERIAL_PHASE_PERC = 1f;
        protected static float BUILD_PHASE_PERC = 90f;

        protected static Dictionary<string, GameObject> loadedModels = new Dictionary<string, GameObject>();
        protected static Dictionary<string, int> instanceCount = new Dictionary<string, int>();

        protected DataSet dataSet = new DataSet();
        protected ObjectBuilder objectBuilder = new ObjectBuilder();

        protected List<MaterialData> materialData;

        protected SingleLoadingProgress objLoadingProgress = new SingleLoadingProgress();

        protected Stats loadStats;

        private Texture2D loadedTexture = null;

        /// <summary>
        /// Load the file assuming its vertical axis is Z instead of Y 
        /// </summary>
        public bool ConvertVertAxis
        {
            get
            {
                return buildOptions != null ? buildOptions.zUp : false;
            }
            set
            {
                if (buildOptions == null)
                {
                    buildOptions = new ImportOptions();
                }
                buildOptions.zUp = value;
            }
        }


        /// <summary>
        /// Rescaling for the model (1 = no rescaling)
        /// </summary>
        public float Scaling
        {
            get
            {
                return buildOptions != null ? buildOptions.modelScaling : 1f;
            }
            set
            {
                if (buildOptions == null)
                {
                    buildOptions = new ImportOptions();
                }
                buildOptions.modelScaling = value;
            }
        }


        /// <summary>
        /// Check if a material library is defined for this model
        /// </summary>
        protected abstract bool HasMaterialLibrary { get; }

#if UNITY_EDITOR
        /// <summary>
        /// Import data as assets in the project (Editor only)
        /// </summary>
        public bool ImportingAssets { get { return !string.IsNullOrEmpty(altTexPath); } }
#endif

        /// <summary>
        /// Event triggered when an object is created.
        /// </summary>
        public event Action<GameObject, string> ModelCreated;

        /// <summary>
        /// Event triggered when an object is successfully loaded.
        /// </summary>
        public event Action<GameObject, string> ModelLoaded;

        /// <summary>
        /// Event triggered if failed to load an object
        /// </summary>
        public event Action<string> ModelError;


        /// <summary>
        /// Get a previusly loaded model by its absolute path
        /// </summary>
        /// <param name="absolutePath">absolute path used to load the model</param>
        /// <returns>The game object previously loaded</returns>
        public static GameObject GetModelByPath(string absolutePath)
        {
            if (loadedModels.ContainsKey(absolutePath))
            {
                return loadedModels[absolutePath];
            }
            return null;
        }


        /// <summary>
        /// Load a model.
        /// </summary>
        /// <param name="objName">name of the GameObject, if empty use file name</param>
        /// <param name="objAbsolutePath">absolute file path</param>
        /// <param name="parentObj">Transform to which attach the loaded object (null=scene)</param>
        /// <returns>You can use StartCoroutine( loader.Load(...) )</returns>
        public async Task<GameObject> Load(string objName, string objAbsolutePath, Transform parentObj, string texturesFolderPath = "", string materialsFolderPath = "")
        {
            string fileName = Path.GetFileName(objAbsolutePath);
            string fileNameNoExt = Path.GetFileNameWithoutExtension(objAbsolutePath);
            string name = objName;
            if (name == null || name == "") objName = fileNameNoExt;

            totalProgress.singleProgress.Add(objLoadingProgress);
            objLoadingProgress.fileName = fileName;
            objLoadingProgress.error = false;
            objLoadingProgress.message = "Loading " + fileName + "...";
            //Debug.LogFormat("Loading {0}\n  from: {1}...", objName, absolutePath);

            await Task.Yield();
            //yield return null;

            // TODO: implementation of a caching mechanism for models downloaded from an URL

            /*
            // if the model was already loaded duplicate the existing object
            if (buildOptions != null && buildOptions.reuseLoaded && loadedModels.ContainsKey(absolutePath) && loadedModels[absolutePath] != null)
            {
                Debug.LogFormat("File {0} already loaded, creating instance.", absolutePath);
                instanceCount[absolutePath]++;
                if (name == null || name == "") objName = objName + "_" + instanceCount[absolutePath];
                objLoadingProgress.message = "Instantiating " + objName + "...";
                while (loadedModels[absolutePath] == null)
                {
                    await Task.Yield();
                    //yield return null;
                }


                GameObject newObj = Instantiate(loadedModels[absolutePath]);
                //yield return newObj;
                OnCreated(newObj, absolutePath);
                newObj.name = objName;

                if (parentObj != null) newObj.transform.parent = parentObj.transform;
                totalProgress.singleProgress.Remove(objLoadingProgress);
                OnLoaded(newObj, absolutePath);

                return;

                //yield break;
            }
            */

            loadedModels[objAbsolutePath] = null; // define a key for the dictionary
            instanceCount[objAbsolutePath] = 0; // define a key for the dictionary

            float lastTime = Time.realtimeSinceStartup;
            float startTime = lastTime;

            await LoadModelFile(objAbsolutePath, texturesFolderPath, materialsFolderPath);

            loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime;

            if (objLoadingProgress.error)
            {
                OnLoadFailed(objAbsolutePath);
                return null;
            }

            lastTime = Time.realtimeSinceStartup;

            if (HasMaterialLibrary)
            {
                await LoadMaterialLibrary(objAbsolutePath, materialsFolderPath);
            }

            loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime;
            lastTime = Time.realtimeSinceStartup;

            await Build(objAbsolutePath, objName, parentObj, texturesFolderPath);

            loadStats.buildTime = Time.realtimeSinceStartup - lastTime;
            loadStats.totalTime = Time.realtimeSinceStartup - startTime;
            /*
            Debug.Log("Done: " + objName
                + "\n  Loaded in " + loadStats.totalTime + " seconds"
                + "\n  Model data parsed in " + loadStats.modelParseTime + " seconds"
                + "\n  Material data parsed in " + loadStats.materialsParseTime + " seconds"
                + "\n  Game objects built in " + loadStats.buildTime + " seconds"
                + "\n    textures: " + loadStats.buildStats.texturesTime + " seconds"
                + "\n    materials: " + loadStats.buildStats.materialsTime + " seconds"
                + "\n    objects: " + loadStats.buildStats.objectsTime + " seconds"
                );
            */
            totalProgress.singleProgress.Remove(objLoadingProgress);
            OnLoaded(loadedModels[objAbsolutePath], objAbsolutePath);

            return loadedModels[objAbsolutePath];
        }


        

        public async Task<GameObject> LoadFromNetwork(string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL, string materialURL, string objName)
        {
            string fileName = objName + ".obj";
#pragma warning disable
            string fileNameNoExt = objName;


            totalProgress.singleProgress.Add(objLoadingProgress);
            objLoadingProgress.fileName = fileName;
            objLoadingProgress.error = false;
            objLoadingProgress.message = "Loading " + fileName + "...";
            //Debug.LogFormat("Loading {0}\n  from: {1}...", objName, absolutePath);

            await Task.Yield();


            loadedModels[objURL] = null; // define a key for the dictionary
            instanceCount[objURL] = 0; // define a key for the dictionary

            float lastTime = Time.realtimeSinceStartup;
            float startTime = lastTime;

            // base model here
            try
            {
                await LoadModelFileNetworked(objURL);
            }

            catch (Exception ex)
            {
                throw ex;
            }


            loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime;

            if (objLoadingProgress.error)
            {
                OnLoadFailed(objURL);
                return null;
            }

            lastTime = Time.realtimeSinceStartup;

            if (HasMaterialLibrary)
            {
                // materials here
                try
                {
                    await LoadMaterialLibrary(materialURL);
                }

                catch(Exception ex)
                {
                    throw ex;
                }
            }

            else
            {
                ObjectImporter.activeDownloads -= 1;
            }


            loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime;
            lastTime = Time.realtimeSinceStartup;

            try
            {
                await NetworkedBuild(null, objName, objURL, diffuseTexURL, bumpTexURL, specularTexURL, opacityTexURL);
            }

            catch (Exception ex)
            {
                throw ex;
            }


            loadStats.buildTime = Time.realtimeSinceStartup - lastTime;
            loadStats.totalTime = Time.realtimeSinceStartup - startTime;
            /*
            Debug.Log("Done: " + objName
                + "\n  Loaded in " + loadStats.totalTime + " seconds"
                + "\n  Model data parsed in " + loadStats.modelParseTime + " seconds"
                + "\n  Material data parsed in " + loadStats.materialsParseTime + " seconds"
                + "\n  Game objects built in " + loadStats.buildTime + " seconds"
                + "\n    textures: " + loadStats.buildStats.texturesTime + " seconds"
                + "\n    materials: " + loadStats.buildStats.materialsTime + " seconds"
                + "\n    objects: " + loadStats.buildStats.objectsTime + " seconds"
                );
            */
            totalProgress.singleProgress.Remove(objLoadingProgress);
            OnLoaded(loadedModels[objURL], objURL);

            return loadedModels[objURL];
        }





        public IEnumerator LoadFromNetworkWebGL(string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL, string materialURL, string objName, Action<GameObject> OnSuccess, Action<Exception> OnError)
        {
            string fileName = objName + ".obj";
#pragma warning disable
            string fileNameNoExt = objName;


            totalProgress.singleProgress.Add(objLoadingProgress);
            objLoadingProgress.fileName = fileName;
            objLoadingProgress.error = false;
            objLoadingProgress.message = "Loading " + fileName + "...";
            //Debug.LogFormat("Loading {0}\n  from: {1}...", objName, absolutePath);


            loadedModels[objURL] = null; // define a key for the dictionary
            instanceCount[objURL] = 0; // define a key for the dictionary

            float lastTime = Time.realtimeSinceStartup;
            float startTime = lastTime;

            // base model here
            
            yield return StartCoroutine(LoadModelFileNetworkedWebGL(objURL, OnError));
            
            if(ObjectImporter.isException) { yield return null; }

            loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime;

            if (objLoadingProgress.error)
            {
                OnLoadFailed(objURL);
                OnError(new Exception("Load failed due to unknown reasons."));
                yield return null;
            }

            lastTime = Time.realtimeSinceStartup;

            if (HasMaterialLibrary)
            {
                // materials here
                yield return StartCoroutine(LoadMaterialLibraryWebGL(materialURL));
            }

            else
            {
                ObjectImporter.activeDownloads -= 1;
            }
            
            if (ObjectImporter.isException) { yield return null; }


            loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime;
            lastTime = Time.realtimeSinceStartup;


            yield return StartCoroutine(NetworkedBuildWebGL(null, objName, objURL, diffuseTexURL, bumpTexURL, specularTexURL, opacityTexURL));

            if (ObjectImporter.isException) { yield return null; }


            loadStats.buildTime = Time.realtimeSinceStartup - lastTime;
            loadStats.totalTime = Time.realtimeSinceStartup - startTime;
            /*
            Debug.Log("Done: " + objName
                + "\n  Loaded in " + loadStats.totalTime + " seconds"
                + "\n  Model data parsed in " + loadStats.modelParseTime + " seconds"
                + "\n  Material data parsed in " + loadStats.materialsParseTime + " seconds"
                + "\n  Game objects built in " + loadStats.buildTime + " seconds"
                + "\n    textures: " + loadStats.buildStats.texturesTime + " seconds"
                + "\n    materials: " + loadStats.buildStats.materialsTime + " seconds"
                + "\n    objects: " + loadStats.buildStats.objectsTime + " seconds"
                );
            */
            totalProgress.singleProgress.Remove(objLoadingProgress);
            OnLoaded(loadedModels[objURL], objURL);

            OnSuccess(loadedModels[objURL]);
        }



        /// <summary>
        /// Parse the model to get a list of the paths of all used textures
        /// </summary>
        /// <param name="absolutePath">absolute path of the model</param>
        /// <returns>List of paths of the textures referenced by the model</returns>
        public abstract string[] ParseTexturePaths(string absolutePath);

        /// <summary>
        /// Load the main model file
        /// </summary>
        /// <param name="absolutePath">absolute file path</param>
        /// <remarks>This is called by Load() method</remarks>
        protected abstract Task LoadModelFile(string absolutePath, string texturesFolderPath = "", string materialsFolderPath = "");



        protected abstract Task LoadModelFileNetworked(string objURL);

        protected abstract IEnumerator LoadModelFileNetworkedWebGL(string objURL, Action<Exception> OnError);

        /// <summary>
        /// Load the material library from the given path.
        /// </summary>
        /// <param name="absolutePath">absolute file path</param>
        /// <remarks>This is called by Load() method</remarks>
        protected abstract Task LoadMaterialLibrary(string absolutePath, string materialsFolderPath = "");

        
        protected abstract Task LoadMaterialLibrary(string materialURL);

        protected abstract IEnumerator LoadMaterialLibraryWebGL(string materialURL);

        /// <summary>
        /// Build the game objects from data set, materials and textures.
        /// </summary>
        /// <param name="absolutePath">absolute file path</param>
        /// <param name="objName">Name of the main game object (model root)</param>
        /// <param name="parentTransform">transform to which the model root will be attached (if null it will be a root aobject)</param>
        /// <remarks>This is called by Load() method</remarks>
        protected async Task Build(string absolutePath, string objName, Transform parentTransform, string texturesFolderPath = "")
        {
            float prevTime = Time.realtimeSinceStartup;
            if (materialData != null)
            {
                //string basePath = GetDirName(absolutePath);
                string basePath = Path.GetDirectoryName(absolutePath);

                objLoadingProgress.message = "Loading textures...";
                int count = 0;
                foreach (MaterialData mtl in materialData)
                {
                    objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count;
                    count++;
                    if (mtl.diffuseTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath);
                        }
                        else
#endif
                        {

                            await LoadMaterialTexture(basePath, mtl.diffuseTexPath, texturesFolderPath);
                            
                            mtl.diffuseTex = loadedTexture;
                        }
                    }

                    if (mtl.bumpTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath);
                        }
                        else
#endif
                        {
                            await LoadMaterialTexture(basePath, mtl.bumpTexPath, texturesFolderPath);
                            
                            mtl.bumpTex = loadedTexture;
                        }
                    }

                    if (mtl.specularTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.specularTex = LoadAssetTexture(mtl.specularTexPath);
                        }
                        else
#endif
                        {
                            await LoadMaterialTexture(basePath, mtl.specularTexPath, texturesFolderPath);
                            
                            mtl.specularTex = loadedTexture;
                        }
                    }

                    if (mtl.opacityTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath);
                        }
                        else
#endif
                        {                          
                            await LoadMaterialTexture(basePath, mtl.opacityTexPath, texturesFolderPath);
                            
                            mtl.opacityTex = loadedTexture;
                        }
                    }
                }
            }
            loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime;
            prevTime = Time.realtimeSinceStartup;

            ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo();

            objLoadingProgress.message = "Loading materials...";

            //yield return null;

#if UNITY_EDITOR
            objectBuilder.alternativeTexPath = altTexPath;
#endif
            objectBuilder.buildOptions = buildOptions;
            bool hasColors = dataSet.colorList.Count > 0;
            bool hasMaterials = materialData != null;
            objectBuilder.InitBuildMaterials(materialData, hasColors);
            float objInitPerc = objLoadingProgress.percentage;
            if (hasMaterials)
            {
                while (objectBuilder.BuildMaterials(info))
                {
                    objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count;
                }
                loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime;
                prevTime = Time.realtimeSinceStartup;
            }

            objLoadingProgress.message = "Building scene objects...";

            GameObject newObj = new GameObject(objName);

            if (buildOptions.hideWhileLoading)
            {
                newObj.SetActive(false);
            }

            if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false);
            OnCreated(newObj, absolutePath);
            ////newObj.transform.localScale = Vector3.one * Scaling;
            float initProgress = objLoadingProgress.percentage;
            objectBuilder.StartBuildObjectAsync(dataSet, newObj);

            while (objectBuilder.BuildObjectAsync(ref info))
            {
                objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups);
                objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups);
            }

            objLoadingProgress.percentage = 100.0f;
            loadedModels[absolutePath] = newObj;
            loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime;

        }




        protected async Task NetworkedBuild(Transform parentTransform, string objName, string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL)
        {
            float prevTime = Time.realtimeSinceStartup;
            if (materialData != null)
            {

                objLoadingProgress.message = "Loading textures...";
                int count = 0;
                foreach (MaterialData mtl in materialData)
                {
                    objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count;
                    count++;
                    if (mtl.diffuseTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath);
                        }
                        else
#endif
                        {
                            if(!string.IsNullOrWhiteSpace(diffuseTexURL))
                            {
                                try
                                {
                                    await LoadMaterialTexture(diffuseTexURL);
                                }
                                catch (Exception exc)
                                {
                                    throw exc;
                                }
                            }

                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }
                            mtl.diffuseTex = loadedTexture;

                        }
                    }

                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.bumpTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath);
                        }
                        else
#endif
                        {

                            if (!string.IsNullOrWhiteSpace(bumpTexURL))
                            {
                                try
                                {
                                    await LoadMaterialTexture(bumpTexURL);
                                }
                                catch (Exception exc)
                                {
                                    throw exc;
                                }
                            }

                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.bumpTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.specularTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.specularTex = LoadAssetTexture(mtl.specularTexPath);
                        }
                        else
#endif
                        {
                            if (!string.IsNullOrWhiteSpace(specularTexURL))
                            {
                                try
                                {
                                    await LoadMaterialTexture(specularTexURL);
                                }
                                catch (Exception exc)
                                {
                                    throw exc;
                                }
                            }
                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.specularTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.opacityTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath);
                        }
                        else
#endif
                        {
                            if (!string.IsNullOrWhiteSpace(opacityTexURL))
                            {
                                try
                                {
                                    await LoadMaterialTexture(opacityTexURL);
                                }
                                catch (Exception exc)
                                {
                                    throw exc;
                                }
                            }
                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.opacityTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;

                }
            }
            loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime;
            prevTime = Time.realtimeSinceStartup;

            ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo();

            objLoadingProgress.message = "Loading materials...";

            //yield return null;

#if UNITY_EDITOR
            objectBuilder.alternativeTexPath = altTexPath;
#endif
            objectBuilder.buildOptions = buildOptions;
            bool hasColors = dataSet.colorList.Count > 0;
            bool hasMaterials = materialData != null;
            objectBuilder.InitBuildMaterials(materialData, hasColors);
            float objInitPerc = objLoadingProgress.percentage;
            if (hasMaterials)
            {
                while (objectBuilder.BuildMaterials(info))
                {
                    objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count;
                    await Task.Delay(0);
                }
                loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime;
                prevTime = Time.realtimeSinceStartup;
            }

            objLoadingProgress.message = "Building scene objects...";

            GameObject newObj = new GameObject(objName);

            if (buildOptions.hideWhileLoading)
            {
                newObj.SetActive(false);
            }

            if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false);
            OnCreated(newObj, objURL);
            ////newObj.transform.localScale = Vector3.one * Scaling;
            float initProgress = objLoadingProgress.percentage;
            objectBuilder.StartBuildObjectAsync(dataSet, newObj);

            while (objectBuilder.BuildObjectAsync(ref info))
            {
                objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups);
                objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups);
                await Task.Delay(0);
            }

            objLoadingProgress.percentage = 100.0f;
            loadedModels[objURL] = newObj;
            loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime;

        }



        
        protected IEnumerator NetworkedBuildWebGL(Transform parentTransform, string objName, string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL)
        {
            float prevTime = Time.realtimeSinceStartup;

            if (materialData != null)
            {

                objLoadingProgress.message = "Loading textures...";
                int count = 0;
                foreach (MaterialData mtl in materialData)
                {
                    objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count;
                    count++;
                    if (mtl.diffuseTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath);
                        }
                        else
#endif
                        {
                            if (!string.IsNullOrWhiteSpace(diffuseTexURL))
                            {
                                yield return StartCoroutine(LoadMaterialTextureWebGL(diffuseTexURL));
                            }

                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }
                            mtl.diffuseTex = loadedTexture;

                        }
                    }

                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.bumpTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath);
                        }
                        else
#endif
                        {

                            if (!string.IsNullOrWhiteSpace(bumpTexURL))
                            {
                                yield return StartCoroutine(LoadMaterialTextureWebGL(bumpTexURL));
                            }

                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.bumpTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.specularTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.specularTex = LoadAssetTexture(mtl.specularTexPath);
                        }
                        else
#endif
                        {
                            if (!string.IsNullOrWhiteSpace(specularTexURL))
                            {
                                yield return StartCoroutine(LoadMaterialTextureWebGL(specularTexURL));
                            }
                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.specularTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;



                    if (mtl.opacityTexPath != null)
                    {
#if UNITY_EDITOR
                        if (ImportingAssets)
                        {
                            mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath);
                        }
                        else
#endif
                        {
                            if (!string.IsNullOrWhiteSpace(opacityTexURL))
                            {
                                yield return StartCoroutine(LoadMaterialTextureWebGL(opacityTexURL));
                            }
                            else
                            {
                                ObjectImporter.activeDownloads -= 1;
                            }

                            mtl.opacityTex = loadedTexture;
                        }
                    }
                    else
                    {
                        ObjectImporter.activeDownloads -= 1;
                    }

                    ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;

                }
            }
            loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime;
            prevTime = Time.realtimeSinceStartup;

            ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo();

            objLoadingProgress.message = "Loading materials...";

            //yield return null;

#if UNITY_EDITOR
            objectBuilder.alternativeTexPath = altTexPath;
#endif
            objectBuilder.buildOptions = buildOptions;
            bool hasColors = dataSet.colorList.Count > 0;
            bool hasMaterials = materialData != null;
            objectBuilder.InitBuildMaterials(materialData, hasColors);
            float objInitPerc = objLoadingProgress.percentage;
            if (hasMaterials)
            {
                while (objectBuilder.BuildMaterials(info))
                {
                    objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count;
                }
                loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime;
                prevTime = Time.realtimeSinceStartup;
            }

            objLoadingProgress.message = "Building scene objects...";

            GameObject newObj = new GameObject(objName);

            if (buildOptions.hideWhileLoading)
            {
                newObj.SetActive(false);
            }

            if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false);
            OnCreated(newObj, objURL);
            ////newObj.transform.localScale = Vector3.one * Scaling;
            float initProgress = objLoadingProgress.percentage;
            objectBuilder.StartBuildObjectAsync(dataSet, newObj);

            while (objectBuilder.BuildObjectAsync(ref info))
            {
                objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups);
                objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups);
            }

            objLoadingProgress.percentage = 100.0f;
            loadedModels[objURL] = newObj;
            loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime;

        }


        /// <summary>
        /// Get the directory name of the given path, appending the final slash if eeded.
        /// </summary>
        /// <param name="absolutePath">the absolute path</param>
        /// <returns>the directory name ending with `/`</returns>
        protected string GetDirName(string absolutePath)
        {
            string basePath;
            if (absolutePath.Contains("//"))
            {
                basePath = absolutePath.Remove(absolutePath.LastIndexOf('/') + 1);
            }
            else
            {
                string dirName = Path.GetDirectoryName(absolutePath);
                basePath = string.IsNullOrEmpty(dirName) ? "" : dirName;
                if (!basePath.EndsWith("/"))
                {
                    basePath += "/";
                }
            }
            return basePath;
        }


        protected virtual void OnLoaded(GameObject obj, string absolutePath)
        {
            if (obj == null)
            {
                if (ModelError != null)
                {
                    ModelError(absolutePath);
                }
            }
            else
            {
                if (buildOptions != null)
                {
                    obj.transform.localPosition = buildOptions.localPosition;
                    obj.transform.localRotation = Quaternion.Euler(buildOptions.localEulerAngles); ;
                    obj.transform.localScale = buildOptions.localScale;
                    if (buildOptions.inheritLayer)
                    {
                        obj.layer = obj.transform.parent.gameObject.layer;
                        MeshRenderer[] mrs = obj.transform.GetComponentsInChildren<MeshRenderer>(true);
                        for (int i = 0; i < mrs.Length; i++)
                        {
                            mrs[i].gameObject.layer = obj.transform.parent.gameObject.layer;
                        }
                    }
                }
                if (buildOptions.hideWhileLoading)
                {
                    obj.SetActive(true);
                }

                if (ModelLoaded != null)
                {
                    ModelLoaded(obj, absolutePath);
                }
            }
        }


        protected virtual void OnCreated(GameObject obj, string absolutePath)
        {
            if (obj == null)
            {
                if (ModelError != null)
                {
                    ModelError(absolutePath);
                }
            }
            else
            {
                if (ModelCreated != null)
                {
                    ModelCreated(obj, absolutePath);
                }
            }
        }


        protected virtual void OnLoadFailed(string absolutePath)
        {
            if (ModelError != null)
            {
                ModelError(absolutePath);
            }
        }


#if UNITY_EDITOR
        /// <summary>
        /// Load a texture from the asset database
        /// </summary>
        /// <param name="texturePath">texture path inside the asset database</param>
        /// <returns>the loaded texture or null on error</returns>
        private Texture2D LoadAssetTexture(string texturePath)
        {
            FileInfo textFileInfo = new FileInfo(texturePath);
            string texpath = altTexPath + textFileInfo.Name;
            texpath = texpath.Replace("//", "/");
            Debug.LogFormat("Loading texture asset '{0}'", texpath);
            return AssetDatabase.LoadAssetAtPath<Texture2D>(texpath);
        }
#endif


        /// <summary>
        /// Convert a texture path to a texture URL and update the progress message
        /// </summary>
        /// <param name="basePath">base texture path</param>
        /// <param name="texturePath">relative texture path</param>
        /// <returns>URL of the texture</returns>
        private string GetTextureUrl(string basePath, string texturePath)
        {
            string texPath = texturePath.Replace("\\", "/").Replace("//", "/");
            if (!Path.IsPathRooted(texPath))
            {
                texPath = basePath + texturePath;
            }
            if (!texPath.Contains("//"))
            {
                texPath = "file:///" + texPath;
            }
            objLoadingProgress.message = "Loading textures...\n" + texPath;
            return texPath;
        }

        private async Task LoadMaterialTexture(string basePath, string path, string texturesFolderPath="")
        {
            loadedTexture = null;
            //string texPath = GetTextureUrl(basePath, path);
            string textPath = string.IsNullOrWhiteSpace(texturesFolderPath) ? basePath + path : (texturesFolderPath + "\\" + path);

            textPath = Path.GetFullPath(textPath);

            //WWW loader = new WWW(texPath);
            //yield return loader;


            if (File.Exists(textPath))
            {

                byte[] result;

                using (FileStream stream = File.Open(textPath, FileMode.Open))
                {
                    result = new byte[stream.Length];
                    await stream.ReadAsync(result, 0, (int)stream.Length);
                }

                if (result.Length > 0)
                {
                    Texture2D tex = new Texture2D(1, 1);
                    tex.LoadImage(result);
                    loadedTexture = tex;
                }
            }

            else
            {
                Debug.LogWarning("Failed to load texture at path  " + textPath + "   BasePath  " + basePath + "  path  " + path);
            }


        }



        private async Task LoadMaterialTexture(string textureURL)
        {
            loadedTexture = null;

            bool isWorking = true;
            byte[] downloadedBytes = null;
            float oldProgress = individualProgress.Value;


            try
            {
                StartCoroutine(DownloadFile(textureURL, individualProgress, (bytes) =>
                {
                    isWorking = false;
                    downloadedBytes = bytes;
                    //loadedText = Encoding.UTF8.GetString(bytes);
                },
                (error) =>
                {
                    ObjectImporter.activeDownloads -= 1;
                    isWorking = false;
                    Debug.LogWarning("Failed to load the associated texture file." + error);
                }));

            }

            catch (Exception exc)
            {
                ObjectImporter.activeDownloads -= 1;
                individualProgress.Value = oldProgress;
                ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;
                isWorking = false;
                throw exc;
            }


            while (isWorking)
            {
                ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;
                await Task.Delay(3);
            }

            ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;

            if (downloadedBytes != null && downloadedBytes.Length > 0)
            {
                Texture2D tex = new Texture2D(1, 1);
                tex.LoadImage(downloadedBytes);
                loadedTexture = tex;
            }
            

            else
            {
                Debug.LogWarning("Failed to load texture.");
            }


        }





        private IEnumerator LoadMaterialTextureWebGL(string textureURL)
        {
            loadedTexture = null;

            bool isWorking = true;
            float oldProgress = individualProgress.Value;


            try
            {
                StartCoroutine(DownloadTexFileWebGL(textureURL, individualProgress, (texture) =>
                {
                    isWorking = false;
                    loadedTexture = texture;
                    //loadedText = Encoding.UTF8.GetString(bytes);
                },
                (error) =>
                {
                    ObjectImporter.activeDownloads -= 1;
                    isWorking = false;
                    Debug.LogWarning("Failed to load the associated texture file." + error);
                }));

            }

            catch (Exception exc)
            {
                ObjectImporter.activeDownloads -= 1;
                individualProgress.Value = oldProgress;
                ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;
                isWorking = false;
                throw exc;
            }


            while (isWorking)
            {
                yield return new WaitForSeconds(0.1f);
                ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;
            }

            ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f;


            if (loadedTexture == null)
            {
                Debug.LogWarning("Failed to load texture.");
            }


        }


        /// <summary>
        /// Load a texture from the URL got from the parameter.
        /// </summary>
#if UNITY_2018_3_OR_NEWER
        private Texture2D LoadTexture(UnityWebRequest loader)
#else
        private Texture2D LoadTexture(WWW loader)
#endif
        {
            string ext = Path.GetExtension(loader.url).ToLower();
            Texture2D tex = null;

            // TODO: add support for more formats (bmp, gif, dds, ...)
            if (ext == ".tga")
            {
                tex = TextureLoader.LoadTextureFromUrl(loader.url);
                //tex = TgaLoader.LoadTGA(new MemoryStream(loader.bytes));
            }
            else if (ext == ".png" || ext == ".jpg" || ext == ".jpeg")
            {
#if UNITY_2018_3_OR_NEWER
                tex = DownloadHandlerTexture.GetContent(loader);
#else
                tex = loader.texture;
#endif
            }
            else
            {
                Debug.LogWarning("Unsupported texture format: " + ext);
            }

            if (tex == null)
            {
                Debug.LogErrorFormat("Failed to load texture {0}", loader.url);
            }
            else
            {
                //tex.alphaIsTransparency = true;
                //tex.filterMode = FilterMode.Trilinear;
            }

            return tex;
        }


        protected struct BuildStats
        {
            public float texturesTime;
            public float materialsTime;
            public float objectsTime;
        }


        protected struct Stats
        {
            public float modelParseTime;
            public float materialsParseTime;
            public float buildTime;
            public BuildStats buildStats;
            public float totalTime;
        }



        public IEnumerator DownloadFile(string url, ReferencedNumeric<float> downloadProgress, Action<byte[]> DownloadComplete, Action<string> OnError)
        {
            //Debug.Log("Into the DownloadFile Coroutine");
            WWW www = null;
            float oldProgress = downloadProgress.Value;

            try
            {
                www = new WWW(url);
            }

            catch (Exception ex)
            {
                downloadProgress.Value = oldProgress;
                OnError(ex.ToString());
            }


            Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress));

            yield return www;

            if (!string.IsNullOrWhiteSpace(www.error))
            {
                downloadProgress.Value = oldProgress;
                OnError(www.error);
            }

            else
            {
                if (www.bytes == null || www.bytes.Length == 0)
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {
                        downloadProgress.Value = oldProgress;
                        OnError("No bytes downloaded. The file might be empty.");
                    }

                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }

                else
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {
                        //Debug.Log("Calling Download Complete");

                        downloadProgress.Value = oldProgress + 1;
                        DownloadComplete(www.bytes);
                    }
                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }


            }

            StopCoroutine(progress);

            www.Dispose();
            //Debug.Log("End of File Download Coroutine.");

        }



        private IEnumerator GetProgress(WWW www, ReferencedNumeric<float> downloadProgress)
        {
            float oldProgress = downloadProgress.Value;

            if (www != null && downloadProgress != null)
            {
                while (!www.isDone && string.IsNullOrWhiteSpace(www.error))
                {
                    yield return new WaitForSeconds(0.1f);
                    downloadProgress.Value = oldProgress + www.progress;
                }

                if(www.isDone && string.IsNullOrWhiteSpace(www.error))
                {
                    downloadProgress.Value = oldProgress + www.progress;
                    Debug.Log("Progress  " + www.progress);
                }

            }

        }



        public IEnumerator DownloadFileWebGL(string url, ReferencedNumeric<float> downloadProgress, Action<string> DownloadComplete, Action<string> OnError)
        {
            WWW www = null;
            float oldProgress = downloadProgress.Value;

            try
            {
                www = new WWW(url);
            }

            catch (Exception ex)
            {
                downloadProgress.Value = oldProgress;
                OnError(ex.ToString());
            }


            Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress));

            yield return www;

            if (!string.IsNullOrWhiteSpace(www.error))
            {
                downloadProgress.Value = oldProgress;
                OnError(www.error);
            }

            else
            {
                if (www.bytes == null || www.bytes.Length == 0)
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {
                        downloadProgress.Value = oldProgress;
                        OnError("No bytes downloaded. The file might be empty.");
                    }

                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }

                else
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {
                        downloadProgress.Value = oldProgress + 1;
                        DownloadComplete(www.text);
                    }
                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }


            }

            try
            {
                StopCoroutine(progress);
            }

            catch (Exception ex)
            {

            }

            www.Dispose();

        }



        public IEnumerator DownloadTexFileWebGL(string url, ReferencedNumeric<float> downloadProgress, Action<Texture2D> DownloadComplete, Action<string> OnError)
        {
            WWW www = null;
            float oldProgress = downloadProgress.Value;

            try
            {
                www = new WWW(url);
            }

            catch (Exception ex)
            {
                downloadProgress.Value = oldProgress;
                OnError(ex.ToString());
            }


            Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress));

            yield return www;

            if (!string.IsNullOrWhiteSpace(www.error))
            {
                downloadProgress.Value = oldProgress;
                OnError(www.error);
            }

            else
            {
                if (www.bytes == null || www.bytes.Length == 0)
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {
                        downloadProgress.Value = oldProgress;
                        OnError("No bytes downloaded. The file might be empty.");
                    }

                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }

                else
                {
                    if (string.IsNullOrWhiteSpace(www.error))
                    {

                        downloadProgress.Value = oldProgress + 1;
                        DownloadComplete(www.texture);
                    }
                    else
                    {
                        downloadProgress.Value = oldProgress;
                        OnError(www.error);
                    }
                }


            }

            StopCoroutine(progress);

            www.Dispose();
        }

        
    }
}