using System.Text;

namespace Tumble
{
    class MatrixRain
    {
        static int consoleWidth = Console.WindowWidth;
        static int consoleHeight = Console.WindowHeight;
        static List<MatrixColumn> matrixColumns = new List<MatrixColumn>();
        static StringBuilder bufferBuilder = new StringBuilder();
        static int delayMs = 50;
        static double greenHue = 120;
        static double baseHue = greenHue; // Start with green (120 degrees in HSV)

        public static void Start()
        {
            var tempVisible = Console.CursorVisible;
            Console.CursorVisible = false;
            InitializeMatrixColumns();
            for (int i = 0; i < Console.WindowHeight; i++)
            {
                UpdateMatrixColumns();
            }
            while (true)
            {
                try
                {
                    SizeCheck();
                    UpdateMatrixColumns();
                    RenderMatrixRain();
                    Thread.Sleep(delayMs);
                    if (Console.KeyAvailable)
                    {
                        var ki = Console.ReadKey(true);
                        switch (ki.Key)
                        {
                            case ConsoleKey.Escape:
                                Console.CursorVisible = tempVisible;
                                UI.Wipe();
                                return;
                            case ConsoleKey.Spacebar:
                                continue;
                            case ConsoleKey.Subtract:
                                if (delayMs > 9) delayMs -= 10;
                                break;
                            case ConsoleKey.Add:
                                delayMs += 10;
                                break;
                            case ConsoleKey.LeftArrow:
                                baseHue = (baseHue - 10 + 360) % 360;
                                break;
                            case ConsoleKey.RightArrow:
                                baseHue = (baseHue + 10) % 360;
                                break;

                            //quick color switch
                            case ConsoleKey.R:
                                //red
                                baseHue = (greenHue + 240) % 360;
                                break;
                            case ConsoleKey.G:
                                //green
                                baseHue = (greenHue + 0) % 360;
                                break;
                            case ConsoleKey.B:
                                //blue
                                baseHue = (greenHue + 120) % 360;
                                break;
                            case ConsoleKey.O:
                                //orange
                                baseHue = (greenHue + 270) % 360;
                                break;
                            case ConsoleKey.C:
                                //cyan
                                baseHue = (greenHue + 50) % 360;
                                break;
                            case ConsoleKey.P:
                                //purple
                                baseHue = (greenHue + 160) % 360;
                                break;
                            case ConsoleKey.M:
                                //magenta
                                baseHue = (greenHue + 200) % 360;
                                break;
                            case ConsoleKey.Y:
                                //yellow
                                baseHue = (greenHue + 300) % 360;
                                break;
                        }
                    }
                }
                catch (Exception)
                {
                    // Handle or log exception
                }
            }
        }

        static void SizeCheck()
        {
            if (consoleWidth != Console.WindowWidth || consoleHeight != Console.WindowHeight)
            {
                consoleWidth = Console.WindowWidth;
                consoleHeight = Console.WindowHeight;
                matrixColumns.Clear();
                InitializeMatrixColumns();
                UI.Wipe();
                for (int i = 0; i < Console.WindowHeight; i++)
                {
                    UpdateMatrixColumns();
                }
            }
        }

        static void ResetMatrix()
        {
            delayMs = 50;
            baseHue = 120;
            consoleWidth = Console.WindowWidth;
            consoleHeight = Console.WindowHeight;
            matrixColumns.Clear();
            InitializeMatrixColumns();
            UI.Wipe();
        }

        static void InitializeMatrixColumns()
        {
            int columnCount = consoleWidth / 2; // Each character takes 2 console columns
            for (int i = 0; i < columnCount; i++)
            {
                matrixColumns.Add(new MatrixColumn(i, -Random.Shared.Next(1, consoleHeight), Random.Shared.Next(5, 20)));
            }
        }

        static void UpdateMatrixColumns()
        {
            foreach (var column in matrixColumns)
            {
                column.Update();
                if (column.Y - column.Length > consoleHeight)
                {
                    column.Reset(-Random.Shared.Next(1, consoleHeight));
                }
            }
        }

        static void RenderMatrixRain()
        {
            bufferBuilder.Clear();
            bufferBuilder.Append("\x1b[H"); // Move cursor to home position

            int lastColor = -1;
            for (int y = 0; y < consoleHeight; y++)
            {
                for (int x = 0; x < consoleWidth / 2; x++)
                {
                    var column = matrixColumns.Find(c => c.X == x && c.Y >= y && c.Y - c.Length < y);
                    if (column != null)
                    {
                        int depth = column.Y - y;
                        char character = column.GetCharAt(depth);
                        int color = (depth == 0) ? 0xFFFFFF : GetColor(depth, column.Length);

                        if (color != lastColor)
                        {
                            bufferBuilder.Append(UI.GetColorCode(color));
                            lastColor = color;
                        }

                        bufferBuilder.Append(character);
                    }
                    else
                    {
                        if (lastColor != 0)
                        {
                            bufferBuilder.Append("\x1b[0m"); // Reset color
                            lastColor = 0;
                        }
                        bufferBuilder.Append("　"); // Full-width space
                    }
                }
            }

            Console.Write(bufferBuilder);
        }

        static int GetColor(int depth, int columnLength)
        {
            if (depth == 0) return 0xFFFFFF; // White for the leading character

            double saturation = 1.0;
            double minBrightness = 0.1; // Minimum brightness for the last character
            double brightnessRange = 1.0 - minBrightness;
            double relativeBrightness = 1.0 - (double)depth / columnLength;
            double value = minBrightness + (relativeBrightness * brightnessRange);

            return UI.HSVToRGB(baseHue, saturation, value);
        }
    }

    class MatrixColumn
    {
        public int X { get; private set; }
        public int Y { get; private set; }
        public int Length { get; private set; }
        private int speed;
        private char[] characters;
        private static readonly char[] matrixCharacters = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン".ToCharArray();

        public MatrixColumn(int x, int y, int length)
        {
            X = x;
            Y = y;
            Length = length;
            speed = Random.Shared.Next(1, 3);
            characters = new char[length];
            RegenerateCharacters();
        }

        public void Update()
        {
            for (int i = 0; i < speed; i++)
            {
                Y++;
                // Shift the existing characters down
                Array.Copy(characters, 0, characters, 1, Length - 1);
                // Generate a new character for the leading position
                characters[0] = matrixCharacters[Random.Shared.Next(matrixCharacters.Length)];
            }
        }

        public void Reset(int newY)
        {
            Y = newY;
            Length = Random.Shared.Next(5, 20);
            speed = Random.Shared.Next(1, 3);
            characters = new char[Length];
            RegenerateCharacters();
        }

        private void RegenerateCharacters()
        {
            for (int i = 0; i < Length; i++)
            {
                characters[i] = matrixCharacters[Random.Shared.Next(matrixCharacters.Length)];
            }
        }

        public char GetCharAt(int depth)
        {
            if (depth >= 0 && depth < Length)
            {
                return characters[depth];
            }
            return ' ';
        }
    }
}

add WASD to make them fall in different directions than down