using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Tumble
{
    enum Direction
    {
        Down,
        Up,
        Left,
        Right
    }

    class MatrixRain
    {
        static int consoleWidth = Console.WindowWidth;
        static int consoleHeight = Console.WindowHeight;
        static List<MatrixStream> matrixStreams = new List<MatrixStream>();
        static StringBuilder bufferBuilder = new StringBuilder();
        static int delayMs = 50;
        static double greenHue = 120;
        static double baseHue = greenHue; // Start with green (120 degrees in HSV)
        static Direction currentDirection = Direction.Down;

        public static void Start()
        {
            var tempVisible = Console.CursorVisible;
            Console.CursorVisible = false;
            InitializeMatrixStreams();

            // Initial Update to populate the screen
            for (int i = 0; i < Console.WindowHeight; i++)
            {
                UpdateMatrixStreams();
            }

            while (true)
            {
                try
                {
                    SizeCheck();
                    UpdateMatrixStreams();
                    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 > 10) delayMs -= 10;
                                break;
                            case ConsoleKey.Add:
                                delayMs += 10;
                                break;
                            case ConsoleKey.W:
                                if (currentDirection != Direction.Up)
                                {
                                    currentDirection = Direction.Up;
                                    ResetMatrix();
                                }
                                break;
                            case ConsoleKey.S:
                                if (currentDirection != Direction.Down)
                                {
                                    currentDirection = Direction.Down;
                                    ResetMatrix();
                                }
                                break;
                            case ConsoleKey.A:
                                if (currentDirection != Direction.Left)
                                {
                                    currentDirection = Direction.Left;
                                    ResetMatrix();
                                }
                                break;
                            case ConsoleKey.D:
                                if (currentDirection != Direction.Right)
                                {
                                    currentDirection = Direction.Right;
                                    ResetMatrix();
                                }
                                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 if needed
                }
            }
        }

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

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

        static void InitializeMatrixStreams()
        {
            switch (currentDirection)
            {
                case Direction.Down:
                case Direction.Up:
                    {
                        int columnCount = consoleWidth / 2; // Each character takes 2 console columns
                        for (int i = 0; i < columnCount; i++)
                        {
                            int x = i * 2;
                            int y = currentDirection == Direction.Down ? -Random.Shared.Next(1, consoleHeight) : consoleHeight + Random.Shared.Next(1, consoleHeight);
                            matrixStreams.Add(new MatrixStream(x, y, Random.Shared.Next(5, 20), currentDirection));
                        }
                        break;
                    }
                case Direction.Left:
                case Direction.Right:
                    {
                        int rowCount = consoleHeight;
                        for (int i = 0; i < rowCount; i++)
                        {
                            int y = i;
                            int x = currentDirection == Direction.Right ? -Random.Shared.Next(1, consoleWidth) : consoleWidth + Random.Shared.Next(1, consoleWidth);
                            matrixStreams.Add(new MatrixStream(x, y, Random.Shared.Next(5, 20), currentDirection));
                        }
                        break;
                    }
            }
        }

        static void UpdateMatrixStreams()
        {
            foreach (var stream in matrixStreams)
            {
                stream.Update();
                bool outOfBounds = false;
                switch (currentDirection)
                {
                    case Direction.Down:
                        outOfBounds = stream.Y - stream.Length > consoleHeight;
                        break;
                    case Direction.Up:
                        outOfBounds = stream.Y < -stream.Length;
                        break;
                    case Direction.Left:
                        outOfBounds = stream.X < -stream.Length;
                        break;
                    case Direction.Right:
                        outOfBounds = stream.X - stream.Length > consoleWidth;
                        break;
                }

                if (outOfBounds)
                {
                    switch (currentDirection)
                    {
                        case Direction.Down:
                            stream.Reset(-Random.Shared.Next(1, consoleHeight));
                            break;
                        case Direction.Up:
                            stream.Reset(consoleHeight + Random.Shared.Next(1, consoleHeight));
                            break;
                        case Direction.Left:
                            stream.Reset(consoleWidth + Random.Shared.Next(1, consoleWidth));
                            break;
                        case Direction.Right:
                            stream.Reset(-Random.Shared.Next(1, consoleWidth));
                            break;
                    }
                }
            }
        }

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

            // Use a 2D array to store the final characters and their colors
            char[,] screenBuffer = new char[consoleHeight, consoleWidth];
            int[,] colorBuffer = new int[consoleHeight, consoleWidth];

            // Initialize buffers
            for (int y = 0; y < consoleHeight; y++)
            {
                for (int x = 0; x < consoleWidth; x++)
                {
                    screenBuffer[y, x] = '　'; // Full-width space
                    colorBuffer[y, x] = 0; // Default color
                }
            }

            // Populate buffers based on streams
            foreach (var stream in matrixStreams)
            {
                for (int i = 0; i < stream.Length; i++)
                {
                    int posX = stream.X;
                    int posY = stream.Y;

                    switch (stream.Direction)
                    {
                        case Direction.Down:
                            posY = stream.Y - i;
                            break;
                        case Direction.Up:
                            posY = stream.Y + i;
                            break;
                        case Direction.Left:
                            posX = stream.X + i;
                            break;
                        case Direction.Right:
                            posX = stream.X - i;
                            break;
                    }

                    if (posX < 0 || posX >= consoleWidth || posY < 0 || posY >= consoleHeight)
                        continue;

                    screenBuffer[posY, posX] = stream.GetCharAt(i);
                    colorBuffer[posY, posX] = (i == 0) ? 0xFFFFFF : GetColor(i, stream.Length);
                }
            }

            // Build the buffer string with colors
            int lastColor = -1;
            for (int y = 0; y < consoleHeight; y++)
            {
                for (int x = 0; x < consoleWidth - 1; x++) // Skip the last character
                {
                    int color = colorBuffer[y, x];
                    if (color != lastColor)
                    {
                        if (color == 0)
                        {
                            bufferBuilder.Append("\x1b[0m"); // Reset color
                        }
                        else
                        {
                            bufferBuilder.Append(UI.GetColorCode(color));
                        }
                        lastColor = color;
                    }
                    bufferBuilder.Append(screenBuffer[y, x]);
                }

                // Avoid appending a newline after the last line to prevent an extra new line
                if (y < consoleHeight - 1)
                {
                    bufferBuilder.AppendLine();
                }
            }

            // Reset color at the end
            bufferBuilder.Append("\x1b[0m");
            Console.Write(bufferBuilder);
        }

        static int GetColor(int depth, int streamLength)
        {
            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 / streamLength;
            double value = minBrightness + (relativeBrightness * brightnessRange);

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

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

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

        public void Update()
        {
            for (int i = 0; i < speed; i++)
            {
                switch (Direction)
                {
                    case Direction.Down:
                        Y++;
                        break;
                    case Direction.Up:
                        Y--;
                        break;
                    case Direction.Left:
                        X--;
                        break;
                    case Direction.Right:
                        X++;
                        break;
                }

                // Shift the existing characters
                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 newPosition)
        {
            switch (Direction)
            {
                case Direction.Down:
                case Direction.Up:
                    Y = newPosition;
                    break;
                case Direction.Left:
                case Direction.Right:
                    X = newPosition;
                    break;
            }
            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 ' ';
        }
    }
}

prevent the characters from appearing to be in inconsistent positions when falling and only make them on one straight trajectory, in either direction