using UnityEngine;
using ExIni;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Util;

namespace CM3D2.MultipleMaids.Plugin
{
    public partial class MultipleMaids
    {
        internal static readonly byte[] pngEnd = Encoding.ASCII.GetBytes("IEND");
        internal static readonly byte[] kankyoHeader = Encoding.ASCII.GetBytes("KANKYO");
        internal const string sceneDirectoryName = "< Scene Root >";
        internal const string kankyoDirectoryName = "< Kankyo Root >";
        internal readonly string saveScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsScene");
        private static string kankyoScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsKankyo");
        private static string sceneData;
        private static List<ScenePng> scenes = new List<ScenePng>(50);
        private GUIStyle selectedButtonStyle;
        private GUIContent infoString;
        private string[] directoryList;
        private Texture2D frame;
        private Texture2D infoHighlight;
        private Rect sceneManagerRect;
        private Rect sceneModalRect;
        private Rect resizeManagerRect;
        private Vector2 sceneManagerScrollPos = Vector2.zero;
        private Vector2 dirListScrollPos = Vector2.zero;
        private bool sceneManagerInitialize = false;
        private bool loadSceneFlag = false;
        private bool overwriteFlag = false;
        private bool createSceneFlag = false;
        private bool quickSaveFlag = false;
        private bool manageSceneFlag = false;
        private bool kankyoModeFlag = false;
        private bool kankyoToggle = false;
        private bool kankyoScene = false;
        private bool createDirectoryFlag = false;
        private bool deleteDirectoryFlag = false;
        private bool deleteSceneFlag = false;
        private bool resizeManager = false;
        private int selectedScene = 0;
        private int selectedDirectory = 0;
        private string currentDirectory = sceneDirectoryName;
        private string textFieldValue = "";

        public void InitializeSceneManager()
        {
            frame = MakeTex(2, 2, Color.white);
            infoHighlight = MakeTex(2, 2, new Color(0f, 0f, 0f, 0.8f));


            selectedButtonStyle = new GUIStyle("button");
            selectedButtonStyle.normal.background = MakeTex(1, 1, new Color(0.5f, 0.5f, 0.5f, 0.4f));
            selectedButtonStyle.normal.textColor = Color.white;

            currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;

            GetSceneDirectories();

            if (Preferences["scenemanager"]["converted"].Value != "true")
            {
                ConvertIni();
                Preferences["scenemanager"]["converted"].Value = "true";
                SaveConfig();
            }

            GetScenes(currentDirectory);
            sceneManagerInitialize = true;
        }

        private void RefreshSceneManager()
        {
            SwitchDirectory(currentDirectory);
            GetSceneDirectories();
            GetScenes(currentDirectory);
        }

        private void SwitchDirectory(string target)
        {
            string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
            string path = target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName)
                ? ""
                : target;
            string targetDirectory = Path.Combine(root, path);

            if (!Directory.Exists(targetDirectory))
            {
                currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
                selectedDirectory = 0;
                GetSceneDirectories();
            }
            else
            {
                selectedDirectory = Array.FindIndex(directoryList, d => d.Equals(target, StringComparison.OrdinalIgnoreCase));
                selectedDirectory = selectedDirectory == -1 ? 0 : selectedDirectory;
            }

            if (target == currentDirectory) return;

            currentDirectory = target;

            GetScenes(target);
        }
        private void GetSceneDirectories()
        {
            if (!Directory.Exists(saveScenePath))
            {
                Directory.CreateDirectory(saveScenePath);
            }

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

            string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
            DirectoryInfo[] directoryInfo = new DirectoryInfo(root).GetDirectories();
            directoryList = new string[directoryInfo.Length + 1];
            directoryList[0] = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;

            for (int i = 0; i < directoryInfo.Length; i++)
            {
                directoryList[i + 1] = directoryInfo[i].Name;
            }
        }

        private void GetScenes(string target)
        {
            string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;

            scenes.Clear();

            if (target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName))
            {
                target = "";
            }

            string workingPath = Path.Combine(root, target);

            DirectoryInfo info = new DirectoryInfo(workingPath);

            foreach (var scene in info.GetFiles("*.png"))
            {
                Texture2D screenshot = new Texture2D(2, 2, TextureFormat.ARGB32, false);
                screenshot.LoadImage(File.ReadAllBytes(scene.FullName));
                scenes.Add(new ScenePng(scene, screenshot));
            }

            selectedScene = scenes.Count == 0 ? 0 : scenes.Count - 1;
            scenes.Sort((a, b) => a.info.LastWriteTime.CompareTo(b.info.LastWriteTime));
            sceneManagerScrollPos.y = 0;
        }

        private void CreateDirectory(string directoryName)
        {
            string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
            directoryName = string.Join("", directoryName.Split(Path.GetInvalidFileNameChars()));
            string newDirectory = Path.Combine(root, directoryName);
            if (!Directory.Exists(newDirectory))
            {
                Directory.CreateDirectory(newDirectory);
            }
            GetSceneDirectories();
            SwitchDirectory(directoryName);
        }

        private void DeleteDirectory()
        {
            string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
            string directory = Path.Combine(root, directoryList[selectedDirectory]);
            if (Directory.Exists(directory))
            {
                DirectoryInfo dirInfo = new DirectoryInfo(directory);
                foreach (var finfo in dirInfo.GetFiles())
                {
                    finfo.Delete();
                }
                dirInfo.Delete();
                currentDirectory = sceneDirectoryName;
            }

            RefreshSceneManager();
        }

        private void DeleteScene()
        {
            string file = scenes[selectedScene].info.FullName;
            if (File.Exists(file))
            {
                scenes[selectedScene].info.Delete();
                scenes.RemoveAt(selectedScene);
            }

            RefreshSceneManager();
        }

        private void OverwriteScene()
        {
            string file = scenes[selectedScene].info.FullName;
            if (!File.Exists(file))
            {
                RefreshSceneManager();
            }
            else
            {
                File.Delete(file);
                scenes.RemoveAt(selectedScene);
                sceneManagerScrollPos.y = 0;
            }
        }

        private void SaveScene()
        {
            string target = currentDirectory;

            bool scene = target.Equals(sceneDirectoryName);
            bool kankyo = target.Equals(kankyoDirectoryName);

            if (scene || kankyo)
                target = "";

            string saveDirectory;

            saveDirectory = kankyoModeFlag
                ? Path.Combine(kankyoScenePath, target)
                : Path.Combine(saveScenePath, target);

            if (!Directory.Exists(saveDirectory))
            {
                thum_byte_to_base64_ = "";
                thum_file_path_ = "";
                RefreshSceneManager();
                return;
            }

            string sceneString = SerializeScene();

            #region MM screenshot processing stuff

            Texture2D screenshot = new Texture2D(1, 1, TextureFormat.ARGB32, false);
            screenshot.LoadImage(File.ReadAllBytes(thum_file_path_));
            float num2 = screenshot.width / (float)screenshot.height;
            Vector2 vector2 = new Vector2(480f, 270f);
            int newWidth = screenshot.width;
            int newHeight = screenshot.height;
            if (vector2.x < (double)screenshot.width && vector2.y < (double)screenshot.height)
            {
                newWidth = (int)vector2.x;
                newHeight = Mathf.RoundToInt(newWidth / num2);
                if (vector2.y < (double)newHeight)
                {
                    newHeight = (int)vector2.y;
                    newWidth = Mathf.RoundToInt(newHeight * num2);
                }
            }
            else if (vector2.x < (double)screenshot.width)
            {
                newWidth = (int)vector2.x;
                newHeight = Mathf.RoundToInt(newWidth / num2);
            }
            else if (vector2.y < (double)screenshot.height)
            {
                newHeight = (int)vector2.y;
                newWidth = Mathf.RoundToInt(newHeight * num2);
            }
            TextureScale.Bilinear(screenshot, newWidth, newHeight);
            #endregion

            thum_byte_to_base64_ = "";
            thum_file_path_ = "";

            string sceneType = kankyoModeFlag ? "mmkankyo" : "mmscene";

            string filePath;

            filePath = Path.Combine(saveDirectory, $"{sceneType}{DateTime.Now:yyyyMMddHHmmss}.png");

            using (FileStream fileStream = File.Create(filePath))
            using (MemoryStream sceneStream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
            {
                byte[] screenshotBuffer = screenshot.EncodeToPNG();
                byte[] sceneBuffer = LZMA.Compress(sceneStream);
                fileStream.Write(screenshotBuffer, 0, screenshotBuffer.Length);
                if (kankyoModeFlag) fileStream.Write(kankyoHeader, 0, kankyoHeader.Length);
                fileStream.Write(sceneBuffer, 0, sceneBuffer.Length);
            }
            scenes.Add(new ScenePng(new FileInfo(filePath), screenshot));
            selectedScene = scenes.Count - 1;
        }

        private void ReadScene()
        {
            string filePath = scenes[selectedScene].info.FullName;

            sceneData = null;

            if (!File.Exists(filePath))
            {
                RefreshSceneManager();
                return;
            }

            using (FileStream fileStream = File.OpenRead(filePath))
            {
                long pos = fileStream.Position = fileStream.Length - pngEnd.Length;
                byte[] buf = new byte[pngEnd.Length];

                while (true)
                {
                    if (pos < 0)
                    {
                        Util.Logger.Log(LogLevel.Error, $"Could not read '{Path.GetFileName(filePath)}'");
                        return;
                    }

                    fileStream.Position = pos;
                    fileStream.Read(buf, 0, pngEnd.Length);

                    if (BytesEqual(buf, pngEnd)) break;

                    --pos;
                }
                fileStream.Position += 4;

                byte[] kankyo = new byte[kankyoHeader.Length];
                fileStream.Read(kankyo, 0, kankyo.Length);

                if (BytesEqual(kankyo, kankyoHeader))
                {
                    kankyoScene = true;
                }
                else
                {
                    kankyoScene = false;
                    fileStream.Position -= kankyoHeader.Length;
                }

                try
                {
                    using (var sceneStream = LZMA.Decompress(fileStream))
                    {
                        sceneData = Encoding.Unicode.GetString(sceneStream.ToArray());
                    }
                }
                catch (Exception e)
                {
                    Util.Logger.Log(LogLevel.Error, $"Failed to decompress scene data because '{e}'\n");
                }
            }
        }

        private static bool BytesEqual(byte[] a, byte[] b)
        {
            if (a.Length != b.Length) return false;

            for (long i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i]) return false;
            }

            return true;
        }

        private void ConvertIni()
        {
            HashSet<int> saveSceneEntries = new HashSet<int>();

            IniSection MMScene = Preferences["scene"];

            foreach (IniKey iniKey in MMScene.Keys)
            {
                string keyName = iniKey.Key;

                if (keyName[0] == 's')
                {
                    int index = Int32.Parse(keyName.Substring(1 + (keyName[1] == 's' ? 1 : 0)));
                    if (!saveSceneEntries.Contains(index))
                    {
                        saveSceneEntries.Add(index);
                        ConvertSceneToPng(index, MMScene);
                    }
                }
            }
        }

        private void ConvertSceneToPng(int index, IniSection MMScene)
        {
            byte[] sceneBuffer;
            byte[] screenshotBuffer = frame.EncodeToPNG();
            string sceneString = MMScene.GetKey($"s{index}")?.RawValue;
            string screenshotString = MMScene.GetKey($"ss{index}")?.RawValue;
            bool kankyo = index >= 10000;

            if (index == 9999 || String.IsNullOrEmpty(sceneString)) return;

            using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
            {
                sceneBuffer = LZMA.Compress(stream);
            }

            DateTime dateSaved = DateTime.Parse(sceneString.Split(',')[0]);

            if (!String.IsNullOrEmpty(screenshotString))
                screenshotBuffer = Convert.FromBase64String(screenshotString);

            string sceneType = kankyo ? "mmkankyo" : "mmscene";
            string savePngFilename = $"{sceneType}{index}_{dateSaved.ToString("yyyyMMddHHmm")}.png";
            string outPath = Path.Combine(kankyo ? kankyoScenePath : saveScenePath, savePngFilename);

            using (BinaryWriter stream = new BinaryWriter(File.Create(outPath)))
            {
                stream.Write(screenshotBuffer);
                if (kankyo) stream.Write(kankyoHeader);
                stream.Write(sceneBuffer);
            }

            File.SetCreationTime(outPath, dateSaved);
            File.SetLastWriteTime(outPath, dateSaved);
        }

        private class ScenePng
        {
            public FileInfo info { get; }
            public Texture2D screenshot { get; }
            public ScenePng(FileInfo info, Texture2D screenshot)
            {
                this.info = info;
                this.screenshot = screenshot;
            }
        }
    }
}
