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 List<IsoItem> allList = new List<IsoItem>();
    private Camera mainCamera;

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

    private List<List<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<List<IsoItem>>();
        for (int i = 0; i < numChunks; i++)
        {
            chunks.Add(new List<IsoItem>());
        }
    }

    public void AddIsoItem(IsoItem item)
    {
        if (items.Add(item))
        {
            if (item.IsoType == IsoItem.IsoTypes.Sprite)
            {
                spriteList.Add(item);
            }
            else
            {
                objectList.Add(item);
            }
            allList.Add(item);
        }
    }
    public void RemoveIsoItem(IsoItem item)
    {
        if (items.Remove(item))
        {
            if (item.IsoType == IsoItem.IsoTypes.Sprite)
            {
                spriteList.Remove(item);
            }
            else
            {
                objectList.Remove(item);
            }
            allList.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 List<IsoItem> ordered = new List<IsoItem>();
    //private HashSet<int> usedChunks = new HashSet<int>();
    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]);
        }

        Ray outRay = new Ray();
        outRay.direction = camForward;

        Ray inRay = new Ray();
        inRay.direction = -camForward;

        int layerIn = LayerMask.GetMask("iso_piece", "leaf", "player_controller");
        int layerOut = LayerMask.GetMask("iso_piece");
        RaycastHit hitInfo = new RaycastHit();

        //usedChunks.Clear();
        int count = spriteList.Count;
        for (int i = 0; i < 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 = 1; // 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++)
                    {
                        int chunkIndex = y * ChunksX + x;
                        List<IsoItem> chunk = chunks[chunkIndex];
                        bool placed = false;
                        for (var j = 0; j < chunk.Count; j++)
                        {
                            if (item.CameraSpace.z > chunk[j].CameraSpace.z)
                            {
                                chunk.Insert(j, item);
                                

                                placed = true;
                                break;
                            }
                        }
                        if (!placed)
                        {
                            chunk.Add(item);
                        }
                        //usedChunks.Add(chunkIndex);
                    }
                }
            }
            */

            //if (item.Moved)
            //{
            outRay.origin = inRay.origin = item.transform.position;

            bool hit = Physics.Raycast(inRay, out hitInfo, item.CameraSpace.z, layerIn);
            //if (hit && hitInfo.collider.transform.IsChildOf(item.transform))
            //{
            //    inRay.origin += inRay.direction * hitInfo.distance * 1.1f;
            //    hit = Physics.Raycast(inRay, out hitInfo, item.CameraSpace.z, layerIn);
            //}
            if (hit)
            {
                IsoItem otherItem = hitInfo.collider.GetComponentInParent<IsoItem>();
                if (otherItem != null)
                {
                    item.Behind.Add(otherItem);
                }
            }

            hit = Physics.Raycast(outRay, out hitInfo, 50, layerIn);
            //if (hit && hitInfo.collider.transform.IsChildOf(item.transform))
            //{
            //    outRay.origin += outRay.direction * hitInfo.distance * 1.1f;
            //    hit = Physics.Raycast(inRay, out hitInfo, item.CameraSpace.z, layerIn);
            //}
            if (hit)
            {
                IsoItem otherItem = hitInfo.collider.GetComponentInParent<IsoItem>();
                if (otherItem != null)
                {
                    otherItem.Behind.Add(item);
                }
            }
            //}

        }

        /*
        //foreach (int i in usedChunks)
        //{
        for (int i = 0; i < numChunks; i++)
        {
            List<IsoItem> chunk = chunks[i];
            for (int j = 0; j < chunk.Count - 1; j++)
            {
                chunk[j].Behind.Add(chunk[j + 1]);/
            }
            chunk.Clear();
        }
        */

        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]);
        }

        int nextUnmarked = 0;
        int total = allList.Count;
        while (nextUnmarked < total)
        {
            IsoItem item = allList[nextUnmarked];
            Visit(item);

            nextUnmarked++;

            while (nextUnmarked < total && allList[nextUnmarked].MarkPerm)
            {
                nextUnmarked++;
            }
        }
    }

    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;
        //for (int i = 0; i < item.Behind.Count; i++)
        //{
        //    Visit(item.Behind[i]);
        //}
        foreach (IsoItem nextItem in item.Behind)
        {
            Visit(nextItem);
        }
        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;
    }
}
