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

//@DOC
// road tile prefabs:
// - must have collider fo roadTileSize
// - they can extend the geometry a bit past that if stitching is ok
// - origin at 0 and content extends from origin forward (and sideways)
// - if a Dynamic child is present that is the group for tile variations
//   its children are objects that may have variations (as multiple children themselves)
//   from these variations of an object, only one will be made active, the rest are destroyed

public class Road : MonoBehaviourSingleton<Road>
{
    const float m_roadTileSize = 48;

    [System.NonSerialized]
    public float m_playerStart = m_roadTileSize; // have 1 tile behind

    const float m_itemsDistributionStartOffset = m_roadTileSize * 1.3f;
    const float m_itemsDistributionEndOffset = m_roadTileSize * 0.3f;

    public GameObject[] m_tilesPrefabs;
    public GameObject m_tileEndPrefab;
    public GameObject m_memoryItemPrefab;

    [System.NonSerialized]
    public float m_distance;

    [System.NonSerialized]
    Transform m_road;

    List<Transform> m_tiles = new List<Transform>();

    float m_updateVisibilityTimer;
    
    public void Create()
    {
        m_road = SceneAccess.Find("Road").transform;

        float targetDistance = GetOptimalRoadTargetDistance();

        m_distance = 0;
        while (m_distance < targetDistance)
        {
            AddTile(m_distance);
            m_distance += m_roadTileSize;
        }

        Debug.Log("Road target distance = " + targetDistance + ", real distance = " + m_distance + " tiles count = " + m_tiles.Count);

        // add ending tile (don't change the road distance with this one)
        AddEndTile(m_distance);

        DistributeMemoryItems();

        UpdateVisibility();

        //@DBG
        //m_playerStart = m_distance - 0.1f;
    }

    float GetOptimalRoadTargetDistance()
    {
        int memoriesCount = MemoryBook.instance.GetNormalPagesCount();
        float step = m_roadTileSize * 0.7f;
//@DBG 
//step = 0.1f;

        float start = m_itemsDistributionStartOffset;
        float end = m_itemsDistributionEndOffset;
        return start + step * memoriesCount + end;
    }

    void AddTile(float z)
    {
        var rti = GFRandom.RangeOpen(0, m_tilesPrefabs.Length);
        var rto = GameObject.Instantiate(m_tilesPrefabs[rti]);
        rto.name = "RT" + (m_tiles.Count + 1).ToString("00") + "_" + m_tilesPrefabs[rti].name;
        
        var rt = rto.transform;
        rt.parent = m_road;
        rt.position = Vector3.forward * z;

        // float size = GF.GetGroupBound(rt.transform).size.z;

        RandomizeTileDynamicElemens(rt);

        m_tiles.Add(rt);
    }

    void AddEndTile(float z)
    {
        var rto = GameObject.Instantiate(m_tileEndPrefab);
        rto.name = "RT_END";

        var rt = rto.transform;
        rt.parent = m_road;
        rt.position = Vector3.forward * z;

        RandomizeTileDynamicElemens(rt);

        m_tiles.Add(rt);
    }

    void RandomizeTileDynamicElemens(Transform tile)
    {
        var dynamicGroup = tile.Find("Dynamic");
        if (!dynamicGroup)
            return;
        for(int i=0; i<dynamicGroup.childCount; i++)
        {
            var dc = dynamicGroup.GetChild(i);
            int r = GFRandom.RangeOpen(0, dc.childCount);
            for (int j = 0; j < dc.childCount; j++)
            {
                var dcv = dc.GetChild(j).gameObject;
                if (j == r)
                {
                    dcv.SetActive(true);
                }
                else
                {
                    dcv.transform.parent = null;
                    GameObject.Destroy(dcv);
                }
            }
        }
    }

    int GetTileIndexAtDistance(float d)
    {
        return Mathf.FloorToInt(d / m_roadTileSize);
    }

    public void OnUpdate()
    {
        m_updateVisibilityTimer -= Time.deltaTime;
        if (m_updateVisibilityTimer < 0)
            UpdateVisibility();
    }

    public void UpdateVisibility()
    {
        Vector3 pos = Camera.main.transform.position;

        float d = pos.z + m_roadTileSize / 2;
        float ti = GetTileIndexAtDistance(d);
        
        for(int i=0; i<m_tiles.Count; i++)
        {
            bool visible = ti - 1 <= i && i <= ti + 2;
            m_tiles[i].gameObject.SetActive(visible);
        }

        m_updateVisibilityTimer = 1f;
    }

    void DistributeMemoryItems()
    {
        int normalCount = MemoryBook.instance.GetNormalPagesCount();
        int totalCount = MemoryBook.instance.GetTotalPagesCount();
        float distance = m_distance - m_itemsDistributionStartOffset - m_itemsDistributionEndOffset;
        float step = distance / normalCount;
        Debug.Log(normalCount + " memory items distributed on " + distance + "m with step=" + step);

        GameObject[] memoryItems = new GameObject[totalCount];
        for (int i = 0; i < totalCount; i++)
        {
            const float SpawnY = 0.3f;
            const float SpawnXMin = 3;// 1.5f;
            const float SpawnXMax = 8;// 3;
            float d = i * step + GFRandom.RangeOpen(-step, step);
            d = Mathf.Clamp(d, 0, distance);
            float x = GFRandom.RangeOpen(SpawnXMin, SpawnXMax) * (GFRandom.RangeClose(0, 1) == 0 ? -1 : 1);
            Vector3 pos = new Vector3(x, SpawnY, m_itemsDistributionStartOffset + d);

            // last 3 items are placed at the start of each end road
            if(i >= normalCount)
            {
                Vector3 endRoadsOrigin = Vector3.forward * m_distance;
                Vector3 roadC = Vector3.forward;
                Vector3 roadL = (new Vector3(-1, 0, 1)).normalized;
                Vector3 roadR = (new Vector3(1, 0, 1)).normalized;
                float endDistance = 10;
                if (i == normalCount)
                    pos = endRoadsOrigin + roadL * endDistance;
                else if(i == normalCount + 1)
                    pos = endRoadsOrigin + roadC * endDistance;
                else if (i == normalCount + 2)
                    pos = endRoadsOrigin + roadR * endDistance;
                pos += Vector3.up * SpawnY;
            }

            memoryItems[i] = GameObject.Instantiate(m_memoryItemPrefab);
            memoryItems[i].name = "MI" + (i + 1).ToString("00");
            var mi = memoryItems[i].GetComponent<MemoryItem>();
            mi.m_memoryPageIdx = i;
            mi.transform.position = pos;

            int tileIdx = GetTileIndexAtDistance(pos.z);
            if (0 <= tileIdx && tileIdx < m_tiles.Count)
                mi.transform.SetParent(m_tiles[tileIdx], true);
            else
                Debug.Assert(false, "Tile out of distance idx=" + tileIdx + " pos.z=" + pos.z);
        }
    }
}
