using System.Diagnostics;

namespace laba3.Subprograms
{
    /// <summary>
    /// TUI classic game "Snake";
    /// The player controls the snake.
    /// The player has to eat apples to increase the length of the snake
    /// and try not to crash into the wall or themselves;
    /// </summary>
    internal class SnakeGame
    {
        int size_x;
        int size_y;
        int score;
        int size_xy;
        Level difficulty;
        Tiles[,] world;
        Snake snake;
        Random rnd;

        private bool gameover;
        /// <summary>
        /// Game world constructor;
        /// </summary>
        /// <param name="difficulty">Game speed</param>
        /// <param name="size_x">Game world size dim x</param>
        /// <param name="size_y">Game world size dim y</param>
        public SnakeGame(Level difficulty, int size_x, int size_y)
        {
            this.size_x = size_x;
            this.size_y = size_y;
            this.difficulty = difficulty;
            world = new Tiles[size_x, size_y];
            rnd = new Random();
            score = 0;
            size_xy = size_x * size_y;
        }
        /// <summary>
        /// Start game;
        /// Console will be clean!;
        /// </summary>
        public void start()
        {
            int msPerTick = difficulty switch
            {
                Level.Low => 500,
                Level.Medium => 250,
                Level.High => 100,
                _ => 500,
            };
            gameover = false;

            Console.Clear();
            Console.CursorVisible = false;
            snake = new Snake(new Point(size_x / 2, size_y / 2, this), 1, Direction.Right, this);

            GenerateFood();

            DrawBackground();
            DrawWorld();

            Stopwatch tickTimer = new Stopwatch();
            while (!gameover)
            {
                tickTimer.Restart();
                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo key = Console.ReadKey(false);
                    snake.HandleKey(key.Key);
                    while (Console.KeyAvailable)
                        Console.ReadKey(false);
                }
                snake.Move();
                long delta = tickTimer.ElapsedMilliseconds;

                Console.SetCursorPosition(1, 0);
                if (delta > msPerTick)
                    Console.ForegroundColor = ConsoleColor.Red;
                Console.Write(delta);

                Thread.Sleep((int)Math.Max(0, msPerTick - delta));
            }
            onStop();
        }

        /// <summary>
        /// Stop game;
        /// </summary>
        public void stop()
        {
            gameover = true;
        }

        private void onStop()
        {
            string gameOverTitle =
@"
 ###                  ##            
#     ## ####   #    #  # # #  #  ##
# ## # # # # # ###   #  # # # ### # 
#  # # # # # # #     #  #  #  #   # 
 ###  ## # # #  ##    ##   #   ## # 
";
            Console.Clear();
            printArt(gameOverTitle, (Console.WindowWidth - 36) / 2, 4, ConsoleColor.DarkYellow, ConsoleColor.DarkGray);

            Console.ResetColor();

            string scoreString = $"Score: {score}";
            Console.SetCursorPosition((Console.WindowWidth - scoreString.Length) / 2, 11);
            Console.Write(scoreString);
            Console.SetCursorPosition((Console.WindowWidth - "Press Enter to continue".Length) / 2, 14);
            Console.CursorVisible = true;
        }

        private void printArt(string art, int x, int y, ConsoleColor primaryColor, ConsoleColor shadowColor)
        {
            int row = 0;
            Console.SetCursorPosition(x, y);
            char shadow = ' ';
            foreach (char ch in art)
            {
                if (ch == '\n')
                {
                    if (shadow == '#')
                    {
                        Console.BackgroundColor = shadowColor;
                        Console.Write(' ');
                    }
                    Console.SetCursorPosition(x, y++ + row);
                    Thread.Sleep(25);
                    shadow = ch;
                }
                else if (ch == ' ')
                {
                    if (shadow == '#')
                    {
                        Console.BackgroundColor = shadowColor;
                        Console.Write(' ');
                    }
                    else
                        Console.CursorLeft++;
                    shadow = ch;
                }
                else if (ch == '#')
                {
                    Console.BackgroundColor = primaryColor;
                    Console.Write(' ');
                    shadow = ch;
                }

            }
        }

        private void addScore()
        {
            score += difficulty switch
            {
                Level.Low => 10,
                Level.Medium => 50,
                Level.High => 100,
                _ => 1,
            };
        }


        /// <summary>
        /// Generate food in random place;
        /// </summary>
        public void GenerateFood()
        {
            int x;
            int y;
            do
            {
                x = rnd.Next(size_x);
                y = rnd.Next(size_y);
            } while (world[x, y] != Tiles.Void);
            world[x, y] = Tiles.Food;
            DrawTile(x, y);
        }

        private void DrawBackground()
        {
            for (int x = 1; x < (size_x + 1) * 2 + 1; x++)
            {
                Console.SetCursorPosition(x, 1);
                Console.Write('#');
                Console.SetCursorPosition(x, size_y + 2);
                Console.Write('#');
            }
            for (int y = 1; y < size_y + 2; y++)
            {
                Console.SetCursorPosition(1, y);
                Console.Write('#');

                Console.SetCursorPosition(size_x * 2 + 1, y);
                Console.Write('#');
            }
        }

        private void DrawTile(int x, int y)
        {
            char symbol;
            switch (world[x, y])
            {
                case Tiles.Void:
                    symbol = ' ';
                    break;
                case Tiles.Snake:
                    symbol = '*';
                    Console.ForegroundColor = ConsoleColor.DarkRed;
                    break;
                case Tiles.Food:
                    symbol = '@';
                    Console.ForegroundColor = ConsoleColor.Red;
                    break;
                default:
                    symbol = '?';
                    break;
            };
            //Console.BackgroundColor = ConsoleColor.DarkGreen;
            Console.SetCursorPosition((x + 1) * 2, y + 2);
            Console.Write(symbol);

            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.White;
        }
        private void DrawWorld()
        {
            for (int x = 0; x < size_x; x++)
            {
                for (int y = 0; y < size_y; y++)
                {
                    DrawTile(x, y);
                }
            }
        }
        private enum Tiles
        {
            Void,
            Snake,
            Food
        }
        public enum Level
        {
            Low,
            Medium,
            High
        }
        private class Snake
        {
            private SnakeGame game;
            private List<Point> body;
            private Direction direction;

            public Snake(Point tail, int length, Direction initialDirection, SnakeGame game)
            {
                this.game = game;
                body = new List<Point>();
                direction = initialDirection;

                for (int i = 0; i < length; i++)
                {
                    Point p = new Point(tail.X, tail.Y, game);
                    body.Add(p);
                }
            }

            public void Move()
            {
                Point head = GetNextPoint();
                Point tail = body.First();

                if (head.X < 0 || head.Y < 0 || head.X >= game.size_x || head.Y >= game.size_y)
                {
                    game.stop();
                    return;
                }

                if (game.world[head.X, head.Y] == Tiles.Snake)
                {
                    game.stop();
                    return;
                }


                body.Add(head);
                if (game.world[head.X, head.Y] != Tiles.Food)
                {
                    body.Remove(tail);
                    tail.UpdateWorld(Tiles.Void);
                }
                else
                {
                    game.addScore();
                    game.GenerateFood();
                    if (body.Count >= game.size_xy) game.stop();
                }
                head.UpdateWorld(Tiles.Snake);
            }

            public Point GetNextPoint()
            {
                Point head = body.Last();
                Point nextPoint = new Point(head.X, head.Y, game);

                switch (direction)
                {
                    case Direction.Right:
                        nextPoint.X++;
                        break;
                    case Direction.Left:
                        nextPoint.X--;
                        break;
                    case Direction.Up:
                        nextPoint.Y--;
                        break;
                    case Direction.Down:
                        nextPoint.Y++;
                        break;
                }

                return nextPoint;
            }

            public void HandleKey(ConsoleKey key)
            {
                switch (key)
                {
                    case ConsoleKey.LeftArrow:
                        if (direction != Direction.Right)
                            direction = Direction.Left;
                        break;
                    case ConsoleKey.RightArrow:
                        if (direction != Direction.Left)
                            direction = Direction.Right;
                        break;
                    case ConsoleKey.UpArrow:
                        if (direction != Direction.Down)
                            direction = Direction.Up;
                        break;
                    case ConsoleKey.DownArrow:
                        if (direction != Direction.Up)
                            direction = Direction.Down;
                        break;
                }
            }
        }

        enum Direction
        {
            Left,
            Right,
            Up,
            Down
        }

        private class Point
        {
            private SnakeGame game { get; set; }
            public int X { get; set; }
            public int Y { get; set; }

            public Point(int x, int y, SnakeGame game)
            {
                this.game = game;
                X = x;
                Y = y;
            }

            public void UpdateWorld(Tiles tile)
            {
                game.world[X, Y] = tile;
                game.DrawTile(X, Y);
            }
        }
    }
}