﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun; 

using PhotonHashTable = ExitGames.Client.Photon.Hashtable;

public class GridHandler : MonoBehaviour {

	/// This script creates the grid and handles visibility in the Scene View

	public Grid grid;											// scriptable Grid object (can't do anything without this)
    [SerializeField] NodeValidator nodeValidator;				// reference to the Node Validator Object
    
    bool m_usableCheck;											// can this node be used to build something?
	private List<Vector3> usableNode = new List<Vector3>();		// list of all the usable nodes as Vector3's for building

    [Header("Visuals")]
    [SerializeField] bool visualizeGrid = true;					// do you want to see the grid in the scene view?
	[SerializeField] bool visualizeUsableGrid = true;			// do you want to see all the usable grid cubes in the scene view? (only in play mode)
    [SerializeField] Color gridColor;							// color of the visual grid

    GridNode gNode;												// node placeholder for creating the list of nodes

    public static bool initializing = false;
    public static bool initialized = false;
    bool cancel = false;


    public IEnumerator InitializeGrid() {
        /// stopwatch is used to test out grid creation/load times, the main purpose
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        if (initializing) {
            cancel = true;
        }

        initializing = true;
        if (PhotonNetwork.IsConnected) {
            PhotonHashTable update = new PhotonHashTable() { { "loading", true } };
            PhotonNetwork.LocalPlayer.SetCustomProperties(update);
        }

        grid.GridBase.Clear();
        grid.GridBaseIndexed.Clear();

        /// create the actual grid
        int currentIndex = 0;
        for (int x = 0; x < grid.gridSize.x * grid.nodeSize; x += grid.nodeSize) {
            for (int z = 0; z < grid.gridSize.z * grid.nodeSize; z += grid.nodeSize) {
                for (int y = 0; y < grid.gridSize.y * grid.nodeSize; y += grid.nodeSize) {

                    var point = grid.GetNodeCenter(new Vector3(x, y, z) + transform.position);
                    m_usableCheck = nodeValidator.CheckNode(point);
                    gNode = new GridNode(point, m_usableCheck);
                    gNode.index = currentIndex;
                    if (m_usableCheck) usableNode.Add(point);
                    grid.GridBase.Add(point, gNode);
                    grid.GridBaseIndexed.Add(currentIndex++, gNode);
                }
            }

            yield return null;

            if (cancel) {
                cancel = false;
                yield break;
            }
        }

        sw.Stop();
        //Debug.Log("Scanning done in " + sw.ElapsedMilliseconds + "ms");
        sw.Reset();
        sw.Start();


        for (int x = 0; x < grid.gridSize.x * grid.nodeSize; x += grid.nodeSize) {
            for (int z = 0; z < grid.gridSize.z * grid.nodeSize; z += grid.nodeSize) {
                for (int y = 0; y < grid.gridSize.y * grid.nodeSize; y += grid.nodeSize) {
                    var m_thisPosition = transform.position;

                    var point = grid.GetNodeCenter(new Vector3(x, y, z) + m_thisPosition);
                    GridNode gridNode = grid.GetNode(point);

                    var forward = grid.GetNodeCenter(new Vector3(x, y, z + grid.nodeSize) + m_thisPosition);
                    GridNode nodeForward = grid.GetNode(forward);
                    gridNode.AddStaticGridNeighbour(Neighbour.Forward, nodeForward);

                    var backward = grid.GetNodeCenter(new Vector3(x, y, z - grid.nodeSize) + m_thisPosition);
                    GridNode nodeBackward = grid.GetNode(backward);
                    gridNode.AddStaticGridNeighbour(Neighbour.Backward, nodeBackward);


                    var right = grid.GetNodeCenter(new Vector3(x + grid.nodeSize, y, z) + m_thisPosition);
                    GridNode nodeRight = grid.GetNode(right);
                    gridNode.AddStaticGridNeighbour(Neighbour.Right, nodeRight);

                    var left = grid.GetNodeCenter(new Vector3(x - grid.nodeSize, y, z) + m_thisPosition);
                    GridNode nodeLeft = grid.GetNode(left);
                    gridNode.AddStaticGridNeighbour(Neighbour.Left, nodeLeft);


                    var up = grid.GetNodeCenter(new Vector3(x, y + grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeUp = grid.GetNode(up);
                    gridNode.AddStaticGridNeighbour(Neighbour.Up, nodeUp);


                    var down = grid.GetNodeCenter(new Vector3(x, y - grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeDown = grid.GetNode(down);
                    gridNode.AddStaticGridNeighbour(Neighbour.Down, nodeDown);

                    var forwardUp = grid.GetNodeCenter(new Vector3(x, y + grid.nodeSize, z + grid.nodeSize) + m_thisPosition);
                    GridNode nodeForwardUp = grid.GetNode(forwardUp);
                    gridNode.AddStaticGridNeighbour(Neighbour.ForwardUp, nodeForwardUp);

                    var forwardDown = grid.GetNodeCenter(new Vector3(x, y - grid.nodeSize, z + grid.nodeSize) + m_thisPosition);
                    GridNode nodeForwardDown = grid.GetNode(forwardDown);
                    gridNode.AddStaticGridNeighbour(Neighbour.ForwardDown, nodeForwardDown);

                    var backwardDown = grid.GetNodeCenter(new Vector3(x, y - grid.nodeSize, z - grid.nodeSize) + m_thisPosition);
                    GridNode nodeBackwardDown = grid.GetNode(backwardDown);
                    gridNode.AddStaticGridNeighbour(Neighbour.BackwardDown, nodeBackwardDown);

                    var backwardUp = grid.GetNodeCenter(new Vector3(x, y + grid.nodeSize, z - grid.nodeSize) + m_thisPosition);
                    GridNode nodeBackwardUp = grid.GetNode(backwardUp);
                    gridNode.AddStaticGridNeighbour(Neighbour.BackwardUp, nodeBackwardUp);

                    var leftDown = grid.GetNodeCenter(new Vector3(x - grid.nodeSize, y - grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeLeftDown = grid.GetNode(leftDown);
                    gridNode.AddStaticGridNeighbour(Neighbour.LeftDown, nodeLeftDown);

                    var leftUp = grid.GetNodeCenter(new Vector3(x - grid.nodeSize, y + grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeLeftUp = grid.GetNode(leftUp);
                    gridNode.AddStaticGridNeighbour(Neighbour.LeftUp, nodeLeftUp);


                    var rightDown = grid.GetNodeCenter(new Vector3(x + grid.nodeSize, y - grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeRightDown = grid.GetNode(rightDown);
                    gridNode.AddStaticGridNeighbour(Neighbour.RightDown, nodeRightDown);

                    var rightUp = grid.GetNodeCenter(new Vector3(x + grid.nodeSize, y + grid.nodeSize, z) + m_thisPosition);
                    GridNode nodeRightUp = grid.GetNode(rightUp);
                    gridNode.AddStaticGridNeighbour(Neighbour.RightUp, nodeRightUp);
                }
            }

            if (cancel) {
                cancel = false;
                yield break;
            }

            yield return null;
        }
        sw.Stop();
        //Debug.Log("Scanning done in " + sw.ElapsedMilliseconds + "ms");
        initializing = false;
        if (PhotonNetwork.IsConnected) {
            PhotonHashTable update = new PhotonHashTable() { { "loading", false } };
            PhotonNetwork.LocalPlayer.SetCustomProperties(update);
        }
    }

    private void Start() {

        //InitializeGrid();

    }

	/// returns the grid's center point (currently not used for anything)
    public Vector3 GetGridCenter() {
        return transform.position + new Vector3(grid.gridSize.x* grid.nodeSize - grid.nodeSize,grid.gridSize.y* grid.nodeSize -grid.nodeSize,grid.gridSize.z * grid.nodeSize - grid.nodeSize) / 2;
    }

	// does the snapping of the grid
    private Vector3 SnapPosition(Vector3 position) {
        int x = Mathf.RoundToInt(transform.position.x / grid.nodeSize);
        int y = Mathf.RoundToInt(transform.position.y / grid.nodeSize);
        int z = Mathf.RoundToInt(transform.position.z / grid.nodeSize);
        
        Vector3 snappedPoint = new Vector3(x * grid.nodeSize, y * grid.nodeSize, z * grid.nodeSize);
        return snappedPoint;
    }
    
	// visuals in the inspector regarding the grid and buildable area
    private void OnDrawGizmos() {

		/// returns the snapped grid position so that it will always work with the nodes
        transform.position = SnapPosition(transform.position);
        
		/// turning on Visualize Grid will allow you to see the entire grid that can be used
		/// this goes through each node on the grid and draws a wireframe of the buildable area
		if (visualizeGrid) {
            Gizmos.color = gridColor;
            for (float x = 0; x < grid.gridSize.x * grid.nodeSize; x += grid.nodeSize) {
                for (float z = 0; z < grid.gridSize.z * grid.nodeSize; z += grid.nodeSize) {
                    for (float y = 0; y < grid.gridSize.y * grid.nodeSize; y += grid.nodeSize) {
                        var point = transform.position + grid.GetNodeCenter(new Vector3(x, y, z));

                        Gizmos.DrawWireCube(point, new Vector3(grid.nodeSize, grid.nodeSize, grid.nodeSize));
                    }
                }
            }
        }

        /// turning on Visualize Usable Grid will allow you to see the grid boxes that are touching existing colliders
		/// these boxes are visual ques for what can actually be built on
		/// although you can build anywhere within the grid, you need to start building on solid ground.
		if (visualizeUsableGrid) {
            Gizmos.color = Color.green;
            for (int i = 0; i < usableNode.Count; i++) {
                Gizmos.DrawWireCube(usableNode[i], new Vector3(grid.nodeSize, grid.nodeSize, grid.nodeSize));
            }
        }
    }
}
