using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static MapManager;
using static TileSet;
using Random = UnityEngine.Random;

public class MapGraphics : MonoBehaviour
{
    [SerializeField] TileSet m_tileset;

    private Tile[,] m_map;
    private Dictionary<Building, GameObject> m_buildings;
    private int m_width;
    private int m_height;

    private GameObject[,] m_graphicsObject;

    public void Initialize(Tile[,] map)
    {
        m_tileset.Setup();
        m_map = map;
        m_width = m_map.GetLength(0);
        m_height = m_map.GetLength(1);
        m_graphicsObject = new GameObject[m_width, m_height];
        m_buildings = new Dictionary<Building, GameObject>();

        for (int x = 0; x < m_width; x++)
        {
            for (int y = 0; y < m_height; y++)
            {
                m_graphicsObject[x, y] = GenerateTile(m_map[x, y]);
                if(m_map[x, y].Visible == false)
                {
                    m_graphicsObject[x, y].SetActive(false);
                }
            }
        }

        // TODO:: Leaking memory here
        Building.OnBuildingConstructed += OnBuildingCreated;
        Building.OnBuildingDemolished += OnBuildingDestroyed;
    }

    private void OnBuildingCreated(Building building)
    {
        var prefab = m_tileset.Buildings[building.BuildingType];
        float width = building.Width * MapManager.TileSize;
        float deltawidth = width / 2f - TileSize / 2;
        float height = building.Height * MapManager.TileSize;
        float deltaheight = height / 2f - TileSize / 2;
        var postion = MapManager.TileToWorld(building.SourceTile);
        postion.x += deltawidth;
        postion.z += deltaheight;
        var go = Instantiate(prefab, postion, Quaternion.Euler(0, 180, 0), transform);
        m_buildings.Add(building, go);
    }

    private void OnBuildingDestroyed(Building building)
    {
        if(m_buildings.ContainsKey(building))
        {
            Destroy(m_buildings[building]);
            m_buildings.Remove(building);
        }
    }

    /// <summary>
    /// Trigger an update of specified tile
    /// </summary>
    /// <param name="x">X Index</param>
    /// <param name="y">Y Index</param>
    public void UpdateTile(Tile t)
    {
        // TODO:: Do more useful thing here, also only update if a change is needed

        int x = t.Position.x;
        int y = t.Position.y;
        var go = m_graphicsObject[x, y];
        Destroy(go);
        m_graphicsObject[x, y] = GenerateTile(m_map[x, y]);
    }

    private Tuple<TileTypes, Quaternion> GetTileType(Tile tile)
    {
        Neighbour neighbours = GetNeighbours(tile);

        // Solid - 8
        {
            if (neighbours == Neighbour.Middle)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Filled, Quaternion.identity);
        }

        // Inner Corners - 7
        {
            if ((neighbours & Neighbour.NECornerInner) == Neighbour.NECornerInner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Inner, Quaternion.identity);
            if ((neighbours & Neighbour.NWCornerInner) == Neighbour.NWCornerInner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Inner, Quaternion.Euler(0, 270, 0));
            if ((neighbours & Neighbour.SECornerInner) == Neighbour.SECornerInner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Inner, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.SWCornerInner) == Neighbour.SWCornerInner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Inner, Quaternion.Euler(0, 180, 0));
        }

        // Diagonals - 6
        {
            if ((neighbours & Neighbour.NW_SE_Diagonal) == Neighbour.NW_SE_Diagonal)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_InnerX2, Quaternion.identity);
            if ((neighbours & Neighbour.NE_SW_Diagonal) == Neighbour.NE_SW_Diagonal)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_InnerX2, Quaternion.Euler(0, 90, 0));
        }

        // T-Junktion - 6
        {
            if ((neighbours & Neighbour.NorthT) == Neighbour.NorthT)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Standalone, Quaternion.Euler(0, 270, 0));
            if ((neighbours & Neighbour.EastT) == Neighbour.EastT)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Standalone, Quaternion.identity);
            if ((neighbours & Neighbour.SouthT) == Neighbour.SouthT)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Standalone, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.WestT) == Neighbour.WestT)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Standalone, Quaternion.Euler(0, 180, 0));
        }

        // Cross Filled - 5
        {
            if ((neighbours & Neighbour.CrossNW) == Neighbour.CrossNW)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_X, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.CrossNE) == Neighbour.CrossNE)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_X, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.CrossSE) == Neighbour.CrossSE)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_X, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.CrossSW) == Neighbour.CrossSW)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_X, Quaternion.Euler(0, 270, 0));
        }

        // Walls - 5
        {
            if ((neighbours & Neighbour.NorthWall) == Neighbour.NorthWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Straight, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.EastWall) == Neighbour.EastWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Straight, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.SouthWall) == Neighbour.SouthWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Straight, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.WestWall) == Neighbour.WestWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Straight, Quaternion.Euler(0, 270, 0));
        }

        // T-Junktion-Filled - 4
        {
            if ((neighbours & Neighbour.NorthTSR) == Neighbour.NorthTSR)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Right, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.EastTSR) == Neighbour.EastTSR)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Right, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.SouthTSR) == Neighbour.SouthTSR)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Right, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.WestTSR) == Neighbour.WestTSR)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Right, Quaternion.Euler(0, 270, 0));

            if ((neighbours & Neighbour.NorthTSL) == Neighbour.NorthTSL)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Left, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.EastTSL) == Neighbour.EastTSL)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Left, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.SouthTSL) == Neighbour.SouthTSL)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Left, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.WestTSL) == Neighbour.WestTSL)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Half_T_Left, Quaternion.Euler(0, 270, 0));
        }

        // Cross - 4
        {
            if ((neighbours & Neighbour.Cross) == Neighbour.Cross)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_X, Quaternion.Euler(0, 0, 0));
        }

        // T-Junktion - 3
        {
            if ((neighbours & Neighbour.NorthTS) == Neighbour.NorthTS)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_T, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.EastTS) == Neighbour.EastTS)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_T, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.SouthTS) == Neighbour.SouthTS)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_T, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.WestTS) == Neighbour.WestTS)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_T, Quaternion.Euler(0, 270, 0));
        }

        // Outer Corners - 3
        {
            if ((neighbours & Neighbour.NWCornerOuter) == Neighbour.NWCornerOuter)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Outer, Quaternion.Euler(0, 270, 0));
            if ((neighbours & Neighbour.NECornerOuter) == Neighbour.NECornerOuter)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Outer, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.SWCornerOuter) == Neighbour.SWCornerOuter)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Outer, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.SECornerOuter) == Neighbour.SECornerOuter)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Outer, Quaternion.Euler(0, 90, 0));
        }

        // Walls - 2
        {
            if ((neighbours & Neighbour.HorizontalWall) == Neighbour.HorizontalWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Straight, Quaternion.Euler(0, 0, 0));
            if ((neighbours & Neighbour.VerticalWall) == Neighbour.VerticalWall)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Straight, Quaternion.Euler(0, 90, 0));
        }

        // Corner - 2
        {
            if ((neighbours & Neighbour.SECorner) == Neighbour.SECorner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Bend, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.SWCorner) == Neighbour.SWCorner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Bend, Quaternion.Euler(0, 270, 0));
            if ((neighbours & Neighbour.NECorner) == Neighbour.NECorner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Bend, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.NWCorner) == Neighbour.NWCorner)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Wall_Bend, Quaternion.Euler(0, 0, 0));
        }

        // Edge - 1
        {
            if ((neighbours & Neighbour.North) == Neighbour.North)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Connected, Quaternion.Euler(0, 90, 0));
            if ((neighbours & Neighbour.East) == Neighbour.East)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Connected, Quaternion.Euler(0, 180, 0));
            if ((neighbours & Neighbour.South) == Neighbour.South)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Connected, Quaternion.Euler(0, 270, 0));
            if ((neighbours & Neighbour.West) == Neighbour.West)
                return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Connected, Quaternion.Euler(0, 0, 0));
        }

        // Single - 0
        return new Tuple<TileTypes, Quaternion>(TileTypes.Standalone_Lonely, Quaternion.Euler(0, 0, 0));
    }

    private GameObject GenerateTile(Tile tile)
    {
        int x = tile.Position.x;
        int y = tile.Position.y;
        Vector3 tilePos = new Vector3(x * TileSize + TileSize - 1, 0, y * TileSize + TileSize - 1);

        switch (tile.Type)
        {
            case Tile.TileType.Ground:
                {
                    List<GameObject> prefabs = m_tileset.Tiles[TileTypes.Ground];
                    int rt = Random.Range(0, prefabs.Count);
                    GameObject go = Instantiate(prefabs[rt], tilePos, Quaternion.identity, transform);
                    go.name = x + ", " + y;
                    return go;
                }
            case Tile.TileType.Wall:
                {
                    var type = GetTileType(tile);
                    var tiles = m_tileset.Tiles[type.Item1];
                    var rand = Random.Range(0, tiles.Count);
                    var go = Instantiate(tiles[rand], tilePos, type.Item2, transform);
                    go.name = x + ", " + y;
                    return go;
                }
            case Tile.TileType.Bedrock:
                {
                    GameObject go = Instantiate(m_tileset.Magma, tilePos, Quaternion.identity, transform);
                    go.name = x + ", " + y;
                    return go;
                }
            case Tile.TileType.Ladder:
                {
                    GameObject go = Instantiate(m_tileset.Ladder, tilePos, Quaternion.identity, transform);
                    go.name = x + ", " + y;
                    return go;
                }
            default:
                return null;
        }

    }

    /// <summary>
    /// Bitflag denoting the eight different neighbours of a tile
    /// </summary>
    [System.Flags]
    enum Neighbour : byte
    {
        None = 0,

        North = 1,
        South = 2,
        West = 4,
        East = 8,

        SouthWest = 16,
        NorthWest = 32,
        SouthEast = 64,
        NorthEast = 128,

        // The different cases - 8
        Middle = 255, // 1111 1111

        // Inner Corner - 7
        NWCornerInner = 255 & ~NorthWest,
        NECornerInner = 255 & ~NorthEast,
        SWCornerInner = 255 & ~SouthWest,
        SECornerInner = 255 & ~SouthEast,

        // Diagonals - 6
        NW_SE_Diagonal = 255 & ~SouthWest & ~NorthEast,
        NE_SW_Diagonal = 255 & ~SouthEast & ~NorthWest,

        // T-Junktions - 6
        NorthT = 255 & ~NorthWest & ~NorthEast,
        EastT = 255 & ~NorthEast & ~SouthEast,
        SouthT = 255 & ~SouthWest & ~SouthEast,
        WestT = 255 & ~SouthWest & ~NorthWest,

        // Walls - 5
        NorthWall = South | East | West | SouthEast | SouthWest,
        EastWall = North | West | South | SouthWest | NorthWest,
        SouthWall = West | North | East | NorthWest | NorthEast,
        WestWall = North | East | South | SouthEast | NorthEast,

        // Cross Filled - 5
        CrossNE = North | West | South | East | NorthEast,
        CrossNW = North | West | South | East | NorthWest,
        CrossSE = North | West | South | East | SouthEast,
        CrossSW = North | West | South | East | SouthWest,

        // Cross - 4
        Cross = North | West | South | East,

        // T-Junktion-Single-Filled - 4
        NorthTSR = West | East | North | NorthEast,
        EastTSR = South | East | North | SouthEast,
        SouthTSR = West | East | South | SouthWest,
        WestTSR = West | South | North | NorthWest,

        NorthTSL = West | East | North | NorthWest,
        EastTSL = South | East | North | NorthEast,
        SouthTSL = West | East | South | SouthEast,
        WestTSL = West | South | North | SouthWest,

        // T-Junktions-Single - 3
        NorthTS = West | East | North,
        EastTS = South | East | North,
        SouthTS = West | East | South,
        WestTS = West | South | North,

        // Outer Corners - 3
        NWCornerOuter = South | SouthEast | East,
        NECornerOuter = South | SouthWest | West,
        SWCornerOuter = North | NorthEast | East,
        SECornerOuter = North | NorthWest | West,

        // Walls 2
        HorizontalWall = West | East,
        VerticalWall = North | South,

        // Corner - 2
        NWCorner = North | West,
        NECorner = North | East,
        SWCorner = South | West,
        SECorner = South | East,
    }

    private Neighbour GetNeighbours(Tile tile)
    {
        Neighbour n = Neighbour.None;
        int x = tile.Position.x;
        int y = tile.Position.y;
        int maxX = m_map.GetLength(0) - 1;
        int maxY = m_map.GetLength(1) - 1;
        if (y == 0 || m_map[x, y - 1].Type == Tile.TileType.Wall)
        {
            n |= Neighbour.South;
            if (y == 0 || x == 0 || m_map[x - 1, y - 1].Type == Tile.TileType.Wall)
            {
                n |= Neighbour.SouthWest;
            }
            if (y == 0 || x == maxX || m_map[x + 1, y - 1].Type == Tile.TileType.Wall)
            {
                n |= Neighbour.SouthEast;
            }
        }
        if (y == maxY || m_map[x, y + 1].Type == Tile.TileType.Wall)
        {
            n |= Neighbour.North;
            if (y == maxY || x == 0 || m_map[x - 1, y + 1].Type == Tile.TileType.Wall)
            {
                n |= Neighbour.NorthWest;
            }
            if (y == maxY || x == maxX || m_map[x + 1, y + 1].Type == Tile.TileType.Wall)
            {
                n |= Neighbour.NorthEast;
            }
        }
        if (x == 0 || m_map[x - 1, y].Type == Tile.TileType.Wall)
        {
            n |= Neighbour.West;
        }
        if (x == maxX || m_map[x + 1, y].Type == Tile.TileType.Wall)
        {
            n |= Neighbour.East;
        }
        return n;
    }
}
