using System.Text.Json.Serialization;

namespace API.Models.Game;

public readonly struct Coordinates
{
    [JsonConstructor]
    public Coordinates(int x, int y)
    {
        X = x;
        Y = y;
    }

    [JsonPropertyName("x")]
    public int X { get; init; }

    [JsonPropertyName("y")]
    public int Y { get; init; }
}

public enum DropResult
{
    OutOfGameField,
    NotAllowedPlayer,
    ColumnFull,
    InvalidFieldValue,
    Placed
}

public enum FieldState
{
    OutOfGameField,
    Empty,
    OccupiedRed,
    OccupiedYellow,
    InvalidFieldValue
}

public class GameField(Coordinates gFs)
{
    public Coordinates GFs = gFs;
    public int[,] CurrentField { get; } = new int[gFs.Y, gFs.X];
    public int[,] BackupField { get; } = new int[gFs.Y, gFs.X];

    public DropResult Drop(int column, int player)
    {
        if (column < 0 || column >= CurrentField.GetLength(1))
            return DropResult.OutOfGameField;

        if (player != 1 && player != 2)
            return DropResult.NotAllowedPlayer;

        int rows = CurrentField.GetLength(0);

        for (int y = rows - 1; y >= 0; y--)
        {
            int currentValue = CurrentField[y, column];

            if (currentValue == 0)
            {
                CreateSave();
                CurrentField[y, column] = player;
                return DropResult.Placed;
            }

            if (currentValue != 1 && currentValue != 2)
                return DropResult.InvalidFieldValue;
        }

        return DropResult.ColumnFull;
    }

    public FieldState CheckField(Coordinates coordinates)
    {
        if (coordinates.X < 0 || coordinates.X >= CurrentField.GetLength(1) ||
            coordinates.Y < 0 || coordinates.Y >= CurrentField.GetLength(0))
            return FieldState.OutOfGameField;

        var currentValue = CurrentField[coordinates.Y, coordinates.X];

        return currentValue switch
        {
            0 => FieldState.Empty,
            1 => FieldState.OccupiedRed,
            2 => FieldState.OccupiedYellow,
            _ => FieldState.InvalidFieldValue
        };
    }

    public bool IsFull()
    {
        int rows = CurrentField.GetLength(0);
        int cols = CurrentField.GetLength(1);
        for (int y = 0; y < rows; y++)
        {
            for (int x = 0; x < cols; x++)
            {
                if (CurrentField[y, x] == 0)
                    return false;
            }
        }
        return true;
    }

    private int CountInDirection(int startX, int startY, int dx, int dy, int player)
    {
        var count = 0;
        var x = startX;
        var y = startY;

        while (
            x >= 0 && x < CurrentField.GetLength(1) &&
            y >= 0 && y < CurrentField.GetLength(0) &&
            CurrentField[y, x] == player)
        {
            count++;
            x += dx;
            y += dy;
        }

        return count;
    }

    private bool CheckLine(int startX, int startY, int dx, int dy, int player)
    {
        int count = CountInDirection(startX + dx, startY + dy, dx, dy, player);
        count += CountInDirection(startX - dx, startY - dy, -dx, -dy, player);
        return count + 1 >= 4;
    }

    public int CheckForWin()
    {
        int rows = CurrentField.GetLength(0);
        int cols = CurrentField.GetLength(1);

        (int dx, int dy)[] directions = [(1, 0), (0, 1), (1, 1), (-1, 1)];

        for (int y = 0; y < rows; y++)
        {
            for (int x = 0; x < cols; x++)
            {
                int player = CurrentField[y, x];

                if (player != 1 && player != 2) continue;

                foreach (var (dx, dy) in directions)
                {
                    if (CheckLine(x, y, dx, dy, player))
                        return player;
                }
            }
        }

        return 0;
    }

    private void CreateSave()
    {
        Array.Copy(CurrentField, BackupField, CurrentField.Length);
    }
}
