using GLTFast;
using GLTFast.Loading;
using GLTFast.Logging;
using GLTFast.Materials;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class GltfLoader : GltfAsset
{
    //public delegate void FinishLoad();
    public static event Action OnFinishLoad;
    private NewDownloadProvider iDownloadProvider;
    public override async Task<bool> Load(
            string gltfUrl,
            IDownloadProvider downloadProvider = null,
            IDeferAgent deferAgent = null,
            IMaterialGenerator materialGenerator = null,
            ICodeLogger logger = null
            )
    {
        var success = false;

        if (Application.isPlaying)
        {
            success = await base.Load(gltfUrl, downloadProvider, deferAgent, materialGenerator, logger);
        }
        else
        {
            success = await base.Load(gltfUrl, downloadProvider, deferAgent, materialGenerator, logger);
        }

        //  OnFinishLoad?.Invoke();
        return success;
    }

    public async Task<bool> LoadWithCancelationToken(
        string gltfUrl,
        NewDownloadProvider downloadProvider = null,
        IDeferAgent deferAgent = null,
        IMaterialGenerator materialGenerator = null,
        ICodeLogger logger = null,
        CancellationToken token = default
        )
    {
        if (downloadProvider == null) downloadProvider = new NewDownloadProvider();

        if (deferAgent == null) deferAgent = new TimeBudgetPerFrameDeferAgent();

        iDownloadProvider = downloadProvider;

        var success = false;
        string CacheFile = GetCacheFile(gltfUrl);
        bool loadLocal = false;

        if (File.Exists(CacheFile))
        {
            // Check if a new file available
            bool needDownload = false;
            // Cache date
            var cacheInfo = new FileInfo(CacheFile);
            var cacheSize = cacheInfo.Length;
            var cacheDate = cacheInfo.LastWriteTimeUtc;
            DateTimeOffset cacheTime = new DateTimeOffset(cacheDate, TimeSpan.Zero);
            // Request date
            UnityWebRequest verifyRequest = UnityWebRequest.Head(gltfUrl);
            verifyRequest.timeout = 5;

            try
            {
                await verifyRequest.SendWebRequest();

                if (verifyRequest.result == UnityWebRequest.Result.Success)
                {
                    string lastModifiedHeader = verifyRequest.GetResponseHeader("Last-Modified");
                    string contentLengthHeader = verifyRequest.GetResponseHeader("Content-Length");

                    bool dateChanged = false;
                    bool sizeChanged = false;

                    if (DateTimeOffset.TryParse(
                            lastModifiedHeader,
                            CultureInfo.InvariantCulture,
                            DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                            out DateTimeOffset remoteTime))
                    {
                        dateChanged = cacheTime.UtcDateTime < remoteTime.UtcDateTime - TimeSpan.FromMinutes(10);
                    }

                    if (long.TryParse(contentLengthHeader, out long remoteSize))
                    {
                        sizeChanged = remoteSize != cacheSize;
                    }

                    needDownload = dateChanged || sizeChanged;
                }
                else
                {
                    Debug.LogError($"Unable to check remote link {gltfUrl}; Error: {verifyRequest.error}");
                }
            }
            catch (Exception ex)
            {
                Debug.LogError($"Unable to check remote link {gltfUrl}; Error: {ex}");
            }

            if (!needDownload)
            {
                loadLocal = true;
                Debug.Log("Load local file: " + CacheFile);
                try
                {
                    success = await LoadAsync(CacheFile, downloadProvider, deferAgent, materialGenerator, logger, token);
                }
                catch (Exception e)
                {
                    //File.Delete(CacheFile);
                }
            }
        }

        if (!loadLocal)
        {
            try
            {
                Debug.Log("Load : " + gltfUrl);
                success = await LoadAsync(gltfUrl, downloadProvider, deferAgent, materialGenerator, logger, token);
                if (success)
                {
                    DateTimeOffset sourceModifiedTime = DateTimeOffset.Parse(
                        downloadProvider.fileDownloadReq.ResponseHeaders["Last-Modified"],
                        CultureInfo.InvariantCulture,
                        DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);

                    if (!CacheFile.Contains(".gltf"))
                    {
                        SaveToCache(CacheFile, downloadProvider.glbData);
                        downloadProvider.glbData = null;
                    }
                    else
                    {
                        SaveGltfToCache(CacheFile, downloadProvider);
                    }
                    // temporarily disable
                    //gltf.Dispose();
                }
            }
            catch (OperationCanceledException ex)
            {
            }
            catch (Exception exc)
            {
                Debug.LogError($"Unable to check remote link {gltfUrl}; Error: {exc}");
            }
            finally
            {

            }
        }
        return success;
    }

    public async Task<bool> LoadAsync(
       string gltfUrl,
       IDownloadProvider downloadProvider = null,
       IDeferAgent deferAgent = null,
       IMaterialGenerator materialGenerator = null,
       ICodeLogger logger = null,
       CancellationToken token = default
       )
    {
        return await XenseAR.AsyncExtension<bool>.FreeMainWhileAwait(Load(gltfUrl, downloadProvider, deferAgent, materialGenerator, logger), token);
    }

    private void SaveGltfToCache(string CacheFile, NewDownloadProvider downloadProvider)
    {
        string DirectoryName = Path.GetDirectoryName(CacheFile);
        SaveAndClearGltfCache(DirectoryName, downloadProvider);
    }

    private string SaveAndClearGltfCache(string DirectoryFolder, NewDownloadProvider downloadProvider)
    {
        string archivePath = DirectoryFolder;

        if (!Directory.Exists(archivePath))
        {
            Directory.CreateDirectory(archivePath);
        }

        Dictionary<string, byte[]> Datas = downloadProvider.Datas;
        Dictionary<string, string> Texts = downloadProvider.Texts;

        foreach (var text in Texts)
        {
            if (text.Key.Contains(".gltf") || text.Key.Contains(".glb"))
            {
                string gltfPath = Path.Combine(archivePath, text.Key);

                JObject root = JObject.Parse(text.Value);

                JArray images = (JArray)root["images"];
                JArray buffers = (JArray)root["buffers"];

                string textureFilePath = "";
                string binFilePath = "";

                if (images != null)
                {
                    foreach (var image in images)
                    {
                        string uri = image.Value<string>("uri");

                        if (!string.IsNullOrEmpty(uri))
                        {
                            //if url is url, save path as file name
                            if (uri.StartsWith("http"))
                            {
                                string fileName = Path.GetFileName(uri.Split("?")[0]);
                                textureFilePath = fileName;
                                image["uri"] = fileName;
                            }
                            else
                            {
                                textureFilePath = uri;
                            }
                        }

                        string texturePath = Path.Combine(archivePath, textureFilePath);

                        //create directory chain if not exist
                        string directoryPath = Path.GetDirectoryName(texturePath);
                        if (!string.IsNullOrEmpty(directoryPath))
                        {
                            Directory.CreateDirectory(directoryPath);
                        }

                        //write byte file
                        if (Datas.ContainsKey(Path.GetFileName(texturePath)))
                        {
                            File.WriteAllBytes(texturePath, Datas[Path.GetFileName(texturePath)]);
                        }
                    }
                }

                if (buffers != null)
                {
                    foreach (var buffer in buffers)
                    {
                        string uri = buffer.Value<string>("uri");
                        if (!string.IsNullOrEmpty(uri))
                        {
                            if (uri.StartsWith("http"))
                            {
                                string fileName = Path.GetFileName(uri.Split("?")[0]);
                                binFilePath = fileName;
                                buffer["uri"] = fileName;
                            }
                            else
                            {
                                binFilePath = uri;
                            }
                        }

                        string binPath = Path.Combine(archivePath, binFilePath);

                        //create directory chain if not exist
                        string directoryPath = Path.GetDirectoryName(binPath);
                        if (string.IsNullOrEmpty(directoryPath))
                        {
                            Directory.CreateDirectory(directoryPath);
                        }

                        //write byte file
                        if (Datas.ContainsKey(Path.GetFileName(binPath)))
                        {
                            File.WriteAllBytes(binPath, Datas[Path.GetFileName(binPath)]);
                        }
                    }
                }

                string gltfData = root.ToString();

                File.WriteAllText(gltfPath, gltfData);
            }
        }
        downloadProvider.Datas.Clear();
        downloadProvider.Texts.Clear();
        return archivePath;
    }
    protected void SaveToCache(string CacheFile, byte[] data)
    {
        if (File.Exists(CacheFile))
        {
            File.Delete(CacheFile);
        }
        File.WriteAllBytes(CacheFile, data);
    }


    public string GetCacheFile(string url)
    {
        string CachePath = Application.persistentDataPath;

        CachePath = Path.Combine(CachePath, "AnchorCaches");
        if (!Directory.Exists(CachePath))
        {
            Directory.CreateDirectory(CachePath);
        }

        Uri uri = new Uri(url);
        string[] folderNames = uri.AbsolutePath.Split("/", StringSplitOptions.RemoveEmptyEntries);
        if (folderNames.Length == 0)
        {
            Debug.Log("Something wrong with the storage!!!");
            return CachePath;
        }
        else
        {
            for (int i = 0; i < folderNames.Length - 1; i++)
            {
                CachePath = Path.Combine(CachePath, folderNames[i]);
                if (!Directory.Exists(CachePath))
                {
                    Directory.CreateDirectory(CachePath);
                }
            }
        }
        string fileName = Path.GetFileName(url);
        string CacheFile = Path.Combine(CachePath, fileName);
        return CacheFile;
    }

    public float getDownloadProgress()
    {
        if (iDownloadProvider != null)
        {
            return iDownloadProvider.getFileDownloadProgress();
        }
        return 0f;
    }

}
