using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection;
using UnityEngine;

namespace COM3D2.MeidoPhotoStudio.Plugin
{
    internal static class Utility
    {
        internal static readonly byte[] pngHeader = { 137, 80, 78, 71, 13, 10, 26, 10 };
        internal static readonly byte[] pngEnd = System.Text.Encoding.ASCII.GetBytes("IEND");
        internal static readonly Regex guidRegEx = new Regex(
            @"^[a-f0-9]{8}(\-[a-f0-9]{4}){3}\-[a-f0-9]{12}$", RegexOptions.IgnoreCase
        );
        public static readonly BepInEx.Logging.ManualLogSource Logger
            = BepInEx.Logging.Logger.CreateLogSource(MeidoPhotoStudio.pluginName);
        public enum ModKey
        {
            Control, Shift, Alt
        }

        public static void LogInfo(object data) => Logger.LogInfo(data);

        public static void LogMessage(object data) => Logger.LogInfo(data);

        public static void LogWarning(object data) => Logger.LogWarning(data);

        public static void LogError(object data) => Logger.LogError(data);

        public static void LogDebug(object data) => Logger.LogDebug(data);

        public static int Wrap(int value, int min, int max)
        {
            max -= 1;
            return value < min ? max : value > max ? min : value;
        }

        public static int GetPix(int num)
        {
            return (int)((1f + (Screen.width / 1280f - 1f) * 0.6f) * num);
        }

        public static float Bound(float value, float left, float right)
        {
            if ((double)left > (double)right) return Mathf.Clamp(value, right, left);
            else return Mathf.Clamp(value, left, right);
        }

        public static int Bound(int value, int left, int right)
        {
            if (left > right) return Mathf.Clamp(value, right, left);
            else return Mathf.Clamp(value, left, right);
        }

        public static Texture2D MakeTex(int width, int height, Color color)
        {
            Color[] colors = new Color[width * height];
            for (int i = 0; i < colors.Length; i++)
            {
                colors[i] = color;
            }
            Texture2D texture2D = new Texture2D(width, height);
            texture2D.SetPixels(colors);
            texture2D.Apply();
            return texture2D;
        }

        public static FieldInfo GetFieldInfo<T>(string field)
        {
            BindingFlags bindingFlags = BindingFlags.Instance
                | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
            return typeof(T).GetField(field, bindingFlags);
        }

        public static TValue GetFieldValue<TType, TValue>(TType instance, string field)
        {
            FieldInfo fieldInfo = GetFieldInfo<TType>(field);
            if (fieldInfo == null || !fieldInfo.IsStatic && instance == null) return default(TValue);
            return (TValue)fieldInfo.GetValue(instance);
        }

        public static void SetFieldValue<TType, TValue>(TType instance, string name, TValue value)
        {
            GetFieldInfo<TType>(name).SetValue(instance, value);
        }

        public static bool GetModKey(ModKey key)
        {
            switch (key)
            {
                case ModKey.Control: return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
                case ModKey.Alt: return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
                case ModKey.Shift: return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
                default: return false;
            }
        }

        public static bool AnyMouseDown()
        {
            return Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2);
        }

        public static string ScreenshotFilename()
        {
            string screenShotDir = Path.Combine(
                GameMain.Instance.SerializeStorageManager.StoreDirectoryPath, "ScreenShot"
            );
            if (!Directory.Exists(screenShotDir))
            {
                Directory.CreateDirectory(screenShotDir);
            }
            return Path.Combine(screenShotDir, $"img{DateTime.Now:yyyyMMddHHmmss}.png");
        }

        public static string TempScreenshotFilename()
        {
            return Path.Combine(Path.GetTempPath(), $"cm3d2_{System.Guid.NewGuid().ToString()}.png");
        }

        public static void ShowMouseExposition(string text, float time = 2f)
        {
            MouseExposition mouseExposition = MouseExposition.GetObject();
            mouseExposition.SetText(text, time);
        }

        public static bool IsGuidString(string guid)
        {
            if (string.IsNullOrEmpty(guid) || guid.Length != 36) return false;
            return guidRegEx.IsMatch(guid);
        }

        public static string HandItemToOdogu(string menu)
        {
            menu = menu.Substring(menu.IndexOf('_') + 1);
            menu = menu.Substring(0, menu.IndexOf("_i_.menu"));
            menu = $"odogu_{menu}";
            return menu;
        }

        public static void FixGameObjectScale(GameObject go)
        {
            Vector3 scale = go.transform.localScale;
            float largest = Mathf.Max(scale.x, Mathf.Max(scale.y, scale.z));
            go.transform.localScale = Vector3.one * (float)Math.Round(largest, 3);
        }

        public static string SanitizePathPortion(string path)
        {
            char[] invalid = Path.GetInvalidFileNameChars();
            path = path.Trim();
            path = string.Join("_", path.Split(invalid)).Replace(".", "").Trim('_');
            return path;
        }

        public static string GP01FbFaceHash(TMorph face, string hash)
        {
            if ((face.bodyskin.PartsVersion >= 120) && (hash != "eyeclose3") && hash.StartsWith("eyeclose"))
            {
                if (hash == "eyeclose") hash += '1';
                hash += TMorph.crcFaceTypesStr[(int)face.GetFaceTypeGP01FB()];
            }
            return hash;
        }

        public static void ResizeToFit(Texture2D texture, int maxWidth, int maxHeight)
        {
            int width = texture.width;
            int height = texture.height;
            if (width != maxWidth || height != maxHeight)
            {
                float scale = Mathf.Min((float)maxWidth / (float)width, (float)maxHeight / (float)height);
                width = Mathf.RoundToInt((float)width * scale);
                height = Mathf.RoundToInt((float)height * scale);
                TextureScale.Bilinear(texture, width, height);
            }
        }

        public static bool BytesEqual(byte[] buffer, byte[] other)
        {
            if (buffer.Length != other.Length) return false;
            for (int i = 0; i < buffer.Length; i++)
            {
                if (buffer[i] != other[i]) return false;
            }
            return true;
        }

        public static bool IsPngFile(Stream stream)
        {
            byte[] buffer = new byte[8];
            stream.Read(buffer, 0, 8);
            stream.Position = 0L;
            return BytesEqual(buffer, pngHeader);
        }

        public static bool SeekPngEnd(Stream stream)
        {
            byte[] buffer = new byte[8];
            stream.Read(buffer, 0, 8);
            if (!BytesEqual(buffer, pngHeader)) return false;
            buffer = new byte[4];
            do
            {
                stream.Read(buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                uint length = System.BitConverter.ToUInt32(buffer, 0);
                stream.Read(buffer, 0, 4);
                stream.Seek(length + 4L, SeekOrigin.Current);
            } while (!BytesEqual(buffer, pngEnd));
            return true;
        }
    }

    internal static class BinaryExtensions
    {
        public static string ReadNullableString(this BinaryReader binaryReader)
        {
            return binaryReader.ReadBoolean() ? binaryReader.ReadString() : null;
        }

        public static void WriteNullableString(this BinaryWriter binaryWriter, string str)
        {
            binaryWriter.Write(str != null);
            if (str != null) binaryWriter.Write(str);
        }

        public static void WriteVector3(this BinaryWriter binaryWriter, UnityEngine.Vector3 vector3)
        {
            binaryWriter.Write(vector3.x);
            binaryWriter.Write(vector3.y);
            binaryWriter.Write(vector3.z);
        }

        public static UnityEngine.Vector3 ReadVector3(this BinaryReader binaryReader)
        {
            return new UnityEngine.Vector3(
                binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()
            );
        }

        public static void WriteQuaternion(this BinaryWriter binaryWriter, UnityEngine.Quaternion quaternion)
        {
            binaryWriter.Write(quaternion.x);
            binaryWriter.Write(quaternion.y);
            binaryWriter.Write(quaternion.z);
            binaryWriter.Write(quaternion.w);
        }

        public static UnityEngine.Quaternion ReadQuaternion(this BinaryReader binaryReader)
        {
            return new UnityEngine.Quaternion
            (
                binaryReader.ReadSingle(), binaryReader.ReadSingle(),
                binaryReader.ReadSingle(), binaryReader.ReadSingle()
            );
        }

        public static void WriteColour(this BinaryWriter binaryWriter, UnityEngine.Color colour)
        {
            binaryWriter.Write(colour.r);
            binaryWriter.Write(colour.g);
            binaryWriter.Write(colour.b);
            binaryWriter.Write(colour.a);
        }

        public static UnityEngine.Color ReadColour(this BinaryReader binaryReader)
        {
            return new Color(
                binaryReader.ReadSingle(),
                binaryReader.ReadSingle(),
                binaryReader.ReadSingle(),
                binaryReader.ReadSingle()
            );
        }
    }
}