using UnityEngine;
using System.Collections.Generic;

public class ShapeManager : MonoBehaviour {

    public GameObject[] ShapePrefabs = null;
    public GameObject[][] Shapes = null;

    public bool AvailableForDragging = false;
    public bool DeletionPaused = false;

    private int PreviewColumn = 0;
    private int PreviewColumnShiftAmount = 0;
    private int PreviewRow = 0;
    private int PreviewRowShiftAmount = 0;

    public void SetPreviewShiftAmounts(int _col, int _colAmount, int _row, int _rowAmount)
    {
        PreviewColumn = _col;
        PreviewColumnShiftAmount = _colAmount;
        PreviewRow = _row;
        PreviewRowShiftAmount = _rowAmount;
    }

    public Vector3 GetWorldPositionForShapeLocation(int x, int y)
    {
        Vector3 result = Vector3.zero;

        result.x = (float)x - (GameSettings.Columns - 1) / 2.0f;
        result.y = (float)y - (GameSettings.Rows - 1) / 2.0f;

        return result;
    }

    public int GetRowForWorldPosition(Vector3 pos)
    {
        int result = (int)(pos.y + (GameSettings.Rows) / 2.0f);

        if (result < 0)
            result = 0;

        if (result >= GameSettings.Rows)
            result = GameSettings.Rows - 1;

        return result;

    }

    public int GetColumnForWorldPosition(Vector3 pos)
    {
        int result = (int)(pos.x + (GameSettings.Columns) / 2.0f);

        if (result < 0)
            result = 0;

        if (result >= GameSettings.Columns)
            result = GameSettings.Columns - 1;

        return result;
    }

    public Bounds GetPlayspaceExtents()
    {
        return new Bounds(Vector3.zero, new Vector3((float)(GameSettings.Columns), (float)(GameSettings.Rows), 100.0f));
    }

    public void InitializeShapeWithDropParameters(int x, int y)
    {
        Vector3 targetPos = GetWorldPositionForShapeLocation(x, y);
        Shapes[x][y].GetComponent<ShapeInfo>().visualDestinationPosition = targetPos;

        targetPos.y = (GameSettings.Rows + 2.0f) / 2.0f;
        Shapes[x][y].transform.position = targetPos;

    }

    public void SnapShapeAtPosition(int x, int y)
    {
        Vector3 targetPos = GetWorldPositionForShapeLocation(x, y);
        Shapes[x][y].transform.position = targetPos;
        Shapes[x][y].GetComponent<ShapeInfo>().visualDestinationPosition = targetPos;
    }

    public void ReturnEverybodyHome()
    {
        for (int y = 0; y < GameSettings.Rows; y++)
        {
            for (int x = 0; x < GameSettings.Columns; x++)
            {
                Shapes[x][y].GetComponent<ShapeInfo>().visualDestinationPosition = GetWorldPositionForShapeLocation(x, y);
            }
        }
    }

    public int CalculateHorizontalShiftAmountFromWorldDistance(float xDelta)
    {
        int result = Mathf.RoundToInt(xDelta);
        result = result % (GameSettings.Columns);
        if (result > GameSettings.Columns / 2)
        {
            result = result - GameSettings.Columns;
        }
        if (result < -GameSettings.Columns / 2)
        {
            result = result + GameSettings.Columns;
        }
        return result;
    }

    public void ShiftRowBasedOnWorldDistance(Vector3 worldPos, float xDelta)
    {
        int ShiftAmount = CalculateHorizontalShiftAmountFromWorldDistance(xDelta);
        int rowToShift = GetRowForWorldPosition(worldPos);

        List<GameObject> ShapesToShift = new List<GameObject>();
        for (int i = 0; i < GameSettings.Columns; i++)
            ShapesToShift.Add(Shapes[i][rowToShift]);

        for (int i = 0; i < GameSettings.Columns; i++)
        {
            // Update the shape array
            int newcol = (i - ShiftAmount) % GameSettings.Columns;
            if (newcol < 0)
                newcol += GameSettings.Columns;
            Shapes[i][rowToShift] = ShapesToShift[newcol];

            // snap each shape that was shifted to it's correct position in the world
            SnapShapeAtPosition(i, rowToShift);
        }
    }

    public int CalculateVerticalShiftAmountFromWorldDistance(float yDelta)
    {
        int result = Mathf.RoundToInt(yDelta);
        result = result % (GameSettings.Rows);
        if (result > GameSettings.Rows / 2)
        {
            result = result - GameSettings.Rows;
        }
        if (result < -GameSettings.Rows / 2)
        {
            result = result + GameSettings.Rows;
        }
        return result;
    }

    public void ShiftColumnBasedOnWorldDistance(Vector3 worldPos, float yDelta)
    {
        int ShiftAmount = CalculateVerticalShiftAmountFromWorldDistance(yDelta);
        int colToShift = GetColumnForWorldPosition(worldPos);

        List<GameObject> ShapesToShift = new List<GameObject>();
        for (int i = 0; i < GameSettings.Rows; i++)
            ShapesToShift.Add(Shapes[colToShift][i]);

        for (int i = 0; i < GameSettings.Rows; i++)
        {
            // Update the shape array
            int newrow = (i - ShiftAmount) % GameSettings.Rows;
            if (newrow < 0)
                newrow += GameSettings.Rows;
            Shapes[colToShift][i] = ShapesToShift[newrow];

            // snap each shape that was shifted to it's correct position in the world
            SnapShapeAtPosition(colToShift, i);
        }
    }
    

    private GameObject SpawnRandomShape()
    {
        // Pick a random shape prefab
        int index = Random.Range(0, ShapePrefabs.Length);

        return GameObject.Instantiate(ShapePrefabs[index]);
    }

    private void InitializeLevel()
    {
        Shapes = new GameObject[GameSettings.Columns][];
        for (int col = 0; col < GameSettings.Columns; col++)
        {
            Shapes[col] = new GameObject[GameSettings.Rows];
        }

        // Initial seed
        for (int y = 0; y < GameSettings.Rows; y++)
        {
            for (int x = 0; x < GameSettings.Columns; x++)
            {
                // Instantiate the prefab
                Shapes[x][y] = SpawnRandomShape();
            }
        }


        // Repeat replacing matches until no matches are left
        bool FoundMatch = false;
        do
        {
            CheckForMatchingPieces();
            FoundMatch = false;

            for (int y = 0; y < GameSettings.Rows; y++)
            {
                for (int x = 0; x < GameSettings.Columns; x++)
                {
                    if (Shapes[x][y].GetComponent<ShapeInfo>().MatchCount > 2)
                    {
                        FoundMatch = true;

                        // Replace this object with a new one
                        DestroyObject(Shapes[x][y]);
                        Shapes[x][y] = SpawnRandomShape();
                    }
                }
            }
        } while (FoundMatch);

        for (int y = 0; y < GameSettings.Rows; y++)
        {
            for (int x = 0; x < GameSettings.Columns; x++)
            {
                // Position the prefab in the world
                // SnapShapeAtPosition(x, y);
                InitializeShapeWithDropParameters(x, y);
                Shapes[x][y].GetComponent<ShapeInfo>().BeginFallingToGravity(0.04f * y + Random.Range(-0.01f, 0.01f));

            }
        }
    }

    public List<GameObject> GetAllShapesInWorldPositionRow(Vector3 pos)
    {
        List<GameObject> result = new List<GameObject>();

        int row = GetRowForWorldPosition(pos);
        for (int x = 0; x < GameSettings.Columns; x++)
        {
            result.Add(Shapes[x][row]);
        }

        return result;
    }

    public List<GameObject> GetAllShapesInWorldPositionColumn(Vector3 pos)
    {
        List<GameObject> result = new List<GameObject>();

        int col = GetColumnForWorldPosition(pos);
        for (int y = 0; y < GameSettings.Rows; y++)
        {
            result.Add(Shapes[col][y]);
        }

        return result;
    }
    

    public bool NoPiecesAreFalling()
    {
        for (int y = 0; y < GameSettings.Rows; y++)
        {
            for (int x = 0; x < GameSettings.Columns; x++)
            {
                if (Shapes[x][y].GetComponent<ShapeInfo>().fallingToGravity)
                    return false;
            }
        }

        return true;
    }

    public bool HasMatchingPieces()
    {
        for (int y = 0; y < GameSettings.Rows; y++)
        {
            for (int x = 0; x < GameSettings.Columns; x++)
            {
                if (Shapes[x][y].GetComponent<ShapeInfo>().MatchCount > 2)
                    return true;
            }
        }

        return false;
    }

    private GameObject GetPreviewAdjustedShape(int col, int row)
    {
        int lookupCol = col;
        int lookupRow = row;

        if (col == PreviewColumn)
        {
            // Shift by preview column amount
            lookupRow -= PreviewColumnShiftAmount;
            if (lookupRow < 0)
                lookupRow += GameSettings.Columns;

            if (lookupRow >= GameSettings.Columns)
                lookupRow -= GameSettings.Columns;
        }

        if (row == PreviewRow)
        {
            // Shift by preview row amount
            lookupCol -= PreviewRowShiftAmount;
            if (lookupCol < 0)
                lookupCol += GameSettings.Rows;

            if (lookupCol >= GameSettings.Rows)
                lookupCol -= GameSettings.Rows;
        }

        return Shapes[lookupCol][lookupRow];
    }

    public void CheckForMatchingPieces()
    {
        for (int x = 0; x < GameSettings.Columns; x++)
        {
            for (int y = 0; y < GameSettings.Rows; y++)
            {
                // Determine our shape
                int shapeID = GetPreviewAdjustedShape(x, y).GetComponent<ShapeInfo>().ShapeTypeID;

                int searchPointX = x;
                int searchPointY = y;

                // Check for horizontal matches

                // Go to the start of the x line
                while (searchPointX > 0 && GetPreviewAdjustedShape(searchPointX - 1, searchPointY).GetComponent<ShapeInfo>().ShapeTypeID == shapeID && !GetPreviewAdjustedShape(searchPointX - 1, searchPointY).GetComponent<ShapeInfo>().fallingToGravity)
                {
                    searchPointX--;
                }

                int xMatchCount = 1; // We know we match ourselves, and our while loop increments immediately
                // Now iterate forwards counting matches
                while (searchPointX < (GameSettings.Columns - 1) && GetPreviewAdjustedShape(searchPointX + 1, searchPointY).GetComponent<ShapeInfo>().ShapeTypeID == shapeID && !GetPreviewAdjustedShape(searchPointX + 1, searchPointY).GetComponent<ShapeInfo>().fallingToGravity)
                {
                    xMatchCount++;
                    searchPointX++;
                }

                // Check for vertical matches
                searchPointX = x;
                searchPointY = y;

                // Go to the top of the y line
                while (searchPointY > 0 && GetPreviewAdjustedShape(searchPointX, searchPointY - 1).GetComponent<ShapeInfo>().ShapeTypeID == shapeID && !GetPreviewAdjustedShape(searchPointX, searchPointY - 1).GetComponent<ShapeInfo>().fallingToGravity)
                {
                    searchPointY--;
                }

                int yMatchCount = 1;
                // Iterate downwards counting matches
                while (searchPointY < (GameSettings.Rows - 1) && GetPreviewAdjustedShape(searchPointX, searchPointY + 1).GetComponent<ShapeInfo>().ShapeTypeID == shapeID && !GetPreviewAdjustedShape(searchPointX, searchPointY + 1).GetComponent<ShapeInfo>().fallingToGravity)
                {
                    yMatchCount++;
                    searchPointY++;
                }

                GetPreviewAdjustedShape(x, y).GetComponent<ShapeInfo>().MatchCount = Mathf.Max(xMatchCount, yMatchCount);

            }
        }
    }

    public void DeleteMatchingPieces()
    {
        int PiecesDeleted = 0;

        for (int x = 0; x < GameSettings.Columns; x++)
        {
            for (int y = 0; y < GameSettings.Rows; y++)
            {
                if (Shapes[x][y].GetComponent<ShapeInfo>().MatchCount > 2 )
                {

                    Shapes[x][y].GetComponent<ShapeInfo>().FlaggedForExplosion = true;

                    // Inform the timekeeper that we are destroy this object
                    // (note, in a perfect world we'd try to make some of this event driven)
                    GameManager.Instance.TimeKeeper.ShapeDestroyedHandler(Shapes[x][y].GetComponent<ShapeInfo>().ShapeTypeID);

                    // Release this object from our tracking and allow it to go and explode itself
                    Shapes[x][y] = null;
                    PiecesDeleted++;

                    GameManager.Instance.ScoreAccumulator.AddShapes(1);
                }
            }
        }

        // Now, for each column, start at the bottom, compressing all the blocks, and then filling in with new ones to the top
        for (int x = 0; x < GameSettings.Columns; x++)
        {
            float startingGravity = 0.0f;
            for (int yDest = 0; yDest < GameSettings.Rows; yDest++)
            {
                if (Shapes[x][yDest] == null)
                {
                    int ySrc = yDest + 1;
                    while (ySrc < GameSettings.Rows && Shapes[x][ySrc] == null)
                    {
                        ySrc++;
                    }

                    if (ySrc < GameSettings.Rows)
                    {
                        // Copy down the piece
                        Shapes[x][yDest] = Shapes[x][ySrc];
                        Shapes[x][ySrc] = null;
                        Shapes[x][yDest].GetComponent<ShapeInfo>().visualDestinationPosition = GetWorldPositionForShapeLocation(x, yDest);
                    }
                    else
                    {
                        // Spawn in a new piece
                        Shapes[x][yDest] = SpawnRandomShape();
                        InitializeShapeWithDropParameters(x, yDest);
                    }
                    Shapes[x][yDest].GetComponent<ShapeInfo>().BeginFallingToGravity(startingGravity);
                    startingGravity += Random.Range(0.030f, 0.050f);
                }
            }

        }

        AvailableForDragging = (PiecesDeleted == 0) && NoPiecesAreFalling();

        if (AvailableForDragging)
        {
            // Finish scoring here
            if (GameManager.Instance.ScoreAccumulator.Score > 0)
                GameManager.Instance.ScoreAccumulator.LockInPoints();
        }
        else
        {
            GameManager.Instance.ScoreAccumulator.AddCombo(1);
        }
    }

    // Use this for initialization
    void Start () {

        InitializeLevel();
	}
	
	// Update is called once per frame
	void Update () {
        CheckForMatchingPieces();

        if (NoPiecesAreFalling() && !DeletionPaused)
        {
            DeleteMatchingPieces();
        }
    }
}
