using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
//using System;

public class TheGrid : MonoBehaviour {

    public GameObject tower;
    public Transform towerParent;
    public GameObject textCharPrefab;
    public Transform textCharParent;
    public SpriteRenderer checkerBoard;
    public Transform leftWall;
    public Transform rightWall;
    public Transform botLeftWall;
    public Transform botRightWall;

    public int[][] grid;
    public int[][] paths;

    public const int WIDTH = 17; // letters
    public const int HEIGHT = 17; // cant use top row though

    public const int TargetX = WIDTH / 2;
    public const int TargetY = 0;

    // Start is called before the first frame update
    void Awake() {
        InitGrid();

        //float z = Camera.main.transform.position.z;
        //Camera.main.transform.position = new Vector3(WIDTH / 2.0f, HEIGHT / 2.0f, z);

        for (int y = 0; y < HEIGHT - 1; ++y) {
            for (int x = 0; x < WIDTH; ++x) {
                if (y == 0) {
                    // letter
                    var letter = SpawnCharText(((char)(x + 65)).ToString(), new Vector3(x, -1));
                    letter.verticalAlignment = VerticalAlignmentOptions.Bottom;
                }
            }
            // number
            var number = SpawnCharText((y + 1).ToString(), new Vector3(-1.25f, y));
            number.horizontalAlignment = HorizontalAlignmentOptions.Right;
        }

        // setup wall colliders
        float hs = 25.0f; // sprite wall half size
        leftWall.position = new Vector3(-hs, hs);
        rightWall.position = new Vector3(hs + WIDTH, hs);
        botLeftWall.position = new Vector3(-hs + WIDTH / 2, -hs);
        botRightWall.position = new Vector3(hs + WIDTH / 2 + 1, -hs);

        checkerBoard.transform.position = new Vector3(WIDTH / 2.0f, (HEIGHT - 1) / 2.0f, 1.0f);
        checkerBoard.size = new Vector2(WIDTH, HEIGHT - 1);


        //string s = "";
        //for (int y = 0; y < HEIGHT; ++y) {
        //    string j = "";
        //    for (int x = 0; x < WIDTH; ++x) {
        //        j += paths[y][x] + " ";
        //    }
        //    s += j + "\n";
        //}
        //Debug.Log(s);

    }

    // todo add resizing options for player
    public void InitGrid() {
        grid = BuildArray(WIDTH, HEIGHT, 0);
        paths = BuildArray(WIDTH, HEIGHT, 0);
        GenerateAllPaths(TargetX, TargetY);
    }

    // put these bad boys on a world space canvas
    TextMeshProUGUI SpawnCharText(string text, Vector3 worldPos) {
        var go = Instantiate(textCharPrefab, worldPos, Quaternion.identity, textCharParent);
        var txt = go.GetComponent<TextMeshProUGUI>();
        txt.text = text;
        return txt;
    }
    public int GetGridValue(char letter, int dumby) {
        int x = letter - 'a';
        int y = dumby - 1;
        return grid[y][x];
    }

    // assumes lowercase letter and int, both in range
    public bool Build(char letter, int dumby, Player player) {
        int x = letter - 'a';
        int y = dumby - 1;
        if (grid[y][x] != 0 || (x == TargetX && y == TargetY)) {
            return false; // spot taken error
        }
        int old = grid[y][x];
        grid[y][x] = 1;
        GenerateAllPaths(TargetX, TargetY);
        // check if all spots in top row has a path
        for (int i = 0; i < WIDTH; ++i) {
            if (paths[HEIGHT - 1][i] < 0) { // no path
                grid[y][x] = old;
                GenerateAllPaths(TargetX, TargetY);
                return false; // block error
            }
        }

        var go = Instantiate(tower, new Vector3(x + 0.5f, y + 0.5f), Quaternion.identity, towerParent);
        var sr = go.GetComponent<SpriteRenderer>();
        sr.color = player.Color;
        Tower t = go.GetComponent<Tower>();
        t.Location = $"{letter}{dumby}";
        t.Owner = player;
        player.Towers.Add(t);
        return true;
    }

    Queue<Vector2i> frontier = new Queue<Vector2i>();
    int greatestCost = 0;
    void GenerateAllPaths(int x0, int y0) { // generate all paths to target point on grid
        if (!InBounds(x0, y0)) {
            Debug.LogWarning("path target out of bounds");
            return;
        }
        greatestCost = 0;

        for (int y = 0; y < HEIGHT; ++y) {
            for (int x = 0; x < WIDTH; ++x) {
                paths[y][x] = -1;
            }
        }

        frontier.Clear();
        frontier.Enqueue(new Vector2i(x0, y0));
        paths[y0][x0] = 0;

        while (frontier.Count > 0) {
            Vector2i v = frontier.Dequeue();
            greatestCost = Mathf.Max(greatestCost, paths[v.y][v.x]);
            for (int i = 0; i < 4; ++i) {
                Vector2i o = v + neighbors2[i];
                if (IsWalkable(o.x, o.y) && paths[o.y][o.x] < 0) {
                    frontier.Enqueue(o);
                    paths[o.y][o.x] = paths[v.y][v.x] + 1;
                }
            }
        }
    }

    public bool InBounds(int x, int y) {
        return x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT;
    }
    public bool IsWalkable(int x, int y) {
        return InBounds(x, y) && grid[y][x] == 0; //walkableTiles.Contains(tiles[x][y]);
    }

    public Vector3 GetPathTarget(Vector3 worldPos) {
        Vector2i v = GetGridCoord(worldPos);

        // if not in bounds then go down
        Vector3 targ = worldPos - new Vector3(0, 1, 0);
        if (!InBounds(v.x, v.y)) {
            return targ;
        }

        int shortest = paths[v.y][v.x];
        if (shortest == 0) { // if at destination then also go down
            return targ;
        }
        // random chance to start from front or back of neighbor array
        // so randomly favors x or y axis basically
        int off = Random.value < 0.5f ? 0 : 3;
        for (int i = 0; i < 4; ++i) {
            Vector2i o = v + neighbors2[Mathf.Abs(off - i)];
            if (IsWalkable(o.x, o.y) && paths[o.y][o.x] < shortest) {
                shortest = paths[o.y][o.x];
                //dir = grid.GetTilePointRandom(o);
                targ = new Vector3(o.x + 0.5f, o.y + 0.5f);
            }
        }

        return targ;

    }

    public Vector2i GetGridCoord(Vector3 worldPos) {
        return new Vector2i(Mathf.FloorToInt(worldPos.x), Mathf.FloorToInt(worldPos.y));
    }

    // 2D array of rows top down (0,0 is topleft)
    int[][] BuildArray(int width, int height, int value) {
        int[][] arr = new int[height][];
        for (int y = 0; y < height; ++y) {
            arr[y] = new int[width];
            for (int x = 0; x < width; ++x) {
                arr[y][x] = value;
            }
        }
        return arr;
    }

    static Vector2i[] neighbors2 = new Vector2i[] {
        new Vector2i(1,0),
        new Vector2i(-1,0),
        new Vector2i(0,1),
        new Vector2i(0,-1)
    };
}

public struct Vector2i : System.IEquatable<Vector2i> {
    public int x;
    public int y;

    public Vector2i(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public static Vector2i operator +(Vector2i a, Vector2i b) {
        return new Vector2i(a.x + b.x, a.y + b.y);
    }

    public static Vector2i operator *(Vector2i a, int s) {
        return new Vector2i(a.x * s, a.y * s);
    }

    public override int GetHashCode() {
        return 31 * x + 17 * y;
    }
    public override bool Equals(object obj) {
        return obj is Vector2i && Equals((Vector2i)obj);
    }
    public bool Equals(Vector2i v) {
        return x == v.x && y == v.y;
    }
    public static bool operator ==(Vector2i a, Vector2i b) {
        return a.x == b.x && a.y == b.y;
    }
    public static bool operator !=(Vector2i a, Vector2i b) {
        return a.x != b.x || a.y != b.y;
    }
    public Vector3 ToVector3() {
        return new Vector3(x, y, 0);
    }
}
