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

public class WFCGenerator : MonoBehaviour
{
    public Tiles[] tiles;
    public Vector2Int maxsize;
    public Tilemap ForeGround;
    public bool gen;
    private Vector2Int yeat;
    private wave[,] waves;
    private Thread genthread;
    private bool done;
    void Start()
    {
        genthread = new Thread(new ThreadStart(Generate));
    }
    void OnDisable()
    {
        genthread.Interrupt();
        genthread.Abort();
    }
    void Update()
    {
        if (gen)
        {
            gen = false;
            genthread = new Thread(new ThreadStart(Generate));
            genthread.Start();
        }
        if (genthread.ThreadState == ThreadState.Stopped && done)
        {
            done = false;
            draw();
        }
    }
    private void NewMap()
    {
        waves = new wave[maxsize.x, maxsize.y];
        for (int i = 0; i < waves.GetLength(0); i++)
        {
            for (int k = 0; k < waves.GetLength(1); k++)
            {
                waves[i, k].possible = new List<Tiles>(tiles);
            }
        }
        for(int i = 0; i < tiles.Length; i++)
        {
            tiles[i].Globalweight = 0;
        }
    }
    private void draw()
    {
        for (int i = 0; i < waves.GetLength(0); i++)
        {
            for (int k = 0; k < waves.GetLength(1); k++)
            {
                if (waves[i, k].possible.Count == 0)
                {
                    ForeGround.SetTile(new Vector3Int(-i, -k, 0), waves[i, k].selected.tile);
                }
            }
        }
    }
    private void Generate()
    {
        while (true)
        {
            //select with low ent
            Debug.Log("getting with low entropy");
            Vector2Int wav = new Vector2Int(0, 0);
            float smallestent = 1000;
            for (int runs = 0; runs < 2; runs++)
            {
                for (int i = 0; i < waves.GetLength(0); i++)
                {
                    for (int k = 0; k < waves.GetLength(1); k++)
                    {
                        if(runs == 0)
                        {
                            float total = 0;
                            float logtotal = 0;
                            foreach(var item in waves[i, k].possible)
                            {
                                total += item.weight;
                                logtotal += Mathf.Log(item.weight, 2);
                            }
                            waves[i, k].entropy = Mathf.Log(total, 2) - (logtotal/total);
                            continue;
                        }
                        if (waves[i, k].entropy < smallestent && waves[i, k].entropy != 0)
                        {
                            smallestent = waves[i, k].possible.Count;
                            wav = new Vector2Int(i, k);
                        }
                    }
                }
            }
            //set down
            Debug.Log("selecting random low entropy");
            System.Random r = new System.Random();
            waves[wav.x, wav.y].selected = waves[wav.x, wav.y].possible[r.Next(0, (int)smallestent)];
            waves[wav.x, wav.y].possible.Clear();
            //propagate
            Debug.Log("started propagation");
            List<Vector2Int> Pos = new List<Vector2Int>();
            Pos.Add(wav);
            while (true)
            {
                if (Pos.Count == 0)
                {
                    break;
                }
                propagate(Pos);
            }
            int check = 0;
            foreach (var item in waves)
            {
                check += item.possible.Count;
            }
            if (check == 0)
            {
                break;
            }
        }
        //draw
        done = true;
    }
    private bool propagate(List<Vector2Int> Pos)
    {
        bool change = false;
        //get working
        List<Tiles> t = new List<Tiles>();
        if (waves[Pos[0].x, Pos[0].y].possible.Count > 0)
        {
            t = waves[Pos[0].x, Pos[0].y].possible;
        }
        else
        {
            t.Add(waves[Pos[0].x, Pos[0].y].selected);
        }
        //checking the one to the left
        if (Pos[0].x - 1 >= 0)
        {
            if (waves[Pos[0].x - 1, Pos[0].y].possible.Count > 0)
            {
                List<Tiles> yeat = new List<Tiles>();
                bool gChange = false;
                foreach (var item in waves[Pos[0].x - 1, Pos[0].y].possible)
                {
                    bool nomatch = true;
                    foreach (var it in t)
                    {
                        if (item.TopRight == it.TopLeft && item.BottomRight == it.BottomLeft)
                        {
                            nomatch = false;
                            break;
                        }
                    }
                    if (nomatch)
                    {
                        yeat.Add(item);
                    }
                }
                foreach (var item in yeat)
                {
                    waves[Pos[0].x - 1, Pos[0].y].possible.Remove(item);
                    change = true;
                    gChange = true;
                }
                if (waves[Pos[0].x - 1, Pos[0].y].possible.Count == 1)
                {
                    waves[Pos[0].x - 1, Pos[0].y].selected = waves[Pos[0].x - 1, Pos[0].y].possible[0];
                    waves[Pos[0].x - 1, Pos[0].y].possible.Clear();
                }
                if (gChange)
                {
                    Pos.Add(new Vector2Int(Pos[0].x - 1, Pos[0].y));
                }
            }
        }
        //checking the one above
        if (Pos[0].y - 1 >= 0)
        {
            if (waves[Pos[0].x, Pos[0].y - 1].possible.Count > 0)
            {
                List<Tiles> yeat = new List<Tiles>();
                bool gChange = false;
                foreach (var item in waves[Pos[0].x, Pos[0].y - 1].possible)
                {
                    bool nomatch = true;
                    foreach (var it in t)
                    {
                        if (item.BottomRight == it.TopRight && item.BottomLeft == it.TopLeft)
                        {
                            nomatch = false;
                            break;
                        }
                    }
                    if (nomatch)
                    {
                        yeat.Add(item);
                    }
                }
                foreach (var item in yeat)
                {
                    waves[Pos[0].x, Pos[0].y - 1].possible.Remove(item);
                    change = true;
                    gChange = true;
                }
                if (waves[Pos[0].x, Pos[0].y - 1].possible.Count == 1)
                {
                    waves[Pos[0].x, Pos[0].y - 1].selected = waves[Pos[0].x, Pos[0].y - 1].possible[0];
                    waves[Pos[0].x, Pos[0].y - 1].possible.Clear();
                }
                if (gChange)
                {
                    Pos.Add(new Vector2Int(Pos[0].x, Pos[0].y - 1));
                }
            }
        }
        //checking the one to the right
        if (Pos[0].x + 1 < waves.GetLength(0))
        {
            if (waves[Pos[0].x + 1, Pos[0].y].possible.Count > 0)
            {
                List<Tiles> yeat = new List<Tiles>();
                bool gChange = false;
                foreach (var item in waves[Pos[0].x + 1, Pos[0].y].possible)
                {
                    bool nomatch = true;
                    foreach (var it in t)
                    {
                        if (item.TopRight == it.TopLeft && item.BottomRight == it.BottomLeft)
                        {
                            nomatch = false;
                            break;
                        }
                    }
                    if (nomatch)
                    {
                        yeat.Add(item);
                    }
                }
                foreach (var item in yeat)
                {
                    waves[Pos[0].x + 1, Pos[0].y].possible.Remove(item);
                    change = true;
                    gChange = true;
                }
                if (waves[Pos[0].x + 1, Pos[0].y].possible.Count == 1)
                {
                    waves[Pos[0].x + 1, Pos[0].y].selected = waves[Pos[0].x + 1, Pos[0].y].possible[0];
                    waves[Pos[0].x + 1, Pos[0].y].possible.Clear();
                }
                if (gChange)
                {
                    Pos.Add(new Vector2Int(Pos[0].x + 1, Pos[0].y));
                }
            }
        }
        //checking the one under
        if (Pos[0].y + 1 < waves.GetLength(1))
        {
            if (waves[Pos[0].x, Pos[0].y + 1].possible.Count > 0)
            {
                List<Tiles> yeat = new List<Tiles>();
                bool gChange = false;
                foreach (var item in waves[Pos[0].x, Pos[0].y + 1].possible)
                {
                    bool nomatch = true;
                    foreach (var it in t)
                    {
                        if (item.TopRight == it.BottomRight && item.TopLeft == it.BottomLeft)
                        {
                            nomatch = false;
                            break;
                        }
                    }
                    if (nomatch)
                    {
                        yeat.Add(item);
                    }
                }
                foreach (var item in yeat)
                {
                    waves[Pos[0].x, Pos[0].y + 1].possible.Remove(item);
                    change = true;
                    gChange = true;
                }
                if (waves[Pos[0].x, Pos[0].y + 1].possible.Count == 1)
                {
                    waves[Pos[0].x, Pos[0].y + 1].selected = waves[Pos[0].x, Pos[0].y + 1].possible[0];
                    waves[Pos[0].x, Pos[0].y + 1].possible.Clear();
                }
                if (gChange)
                {
                    Pos.Add(new Vector2Int(Pos[0].x, Pos[0].y + 1));
                }
            }
        }
        Pos.Remove(Pos[0]);
        return change;
    }
    private struct wave
    {
        public List<Tiles> possible;
        public Tiles selected;
        public float entropy;
    }

    [System.Serializable]
    public struct Tiles
    {
        public TileBase tile;
        public float weight;
        [Header("False Black / True White")]
        public bool TopLeft;
        public bool TopRight;
        public bool BottomLeft;
        public bool BottomRight;
        [System.NonSerialized]public float Globalweight;
    }
}