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

[System.Serializable]
public class Tileset
{
    public GameObject[] tiles;
    public GameObject[] decoration;
    public GameObject[] background;
}

[System.Serializable]
public class WorldGenConfig
{
    public string name;
    public int distanceFromCore;
    public int tilesetIndex;
    public Texture2D layout;
    public bool hasSun;
    public GameObject[] miscElements;
}

public class World : MonoBehaviour {

    [Header("Generation")]
    [System.NonSerialized]
    public WorldGenConfig configuration;
    public int configurationIndex;
    
    public static World currentInstance
    {
        get { return Game.instance.worlds[currentWorldIndex].world; }
    }

    public delegate void OnWorldChangeDelegate(int prev, int current);
    public static OnWorldChangeDelegate onWorldChange;

    public static int currentWorldIndex
    {
        get { return _currentWorldIndex; }
        set
        {
            if (value == _currentWorldIndex) return;
            var prev = _currentWorldIndex;
            _currentWorldIndex = value;
            if (onWorldChange != null)
                onWorldChange(prev, value);
        }
    }
    private static int _currentWorldIndex = -1;

    public int width { get; private set; }
    public int height { get; private set; }
    public int indexCount { get; private set; }

    public int distanceFromCore;

    [System.Flags]
    public enum BlockMask
    {
        None = 0,
        Wall = 1,
        Hole = 2,
        Drone = 4,
        Warp = 8,

        Impassable = Wall | Hole | Drone,
        ImpassableWithClaw = Wall | Drone,
        CannotDrop = Wall | Hole | Drone | Warp,
    }

    [Header("Debug")]
    public BlockMask[] blocked; //marks tiles where drones cannot go
    public Interactible[] interactableMap;

    public HashSet<int> warpTiles;
    public HashSet<int> emptyTiles;
    
    void Init()
    {
        distanceFromCore = configuration.distanceFromCore;
        width = configuration.layout.width;
        height = configuration.layout.height;
        indexCount = width * height;
        blocked = new BlockMask[width * height];
        interactableMap = new Interactible[width * height];
        warpTiles = new HashSet<int>();
        emptyTiles = new HashSet<int>();
    }

    public void AddWarp(Vector3 pos)
    {
        var index = GetIndex(pos);
        blocked[index] |= BlockMask.Warp;
        warpTiles.Add(index);
    }

    public void AddPrefab(GameObject prefab, Vector3 pos, Quaternion rotation)
    {
        Instantiate(prefab, pos, rotation);
    }

    public void RemoveObject(GameObject o)
    {
        Destroy(o);
    }

    public static World Create(int configIndex)
    {
        var config = Game.instance.configurations[configIndex];
        var go = new GameObject(config.name);
        go.transform.position = new Vector3(configIndex * 100, 0, 0);

        var world = go.AddComponent<World>();
        world.configurationIndex = configIndex;
        world.configuration = config;

        world.Init();

        return world;
    }

    public void Gen () {
        var pixels = configuration.layout.GetPixels();
        var tileset = Game.instance.tilesets[configuration.tilesetIndex];
        for (int i = 0; i < pixels.Length; i++)
        {
            int x = i % width;
            int y = i / width;
            var pixel = pixels[i];
            var tileIndex = Mathf.FloorToInt(pixel.r * 256) / 20;
            var decorIndex = Mathf.FloorToInt(pixel.g * 256) / 20;
            var decorRotation = Mathf.FloorToInt(pixel.b * 360);

            if (tileIndex < tileset.tiles.Count() && tileset.tiles[tileIndex] != null)
            {
                var tile = tileset.tiles[tileIndex];
                var go = Instantiate(tile);
                go.transform.SetParent(transform);
                go.transform.localPosition = new Vector3(x, 0, y);

                var gridTile = Instantiate(Game.instance.gridPrefab);
                gridTile.transform.SetParent(transform);
                gridTile.transform.localPosition = new Vector3(x, 0, y);
            }
            else
            {
                blocked[i] |= BlockMask.Wall;
            }

            if (decorIndex < tileset.decoration.Count() && tileset.decoration[decorIndex] != null)
            {
                var decor = tileset.decoration[decorIndex];
                var go = Instantiate(decor);
                go.transform.SetParent(transform);
                go.transform.localPosition = new Vector3(x, 0, y);
                go.transform.localRotation = Quaternion.Euler(0, decorRotation, 0);
                go.name = decor.name;
            }
        }

        foreach (var bg in tileset.background)
        {
            var go = Instantiate(bg);
            go.transform.SetParent(transform);
            go.transform.localPosition = Vector3.zero;
        }

        foreach (var bg in configuration.miscElements)
        {
            var go = Instantiate(bg);
            go.transform.SetParent(transform);
            go.transform.localPosition = Vector3.zero;
        }
    }

    public int GetIndex(Vector3 pos)
    {
        return GetIndex(Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z));
    }

    public int GetIndex(int x, int y)
    {
        y %= 100;
        x %= 100;
        return (y) * width + x;
    }

    public Vector3 GetLocalPosition(int index)
    {
        var x = index % width;
        var y = index / width;
        return new Vector3(x, 0, y);
    }

    public Vector3 GetWorldPos()
    {
        return transform.position;
    }

    public Vector3 GetPosition(int index)
    {
        return GetLocalPosition(index) + GetWorldPos();
    }

    public bool IsValid(Vector3 pos)
    {
        return IsValid(Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z));
    }

    public bool IsValid(int x, int y)
    {
        x -= 100 * configurationIndex;
        if (emptyTiles.Contains(GetIndex(x, y)))
            return false;
        return x >= 0 && y >= 0 && x < width && y < height;
    }

    public bool IsValid(int index)
    {
        return index >= 0 && index < width * height;
    }

    public static int GetWorldIndex(Vector3 pos)
    {
        return GetWorldIndex(Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z));
    }

    public static int GetWorldIndex(int x, int y)
    {
        return x / 100;
    }

    public bool IsOccupied(int index)
    {
        return (blocked[index] & BlockMask.Drone) != 0;
    }
}
