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

[SelectionBase]
public class IsoHandler : MonoBehaviour {
    private HashSet<IsoItem> items = new HashSet<IsoItem>();
    private List<IsoItem> spriteList = new List<IsoItem>();
    private List<IsoItem> objectList = new List<IsoItem>();
    private Camera mainCamera;

    public int ChunksX = 40;
    public int ChunksY = 40;
    private int numChunks;

    private List<HashSet<IsoItem>> chunks;

    // Use this for initialization
    void Start()
    {
        numChunks = ChunksX * ChunksY;
        mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();
        mainCamera.transparencySortMode = TransparencySortMode.Orthographic;

        chunks = new List<HashSet<IsoItem>>();
        for (int i = 0; i < numChunks; i++)
        {
            chunks.Add(new HashSet<IsoItem>());
        }
    }

    public void AddIsoItem(IsoItem item)
    {
        if (items.Add(item))
        {
            if (item.IsoType == IsoItem.IsoTypes.Sprite)
            {
                spriteList.Add(item);
            }
            else
            {
                objectList.Add(item);
            }
        }
    }
    public void RemoveIsoItem(IsoItem item)
    {
        if (items.Remove(item))
        {
            if (item.IsoType == IsoItem.IsoTypes.Sprite)
            {
                spriteList.Remove(item);
            }
            else
            {
                objectList.Remove(item);
            }
        }
    }

    int ItemSort(IsoItem a, IsoItem b)
    {
        float diff = b.CameraSpace.z - a.CameraSpace.z;
        if (diff > 0) return 1;
        if (diff < 0) return -1;
        return 0;
    }

    private float nextDepth = 0;
    private Vector3 camForward;
    private Vector3 camRight;
    private Vector3 camUp;
    private Vector3 camPos;
    private HashSet<IsoItem> remaining = new HashSet<IsoItem>();
    private List<IsoItem> ordered = new List<IsoItem>();
    private List<IsoItem> tmpSort = new List<IsoItem>();
    void LateUpdate()
    {
        camForward = mainCamera.transform.forward;
        camRight = mainCamera.transform.right;
        camUp = mainCamera.transform.up;
        camPos = mainCamera.transform.position;

        float worldSizeH = mainCamera.orthographicSize * 2;
        float worldSizeW = worldSizeH * mainCamera.aspect;

        Vector3 worldBottomLeft = mainCamera.ViewportToWorldPoint(new Vector3());

        remaining.Clear();
        ordered.Clear();

        nextDepth = 10;

        for (int i = 0; i < spriteList.Count; i++)
        {
            spriteList[i].Behind.Clear();
            spriteList[i].MarkTemp = false;
            spriteList[i].MarkPerm = false;
            remaining.Add(spriteList[i]);
        }

        for (int i = 0; i < objectList.Count; i++)
        {
            objectList[i].Behind.Clear();
            objectList[i].MarkTemp = false;
            objectList[i].MarkPerm = false;
            remaining.Add(objectList[i]);
        }

        for (int i = 0; i < spriteList.Count; i++)
        {
            IsoItem item = spriteList[i];
            Vector3 screenSpace = mainCamera.WorldToViewportPoint(item.transform.position);
            //item.CameraSpace.x = worldBottomLeft.x + screenSpace.x * worldSizeW;
            //item.CameraSpace.y = worldBottomLeft.y + screenSpace.y * worldSizeH;
            //item.CameraSpace.z = screenSpace.z;
            item.CameraSpace = screenSpace;
            if (item.IsoRadius > 0)
            {
                float scale = Mathf.Max(item.LinkedImage.transform.lossyScale.x, Mathf.Max(item.LinkedImage.transform.lossyScale.y, item.LinkedImage.transform.lossyScale.z));
                float radiusCamX = item.IsoRadius / worldSizeW * scale;
                float radiusCamY = item.IsoRadius / worldSizeH * scale;
                int leftBucket = Mathf.Max(0, Mathf.Min(ChunksX - 1, (int)Mathf.Floor((screenSpace.x - radiusCamX) * ChunksX)));
                int rightBucket = Mathf.Max(0, Mathf.Min(ChunksX - 1, (int)Mathf.Ceil((screenSpace.x + radiusCamX) * ChunksX)));

                int bottomBucket = Mathf.Max(0, Mathf.Min(ChunksY - 1, (int)Mathf.Floor((screenSpace.y - radiusCamY) * ChunksY)));
                int topBucket = Mathf.Max(0, Mathf.Min(ChunksY - 1, (int)Mathf.Ceil((screenSpace.y + radiusCamY) * ChunksY)));

                for (int x = leftBucket; x <= rightBucket; x++)
                {
                    for (int y = bottomBucket; y <= topBucket; y++)
                    {
                        HashSet<IsoItem> chunk = chunks[y * ChunksX + x]; ;
                        chunk.Add(item);
                    }
                }
            }

            int layer = LayerMask.GetMask("iso_piece");
            RaycastHit hitInfo = new RaycastHit();
            if (Physics.Raycast(new Ray(item.transform.position, -camForward), out hitInfo, item.CameraSpace.z, layer))
            {
                IsoItem otherItem = hitInfo.collider.GetComponentInParent<IsoItem>();
                if (otherItem != null)
                {
                    item.Behind.Add(otherItem);
                }
            }
            if (Physics.Raycast(new Ray(item.transform.position, camForward), out hitInfo, 50, layer))
            {
                IsoItem otherItem = hitInfo.collider.GetComponentInParent<IsoItem>();
                if (otherItem != null)
                {
                    otherItem.Behind.Add(item);
                }
            }
            
        }

        for (int i = 0; i < numChunks; i++)
        {
            if (chunks[i].Count > 0)
            {
                tmpSort.Clear();
                tmpSort.AddRange(chunks[i]);
                chunks[i].Clear();
                tmpSort.Sort(ItemSort);

                for (int j = 0; j < tmpSort.Count - 1; j++)
                {
                    tmpSort[j].Behind.Add(tmpSort[j + 1]);
                    if (tmpSort[j].CameraSpace.z < tmpSort[j + 1].CameraSpace.z)
                    {
                        bool bp = true;
                    }
                }
            }
        }

        for (int i = 0; i < objectList.Count; i++)
        {
            IsoItem item = objectList[i];
            item.CameraSpace = mainCamera.WorldToViewportPoint(item.LinkedImage.transform.position);
        }

        objectList.Sort(ItemSort);
        for (int i = 0; i < objectList.Count - 1; i++)
        {
            objectList[i].Behind.Add(objectList[i + 1]);
        }

        while (remaining.Count > 0)
        {
            IsoItem item = remaining.First();
            Visit(item);
        }
    }

    private void Visit(IsoItem item)
    {
        if (item.MarkPerm) return;
        if (item.MarkTemp)
        {            
            Debug.LogError("CYCLICAL RENDER GRAPH");
            item.MarkTemp = false;
            item.MarkPerm = true;
            remaining.Remove(item);
            return;
        }
        item.MarkTemp = true;
        foreach (IsoItem newItem in item.Behind)
        {
            Visit(newItem);
        }
        item.MarkTemp = false;
        item.MarkPerm = true;
        remaining.Remove(item);

        item.SetDepth = nextDepth;

        Vector3 camPos = item.CameraSpace;
        camPos.z = nextDepth;
        item.LinkedImage.transform.position = mainCamera.ViewportToWorldPoint(camPos);

        nextDepth += 0.01f;
    }
}
