using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using System.Collections.Generic;
using System.Linq;

namespace GLTFast.Loading
{
    public class NewDownloadProvider : IDownloadProvider
    {
        public NewAwaitableDownload fileDownloadReq = new NewAwaitableDownload();
        public NewAwaitableTextureDownload textureDownloadReq = new NewAwaitableTextureDownload();

        public LoadFileLocal fileLoadReq = new LoadFileLocal();
        public LoadTextureLocal textureLoadReq = new LoadTextureLocal();
        private bool IsLocalFile;

        public Dictionary<string, byte[]> Datas = new Dictionary<string, byte[]>();
        public Dictionary<string, string> Texts = new Dictionary<string, string>();
        public byte[] glbData;

        public async Task<IDownload> Request(Uri url)
        {
            IsLocalFile = url.IsFile;
            if (IsLocalFile)
            {
                var fileReq = new LoadFileLocal(url);
                fileLoadReq = fileReq;
                return fileReq;
            }

            var req = new NewAwaitableDownload(url);
            fileDownloadReq = req;
            await req.WaitAsync();

            if (req.Success)
            {
                GetDatasByType(url, req);
            }

            return req;
        }

        public void Clear()
        {
            Datas.Clear();
            Texts.Clear();
            glbData = null;
        }

        private void GetDatasByType(Uri url, NewAwaitableDownload req)
        {
            string filename = Path.GetFileName(url.AbsolutePath);
            string extension = Path.GetExtension(url.AbsolutePath);
            if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".ktx2")
            {
                if (Datas.ContainsKey(filename))
                {
                    Datas[filename] = req.Data;
                }
                else
                {
                    Datas.Add(filename, req.Data);
                }
            }
            else if (extension == ".gltf")
            {
                if (Texts.ContainsKey(filename))
                {
                    Texts[filename] = req.Text;
                }
                else
                {
                    Texts.Add(filename, req.Text);
                }
            }
            else if (extension == ".bin")
            {
                if (Datas.ContainsKey(filename))
                {
                    Datas[filename] = req.Data;
                }
                else
                {
                    Datas.Add(filename, req.Data);
                }
            }
            else if (extension == ".glb")
            {
                if (Datas.ContainsKey(filename))
                {
                    Datas[filename] = req.Data;
                }
                else
                {
                    Datas.Add(filename, req.Data);
                }

                glbData = req.Data;
            }
        }

        public async Task<ITextureDownload> RequestTexture(Uri url, bool nonReadable)
        {
            IsLocalFile = url.IsFile;
            if (IsLocalFile)
            {
                var fileReq = new LoadTextureLocal(url, nonReadable);
                textureLoadReq = fileReq;
                return fileReq;
            }

            var req = new NewAwaitableTextureDownload(url, nonReadable);
            textureDownloadReq = req;
            await req.WaitAsync();

            if (req.Success)
            {
                string filename = Path.GetFileName(url.AbsolutePath);
                string extension = Path.GetExtension(url.AbsolutePath);
                if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".ktx2")
                {
                    if (Datas.ContainsKey(filename))
                    {
                        Datas[filename] = req.Data;
                    }
                    else
                    {
                        Datas.Add(filename, req.Data);
                    }
                }
            }

            return req;
        }

        public float getFileDownloadProgress()
        {
            if (IsLocalFile)
                return fileLoadReq.getProgress();
            else
                return fileDownloadReq != null ? fileDownloadReq.getProgress() : 0f;
        }

        public float getTextureDownloadProgress()
        {
            return textureDownloadReq.getProgress();
        }

        public void cancelDownload()
        {
            fileDownloadReq.cancelRequest();
        }
    }

    public class NewAwaitableDownload : IDownload
    {
        const string GLB_MIME = "model/gltf-binary";
        const string GLTF_MIME = "model/gltf+json";

        protected UnityWebRequest request;
        protected UnityWebRequestAsyncOperation asynOperation;

        public float getProgress()
        {
            if (request != null)
                return request.downloadProgress;
            else
                return 0;
        }

        public void cancelRequest()
        {
            if (request != null && !request.isDone)
                request.Abort();
        }


        public NewAwaitableDownload() { }

        public NewAwaitableDownload(Uri url)
        {
            Init(url);
        }

        public void Init(Uri url)
        {
            request = UnityWebRequest.Get(url);
           // request.certificateHandler = new AcceptAllCertificatesSignedWithASpecificKeyPublicKey();
            asynOperation = request.SendWebRequest();
        }

        public async Task WaitAsync()
        {
            while (!asynOperation.isDone)
            {
                await Task.Yield();
            }
        }

        public object Current { get { return asynOperation; } }
        public bool MoveNext() { return !asynOperation.isDone; }
        public void Reset() { }

        public void Dispose()
        {
            request.Dispose();
            request = null;
        }

#if UNITY_2020_1_OR_NEWER
        public bool Success => request != null && request.isDone && request.result == UnityWebRequest.Result.Success;
#else
        public bool success => request.isDone && !request.isNetworkError && !request.isHttpError;
#endif

        public string Error => request == null ? "Request disposed" : request.error;
        public byte[] Data => request?.downloadHandler.data;
        public string Text => request?.downloadHandler.text;
        public bool? IsBinary
        {
            get
            {
                if (Success)
                {
                    string contentType = request.GetResponseHeader("Content-Type");
                    if (contentType == GLB_MIME)
                        return true;
                    if (contentType == GLTF_MIME)
                        return false;
                }
                return null;
            }
        }

    }

    public class NewAwaitableTextureDownload : NewAwaitableDownload, ITextureDownload
    {

        public NewAwaitableTextureDownload() : base() { }
        public NewAwaitableTextureDownload(Uri url) : base(url) { }

        public NewAwaitableTextureDownload(Uri url, bool nonReadable)
        {
            Init(url, nonReadable);
        }

        protected static UnityWebRequest CreateRequest(Uri url, bool nonReadable)
        {
            return UnityWebRequestTexture.GetTexture(url, nonReadable);
        }

        public void Init(Uri url, bool nonReadable)
        {
            request = CreateRequest(url, nonReadable);
            //request.certificateHandler = new AcceptAllCertificatesSignedWithASpecificKeyPublicKey();
            asynOperation = request.SendWebRequest();
        }

        public Texture2D Texture
        {
            get
            {
                return (request.downloadHandler as DownloadHandlerTexture).texture;
            }
        }
    }

    public class LoadFileLocal : IDownload
    {
        const string GLB_MIME = "model/gltf-binary";
        const string GLTF_MIME = "model/gltf+json";

        private string error;
        private bool success;
        private byte[] data;
        private string text;
        private bool isBinary;

        public float getProgress()
        {
             return 1;
        }


        public LoadFileLocal() { }

        public LoadFileLocal(Uri url)
        {
            Init(url);
        }

        public void Init(Uri url)
        {
            try
            {
                GetDataByType(url);
            }
            catch(Exception ex)
            {
                error = ex.Message;
                success = false;
            }
            
        }

        public object Current { get { return null; } }
        public bool MoveNext() { return true; }
        public void Reset() { }

        public void Dispose()
        {
            data = null;
            text = null;
        }

        public bool Success => success;

        public string Error { get { return error; } }
        public byte[] Data { get { return data; } }
        public string Text { get { return text; } }
        public bool? IsBinary
        {
            get
            {
                return isBinary;
            }
        }

        private void GetDataByType(Uri url)
        {
            string extension = Path.GetExtension(url.LocalPath);
            if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".ktx2")
            {
                data = File.ReadAllBytes(url.LocalPath);
                success = true;
            }
            else if (extension == ".gltf")
            {
                data = File.ReadAllBytes(url.LocalPath);
                text = File.ReadAllText(url.LocalPath);
                isBinary = false;
                success = true;
            }
            else if (extension == ".bin")
            {
                data = File.ReadAllBytes(url.LocalPath);
                success = true;
            }
            else if (extension == ".glb")
            {
                data = File.ReadAllBytes(url.LocalPath);
                isBinary = true;
                success = true;
            }
            else
            {
                success = false;
            }
        }
    }

    public class LoadTextureLocal : ITextureDownload
    {
        public LoadTextureLocal() { }

        public LoadTextureLocal(Uri url, bool nonReadable)
        {
            Init(url, nonReadable);
        }

        private void Init(Uri url, bool nonReadable)
        {
            try
            {
                string extension = Path.GetExtension(url.LocalPath);
                if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".ktx2")
                {
                    data = File.ReadAllBytes(url.LocalPath);
                    texture = new Texture2D(4, 4);
                    success = texture.LoadImage(data, nonReadable);
                    isBinary = true;
                }
                else
                {
                    success = false;
                }
            }
            catch (Exception ex)
            {

                error = ex.Message;
                success = false;
            }
        }

        public float getProgress()
        {
            return 1;
        }

        private string error;
        private bool success;
        private byte[] data;
        private string text;
        private bool isBinary;
        private Texture2D texture;

        public Texture2D Texture => texture;

        public bool Success => success;

        public string Error => error;

        public byte[] Data => data;

        public string Text => text;

        public bool? IsBinary => isBinary;

        public void Dispose()
        {
            data = null;
            text = null;
            texture = null;
        }
    }

    public class NewDeferAgent : IDeferAgent
    {
        public async Task BreakPoint()
        {
            if (ShouldDefer())
            {
                await Task.Yield();
            }
        }


        public async Task BreakPoint(float duration)
        {
            if (ShouldDefer(duration))
            {
                await Task.Yield();
            }
        }

        public bool ShouldDefer()
        {
            return false;
        }

        public bool ShouldDefer(float duration)
        {
            return false;
        }
    }
}
