using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace UnityAssistant
{
    public static class JsonUtil
    {
        public static string Quote(string s)
        {
            if (s == null) return "null";
            var sb = new StringBuilder(s.Length + 2);
            sb.Append('"');
            foreach (var c in s)
            {
                switch (c)
                {
                    case '\\': sb.Append("\\\\"); break;
                    case '"': sb.Append("\\\""); break;
                    case '\n': sb.Append("\\n"); break;
                    case '\r': sb.Append("\\r"); break;
                    case '\t': sb.Append("\\t"); break;
                    case '\b': sb.Append("\\b"); break;
                    case '\f': sb.Append("\\f"); break;
                    default:
                        if (c < ' ') sb.Append("\\u").Append(((int)c).ToString("x4"));
                        else sb.Append(c);
                        break;
                }
            }
            sb.Append('"');
            return sb.ToString();
        }

        public static object Parse(string json)
        {
            int i = 0;
            return ParseValue(json, ref i);
        }

        public static Dictionary<string, object> ParseObject(string json)
        {
            return Parse(json) as Dictionary<string, object>;
        }

        private static object ParseValue(string s, ref int i)
        {
            SkipWs(s, ref i);
            if (i >= s.Length) return null;
            char c = s[i];
            if (c == '{') return ParseObj(s, ref i);
            if (c == '[') return ParseArr(s, ref i);
            if (c == '"') return ParseStr(s, ref i);
            if (c == 't' || c == 'f') return ParseBool(s, ref i);
            if (c == 'n') { i += 4; return null; }
            return ParseNum(s, ref i);
        }

        private static Dictionary<string, object> ParseObj(string s, ref int i)
        {
            var dict = new Dictionary<string, object>();
            i++;
            SkipWs(s, ref i);
            if (i < s.Length && s[i] == '}') { i++; return dict; }
            while (i < s.Length)
            {
                SkipWs(s, ref i);
                string key = ParseStr(s, ref i);
                SkipWs(s, ref i);
                i++;
                var val = ParseValue(s, ref i);
                dict[key] = val;
                SkipWs(s, ref i);
                if (i < s.Length && s[i] == ',') { i++; continue; }
                if (i < s.Length && s[i] == '}') { i++; break; }
            }
            return dict;
        }

        private static List<object> ParseArr(string s, ref int i)
        {
            var list = new List<object>();
            i++;
            SkipWs(s, ref i);
            if (i < s.Length && s[i] == ']') { i++; return list; }
            while (i < s.Length)
            {
                list.Add(ParseValue(s, ref i));
                SkipWs(s, ref i);
                if (i < s.Length && s[i] == ',') { i++; continue; }
                if (i < s.Length && s[i] == ']') { i++; break; }
            }
            return list;
        }

        private static string ParseStr(string s, ref int i)
        {
            var sb = new StringBuilder();
            i++;
            while (i < s.Length)
            {
                char c = s[i++];
                if (c == '"') return sb.ToString();
                if (c == '\\' && i < s.Length)
                {
                    char e = s[i++];
                    switch (e)
                    {
                        case '"': sb.Append('"'); break;
                        case '\\': sb.Append('\\'); break;
                        case '/': sb.Append('/'); break;
                        case 'n': sb.Append('\n'); break;
                        case 'r': sb.Append('\r'); break;
                        case 't': sb.Append('\t'); break;
                        case 'b': sb.Append('\b'); break;
                        case 'f': sb.Append('\f'); break;
                        case 'u':
                            if (i + 4 <= s.Length)
                            {
                                var hex = s.Substring(i, 4);
                                sb.Append((char)int.Parse(hex, NumberStyles.HexNumber));
                                i += 4;
                            }
                            break;
                    }
                }
                else sb.Append(c);
            }
            return sb.ToString();
        }

        private static bool ParseBool(string s, ref int i)
        {
            if (s[i] == 't') { i += 4; return true; }
            i += 5; return false;
        }

        private static object ParseNum(string s, ref int i)
        {
            int start = i;
            if (s[i] == '-') i++;
            while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '.' || s[i] == 'e' || s[i] == 'E' || s[i] == '+' || s[i] == '-')) i++;
            var sub = s.Substring(start, i - start);
            if (sub.Contains(".") || sub.Contains("e") || sub.Contains("E"))
                return double.Parse(sub, CultureInfo.InvariantCulture);
            if (long.TryParse(sub, out var l)) return l;
            return double.Parse(sub, CultureInfo.InvariantCulture);
        }

        private static void SkipWs(string s, ref int i)
        {
            while (i < s.Length && char.IsWhiteSpace(s[i])) i++;
        }

        public static string Get(Dictionary<string, object> d, string key, string def = "")
        {
            if (d != null && d.TryGetValue(key, out var v) && v != null) return v.ToString();
            return def;
        }

        public static bool GetBool(Dictionary<string, object> d, string key, bool def = false)
        {
            if (d != null && d.TryGetValue(key, out var v) && v is bool b) return b;
            return def;
        }

        public static double GetNum(Dictionary<string, object> d, string key, double def = 0)
        {
            if (d == null || !d.TryGetValue(key, out var v) || v == null) return def;
            if (v is double dd) return dd;
            if (v is long ll) return ll;
            if (double.TryParse(v.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var p)) return p;
            return def;
        }

        public static List<object> GetArr(Dictionary<string, object> d, string key)
        {
            if (d != null && d.TryGetValue(key, out var v)) return v as List<object>;
            return null;
        }

        public static Dictionary<string, object> GetObj(Dictionary<string, object> d, string key)
        {
            if (d != null && d.TryGetValue(key, out var v)) return v as Dictionary<string, object>;
            return null;
        }

        public static string ToJson(object value)
        {
            if (value == null) return "null";
            if (value is string s) return Quote(s);
            if (value is bool b) return b ? "true" : "false";
            if (value is double d) return d.ToString("R", CultureInfo.InvariantCulture);
            if (value is float f) return f.ToString("R", CultureInfo.InvariantCulture);
            if (value is long || value is int || value is short || value is byte)
                return System.Convert.ToString(value, CultureInfo.InvariantCulture);
            if (value is Dictionary<string, object> dict)
            {
                var sb = new StringBuilder("{");
                bool first = true;
                foreach (var kv in dict)
                {
                    if (!first) sb.Append(",");
                    first = false;
                    sb.Append(Quote(kv.Key)).Append(":").Append(ToJson(kv.Value));
                }
                sb.Append("}");
                return sb.ToString();
            }
            if (value is List<object> list)
            {
                var sb = new StringBuilder("[");
                for (int i = 0; i < list.Count; i++)
                {
                    if (i > 0) sb.Append(",");
                    sb.Append(ToJson(list[i]));
                }
                sb.Append("]");
                return sb.ToString();
            }
            return Quote(value.ToString());
        }
    }
}
