using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using UnityEngine;
using Random = UnityEngine.Random;

public enum ETextureID
{
    bad_value = -1,
    bottom_edge = 4,
    bottom_edge_max = 7,
    grass_below = 8,
    grass_below_max = 11,
    empty_space = 12,
    grass = 13,
    grass_alt_min = 14,
    grass_alt_max = 15,
    water = 16,
    water_alt_min = 17,
    water_alt_max = 19,
    water_top_min = 20,
    water_top_max = 23,

}

public class MapGrid
{
    private List<ETextureID> listWorldTiles;
    private int iWorldSizeX;
    private int iWorldSizeY;
    private int iMaxTiles;
    private GameManager theGame;

    public int Width
    {
        get
        {
            return iWorldSizeX;
        }
    }
    public int Height
    {
        get
        {
            return iWorldSizeY;
        }
    }

    public bool IsHangingEdgeTile(ETextureID t)
    {
        return t >= ETextureID.bottom_edge && t <= ETextureID.grass_below_max;
    }

    public bool IsEmptySpace(ETextureID t)
    {
        return t == ETextureID.empty_space;
    }

    public bool IsGrass(ETextureID t)
    {
        return t >= ETextureID.grass && t <= ETextureID.grass_alt_max;
    }

    public bool IsWater(ETextureID t)
    {
        return t >= ETextureID.water && t <= ETextureID.water_top_max;
    }

    public bool IsOKToBorderWater(ETextureID t)
    {
        return IsGrass(t) || IsWater(t);
    }

    public ETextureID GetRandomTextureForTerrain(ETerrainType terrain)
    {
        switch (terrain)
        {
            case ETerrainType.grass:
                return (ETextureID) Random.Range((int) ETextureID.grass, (int) ETextureID.grass_alt_max + 1);
            case ETerrainType.water:
                return (ETextureID)Random.Range((int)ETextureID.water, (int)ETextureID.water_alt_max + 1);
            case ETerrainType.edge:
                return (ETextureID)Random.Range((int)ETextureID.grass_below, (int)ETextureID.grass_below_max + 1);
            case ETerrainType.bottom_edge:
                return (ETextureID)Random.Range((int)ETextureID.bottom_edge, (int)ETextureID.bottom_edge_max + 1);
            case ETerrainType.water_top_edge:
                return (ETextureID)Random.Range((int)ETextureID.water_top_min, (int)ETextureID.water_top_max + 1);
            case ETerrainType.empty_space:
                return ETextureID.empty_space;
        }

        return ETextureID.empty_space;
    }


    public ETerrainType GetTerrainForTile(Vector2 vPos)
    {
        return GetTerrainForTile((int)vPos.x, (int)vPos.y);
    }

    public ETerrainType GetTerrainForTile(int idx)
    {
        ETextureID tileTex = GetTile(idx);
        return GetTerrainFromTexture(tileTex);
    }

    public ETerrainType GetTerrainForTile(int x, int y)
    {
        //get the tile 
        ETextureID tileTex = GetTile(x, y);
        return GetTerrainFromTexture(tileTex);
    }

    public ETerrainType GetTerrainFromTexture(ETextureID t)
    {
        if (t == ETextureID.bad_value)
        {
            return ETerrainType.MAX;
        }
        else if (IsGrass(t))
        {
            return ETerrainType.grass;
        }
        else if (IsWater(t))
        {
            return ETerrainType.water;
        }
        else if (IsEmptySpace(t))
        {
            return ETerrainType.empty_space;
        }
        else if (IsHangingEdgeTile(t))
        {
            return ETerrainType.empty_space;
        }
        
        //uh
        return ETerrainType.MAX;
    }

    public void ChangeAreaToTerrain(int iTileIdx, int iRadius,  ETerrainType changeTo)
    {
        Vector2 vCenter = GetVectorForTile(iTileIdx);

        for (int y = (int)vCenter.y - iRadius; y <= vCenter.y + iRadius; y++)
            for (int x = (int)vCenter.x - iRadius; x <= vCenter.x + iRadius; x++)
            {
                ChangeTileToTerrain( x + y * iWorldSizeX, changeTo);
            }
    }

    public void ChangeTileToTerrain(int iTileID, ETerrainType changeTo)
    {
        SetTile(iTileID, GetRandomTextureForTerrain(changeTo));
        if (changeTo == ETerrainType.empty_space)
        {
            /* do nothing and hope we do a full reset afterwards
            //check above us, if above us is grass, do the edge testing
            int iTileAbove = iTileID + iWorldSizeX;
            if (GetTile(iTileAbove) == ETextureID.grass)
            {
                ConvertBottomEdgeTilesBecauseIAmGrass(iTileAbove);
            }
             * */
        }
        else if (changeTo == ETerrainType.grass)
        {
            //check below us to see if we need to make edges
            ConvertBottomEdgeTilesBecauseIAmGrass(iTileID);
        }
        else if (changeTo == ETerrainType.water)
        {
            ConvertTopEdgeTilesBecauseIAmWater(iTileID);
        }

    }

    public float SqDistFromVectorToTile(Vector2 vPos, int iTileIdx)
    {
        Vector2 vTilePos = GetVectorForTile(iTileIdx);
        return (vTilePos - vPos).sqrMagnitude;
    }

    public Vector2 GetVectorForTile(int iTileIdx)
    {
        return new Vector2(iTileIdx % iWorldSizeX, iTileIdx / iWorldSizeX);
    }

    public int GetIndexForTileVector(Vector2 vLoc)
    {
        return (int) vLoc.x + (int) vLoc.y * iWorldSizeX;
    }

    public void InitMap(int iWidth, int iHeight, Vector2 vStartPosition, LDTerrainManager theTerrainManager)
    {
        theGame = GameManager.GetGame();
        iWorldSizeX = iWidth;
        iWorldSizeY = iHeight;
        iMaxTiles = iWorldSizeX * iWorldSizeY;

        listWorldTiles = new List<ETextureID>();
        List<int> listGrassTiles = new List<int>();


        //Create the darkness of space
        for (int t = 0; t < iMaxTiles; t++)
        {
            listWorldTiles.Add(ETextureID.empty_space);
        }


        //Starter Island
        CreateGrassIsland((int)vStartPosition.x, (int)vStartPosition.y, 5, 1.0f, 0f);

        Vector3 vStartAdjusted = vStartPosition;
        vStartAdjusted.x += 0.5f;
        vStartAdjusted.y += 0.5f;

        theGame.SpawnActorPrefab(vStartAdjusted, theTerrainManager.Prefab_StartingTowerActor);

        vStartAdjusted.y += 2.0f;
        theTerrainManager.PlayerActor.vWorldPos = vStartAdjusted;

        //CreateLakeRecursively(22, 20, 1.5f);

        /*
        CreateGrassIsland(40, 20, 6, 1.0f, 0.1f);
        CreateGrassIsland(50, 15, 7, 0.8f, 0.2f);
        CreateGrassIsland(50, 28, 7, 1.0f, 0.1f);
        CreateGrassIsland(60, 22, 8, 0.9f, 0.3f);

        CreateLakeRecursively(20, 20, 6);
        
        CreateLakeRecursively(Random.Range(42, 60), Random.Range(18, 24), Random.Range(3, 6));
        CreateLakeRecursively(Random.Range(42, 60), Random.Range(18, 24), Random.Range(3,6));
        */

        ConvertAllBottomEdgeTilesToRock();

    }

    public void CreateGrassIsland(int xCenter, int yCenter, int iRadius, float fDensity, float fFudgeRange)
    {
        float fRadSq = (float) (iRadius * iRadius);

        for(int x = xCenter - iRadius; x <= xCenter + iRadius; x++ )
            for (int y = yCenter - iRadius; y <= yCenter + iRadius; y++)
            {
                float fRoll = Random.value;

                //the center tile should always be grass, neighbors depend on density
                float fDistSq = Vector2.SqrMagnitude(new Vector2(x - xCenter, y - yCenter));
                float fRatio = fDistSq / fRadSq;

                //fudgey fudge
                fRatio = fRatio * Random.Range(1.0f - fFudgeRange, 1.0f + fFudgeRange);

                if( fRatio < fDensity )
                {
                    SetTile(x, y, (ETextureID) Random.Range((int) ETextureID.grass, (int) ETextureID.grass_alt_max + 1));
                }
            }
    }

    public void ConvertAllBottomEdgeTilesToRock()
    {
        //for every grass tile, go through, and if the tile underneath it is NOT grass,
        //turn it into dirt edge
        for (int iChangeTileIdx = 0; iChangeTileIdx < iMaxTiles; iChangeTileIdx++)
        {
            if (IsGrass(GetTile(iChangeTileIdx)))
            {
                ConvertBottomEdgeTilesBecauseIAmGrass(iChangeTileIdx);
            }
            else if (IsWater(GetTile(iChangeTileIdx)))
            {
                ConvertTopEdgeTilesBecauseIAmWater(iChangeTileIdx);
            }

        }

    }

    void ConvertBottomEdgeTilesBecauseIAmGrass(int iChangeTileIdx)
    {
        //check the tile directly below it because 0 is world bottom and life isn't fair
        int belowIndex1 = iChangeTileIdx - iWorldSizeX;
        int belowIndex2 = iChangeTileIdx - iWorldSizeX * 2;

        ETextureID belowTile1 = GetTile(belowIndex1);
        if (IsEmptySpace(belowTile1) || IsHangingEdgeTile(belowTile1))
        {
            listWorldTiles[belowIndex1] = GetRandomTextureForTerrain(ETerrainType.edge);

            ETextureID belowTile2 = GetTile(belowIndex2);
            if (IsEmptySpace(belowTile2) || IsHangingEdgeTile(belowTile2))
            {
                listWorldTiles[belowIndex2] = GetRandomTextureForTerrain(ETerrainType.bottom_edge);
            }
        }
    }

    void ConvertTopEdgeTilesBecauseIAmWater(int iChangeTileIdx)
    {
        //check the tile directly above it because 0 is world bottom and life isn't fair
        int aboveIndex = iChangeTileIdx + iWorldSizeX;

        ETextureID above1 = GetTile(aboveIndex);
        if (IsEmptySpace(above1) || IsHangingEdgeTile(above1) || IsGrass(above1))
        {
            listWorldTiles[iChangeTileIdx] = GetRandomTextureForTerrain(ETerrainType.water_top_edge);
        }

        //now look below me, and if it is water, make it not the edge
        int belowIndex = iChangeTileIdx - iWorldSizeX;

        ETextureID below1 = GetTile(belowIndex);
        if (IsWater(below1) ) 
        {
            listWorldTiles[belowIndex] = GetRandomTextureForTerrain(ETerrainType.water);
        }
    }

    void LookForTilesSurroundedByGrass()
    {
        //let's look for clumps of grass-- one grass surrounded by at least 9 other not-space tiles, and turn them into water.
        List<int> listPotentialWaterTiles = new List<int>();
        for (int t = 0; t < iMaxTiles; t++)
        {
            ETextureID currentTile = GetTile(t);

            //if it is not grass, continue
            if (!IsGrass(currentTile))
            {
                continue;
            }

            //check all the neighbors around 
            bool bIsValid = true;
            for (ECompassDirection dir = ECompassDirection.north; dir < ECompassDirection.max; dir++)
            {
                if (!CheckTileNeighbor(t, dir, IsGrass))
                {
                    bIsValid = false;
                }
            }

            //if all the neighbors are grass, throw it in the list
            if (bIsValid)
            {
                listPotentialWaterTiles.Add(t);
            }

        }

        //turn them all into wet wet water
        for (int t = 0; t < listPotentialWaterTiles.Count; t++)
        {
            int iTileIdx = listPotentialWaterTiles[t];
            listWorldTiles[iTileIdx] = (ETextureID)Random.Range((int)ETextureID.water, (int)ETextureID.water_alt_max + 1);
        }
    }

    void CreateLakeRecursively(int x, int y, float fStepsLeft)
    {
        CreateLakeRecursively(x + ( y * iWorldSizeX) , fStepsLeft);
    }

    void CreateLakeRecursively(int iLakeTileID, float fStepsLeft)
    {
        //don't recurse out forever
        if (fStepsLeft <= 0f)
        {
            return;
        }

        //if this tile is not grass, return
        if (!IsGrass(GetTile(iLakeTileID)))
        {
            return;
        }

        //if this tile is surrounded by grass or water, then we can turn it into a water tile
        bool bIsValid = true;
        for (ECompassDirection dir = ECompassDirection.north; dir < ECompassDirection.max; dir++)
        {
            if (!CheckTileNeighbor(iLakeTileID, dir, IsOKToBorderWater))
            {
                bIsValid = false;
            }
        }

        if (bIsValid)
        {
            SetTile(iLakeTileID, (ETextureID)Random.Range((int)ETextureID.water, (int)ETextureID.water_alt_max + 1));
            for (ECompassDirection dir = ECompassDirection.north; dir < ECompassDirection.max; dir++)
            {
                CreateLakeRecursively(GetTileNeighborIndex(iLakeTileID, dir), fStepsLeft - ( ((int)(dir) % 2 == 0) ? 1.0f :1.4f ));
            }
        }

    }

    public int GetTileNeighborIndex(int iBaseX, int iBaseY, ECompassDirection dir)
    {
        return GetTileNeighborIndex( iBaseX + (iBaseY * iWorldSizeX), dir);
    }

    public int GetTileNeighborIndex(int iBaseIdx, ECompassDirection dir)
    {
        switch (dir)
        {
            case ECompassDirection.north:
                iBaseIdx += iWorldSizeX;
                break;
            case ECompassDirection.northeast:
                iBaseIdx += iWorldSizeX;
                iBaseIdx++;
                break;
            case ECompassDirection.east:
                iBaseIdx++;
                break;
            case ECompassDirection.southeast:
                iBaseIdx -= iWorldSizeX;
                iBaseIdx++;
                break;
            case ECompassDirection.south:
                iBaseIdx -= iWorldSizeX;
                break;
            case ECompassDirection.southwest:
                iBaseIdx -= iWorldSizeX;
                iBaseIdx--;
                break;
            case ECompassDirection.west:
                iBaseIdx--;
                break;
            case ECompassDirection.northwest:
                iBaseIdx += iWorldSizeX;
                iBaseIdx--;
                break;
        }

        return iBaseIdx;
    }

    public bool CheckTileNeighbor(int iBaseIdx, ECompassDirection dir, Func<ETextureID, bool> condition)
    {
        int buddyIdx = GetTileNeighborIndex(iBaseIdx, dir);
        return condition(GetTile(buddyIdx));
    }

    public ETextureID GetTile(int tileID)
    {
        if (tileID < 0 || tileID >= iMaxTiles)
        {
            return ETextureID.bad_value;
        }

        return listWorldTiles[tileID];
    }

    public ETextureID GetTile(int x, int y)
    {
        int iTileID = x + y * iWorldSizeX;
        return GetTile(iTileID);
    }

    public bool SetTile(int id, ETextureID newTex)
    {
        if (id < 0 || id >= iMaxTiles)
        {
            return false;
        }

        listWorldTiles[id] = newTex;
        return true;
    }

    public bool SetTile(int x, int y, ETextureID newTex)
    {
        return SetTile( x + (y* iWorldSizeX), newTex);
    }

}