using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using UnityEngine;

public class KF3AssetLibrary : AssetLibrary
{
    public List<CharaData> CharaData = new List<CharaData>();
    public List<KF3AssetBundle> AssetBundles = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Characters = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Animations = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> AnimationSets = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Ceruleans = new List<KF3AssetBundle>();
    public Dictionary<string, List<KF3AssetBundle>> CeruleansD = new Dictionary<string, List<KF3AssetBundle>>();
    public List<KF3AssetBundle> CeruleanAnimationSets = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Effects = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Furniture = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Icons = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Miracles = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Skies = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Stages = new List<KF3AssetBundle>();
    public List<KF3AssetBundle> Misc = new List<KF3AssetBundle>();

    public static KF3AssetLibrary Instance => GetInstance<KF3AssetLibrary>();

    public IEnumerator Init()
    {
        _mainInstance = this;

        string charaData = LoadGzipFile("CHARA_DATA");
        if (charaData != "error")
        {
            Error.Log(Color.green, "Loaded Chara_Data from KF3 cache");
            CharaData = JsonHelper.getJsonArray<CharaData>(charaData).ToList();
        }

        KF3Names.LoadCeruleanNames();
        
        if (GetComponent<KF3ModelViewerMain>().IsOnline)
        {
            yield return GetAbList();
        }
        if (AssetBundles.Count <= 0)
        {
            Error.Log(Color.red, "Failed to load ab_list.txt");
        }
        yield break;
    }

    public string LoadGzipFile(string fileName)
    {
        string openFile = Path.Combine(Settings.CacheDirectory, "CHARA_DATA.d");
        if (!File.Exists(openFile)) { Error.Log(Color.blue, "CHARA_DATA cache file not found, character names may be outdated"); return "error"; }
        using (Stream decompress = new GZipStream(File.OpenRead(openFile), CompressionMode.Decompress))
        using (StreamReader stringWriter = new StreamReader(decompress))
        {
            return stringWriter.ReadToEnd();
        }
    }

    private IEnumerator GetAbList()
    {
        //LOAD GAME VERSION
        Error.Log(Color.green, $"Game Version is {Settings.GameVersion}");

        //LOAD ASSETS URL
        Error.Log(Color.green, $"Assets server address is: {Settings.AssetsUrl}");

        //LOAD AB_LIST
        string filePath = Path.Combine(Settings.AssetListDirectory, "ab_list.txt");
        if (File.Exists(filePath))
        {
            LoadText(File.ReadAllText(filePath));
            Error.Log(Color.green, $"Loaded ab file from {filePath}");
        }
        else if (KF3ModelViewerMain.Instance.IsOnline)
        {
            WWW www = new WWW(Settings.AssetsUrl + "/Windows/1.0.0/ja/ab_list.txt");
            yield return www;
            string response = www.text;
            if (!string.IsNullOrEmpty(response))
            {
                LoadText(response);
                Error.Log(Color.green, $"ab file downloaded");
                File.WriteAllText(filePath, response);
            }
            else
            {
                Error.Log(Color.red, $"Failed to load ab file");
            }
        }
        else
        {
            Error.Log(Color.red, $"Failed to load ab file");
        }

        //Load loose assets
        foreach(var fileName in Directory.GetFiles(Settings.LooseAssetsDirectory))
        {
            string name = Path.GetFullPath(fileName);
            KF3AssetBundle bundle = new KF3AssetBundle(Path.GetFileName(name));
            bundle.FullPath = name;
            AddToCategory(bundle, bundle.DisplayName);
        }
    }

    public KF3AssetBundle Find(string fileName)
    {
        return AssetBundles.FirstOrDefault(ab => ab.FileName == fileName);
    }

    public IEnumerator LoadAsset(KF3AssetBundle bundle, Action<AssetBundle> callback)
    {
        if(bundle == null)
        {
            callback?.Invoke(null);
            yield break;
        }
        if (File.Exists(bundle.FullPath))
        {
            callback?.Invoke(AssetBundle.LoadFromFile(bundle.FullPath));
            yield break;
        }
        else if (KF3ModelViewerMain.Instance.IsOnline)
        {
            AssetBundle assetBundle = null;
            yield return DownloadAssetBundle(bundle, (ab) =>
            {
                assetBundle = ab;
            });
            if(assetBundle != null)
            {
                callback?.Invoke(assetBundle);
                yield break;
            }
            else
            {
                callback?.Invoke(null);
                yield break;
            }
        }
        else
        {
            Error.Log(Color.yellow, $"{bundle.FileName} Does not exist");
            callback?.Invoke(null);
        }
    }

    public IEnumerator DownloadAssetBundle(KF3AssetBundle bundle, System.Action<AssetBundle> callback)
    {
        string saveTo = bundle.FullPath;
        string url = Settings.AssetsUrl + "/Windows/1.0.0/ja/assets/" + bundle.FileName;

        Error.Log($"Downloading\n{url}\nto\n{saveTo}", Color.blue);

        WWW www = new WWW(url);
        yield return www;
        if (!string.IsNullOrEmpty(www.error))
        {
            Error.Log(Color.red, www.error);
            www.Dispose();
            callback(null);
            yield break;
        }
        byte[] bytes = www.bytes;
        Directory.CreateDirectory(Path.GetDirectoryName(saveTo));
        File.WriteAllBytes(saveTo, bytes);
        www.Dispose();
        Error.Log(Color.green, $"Download Successful");
        callback(AssetBundle.LoadFromFile(saveTo));
    }

    /// <summary>
    /// Load AssetBundle list
    /// </summary>
    private void LoadText(string abListTxt)
    {
        int loaded = 0;
        int total = 0;
        using (StringReader reader = new StringReader(abListTxt))
        {
            while (reader.Peek() >= 0)
            {
                total++;
                string line = reader.ReadLine();
                if (string.IsNullOrEmpty(line))
                {
                    continue;
                }
                loaded++;
                string[] temp = line.Split(new string[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
                string bundleName = temp[0];

                KF3AssetBundle bundle = new KF3AssetBundle(bundleName);

                AddToCategory(bundle, bundleName);
            }
        }
        Error.Log(Color.green, $"Loaded {loaded}/{total} AB lines");
    }

    private void AddToCategory(KF3AssetBundle bundle, string bundleName)
    {
        //add to global list
        AssetBundles.Add(bundle);

        //add to characters list
        if (bundleName.StartsWith("ch_") && bundleName.EndsWith("_a.prefab"))
        {
            bundle.IdStr = bundleName.Split('_', '.')[1];
            bundle.IdNum = int.Parse(bundle.IdStr);
            if (bundle.IdNum < 10000)
            {
                Characters.Add(bundle);
            }
            else
            {
                bundle.DisplayName = KF3Names.GetEnemyName(bundle.IdNum);
                Ceruleans.Add(bundle);
                if (CeruleansD.ContainsKey(bundle.DisplayName))
                {
                    CeruleansD[bundle.DisplayName].Add(bundle);
                }
                else
                {
                    CeruleansD.Add(bundle.DisplayName, new List<KF3AssetBundle>() { bundle });
                }
            }
        }
        else if (bundleName.StartsWith("icon_"))
        {
            Icons.Add(bundle);
        }
        else if (bundleName.StartsWith("captainroom_"))
        {
            bundle.DisplayName = bundle.DisplayName.Replace("captainroom_obj_", "").Replace("_mdl.prefab", "");
            Furniture.Add(bundle);
        }
        else if (bundleName.StartsWith("ch_") && bundleName.EndsWith("_mot.prefab"))
        {
            bundle.IdStr = bundleName.Split('_', '.')[1];
            bundle.IdNum = int.Parse(bundle.IdStr);
            Animations.Add(bundle);
            if (bundleName.EndsWith("a_mot.prefab"))
            {
                if (bundle.IdNum < 10000)
                {
                    bundle.DisplayName = bundle.IdStr + "_" + KF3Names.GetCharaName(bundle.IdNum);
                    AnimationSets.Add(bundle);
                }
                else
                {
                    bundle.DisplayName = KF3Names.GetEnemyName(bundle.IdNum);
                    CeruleanAnimationSets.Add(bundle);
                }
            }
        }
        else if (bundleName.StartsWith("st_"))
        {
            Stages.Add(bundle);
        }
        else if (bundleName.StartsWith("sky_"))
        {
            Skies.Add(bundle);
        }
        else if (bundleName.StartsWith("ef_auth"))
        {
            bundle.DisplayName = bundleName.Replace("ef_auth", "");
            Miracles.Add(bundle);
        }
        else if (bundleName.StartsWith("ef_"))
        {
            Effects.Add(bundle);
            Misc.Add(bundle);
        }
    }

    /// <summary>
    /// Get a list of costumes from selected character id
    /// </summary>
    public List<KF3AssetBundle> GetCostumesFromId(int id)
    {
        if (id > 9999)
        {
            id = id / 10;
            return AssetBundles.Where(k => k.FileName.StartsWith($"ch_{id.ToString().PadLeft(4, '0')}") && !k.FileName.Contains("mot") && k.FileName.Split(new char[] { '.', '_' }).Length == 4)
                .ToList().ConvertAll(o=>(KF3AssetBundle)o);
        }
        else
        {
            return AssetBundles.Where(k => k.FileName.StartsWith($"ch_{id.ToString().PadLeft(4, '0')}") && !k.FileName.Contains("mot") && k.FileName.Split(new char[] { '.', '_' }).Length == 4)
                .ToList().ConvertAll(o => (KF3AssetBundle)o);
        }
    }

    public KF3AssetBundle GetAnimationSet(string name)
    {
        if(int.TryParse(name.Split('_','.')[1], out int id)){
            return GetAnimationSet(id);
        }
        return (KF3AssetBundle)AnimationSets.FirstOrDefault(a => a.DisplayName == name || a.FileName == name);
    }

    public KF3AssetBundle GetAnimationSet(int id)
    {
        return (KF3AssetBundle)AnimationSets.FirstOrDefault(a => int.Parse(a.FileName.Split('_', '.')[1]) == id);
    }

    public List<KF3AssetBundle> GetMisc()
    {
        return Misc.ConvertAll(k=>(KF3AssetBundle)k);
    }

    public List<KF3AssetBundle> GetMiracles()
    {
        return Miracles.ConvertAll(k => (KF3AssetBundle)k);
    }

    public List<KF3AssetBundle> GetCeruleans(string name)
    {
        return CeruleansD[name];
    }

    public KF3AssetBundle GetFurnitureBundle(string bundleName)
    {
        return (KF3AssetBundle)Furniture.FirstOrDefault(a => a.DisplayName == bundleName || a.FileName == bundleName);
    }
    public KF3AssetBundle GetMiscBundle(string bundleName)
    {
        return (KF3AssetBundle)Misc.FirstOrDefault(a => a.DisplayName == bundleName || a.FileName == bundleName);
    }
    public KF3AssetBundle GetMiracleBundle(string bundleName)
    {
        return (KF3AssetBundle)Miracles.FirstOrDefault(a => a.DisplayName == bundleName || a.FileName == bundleName);
    }
}
