using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum ETerrainType
{
    empty_space = 0,
    grass,
    water,
    edge,
    bottom_edge,
    water_top_edge,
    MAX
}

public class LDTerrainManager : MonoBehaviour
{
    public GameObject Prefab_Star;


    public Vector3[] newVertices;
    public Vector2[] newUV;
    public int[] newTriangles;

    private MeshFilter myFilter;
    private Mesh myMesh;
    private MeshRenderer myRenderer;

    public int iWorldWidth;
    public int iWorldHeight;

    public Vector2 vStartLocation;

    private int iMeshWidthInTiles = 10;
    private int iMeshHeightInTiles = 10;

    private Vector2 vCurrentTopLeftPos;

    [Header("Starting Objects")]
    public LDGameActor PlayerActor;
    public GameObject Prefab_StartingTowerActor;


    [HideInInspector]
    public MapGrid theMap;
    private LDCameraManager theCamera;

    private Vector2 vOldBottomLeftPosFloor;

    private bool bFirstUpdate = true;
    /*
     *  quads 
     *  
     *  2   3
     * 
     *  0   1
     */

    /*
     *  Triangles
     *  
     *  Lower Left, then Upper Right, wrapped clockwise
     * 
     *  0,2,1
     *  
     *  2,3,1
     * 
     *  UVs
     * 
     *  2  3
     * 
     *  0  1
     */
	// Use this for initialization
	void Start ()
	{
	    myFilter = GetComponent<MeshFilter>();
	    myRenderer = GetComponent<MeshRenderer>();
        
        theMap = new MapGrid();
	    theCamera = Camera.main.GetComponent<LDCameraManager>();
	}
	
	// Update is called once per frame
	void Update () 
    {
        if (bFirstUpdate)
        {
            GenerateWorld();
            bFirstUpdate = false;
        }

        UpdateMeshPosition(theCamera.GetWorldPosition());
    }

    public void PrepareNewGame()
    {
        GenerateWorld();
    }

    void BuildMesh()
    {
        if (myMesh != null)
        {
            return;
        }

        myMesh = new Mesh();
        myFilter.mesh = myMesh;

        //generate verts
        newVertices = new Vector3[iMeshWidthInTiles * iMeshHeightInTiles *4];
        int iVertIndex = 0;
        for (int y = 0; y < iMeshHeightInTiles; y++)
        {
            for (int x = 0; x < iMeshWidthInTiles; x++)
            {
                newVertices[iVertIndex++] = new Vector3(x, y, 0);
                newVertices[iVertIndex++] = new Vector3(x + 1, y, 0);
                newVertices[iVertIndex++] = new Vector3(x, y+1, 0);
                newVertices[iVertIndex++] = new Vector3(x + 1, y+1, 0);
            }
        }

        //triangles
        int iTriIndex = 0;
        iVertIndex = 0;
        newTriangles = new int[iMeshWidthInTiles * iMeshHeightInTiles * 6];
        for (int y = 0; y < iMeshHeightInTiles; y++)
        {
            for (int x = 0; x < iMeshWidthInTiles; x++)
            {
                //bottom left
                // 0, 2, 1
                newTriangles[iTriIndex++] = iVertIndex;
                newTriangles[iTriIndex++] = iVertIndex + 2;
                newTriangles[iTriIndex++] = iVertIndex + 1;

                //top right 
                // 2, 3, 1
                newTriangles[iTriIndex++] = iVertIndex + 2;
                newTriangles[iTriIndex++] = iVertIndex + 3;
                newTriangles[iTriIndex++] = iVertIndex + 1;

                iVertIndex += 4;

            }
        }

        //uvs
        //should be based on the map value but whatever
        float fTextureWidth = myRenderer.material.mainTexture.width;
        float fTextureHeight = myRenderer.material.mainTexture.height;
        float fPxSize = 24.0f;

        float fPxWidth = fPxSize / fTextureWidth;
        float fPxHeight = fPxSize / fTextureHeight;



        int idxUVs = 0;
        newUV = new Vector2[iMeshHeightInTiles * iMeshWidthInTiles * 4];
        for (int y = 0; y < iMeshHeightInTiles; y++)
        {
            for (int x = 0; x < iMeshWidthInTiles; x++)
            {
                float fTileLocX = fPxWidth * Random.Range(0, 16);
                float fTileLocY = fPxHeight * Random.Range(0, 20);

                newUV[idxUVs++] = new Vector2(fTileLocX,fTileLocY);
                newUV[idxUVs++] = new Vector2(fTileLocX + fPxWidth, fTileLocY);
                newUV[idxUVs++] = new Vector2(fTileLocX, fTileLocY + fPxHeight);
                newUV[idxUVs++] = new Vector2(fTileLocX + fPxWidth, fTileLocY + fPxHeight);
            }
        }


        myMesh.vertices = newVertices;
        myMesh.uv = newUV;
        myMesh.triangles = newTriangles;
    }

    public void UpdateUVs()
    {
        float fTextureWidth = myRenderer.material.mainTexture.width;
        float fTextureHeight = myRenderer.material.mainTexture.height;
        float fPxSize = 24.0f;

        float fPxWidth = fPxSize / fTextureWidth;
        float fPxHeight = fPxSize / fTextureHeight;

        int iNumTilesX = (int)(fTextureWidth / fPxSize);
        int iNumTilesY = (int)(fTextureHeight / fPxSize);

        int idxUVs = 0;
        int iTileID = 0;
        newUV = myMesh.uv;
        for (int y = 0; y < iMeshHeightInTiles; y++)
        {
            for (int x = 0; x < iMeshWidthInTiles; x++)
            {
                int xLoc = (int)vOldBottomLeftPosFloor.x + x;
                int yLoc = (int)vOldBottomLeftPosFloor.y + y;

                iTileID = xLoc + (yLoc * theMap.Width);

                int iTerrain = (int)theMap.GetTile(iTileID);

                int iTileXValue = iTerrain % iNumTilesX;
                int iTileYValue = iTerrain / iNumTilesX;

                float fTileLocX = fPxWidth * iTileXValue;
                float fTileLocY = fPxHeight * iTileYValue;

                newUV[idxUVs++] = new Vector2(fTileLocX, fTileLocY);
                newUV[idxUVs++] = new Vector2(fTileLocX + fPxWidth, fTileLocY);
                newUV[idxUVs++] = new Vector2(fTileLocX, fTileLocY + fPxHeight);
                newUV[idxUVs++] = new Vector2(fTileLocX + fPxWidth, fTileLocY + fPxHeight);
            }
        }

        myMesh.uv = newUV;
    }

    void GenerateWorld()
    {
        //build the map
        theMap.InitMap(iWorldWidth,iWorldHeight, vStartLocation, this);

        //ask the camera how many tiles we're going to show on screen
        LDCameraManager cm = Camera.main.GetComponent<LDCameraManager>();
        int iPxPerTile = cm.pxPerTile;

        int iNumTilesWide = Screen.width / iPxPerTile;
        int iNumTilesTall = Screen.height / iPxPerTile;

        //make a little border space around the screen
        iMeshWidthInTiles = iNumTilesWide + 4;
        iMeshHeightInTiles = iNumTilesTall += 4;

        BuildMesh();

        Vector3 vPos = new Vector3(iMeshWidthInTiles / 2, iMeshHeightInTiles / 2, -10);
        theCamera.SetTransformBase(vPos);

        AddStars();


    }

    void AddStars()
    {
        if (GetComponentInChildren<LDBackgroundStar>() == null)
        {
            int iNumStars = 800;
            for (int t = 0; t < iNumStars; t++)
            {
                GameObject newStar = Instantiate(Prefab_Star);
                newStar.transform.parent = transform;

                LDBackgroundStar s = newStar.GetComponent<LDBackgroundStar>();

                float fParallax = Random.Range(3f, 8f);
                s.Init(new Vector2(Random.Range(10, 190), Random.Range(10, 190)), fParallax);

                Vector3 vScale = newStar.transform.localScale;
                vScale /= fParallax;
                newStar.transform.localScale = vScale;
            }
        }
    }

    public void UpdateMeshPosition(Vector2 vCamPosInGameWorld)
    {
        //take the camera's position and translate it into a new bottom left
        Vector2 vNewBottomLeft = vCamPosInGameWorld;
        vNewBottomLeft.x -= iMeshWidthInTiles / 2;
        vNewBottomLeft.y -= iMeshHeightInTiles / 2;

        //keep it all in bounds
        if (vNewBottomLeft.x < 0)
        {
            vNewBottomLeft.x = 0;
        }
        else if (vNewBottomLeft.y < 0)
        {
            vNewBottomLeft.y = 0;
        }

        //If we've crossed a tile border, the UVs need to change.
        if (vOldBottomLeftPosFloor.x != Mathf.Floor(vNewBottomLeft.x) ||
            vOldBottomLeftPosFloor.y != Mathf.Floor(vNewBottomLeft.y))
        {
            vOldBottomLeftPosFloor.x = Mathf.Floor(vNewBottomLeft.x);
            vOldBottomLeftPosFloor.y = Mathf.Floor(vNewBottomLeft.y);
            UpdateUVs();
        }

        int pxPerTile = theCamera.pxPerTile;

        //get the floaty remainder parts of these values and use them to shift tiles around
        float fXOffset = vNewBottomLeft.x - (int)vNewBottomLeft.x;
        float fYOffset = vNewBottomLeft.y - (int)vNewBottomLeft.y;

        //as X advances (east), the tiles shift x negative
        Vector3 vNewPos = new Vector3(0,0,0);
        vNewPos.x = fXOffset * -1;
        //as Y advances (north), the tiles shift y negative;
        vNewPos.y = fYOffset * -1;

        //quantize for pixel perfection
        vNewPos.x *= pxPerTile;
        vNewPos.x = Mathf.Floor(vNewPos.x);
        vNewPos.x /= pxPerTile;

        vNewPos.y *= pxPerTile;
        vNewPos.y = Mathf.Floor(vNewPos.y);
        vNewPos.y /= pxPerTile;

        vNewPos = theCamera.QuantizeVectorForPixelPerfection(vNewPos);
        transform.position = vNewPos;
    }
}
