using System.Numerics;
using Raylib_cs;

namespace Tiles;

public class Game
{
    const int _tileSize = 8;
    const int _worldWidth = 2000;
    const int _worldHeight = 1000;
    
    private int _screenWidth;
    private int _screenHeight;


    private Dictionary<string, int> _tileNameToId = new();
    private Dictionary<int, Tile> _tileIdToTile = new();
    private Dictionary<(int x, int y), int> _worldGrid = new();
    
    private Vector2 _playerPosition;
    private float _playerSpeed = 2f;
    
    private Vector2 _pointerPosition = Vector2.Zero;
    
    private Camera2D _camera;

    private int _currentSelectedTileId = 1;
    private int _currentSelectedIndex = 0;
    
    public Game(int screenWidth, int screenHeight)
    {
        _screenWidth = screenWidth;
        _screenHeight = screenHeight;
        Raylib.InitWindow(_screenWidth, _screenHeight, "Tiles");
        Raylib.SetTargetFPS(60);
        
        _playerPosition = GetGlobalPositionFromGrid(500, 500);

        _camera = new Camera2D()
        {
            Target = _playerPosition,
            Offset = new Vector2(_screenWidth / 2, _screenHeight / 2),
            Rotation = 0f,
            Zoom = 4f
        };

        SetupTiles();
        
         GenerateWorld();

        while (!Raylib.WindowShouldClose())
        {
            HandleInput();
            _camera.Target = _playerPosition;
            
            Raylib.BeginDrawing();
            Raylib.ClearBackground(new Color(20, 70, 100, 255));
            Raylib.BeginMode2D(_camera);
            
            DrawWorld();
            DrawPlayer();
            DrawPointer();
            DrawDebug();
           
            Raylib.EndMode2D();
            Raylib.EndDrawing();
        }
    }

    private void HandleInput()
    {
        if (Raylib.IsKeyDown(KeyboardKey.W)) _playerPosition += new Vector2(0, -1) * _playerSpeed;
        if (Raylib.IsKeyDown(KeyboardKey.A)) _playerPosition += new Vector2(-1, 0) * _playerSpeed;
        if (Raylib.IsKeyDown(KeyboardKey.S)) _playerPosition += new Vector2(0, 1) * _playerSpeed;
        if (Raylib.IsKeyDown(KeyboardKey.D)) _playerPosition += new Vector2(1, 0) * _playerSpeed;

        if (Raylib.IsKeyReleased(KeyboardKey.Right))
        {
            _currentSelectedIndex = (_currentSelectedIndex + 1) % _tileNameToId.Count;
            if (_currentSelectedIndex == 0)
            {
                _currentSelectedIndex = 1;
            }
            _currentSelectedTileId = _currentSelectedIndex;
        }
        else if (Raylib.IsKeyReleased(KeyboardKey.Left))
        {
            _currentSelectedIndex = (_currentSelectedIndex - 1 + _tileNameToId.Count) % _tileNameToId.Count;
            if (_currentSelectedIndex == 0)
            {
                _currentSelectedIndex = _tileNameToId.Count - 1;
            }
            _currentSelectedTileId = _currentSelectedIndex;
        }

        
        if (Raylib.IsMouseButtonDown(MouseButton.Left))
        {
            SetTile((int)_pointerPosition.X, (int)_pointerPosition.Y, _currentSelectedTileId);
        }
        if (Raylib.IsMouseButtonDown(MouseButton.Right))
        {
            SetTile((int)_pointerPosition.X, (int)_pointerPosition.Y, 0);
        }
        
        if (Raylib.IsKeyReleased(KeyboardKey.Up)) _playerSpeed += 1f;
        if (Raylib.IsKeyReleased(KeyboardKey.Down)) _playerSpeed -= 1f;
    }

    private void SetupTiles()
    {
        _tileNameToId.Add("air", 0);

        int index = 1;
        
        foreach (var path in Directory.GetFiles("data/core/tiles/", "*.json"))
        {
            Tile tile;

            try
            {
                tile = Tile.GenerateTileFromJson(path);
        
                if (tile == null) throw new Exception($"Couldn't load tile {path}");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
    
            Console.WriteLine($"Tile {tile.Name} loaded");
            _tileIdToTile.Add(index, tile);
            _tileNameToId.Add(tile.Name, index);
            index++;
        }
    }

    private void DrawWorld()
    {
        // Calculate the boundaries of the visible region in grid coordinates
        int startX = Math.Max((int)(_camera.Target.X - (_screenWidth / 2 / _camera.Zoom)) / _tileSize, 0);
        int startY = Math.Max((int)(_camera.Target.Y - (_screenHeight / 2 / _camera.Zoom)) / _tileSize, 0);
        int endX = Math.Min((int)(_camera.Target.X + (_screenWidth / 2 / _camera.Zoom)) / _tileSize, _worldWidth - 1);
        int endY = Math.Min((int)(_camera.Target.Y + (_screenHeight / 2 / _camera.Zoom)) / _tileSize, _worldHeight - 1);

        // Loop only through the visible tiles
        for (int x = startX; x <= endX; x++)
        {
            for (int y = startY; y <= endY; y++)
            {
                (int x, int y) position = new(x, y);
                if (_worldGrid.TryGetValue(position, out var tileType) && tileType != 0)
                {
                    Raylib.DrawTexture(_tileIdToTile[tileType].Texture, x * _tileSize, y * _tileSize, Color.White);
                }
            }
        }
    }


    private void DrawPlayer()
    {
        Raylib.DrawRectangle((int)_playerPosition.X, (int)_playerPosition.Y, 16, 32, new(0,0,100,255));
    }

    private void DrawPointer()
    {
        Vector2 mousePosition = Raylib.GetMousePosition();
        Vector2 worldPosition = Raylib.GetScreenToWorld2D(mousePosition, _camera);
        Vector2 gridPosition = new Vector2((int)(worldPosition.X / _tileSize), (int)(worldPosition.Y / _tileSize));
        _pointerPosition = new Vector2(gridPosition.X, gridPosition.Y);
        
        Raylib.DrawRectangle((int)_pointerPosition.X*_tileSize, (int)_pointerPosition.Y*_tileSize, _tileSize, _tileSize, new Color(255, 255, 0, 80));
    }
    
    private void DrawDebug()
    {
        int textSize = 10;
        
        int textPositionX = (int)(_camera.Target.X - _screenWidth / 2 / _camera.Zoom) + 10;
        int textPositionY = (int)(_camera.Target.Y - _screenHeight / 2 / _camera.Zoom) + 10;
        
        string lookingAt = "";
        if (GetTile((int)_pointerPosition.X, (int)_pointerPosition.Y) != 0)
        {
            lookingAt = _tileIdToTile[GetTile((int)_pointerPosition.X, (int)_pointerPosition.Y)].Name;
        }
        else
        {
            lookingAt = "air";
        }

        Raylib.DrawText("FPS: " + Raylib.GetFPS(), textPositionX, textPositionY, textSize, Color.White);
        Raylib.DrawText(_tileIdToTile[_currentSelectedTileId].Name, textPositionX, textPositionY + (textSize*2), textSize, Color.White);
        Raylib.DrawText("Coords: " + GetGridPositionFromGlobalPosition((int)_playerPosition.X, (int)_playerPosition.Y).ToString().ToUpper(), 
            textPositionX, textPositionY + (textSize * 3), textSize, Color.White);
        Raylib.DrawText("Speed: " + _playerSpeed, textPositionX, textPositionY + (textSize*4), textSize, Color.White);
        Raylib.DrawText("Pointer: " + _pointerPosition + " " + lookingAt, textPositionX, textPositionY + (textSize*5), textSize, Color.White);
    }
    
    private void GenerateWorld()
    {
        int totalHeight = _worldHeight;

        int grassHeight = (int)(totalHeight * 0.01);
        int dirtHeight = (int)(totalHeight * 0.10);
        int airHeight = (int)(totalHeight * 0.50);
        int rockHeight = totalHeight - (airHeight + grassHeight + dirtHeight);

        for (int x = 0; x < _worldWidth; x++)
        {
            for (int y = 0; y < totalHeight; y++)
            {
                string tileType;
                
                if (y >= airHeight && y < airHeight + grassHeight)
                {
                    tileType = "grass";
                    SetTile(x, y, _tileNameToId[tileType]);
                }
                else if (y >= airHeight + grassHeight && y < airHeight + grassHeight + dirtHeight)
                {
                    tileType = "dirt";
                    SetTile(x, y, _tileNameToId[tileType]);
                }
                else if (y >= airHeight + grassHeight + dirtHeight)
                {
                    tileType = "rock";
                    SetTile(x, y, _tileNameToId[tileType]);
                }
            }
        }
    }

    private Vector2 GetGridPositionFromGlobalPosition(int x, int y)
    {
        int gridX = x / _tileSize;
        int gridY = y / _tileSize;
        
        if (gridX >= 0 && gridX < _worldWidth && gridY >= 0 && gridY < _worldHeight)
        {
            return new Vector2(gridX, gridY);
        }
        
        return new Vector2(0, 0);
    }

    private void SetTile(int x, int y, int id)
    {
        _worldGrid[(x, y)] = id;
    }

    private int GetTile(int x, int y)
    {
        if (_worldGrid.ContainsKey((x,y))) return _worldGrid[(x,y)];
        return 0;
    }

    private Vector2 GetGlobalPositionFromGrid(int x, int y)
    {
        return new(x * _tileSize, y * _tileSize);
    }
}