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

[System.Serializable]
public struct SpecialWeight
{
    public GameObject prefab;
    [Range(0f, 1f)]
    public float weight;

    public static GameObject weightedRandom(SpecialWeight[] items)
    {
        var weightSum = items.Sum(i => i.weight);
        var randomIndex = Random.Range(0f, weightSum);
        var itemIndex = 0f;
        foreach (var item in items)
        {
            itemIndex += item.weight;
            if (randomIndex <= itemIndex)
                return item.prefab;
        }
        return null;
    }
}

[System.Serializable]
public struct ZoneData
{
    public int allowedTiles;
    public int minWorldSize;
    public int maxWorldSize;
    public int radius;
    public int radiusSquared { get { return radius * radius; } }
    public float worldFrequency;
    public GameObject border;
    public SpecialWeight[] specialWorlds;
    public float specialWorldFrequency;
    public GameObject[] forcedEvents;
}

public class Game : MonoBehaviour {

    public static Game instance
    {
        get { return GameObject.Find("Game").GetComponent<Game>(); }
    }

    public static World player
    {
        get
        {
            if (_player == null)
            {
                var go = GameObject.FindWithTag("Player");
                if (go != null) _player = go.GetComponent<World>();
            }
            return _player;
        }
    }
    static World _player;

    public static Hints hints
    {
        get
        {
            if (_hints == null)
            {
                var go = GameObject.Find("Hints");
                if (go != null) _hints = go.GetComponent<Hints>();
            }
            return _hints;
        }
    }
    static Hints _hints;

    public GameObject playerWorldPrefab;
    public GameObject worldPrefab;

    public ZoneData[] zones;
    public static ZoneData zone;
    public ZoneData ZoneAt(Vector3 pos)
    {
        var dist = Vector3.SqrMagnitude(pos);
        int i;
        for (i = 0; zones.Length-1 > i && zones[i].radiusSquared < dist; i++) ;
        return zones[i];
    }

    public static List<GameObject> stars = new List<GameObject>();
    public static List<GameObject> gods = new List<GameObject>();

    [Header("Spawn grid")]
    public int multiverseRadius = 128;
    public float gridSpacing = 10f;

    [Header("Runtime tracking")]
    public static HashSet<Tile> lifeContainingTiles = new HashSet<Tile>();

	void Start () {
        var playerWorld = Instantiate(playerWorldPrefab).GetComponent<World>();
        playerWorld.Generate(zones[0], true);
        playerWorld.tag = "Player";

        zone = zones[0];

        var size = multiverseRadius * 2 + 1;
        var maxIndex = Mathf.FloorToInt((size*size) / 2);
        var minIndex = -maxIndex;

        var eventCount = zones.Sum(z => z.forcedEvents.Length);
        int[] eventSlots = new int[eventCount];
        int eventIndex = 0;
        for (int zoneIndex = 0; zoneIndex < zones.Length; zoneIndex++)
        {
            var zRadius = Mathf.RoundToInt(zones[zoneIndex].radius/gridSpacing);
            var zRadiusSquared = zRadius * zRadius;
            var zPrevRadius = zoneIndex == 0 ? 0 : 
                Mathf.RoundToInt(zones[zoneIndex - 1].radius/gridSpacing);
            var zPrevRadiusSquared = zPrevRadius * zPrevRadius;
            var zSize = zRadius * 2 + 1;
            for (int i = 0; i < zones[zoneIndex].forcedEvents.Length; i++)
            {
                int x, y, xxyy;
                do {
                    x = Random.Range(-zRadius, zRadius + 1);
                    y = Random.Range(-zRadius, zRadius + 1);
                    xxyy = x * x + y * y;
                } while (!(xxyy <= zRadiusSquared && xxyy > zPrevRadiusSquared));
                eventSlots[eventIndex++] = y*size+x;
            }
        }

        for (int i = minIndex; i < maxIndex; i++)
        {
            if (i == 0) continue;
            var x = i % size;
            if (x < -multiverseRadius) x += size;
            if (x > multiverseRadius) x -= size;
            var y = i / size;
            var newPos = new Vector3(x * gridSpacing, 0, y * gridSpacing);
            var localZone = ZoneAt(newPos);

            GameObject eventPrefab = null;

            for (int j=0; j<eventCount; j++)
            {
                if (i == eventSlots[j])
                {
                    foreach (var z in zones)
                    {
                        if (z.forcedEvents.Length <= j)
                            j -= z.forcedEvents.Length;
                        else
                        {
                            eventPrefab = z.forcedEvents[j];
                            break;
                        }
                    }
                    if (eventPrefab != null) break;
                }
            }
            GameObject go = null;

            if (eventPrefab != null)
            {
                newPos.y = eventPrefab.transform.position.y;
                go = Instantiate(eventPrefab);
                go.transform.position = newPos;
            }
            else if (Random.Range(0f, 1f) < localZone.worldFrequency)
            {
                var isSpecial = (Random.Range(0f, 1f) < localZone.specialWorldFrequency);
                var prefab = isSpecial ? SpecialWeight.weightedRandom(localZone.specialWorlds) : worldPrefab;
                go = Instantiate(prefab);
                if (!isSpecial) go.GetComponent<World>().Generate(localZone);
                newPos.y = go.transform.position.y;
                go.transform.position = newPos;
            }
        }
    }

    public void Update()
    {
        if (Input.GetKey("escape")) Application.Quit();
    }
}
