// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Text;
using StringBuilder = System.Text.StringBuilder;

namespace UniLua
{
    internal static class LuaJsonLib
    {
        public const string LIB_NAME = "json";

        private static readonly string[] ESCAPE_CHARS = new string[]
        {
            "\"", "\\", "/", "\b", "\f", "\n", "\r", "\t"
        };

        public static int OpenLib(ILuaState lua)
        {
            NameFuncPair[] define = new NameFuncPair[]
            {
                new NameFuncPair("encode", Json_Encode),
                new NameFuncPair("decode", Json_Decode),
                new NameFuncPair("null", Json_Null),
                new NameFuncPair("parse", Json_Parse),
                new NameFuncPair("stringify", Json_Stringify)
            };

            lua.L_NewLib(define);
            return 1;
        }

        private static int Json_Encode(ILuaState lua)
        {
            lua.L_CheckAny(1);
            LuaType t = lua.Type(1);

            if (t == LuaType.LUA_TNIL)
            {
                lua.PushString("null");
                return 1;
            }

            if (t == LuaType.LUA_TBOOLEAN)
            {
                lua.PushString(lua.ToBoolean(1) ? "true" : "false");
                return 1;
            }

            if (t == LuaType.LUA_TNUMBER)
            {
                lua.PushString(lua.ToNumber(1).ToString());
                return 1;
            }

            if (t == LuaType.LUA_TSTRING)
            {
                string s = lua.ToString(1);
                lua.PushString(JsonEscapeString(s));
                return 1;
            }

            if (t == LuaType.LUA_TTABLE)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("{");
                
                bool first = true;
                lua.PushNil();
                while (lua.Next(1))
                {
                    if (!first)
                    {
                        sb.Append(",");
                    }
                    first = false;

                    string key = lua.ToString(-2);
                    if (lua.Type(-1) == LuaType.LUA_TSTRING)
                    {
                        sb.Append("\"");
                        sb.Append(JsonEscapeString(key));
                        sb.Append("\":");
                    }
                    else
                    {
                        sb.Append(key);
                        sb.Append(":");
                    }

                    lua.PushValue(-1);
                    int result = Json_Encode(lua);
                    lua.Pop(1);

                    if (lua.Type(-1) == LuaType.LUA_TSTRING)
                    {
                        sb.Append("\"");
                        sb.Append(JsonEscapeString(lua.ToString(-1)));
                    }
                    else
                    {
                        sb.Append(lua.ToString(-1));
                    }
                }

                sb.Append("}");
                lua.PushString(sb.ToString());
                return 1;
            }

            lua.PushNil();
            return 1;
        }

        private static int Json_Decode(ILuaState lua)
        {
            lua.L_CheckString(1);
            string json = lua.ToString(1).Trim();
            
            if (json == "null" || json.Length == 0)
            {
                lua.PushNil();
                return 1;
            }

            if (json == "true")
            {
                lua.PushBoolean(true);
                return 1;
            }

            if (json == "false")
            {
                lua.PushBoolean(false);
                return 1;
            }

            if ((json.StartsWith("\"") && json.EndsWith("\"")) ||
                (json.StartsWith("'") && json.EndsWith("'")))
            {
                string unquoted = json.Substring(1, json.Length - 2);
                lua.PushString(unquoted);
                return 1;
            }

            if (json.StartsWith("{") || json.StartsWith("["))
            {
                try
                {
                    int pos = 0;
                    object result = ParseJson(json, ref pos);
                    PushLuaValue(lua, result);
                    return 1;
                }
                catch
                {
                    lua.PushNil();
                    return 1;
                }
            }

            if (char.IsDigit(json[0]))
            {
                double num = double.Parse(json);
                lua.PushNumber(num);
                return 1;
            }

            lua.PushNil();
            return 1;
        }

        private static object ParseJson(string json, ref int pos)
        {
            json = json.Trim();
            
            if (json.StartsWith("{"))
            {
                return ParseJsonObject(json, ref pos);
            }
            else if (json.StartsWith("["))
            {
                return ParseJsonArray(json, ref pos);
            }
            
            return null;
        }

        private static Dictionary<string, object> ParseJsonObject(string json, ref int pos)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            string content = json.Substring(1, json.Length - 2);
            int contentPos = 0;

            while (contentPos < content.Length)
            {
                contentPos = SkipWhitespace(content, contentPos);
                
                if (contentPos >= content.Length)
                {
                    break;
                }

                if (content[contentPos] == '}')
                {
                    contentPos++;
                    break;
                }

                string key = ParseJsonString(content, ref contentPos);
                contentPos = SkipWhitespace(content, contentPos);

                if (contentPos >= content.Length || content[contentPos] != ':')
                {
                    break;
                }
                contentPos++;

                object value = ParseJsonValue(content, ref contentPos);
                if (key != null)
                {
                    dict[key] = value;
                }

                contentPos = SkipWhitespace(content, contentPos);
                if (contentPos < content.Length && content[contentPos] == ',')
                {
                    contentPos++;
                }
            }

            pos = contentPos;
            return dict;
        }

        private static List<object> ParseJsonArray(string json, ref int pos)
        {
            List<object> list = new List<object>();
            string content = json.Substring(1, json.Length - 2);
            int contentPos = 0;

            while (contentPos < content.Length)
            {
                contentPos = SkipWhitespace(content, contentPos);
                
                if (contentPos >= content.Length)
                {
                    break;
                }

                if (content[contentPos] == ']')
                {
                    contentPos++;
                    break;
                }

                object value = ParseJsonValue(content, ref contentPos);
                if (value != null)
                {
                    list.Add(value);
                }

                contentPos = SkipWhitespace(content, contentPos);
                if (contentPos < content.Length && content[contentPos] == ',')
                {
                    contentPos++;
                }
            }

            pos = contentPos;
            return list;
        }

        private static object ParseJsonValue(string content, ref int pos)
        {
            pos = SkipWhitespace(content, pos);

            if (pos >= content.Length)
            {
                return null;
            }

            char c = content[pos];

            if (c == '{')
            {
                return ParseJsonObject(content, ref pos);
            }
            else if (c == '[')
            {
                return ParseJsonArray(content, ref pos);
            }
            else if (c == '"')
            {
                return ParseJsonString(content, ref pos);
            }
            else if (c == 't' || c == 'f' || c == 'n')
            {
                string keyword = "";
                while (pos < content.Length && char.IsLetter(content[pos]))
                {
                    keyword += content[pos];
                    pos++;
                }

                if (keyword == "true")
                {
                    return true;
                }
                else if (keyword == "false")
                {
                    return false;
                }
                else if (keyword == "null")
                {
                    return null;
                }

                return null;
            }
            else if (char.IsDigit(c) || c == '-')
            {
                string numStr = "";
                while (pos < content.Length && (char.IsDigit(content[pos]) || content[pos] == '-' || content[pos] == '.'))
                {
                    numStr += content[pos];
                    pos++;
                }

                if (double.TryParse(numStr, out double num))
                {
                    return num;
                }
            }

            return null;
        }

        private static string ParseJsonString(string content, ref int pos)
        {
            if (pos >= content.Length || content[pos] != '"')
            {
                return null;
            }

            pos++;

            StringBuilder sb = new StringBuilder();
            while (pos < content.Length)
            {
                char c = content[pos];
                pos++;

                if (c == '"')
                {
                    break;
                }
                else if (c == '\\')
                {
                    if (pos >= content.Length)
                    {
                        sb.Append('\\');
                        break;
                    }

                    pos++;
                    char escapeChar = content[pos];
                    pos++;

                    switch (escapeChar)
                    {
                        case '"':
                            sb.Append('"');
                            break;
                        case '\\':
                            sb.Append('\\');
                            break;
                        case '/':
                            sb.Append('/');
                            break;
                        case 'b':
                            sb.Append('\b');
                            break;
                        case 'f':
                            sb.Append('\f');
                            break;
                        case 'n':
                            sb.Append('\n');
                            break;
                        case 'r':
                            sb.Append('\r');
                            break;
                        case 't':
                            sb.Append('\t');
                            break;
                        case 'u':
                            if (pos + 3 < content.Length)
                            {
                                string hex = content.Substring(pos, 4);
                                if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out int code))
                                {
                                    sb.Append((char)code);
                                    pos += 3;
                                }
                            }
                            break;
                        default:
                            sb.Append(escapeChar);
                            break;
                    }
                }
                else
                {
                    sb.Append(c);
                }
            }

            return sb.ToString();
        }

        private static int SkipWhitespace(string content, int pos)
        {
            while (pos < content.Length && char.IsWhiteSpace(content[pos]))
            {
                pos++;
            }
            return pos;
        }

        private static void PushLuaValue(ILuaState lua, object value)
        {
            if (value == null)
            {
                lua.PushNil();
            }
            else if (value is bool)
            {
                lua.PushBoolean((bool)value);
            }
            else if (value is double)
            {
                lua.PushNumber((double)value);
            }
            else if (value is string)
            {
                lua.PushString((string)value);
            }
            else if (value is Dictionary<string, object>)
            {
                lua.NewTable();
                foreach (var kvp in (Dictionary<string, object>)value)
                {
                    lua.PushString(kvp.Key);
                    PushLuaValue(lua, kvp.Value);
                    lua.SetField(-3, kvp.Key);
                }
            }
            else if (value is List<object>)
            {
                lua.NewTable();
                int index = 1;
                foreach (var item in (List<object>)value)
                {
                    PushLuaValue(lua, item);
                    lua.RawSetI(-2, index++);
                }
            }
        }

        private static string JsonEscapeString(string s)
        {
            if (string.IsNullOrEmpty(s))
            {
                return "";
            }

            StringBuilder sb = new StringBuilder();
            foreach (char c in s)
            {
                switch (c)
                {
                    case '"':
                        sb.Append("\\\"");
                        break;
                    case '\\':
                        sb.Append("\\\\");
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    default:
                        sb.Append(c);
                        break;
                }
            }
            return sb.ToString();
        }

        private static int Json_Null(ILuaState lua)
        {
            lua.PushNil();
            return 1;
        }

        private static int Json_Parse(ILuaState lua)
        {
            return Json_Decode(lua);
        }

        private static int Json_Stringify(ILuaState lua)
        {
            return Json_Encode(lua);
        }
    }
}