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

public class ShuffleLayer
{
    public int Depth;
    public List<ShuffleLayer> Childs;
    public Vector3 Position;
    public Vector3 RawPosition;
    public int Size;
    public Vector3 Offset;

    public bool IsVisibleByCamera = false;
    public Bounds BoxBounds;
    public List<Transform> Spawned;

    public ShuffleLayer(int InDepth, Vector3 InPosition, Vector3 InRawPosition, Vector3 InOffset, int InSize) {
        Depth = InDepth;
        Position = InPosition;
        RawPosition = InRawPosition;
        Size = InSize;
        Offset = InOffset;
        BoxBounds = new Bounds(Position + Vector3.one * Size * 0.5f, Vector3.one * (Size - (2 - Depth) * 0.2f));
        Spawned = new List<Transform>();
    }

    public void GenerateChilds() {
        if (Depth <= 0) return;
        Childs = new List<ShuffleLayer>();
        int HalfSize = Size / 2;
        for(int x=0;x<2;++x) {
            for (int y = 0; y < 2; ++y) {
                for (int z = 0; z < 2; ++z) {
                    Vector3 Offset = new Vector3(x * HalfSize, y * HalfSize, z * HalfSize);
                    ShuffleLayer NewLayer = new ShuffleLayer(Depth - 1, Position + Offset, RawPosition + Offset, Offset, HalfSize);
                    NewLayer.GenerateChilds();
                    Childs.Add(NewLayer);
                }
            }
        }
    }

    public void DrawDebugBox() {
        Gizmos.color = new Color(1.0f, Depth/3.0f, 0.0f);
        //if (Spawned.Count>0)
        Gizmos.DrawWireCube(BoxBounds.center, BoxBounds.size);

        if (Depth <= 0) return;

        foreach (var Child in Childs) {
            Child.DrawDebugBox();
        }
    }
}

public class ShuffleElement {
    public Vector3 Position;
    public Transform Object;

    public ShuffleElement(Vector3 InPosition, Transform InObject) {
        Position = InPosition;
        Object = InObject;
    }
}

public class WorldShuffler : MonoBehaviour {

    public Transform ShuffleTarget;
    public Transform World;
    public int BlockSize = 4;
    public int BlockDepth = 4;

    public Transform DebugCube;

    private Vector3 MinBounds;
    private Vector3 MaxBounds;
    private int SizeX;
    private int SizeY;
    private int SizeZ;

    public int TopLayerX = 4;
    public int TopLayerY = 1;
    public int TopLayerZ = 4;

    private List<List<List<List<ShuffleElement>>>> RawGrid;

    private List<ShuffleLayer> TopLayers;

    Vector3 SnapVector(Vector3 V) {
        Vector3 Result = new Vector3();
        Result.x = Mathf.Floor(V.x / BlockSize) * BlockSize;
        Result.y = Mathf.Floor(V.y / BlockSize) * BlockSize;
        Result.z = Mathf.Floor(V.z / BlockSize) * BlockSize;
        return Result;
    }

    void AddToRawGrid(Transform Obj) {
        if (!Obj) return;

        Vector3 SnapPos = Obj.position - Vector3.one * 2.0f - MinBounds;
        int PX = Mathf.FloorToInt(SnapPos.x / BlockSize);
        int PY = Mathf.FloorToInt(SnapPos.y / BlockSize);
        int PZ = Mathf.FloorToInt(SnapPos.z / BlockSize);

        if (PX < 0 || PX >= SizeX) return;
        if (PY < 0 || PY >= SizeY) return;
        if (PZ < 0 || PZ >= SizeZ) return;

        Vector3 BasePosition = new Vector3(PX * BlockSize, PY * BlockSize, PZ * BlockSize);

        RawGrid[PX][PY][PZ].Add(new ShuffleElement(Obj.position - BasePosition, Obj));
    }

    List<ShuffleElement> GetGridElements(Vector3 Position) {

        Vector3 SnapPos = Position + Vector3.one * BlockSize * 0.5f;
        int PX = Mathf.FloorToInt(SnapPos.x / BlockSize);
        int PY = Mathf.FloorToInt(SnapPos.y / BlockSize);
        int PZ = Mathf.FloorToInt(SnapPos.z / BlockSize);

        if (PX < 0 || PX >= SizeX) return null;
        if (PY < 0 || PY >= SizeY) return null;
        if (PZ < 0 || PZ >= SizeZ) return null;

        return RawGrid[PX][PY][PZ];
    }

    List<ShuffleElement> GetGridElementsWrap(Vector3 Position) {

        Vector3 SnapPos = Position + Vector3.one * BlockSize * 0.5f;
        int PX = Mathf.FloorToInt(SnapPos.x / BlockSize);
        int PY = Mathf.FloorToInt(SnapPos.y / BlockSize);
        int PZ = Mathf.FloorToInt(SnapPos.z / BlockSize);

        PX = ((PX % SizeX) + SizeX) % SizeX;
        PY = ((PY % SizeY) + SizeY) % SizeY;
        PZ = ((PZ % SizeZ) + SizeZ) % SizeZ;

        return RawGrid[PX][PY][PZ];
    }

    void CreateShuffleLayers() {

        TopLayers = new List<ShuffleLayer>();

        int TopX = SizeX;
        int TopY = SizeY;
        int TopZ = SizeZ;
        int LayerBlockSize = BlockSize;
        for (int l=0;l< BlockDepth; ++l) {
            TopX = Mathf.CeilToInt((float)TopX / 2.0f);
            TopY = Mathf.CeilToInt((float)TopY / 2.0f);
            TopZ = Mathf.CeilToInt((float)TopZ / 2.0f);
            LayerBlockSize *= 2;
        }

        Vector3 TargetOffset = SnapVector(ShuffleTarget.position) - Vector3.one * BlockSize * 0.5f;

        for (int x=0; x<TopLayerX; ++x) {
            for (int y = 0; y < TopLayerY; ++y) {
                for (int z = 0; z < TopLayerZ; ++z) {
                    Vector3 DestPosition = new Vector3(x * LayerBlockSize, y * LayerBlockSize, z * LayerBlockSize);
                    TopLayers.Add(new ShuffleLayer(BlockDepth, DestPosition + TargetOffset, DestPosition, Vector3.zero, LayerBlockSize));
                }
            }
        }

        foreach(var Layer in TopLayers) {
            Layer.GenerateChilds();
        }
    }

    void ShuffleInit() {
        foreach (var Layer in TopLayers) {
            ShuffleLayerInit(Layer);
        }
    }

    void InstanciateLayer(ShuffleLayer Layer) {
        if (Layer.Depth <= 0) {

            List<ShuffleElement> Cell = GetGridElementsWrap(Layer.RawPosition);
            if (Cell != null) {
                foreach(var Spawn in Layer.Spawned) {
                    GameObject.Destroy(Spawn.gameObject);
                }
                Layer.Spawned.Clear();
                foreach (var Element in Cell) {
                    Transform NewCube = Transform.Instantiate(Element.Object);
                    if (NewCube) {
                        NewCube.position = Layer.Position + Vector3.one * Layer.Size * 0.5f;// + Element.Position;
                    }
                    Layer.Spawned.Add(NewCube);
                }
            }
        }
    }

    void ShuffleLayerInit(ShuffleLayer Layer) {
        if (Layer.Depth <= 0) {
            InstanciateLayer(Layer);
            return;
        }

        foreach (var Child in Layer.Childs) {
            ShuffleLayerInit(Child);
        }
    }

    // Use this for initialization
    void Start () {

        MinBounds = SnapVector(transform.position - transform.localScale * 0.5f);
        MaxBounds = SnapVector(transform.position + transform.localScale * 0.5f);

        SizeX = Mathf.FloorToInt((MaxBounds.x - MinBounds.x) / BlockSize);
        SizeY = Mathf.FloorToInt((MaxBounds.y - MinBounds.y) / BlockSize);
        SizeZ = Mathf.FloorToInt((MaxBounds.z - MinBounds.z) / BlockSize);

        RawGrid = new List<List<List<List<ShuffleElement>>>>();
        for (int ix=0; ix<SizeX; ++ix)
        {
            var CellX = new List<List<List<ShuffleElement>>>();
            for (int iy = 0; iy < SizeY; ++iy)
            {
                var CellY = new List<List<ShuffleElement>>();
                for (int iz = 0; iz < SizeZ; ++iz)
                {
                    var CellZ = new List<ShuffleElement>();                    
                    CellY.Add(CellZ);
                }
                CellX.Add(CellY);
            }
            RawGrid.Add(CellX);
        }
                
        Transform[] AllChildren = World.GetComponentsInChildren<Transform>();
        foreach (Transform Child in AllChildren)
        {
            if (Child != World) {
                AddToRawGrid(Child);
            }
        }
        
        CreateShuffleLayers();

        World.gameObject.SetActive(false);

        ShuffleInit();
    }


    void SwitchLayer(ShuffleLayer Layer) {
        if (Layer.Depth <= 0) {
            InstanciateLayer(Layer);
            return;
        }

        foreach (var Child in Layer.Childs) {
            Child.RawPosition = Layer.RawPosition + Child.Offset;
            Child.IsVisibleByCamera = true;
            SwitchLayer(Child);
        }
    }

    void UpdateLayer(ShuffleLayer Layer, bool IsParentVisible) {

        bool CurrentVisible = false;
        if (IsParentVisible) {
            CurrentVisible = GeometryUtility.TestPlanesAABB(GeometryUtility.CalculateFrustumPlanes(GameManager.Instance.Cam), Layer.BoxBounds);
            if (!CurrentVisible && Layer.IsVisibleByCamera) {
                Vector3 PlayerToObject = GameManager.Instance.GetPlayerPosition() - Layer.Position;
                //if (PlayerToObject.magnitude > (Mathf.Sqrt(Layer.Size*2.0f) + 1.0f)) {
                if (PlayerToObject.magnitude > (Mathf.Sqrt(Layer.Size * Layer.Size * 2.0f) + 1.0f)) {
                    //if (Layer.Depth == 3) {
                        Layer.RawPosition = new Vector3(Random.Range(0, SizeX), Random.Range(0, SizeY), Random.Range(0, SizeZ));
                        SwitchLayer(Layer);
                    //}
                }
            }
        }
        Layer.IsVisibleByCamera = IsParentVisible && CurrentVisible;

        if(Layer.Depth>0) {
            foreach (var Child in Layer.Childs) {
                UpdateLayer(Child, CurrentVisible);
            }
        }
    }

    void UpdateLayers() {
        foreach (var Layer in TopLayers) {
            UpdateLayer(Layer, true);
        }
    }

	
	// Update is called once per frame
	void Update () {

        UpdateLayers();

    }

    void OnDrawGizmos() {
        if (TopLayers != null) {
            foreach (var Layer in TopLayers) {
                Layer.DrawDebugBox();
            }
        }
    }
}
