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

namespace MinecraftClient.Mapping.BlockPalettes
{
    /// <summary>
    /// Generator for MCC Block Palette mappings
    /// </summary>
    /// <remarks>
    /// Example for generating MaterialXXX.cs and PaletteXXX.cs from blocks.json:
    ///
    /// MinecraftClient.Mapping.BlockPalettes.BlockPaletteGenerator.JsonToClass(
    ///     @"C:\Path\To\blocks.json",
    ///     @"C:\Path\To\Output\PaletteXXX.cs",
    ///     @"C:\Path\To\Output\MaterialXXX.cs"
    /// );
    ///
    /// Place the above example inside the Main() method of Program.cs, adjust paths then compile and run.
    /// Do not forget to remove the temporay call to JsonToClass() from Main() once you are done.
    /// </remarks>
    public static class BlockPaletteGenerator
    {
        /// <summary>
        /// Generate mapping from Minecraft blocks.json
        /// </summary>
        /// <param name="blocksJsonFile">path to blocks.json</param>
        /// <remarks>java -cp minecraft_server.jar net.minecraft.data.Main --reports</remarks>
        /// <returns>state => block name mappings</returns>
        public static void GenerateBlockPalette(string blocksJsonFile)
        {
            BlockPaletteGenerator.JsonToClass(blocksJsonFile, "Palette", "Material");
        }

        /// <summary>
        /// Generate mapping from Minecraft blocks.json
        /// </summary>
        /// <param name="blocksJsonFile">path to blocks.json</param>
        /// <param name="outputClass">output path for blocks.cs</param>
        /// <param name="outputEnum">output path for material.cs</param>
        /// <remarks>java -cp minecraft_server.jar net.minecraft.data.Main --reports</remarks>
        /// <returns>state => block name mappings</returns>
        public static void JsonToClass(string blocksJsonFile, string outputClass, string? outputEnum = null)
        {
            string outputPalettePath = Path.Combine(Path.GetDirectoryName(blocksJsonFile)!, outputClass + "XXX.cs");
            string outputEnumPath = Path.Combine(Path.GetDirectoryName(blocksJsonFile)!, outputEnum + "XXX.cs");

            HashSet<int> knownStates = new();
            Dictionary<string, HashSet<int>> blocks = new();

            Json.JSONData palette = Json.ParseJson(File.ReadAllText(blocksJsonFile, Encoding.UTF8));
            foreach (KeyValuePair<string, Json.JSONData> item in palette.Properties)
            {
                //minecraft:item_name => ItemName
                string blockType = String.Concat(
                    item.Key.Replace("minecraft:", "")
                    .Split('_')
                    .Select(word => char.ToUpper(word[0]) + word.Substring(1))
                );

                if (blocks.ContainsKey(blockType))
                    throw new InvalidDataException("Duplicate block type " + blockType + "!?");
                blocks[blockType] = new HashSet<int>();

                foreach (Json.JSONData state in item.Value.Properties["states"].DataArray)
                {
                    int id = int.Parse(state.Properties["id"].StringValue, NumberStyles.Any, CultureInfo.CurrentCulture);

                    if (knownStates.Contains(id))
                        throw new InvalidDataException("Duplicate state id " + id + "!?");

                    knownStates.Add(id);
                    blocks[blockType].Add(id);
                }
            }

            HashSet<string> materials = new();
            List<string> outFile = new();
            outFile.AddRange(new[] {
                "using System.Collections.Generic;",
                "",
                "namespace MinecraftClient.Mapping.BlockPalettes",
                "{",
                "    public class PaletteXXX : BlockPalette",
                "    {",
                "        private static readonly Dictionary<int, Material> materials = new();",
                "",
                "        static PaletteXXX()",
                "        {",
            });

            foreach (KeyValuePair<string, HashSet<int>> blockType in blocks)
            {
                if (blockType.Value.Count > 0)
                {
                    List<int> idList = blockType.Value.ToList();
                    string materialName = blockType.Key;
                    materials.Add(materialName);

                    if (idList.Count > 1)
                    {
                        idList.Sort();
                        Queue<int> idQueue = new(idList);

                        while (idQueue.Count > 0)
                        {
                            int startValue = idQueue.Dequeue();
                            int endValue = startValue;
                            while (idQueue.Count > 0 && idQueue.Peek() == endValue + 1)
                                endValue = idQueue.Dequeue();
                            if (endValue > startValue)
                            {
                                outFile.Add("            for (int i = " + startValue + "; i <= " + endValue + "; i++)");
                                outFile.Add("                materials[i] = Material." + materialName + ";");
                            }
                            else outFile.Add("            materials[" + startValue + "] = Material." + materialName + ";");
                        }
                    }
                    else outFile.Add("            materials[" + idList[0] + "] = Material." + materialName + ";");
                }
                else throw new InvalidDataException("No state id  for block type " + blockType.Key + "!?");
            }

            outFile.AddRange(new[] {
                "        }",
                "",
                "        protected override Dictionary<int, Material> GetDict()",
                "        {",
                "            return materials;",
                "        }",
                "    }",
                "}"
            });

            File.WriteAllLines(outputPalettePath, outFile);

            if (outputEnum != null)
            {
                outFile = new List<string>();
                outFile.AddRange(new[] {
                    "namespace MinecraftClient.Mapping",
                    "{",
                    "    public enum Material",
                    "    {"
                });
                foreach (string material in materials)
                    outFile.Add("        " + material + ",");
                outFile.AddRange(new[] {
                    "    }",
                    "}"
                });
                File.WriteAllLines(outputEnumPath, outFile);
            }
        }
    }
}
