using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MazePathfinding
{
    public class Cell
    {
        public int Row { get; set; }
        public int Col { get; set; }
        public int X { get; set; }
        public int Y { get; set; }
        public bool[] Walls { get; set; } = new bool[4]; // top, right, bottom, left
        public bool Visited { get; set; }

        public Cell(int row, int col, int cellSize)
        {
            Row = row;
            Col = col;
            X = col * cellSize;
            Y = row * cellSize;
            for (int i = 0; i < 4; i++) Walls[i] = true;
            Visited = false;
        }

        public void Draw(Graphics g, int cellSize)
        {
            if (Visited)
            {
                using (var brush = new SolidBrush(Color.White))
                    g.FillRectangle(brush, X, Y, cellSize, cellSize);
            }

            using (var pen = new Pen(Color.Black, 2))
            {
                if (Walls[0]) g.DrawLine(pen, X, Y, X + cellSize, Y);
                if (Walls[1]) g.DrawLine(pen, X + cellSize, Y, X + cellSize, Y + cellSize);
                if (Walls[2]) g.DrawLine(pen, X + cellSize, Y + cellSize, X, Y + cellSize);
                if (Walls[3]) g.DrawLine(pen, X, Y + cellSize, X, Y);
            }
        }

        public void Highlight(Graphics g, Color color, int cellSize)
        {
            using (var brush = new SolidBrush(color))
                g.FillRectangle(brush, X, Y, cellSize, cellSize);
        }
    }

    public class MazeForm : Form
    {
        private const int WIDTH = 600;
        private int rows = 20;
        private int cellSize;
        private Cell[,] grid;
        private Cell start;
        private Cell end;
        private List<Cell> path = new List<Cell>();
        private HashSet<Cell> openSet = new HashSet<Cell>();
        private System.Windows.Forms.Timer timer;
        private bool isSearching = false;

        public MazeForm()
        {
            Text = "A* Maze Pathfinding - Press SPACE to solve, UP/DOWN to resize";
            ClientSize = new Size(WIDTH, WIDTH);
            DoubleBuffered = true;
            KeyPreview = true;
            FormBorderStyle = FormBorderStyle.FixedSingle;
            MaximizeBox = false;

            cellSize = WIDTH / rows;
            CreateMaze(rows);

            timer = new System.Windows.Forms.Timer { Interval = 16 };
            timer.Tick += (s, e) => Invalidate();
            timer.Start();

            this.KeyDown += MazeForm_KeyDown;
            this.Paint += MazeForm_Paint;
        }

        private void CreateMaze(int r)
        {
            rows = r;
            cellSize = WIDTH / rows;
            grid = new Cell[rows, rows];

            for (int i = 0; i < rows; i++)
                for (int j = 0; j < rows; j++)
                    grid[i, j] = new Cell(i, j, cellSize);

            GenerateMaze(grid[0, 0]);
            start = grid[0, 0];
            end = grid[rows - 1, rows - 1];
            path.Clear();
            openSet.Clear();
        }

        private void GenerateMaze(Cell current)
        {
            var stack = new Stack<Cell>();
            current.Visited = true;
            var random = new Random();

            while (true)
            {
                var neighbors = new List<Cell>();
                int r = current.Row, c = current.Col;

                if (r > 0 && !grid[r - 1, c].Visited) neighbors.Add(grid[r - 1, c]);
                if (r < rows - 1 && !grid[r + 1, c].Visited) neighbors.Add(grid[r + 1, c]);
                if (c > 0 && !grid[r, c - 1].Visited) neighbors.Add(grid[r, c - 1]);
                if (c < rows - 1 && !grid[r, c + 1].Visited) neighbors.Add(grid[r, c + 1]);

                if (neighbors.Count > 0)
                {
                    var nextCell = neighbors[random.Next(neighbors.Count)];
                    stack.Push(current);
                    RemoveWalls(current, nextCell);
                    nextCell.Visited = true;
                    current = nextCell;
                }
                else if (stack.Count > 0)
                {
                    current = stack.Pop();
                }
                else
                {
                    break;
                }
            }
        }

        private void RemoveWalls(Cell a, Cell b)
        {
            int dx = a.Col - b.Col;
            int dy = a.Row - b.Row;

            if (dx == 1)
            {
                a.Walls[3] = false;
                b.Walls[1] = false;
            }
            else if (dx == -1)
            {
                a.Walls[1] = false;
                b.Walls[3] = false;
            }

            if (dy == 1)
            {
                a.Walls[0] = false;
                b.Walls[2] = false;
            }
            else if (dy == -1)
            {
                a.Walls[2] = false;
                b.Walls[0] = false;
            }
        }

        private async void MazeForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (isSearching) return;

            if (e.KeyCode == Keys.Space)
            {
                isSearching = true;
                path = await Task.Run(() => AStar(start, end));
                isSearching = false;
                Invalidate();
            }
            else if (e.KeyCode == Keys.Up)
            {
                CreateMaze(rows + 1);
                Invalidate();
            }
            else if (e.KeyCode == Keys.Down && rows > 5)
            {
                CreateMaze(rows - 1);
                Invalidate();
            }
        }

        private int Heuristic(Cell a, Cell b)
        {
            return Math.Abs(a.Row - b.Row) + Math.Abs(a.Col - b.Col);
        }

        private List<Cell> AStar(Cell startCell, Cell endCell)
        {
            var openSetQueue = new SortedSet<(int f, int count, Cell cell)>(
                Comparer<(int f, int count, Cell cell)>.Create((a, b) =>
                {
                    int cmp = a.f.CompareTo(b.f);
                    return cmp != 0 ? cmp : a.count.CompareTo(b.count);
                }));

            int count = 0;
            openSetQueue.Add((0, count, startCell));
            var cameFrom = new Dictionary<Cell, Cell>();
            var gScore = new Dictionary<Cell, int>();
            var fScore = new Dictionary<Cell, int>();

            for (int i = 0; i < rows; i++)
                for (int j = 0; j < rows; j++)
                {
                    gScore[grid[i, j]] = int.MaxValue;
                    fScore[grid[i, j]] = int.MaxValue;
                }

            gScore[startCell] = 0;
            fScore[startCell] = Heuristic(startCell, endCell);

            openSet.Clear();
            openSet.Add(startCell);

            while (openSetQueue.Count > 0)
            {
                var current = openSetQueue.Min.cell;
                openSetQueue.Remove(openSetQueue.Min);
                openSet.Remove(current);

                if (current == endCell)
                {
                    var pathResult = new List<Cell>();
                    while (cameFrom.ContainsKey(current))
                    {
                        current = cameFrom[current];
                        pathResult.Add(current);
                    }
                    openSet.Clear();
                    return pathResult;
                }

                foreach (var neighbor in GetNeighbors(current))
                {
                    int tempG = gScore[current] + 1;
                    if (tempG < gScore[neighbor])
                    {
                        cameFrom[neighbor] = current;
                        gScore[neighbor] = tempG;
                        fScore[neighbor] = tempG + Heuristic(neighbor, endCell);

                        if (!openSet.Contains(neighbor))
                        {
                            count++;
                            openSetQueue.Add((fScore[neighbor], count, neighbor));
                            openSet.Add(neighbor);
                        }
                    }
                }

                try
                {
                    Invoke(new Action(() => Invalidate()));
                }
                catch { }
                Thread.Sleep(100);
            }

            openSet.Clear();
            return new List<Cell>();
        }

        private List<Cell> GetNeighbors(Cell cell)
        {
            var neighbors = new List<Cell>();
            int r = cell.Row, c = cell.Col;

            if (!cell.Walls[0] && r > 0) neighbors.Add(grid[r - 1, c]);
            if (!cell.Walls[1] && c < rows - 1) neighbors.Add(grid[r, c + 1]);
            if (!cell.Walls[2] && r < rows - 1) neighbors.Add(grid[r + 1, c]);
            if (!cell.Walls[3] && c > 0) neighbors.Add(grid[r, c - 1]);

            return neighbors;
        }

        private void MazeForm_Paint(object sender, PaintEventArgs e)
        {
            var g = e.Graphics;
            g.Clear(Color.White);

            for (int i = 0; i < rows; i++)
                for (int j = 0; j < rows; j++)
                    grid[i, j].Draw(g, cellSize);

            foreach (var cell in openSet)
                cell.Highlight(g, Color.LimeGreen, cellSize);

            foreach (var cell in path)
                cell.Highlight(g, Color.Yellow, cellSize);

            start.Highlight(g, Color.Blue, cellSize);
            end.Highlight(g, Color.Red, cellSize);

            // Redraw walls over highlights
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < rows; j++)
                    grid[i, j].Draw(g, cellSize);
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MazeForm());
        }
    }
}