using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Tilemaps;

public class MazeController : MonoBehaviour
{
    // Options
    public Vector2Int gridSize = new Vector2Int(1,1);
    // For debug mode
    public InputField gridSizeXField;
    public InputField gridSizeYField;
    private void Update()
    {
        if (gridSizeXField != null && gridSizeYField != null)
        {
            gridSize.x = int.Parse(gridSizeXField.text);
            gridSize.y = int.Parse(gridSizeYField.text);
        }
    }

    public Vector2Int roomSize = new Vector2Int(1, 1);
    public int wallSize = 1;
    public int externalWallSize = 0;
    public Tilemap wallTilemap;

    // Bidimensional array of the grid's rooms
    public RoomV2[,] roomGrid;
    //public GameObject[,] roomGrid;
    //public EnumRoomType[,] roomTypeGrid;

    // What to instantiate as a room
    //public GameObject roomObject;

    // Enum for opening walls between rooms
    private enum WallSide { UP, DOWN, LEFT, RIGHT }

    // When the gameObject is started, a default random maze is created
    private void Start()
    {
        roomGrid = new RoomV2[gridSize.x, gridSize.y];
        createMaze();
    }

    // Creates a maze
    public void createMaze()
    {
        reloadGrid();
        //roomTypeGrid = new EnumRoomType[gridSize.x, gridSize.y];
        fillTilemapWithExternalWall();
        emptyAllRooms();
        generatePath();
        instantiateRooms();
        //paintPath();
        //createAllRooms();
    }

    // Reloads the grid to rooms without type for size changes
    public void reloadGrid()
    {
        cleanMaze();
        roomGrid = new RoomV2[gridSize.x, gridSize.y];
        createAllRooms();
    }

    // Redo with same path, reroll rooms
    public void actionRerollRooms()
    {
        foreach (RoomV2 room in roomGrid)
        {
            room.rerollRoom();
        }
    }

    // Returns the starting tile of a room
    private Vector2Int getRoomCorner(int roomX, int roomY)
    {
        Vector2Int result = new Vector2Int();
        result.x = 1 + roomX * (wallSize + roomSize.x);
        result.y = 1 + (roomY * (wallSize + roomSize.y));
        return result;
    }

    // Fills the tilemap with ground tiles
    private void fillTilemap()
    {
        for(int h = 0; h <= gridSize.x * (roomSize.x + wallSize); h++)
        {
            for (int v = 0; v <= gridSize.y * (roomSize.y + wallSize); v++)
            {
                MyUtils.drawTile(wallTilemap, new Vector3Int(h,v,0), "Ground");
            }
        }
    }
    private void fillTilemapWithExternalWall()
    {
        for (int h = 0 - externalWallSize; h <= gridSize.x * (roomSize.x + wallSize) + externalWallSize; h++)
        {
            for (int v = 0 - externalWallSize; v <= gridSize.y * (roomSize.y + wallSize) + externalWallSize; v++)
            {
                MyUtils.drawTile(wallTilemap, new Vector3Int(h, v, 0), "Ground");
            }
        }
    }

    // Fills the given room's tiles with the given tile
    private void fillRoom(int roomX, int roomY, string tile)
    {
        Vector2Int startingTile = getRoomCorner(roomX, roomY);
        for (int h = startingTile.x; h < startingTile.x + roomSize.x; h++)
        {
            for (int v = startingTile.y; v < startingTile.y + roomSize.y; v++)
            {
                MyUtils.drawTile(wallTilemap, new Vector3Int(h, v, 0), tile);
            }

        }
    }
    private void emptyAllRooms()
    {
        for (int h = 0; h < gridSize.x; h++)
        {
            for (int v = 0; v < gridSize.y; v++)
            {
                fillRoom(h, v, null);
            }
        }
    }

    // Creates the rooms without a type
    private void createRoom(Vector2Int roomPosition)
    {
        Vector2Int position = getRoomCorner(roomPosition.x, roomPosition.y);
        roomGrid[roomPosition.x, roomPosition.y] = new RoomV2(gameObject.transform, new Vector3(position.x, position.y, 0), roomSize);
        //GameObject room = Instantiate(roomObject, gameObject.transform);
        //room.GetComponent<RoomV2>().setData(new Vector3(position.x, position.y, 0), roomSize, type);
        //roomGrid[roomPosition.x, roomPosition.y] = room;
    }
    private void createAllRooms()
    {
        for (int h = 0; h < gridSize.x; h++)
        {
            for (int v = 0; v < gridSize.y; v++)
            {
                createRoom(new Vector2Int(h, v));
            }
        }
    }

    // Opens the given side of the given room
    private void openWall(Vector2Int room,  WallSide side)
    {
        Vector2Int roomLoc;
        Vector3Int startTile;
        Vector2 center;

        switch (side)
        {
            case WallSide.UP:
                roomLoc = getRoomCorner(room.x, room.y);
                startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
                center = MyUtils.getRoomCenterV2(startTile, roomSize);
                for (int v = 0; v < wallSize; v++)
                {
                    MyUtils.drawTile(wallTilemap, new Vector3Int((int) center.x, startTile.y + roomSize.y + v, 0), null);
                    if(roomSize.x % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, startTile.y + roomSize.y + v, 0), null);
                }
                break;
            case WallSide.DOWN:
                roomLoc = getRoomCorner(room.x, room.y);
                startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
                center = MyUtils.getRoomCenterV2(startTile, roomSize);
                for (int v = 0; v < wallSize; v++)
                {
                    MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x, startTile.y - v - 1, 0), null);
                    if (roomSize.x % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, startTile.y - v - 1, 0), null);
                }
                break;
            case WallSide.RIGHT:
                roomLoc = getRoomCorner(room.x, room.y);
                startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
                for (int h = 0; h < wallSize; h++)
                {
                    MyUtils.drawTile(wallTilemap, new Vector3Int(startTile.x + roomSize.x + h, startTile.y + 1, 0), null);
                    MyUtils.drawTile(wallTilemap, new Vector3Int(startTile.x + roomSize.x + h, startTile.y + 2, 0), null);
                }
                break;
            case WallSide.LEFT:
                roomLoc = getRoomCorner(room.x, room.y);
                startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
                for (int h = 0; h < wallSize; h++)
                {
                    MyUtils.drawTile(wallTilemap, new Vector3Int(startTile.x - h - 1, startTile.y + 1, 0), null);
                    MyUtils.drawTile(wallTilemap, new Vector3Int(startTile.x - h - 1, startTile.y + 2, 0), null);
                }
                break;
            default:
                break;
        }
    }

    // Paints the middle of the room with the given tile
    private void paintRoomMiddle(Vector2Int room, string tile)
    {
        Vector2Int roomLoc = getRoomCorner(room.x, room.y);
        Vector3Int startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
        Vector2 center = MyUtils.getRoomCenterV2(startTile, roomSize);
        
        MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x, (int)center.y, 0), tile);
        if (roomSize.x % 2 == 0)
        {
            MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, (int)center.y, 0), tile);
            if (roomSize.y % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, (int)center.y - 1, 0), tile);
        }
        if (roomSize.y % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x, (int)center.y - 1, 0), tile);
    }
    private void paintAllMiddles()
    {
        for (int h = 0; h < gridSize.x; h++)
        {
            for (int v = 0; v < gridSize.y; v++)
            {
                paintRoomMiddle(new Vector2Int(h, v), "Red");
            }
        }
    }

    // Paints the middle of the room depending on the room type
    private void paintRoomType(Vector2Int room)
    {
        string tile = null;

        switch (roomGrid[room.x, room.y].getType())
        {
            case EnumRoomType.START:
                tile = "Highlight";
                break;
            case EnumRoomType.NORMAL:
                tile = "Green";
                break;
            case EnumRoomType.DOWN:
                tile = "Blue";
                break;
            case EnumRoomType.UP:
                tile = "Blue";
                break;
            case EnumRoomType.FINISH:
                tile = "Highlight";
                break;
            default:
                break;
        }

        Vector2Int roomLoc = getRoomCorner(room.x, room.y);
        Vector3Int startTile = new Vector3Int(roomLoc.x, roomLoc.y, 0);
        Vector2 center = MyUtils.getRoomCenterV2(startTile, roomSize);

        MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x, (int)center.y, 0), tile);
        if (roomSize.x % 2 == 0)
        {
            MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, (int)center.y, 0), tile);
            if (roomSize.y % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x - 1, (int)center.y - 1, 0), tile);
        }
        if (roomSize.y % 2 == 0) MyUtils.drawTile(wallTilemap, new Vector3Int((int)center.x, (int)center.y - 1, 0), tile);
    }
    private void paintPath()
    {
        for (int h = 0; h < gridSize.x; h++)
        {
            for (int v = 0; v < gridSize.y; v++)
            {
                paintRoomType(new Vector2Int(h, v));
            }
        }
    }

    // Generates the path in the roomGrid (Sets the room types)
    private void generatePath()
    {
        int currentLayer = gridSize.y - 1;

        // Resets the rooms' types
        resetRoomTypeGrid();

        // Sets the starting room
        int startingRoom = Random.Range(0, gridSize.x);
        roomGrid[startingRoom, currentLayer].setType(EnumRoomType.START);

        // Current room
        Vector2Int currentRoom = new Vector2Int(startingRoom, currentLayer);
        EnumRoomType currentRoomType = EnumRoomType.START;
        int roomCount = 0;

        // false is left, true is right
        bool direction = true;

        // If true, advances, else goes down
        bool advance = false;

        // True when the path finishes
        bool finish = false;

        // Select optimal direction
        if (currentRoom.x < gridSize.x / 2)
        {
            direction = true;
        }
        else direction = false;

        // Room loop until the path finishes
        while (!finish)
        {
            roomCount++;

            // Select to advance or go down
            currentRoomType = roomGrid[currentRoom.x, currentRoom.y].getType();
            if (currentRoomType != EnumRoomType.START && currentRoomType != EnumRoomType.UP)
            {
                if (Random.Range(1, 7) < 5) advance = true;
                else
                {
                    advance = false;
                }
            }
            else
            {
                advance = true;
            }

            // Advance case
            // Checks if it can advance, otherwise goes down
            if (advance)
            {
                // Go right
                if (direction)
                {
                    // Checks if there's a room to advance
                    if (roomExists(currentRoom.x + 1, currentRoom.y) == true)
                    {
                        if (currentRoomType != EnumRoomType.START && currentRoomType != EnumRoomType.UP)
                        {
                            roomGrid[currentRoom.x, currentRoom.y].setType(EnumRoomType.NORMAL);
                        }
                        openWall(currentRoom, WallSide.RIGHT);
                        currentRoom.x++;
                    }
                    else advance = false;
                }
                // Go left
                else
                {
                    if (roomExists(currentRoom.x - 1, currentRoom.y) == true)
                    {
                        if (currentRoomType != EnumRoomType.START && currentRoomType != EnumRoomType.UP)
                        {
                            roomGrid[currentRoom.x, currentRoom.y].setType(EnumRoomType.NORMAL);
                        }
                        openWall(currentRoom, WallSide.LEFT);
                        currentRoom.x--;
                    }
                    else advance = false;
                }
            }

            // Down case
            if (!advance)
            {
                // If there's a bottom room
                if (roomExists(currentRoom.x, currentRoom.y - 1) == true)
                {
                    roomGrid[currentRoom.x, currentRoom.y].setType(EnumRoomType.DOWN);
                    roomGrid[currentRoom.x, currentRoom.y - 1].setType(EnumRoomType.UP);
                    openWall(currentRoom, WallSide.DOWN);
                    currentRoom.y--;

                    // Select optimal direction
                    if (currentRoom.x < gridSize.x / 2)
                    {
                        direction = true;
                    }
                    else direction = false;
                }
                else
                {
                    roomGrid[currentRoom.x, currentRoom.y].setType(EnumRoomType.FINISH);
                    finish = true;
                }
            }
        }
    }

    // Resets the grid's rooms' types to fill
    private void resetRoomTypeGrid()
    {
        for (int h = 0; h < gridSize.x; h++)
        {
            for (int v = 0; v < gridSize.y; v++)
            {
                roomGrid[h, v].setType(EnumRoomType.FILL);
            }
        }
    }

    // Returns true if the given room exists in the grid
    private bool roomExists(int x, int y)
    {
        if (x < 0 || x >= gridSize.x)
        {
            return false;
        }
        if (y < 0 || y >= gridSize.y)
        {
            return false;
        }
        return true;
    }

    // Clears the maze for a remake
    private void cleanMaze()
    {
        wallTilemap.ClearAllTiles();
        foreach (RoomV2 room in roomGrid)
        {
            if (room != null) room.destroyGenerated();
        }
    }

    // Generates the rooms of the grid
    private void instantiateRooms()
    {
        foreach (RoomV2 room in roomGrid)
        {
            room.createRoom();
        }
    }
}
