using System.Collections.Generic;
using UnityEngine;

public class MapGenerator : MonoBehaviour
{
    public enum FieldType
    {
        Empty = -1,
        Mine = 0,
        Junk = 1,
        Treasure = 2
    }

    public static System.Action<MapData> OnMapGenerated;
    public static System.Action<List<GameObject>> OnMapPopulated;

    [SerializeField] int MapWidth = 20;
    [SerializeField] int MapHeight = 20;
    [SerializeField] float Spacing = 2f;
    [SerializeField] Vector2 OffsetMax = new Vector2(0.5f, 0.5f);
    [SerializeField, Range(0, 1)] float OffsetWeight = 1;
    [SerializeField] int MineAmount = 80;
    [SerializeField] int JunkAmount = 20;
    [SerializeField] int TreasureAmount = 2;
    [SerializeField] LayerMask GroundMask;
    [SerializeField] GameObject[] Prefabs;
    [SerializeField] float RaycastHeight = 20;
    [SerializeField] float SpawnDepth = 1.5f;
    [SerializeField] bool ShowRange = true;
    [SerializeField] bool ShowMapData = false;

    private MapData _mapData;

    private void Start()
    {
        GenerateMapData();
        PopulateMap();
    }

    private void GenerateMapData()
    {
        _mapData = new MapData(MapWidth, MapHeight);

        _mapData.PlantAmount(FieldType.Mine, MineAmount);
        _mapData.PlantAmount(FieldType.Junk, JunkAmount);
        _mapData.PlantAmount(FieldType.Treasure, TreasureAmount);
        _mapData.GenerateOffsets(OffsetMax.x, OffsetMax.y);

        OnMapGenerated?.Invoke(_mapData);
    }

    private void PopulateMap()
    {
        List<GameObject> gos = new List<GameObject>();
        for (int x = 0; x < _mapData.Fields.GetLength(0); x++)
        {
            for (int y = 0; y < _mapData.Fields.GetLength(1); y++)
            {
                if (_mapData.GetFieldAt(x, y) == FieldType.Empty) continue;

                //Debug.Log($"Populate {FieldTypeToPrefab(_mapData.Fields[x, y])}");
                gos.Add(Populate(x, y));
            }
        }

        OnMapPopulated?.Invoke(gos);
    }

    private GameObject Populate(int x, int y)
    {
        Vector3 pos = transform.position + new Vector3(x * Spacing, RaycastHeight, y * Spacing) + (_mapData.Offsets[x, y] * OffsetWeight);
        if (!Physics.Raycast(pos, Vector3.down, out RaycastHit hitInfo, 255, GroundMask))
        {
            Debug.LogWarning($"Couldn't populate map at {pos}({x}:{y}) with the {FieldTypeToPrefab(_mapData.Fields[x, y])}");
            return null;
        }
        pos.y = hitInfo.point.y - SpawnDepth;
        return Instantiate(FieldTypeToPrefab(_mapData.Fields[x, y]), pos, Quaternion.identity, transform);
    }

    private GameObject FieldTypeToPrefab(int fieldTypeIndex)
    {
        return Prefabs[fieldTypeIndex];
    }

    private GameObject FieldTypeToPrefab(FieldType type)
    {
        return FieldTypeToPrefab((int)type);
    }

    private void OnDrawGizmos()
    {
        if (ShowRange)
        {
            Vector3 position = transform.position + new Vector3(MapWidth * Spacing, 0, MapHeight * Spacing) * 0.5f;
            Vector3 size = new Vector3(MapWidth * Spacing, 0, MapHeight * Spacing);
            Gizmos.color = Color.green;
            Gizmos.DrawWireCube(position, size);
        }
        if(ShowMapData)
            _mapData.DrawGizmos(Spacing, OffsetWeight);
    }

    public struct MapData
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        public int[,] Fields { get; private set; }
        public Vector3[,] Offsets { get; private set; }

        public MapData(int width, int height)
        {
            Width = width;
            Height = height;

            Fields = new int[width, height];
            Offsets = new Vector3[width, height];

            for (int x = 0; x < Fields.GetLength(0); x++)
            {
                for (int y = 0; y < Fields.GetLength(1); y++)
                {
                    Fields[x,y] = (int)FieldType.Empty;
                }
            }
        }

        public void GenerateOffsets(float maxOffsetX, float maxOffsetY)
        {
            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    Offsets[x, y] = new Vector3(Random.Range(-maxOffsetX, maxOffsetX), 0, Random.Range(-maxOffsetY, maxOffsetY));
                }
            }
        }

        public bool Plant(FieldType type, bool overrideNonEmpty = false)
        {
            int x = Random.Range(0, Width);
            int y = Random.Range(0, Height);

            if (!overrideNonEmpty && Fields[x, y] != (int)FieldType.Empty) return false;

            Fields[x, y] = (int)type;
            return true;
        }

        public void PlantAmount(FieldType type, int amount)
        {
            for (int amountLeft = amount; amountLeft > 0; amountLeft--)
            {
                int maxTries = 999;
                bool retry;
                do
                {
                    retry = !Plant(type);
                }
                while (retry && maxTries-- > 0);

                if (maxTries == 0)
                    Debug.LogWarning("Plant amount failed after 999 tries");
            }
        }

        public FieldType GetFieldAt(int x, int y)
        {
            return (FieldType)Fields[x, y];
        }

        public void DrawGizmos(float spacing, float offsetWeight)
        {
            if (Fields == null) return;
            if (Fields.Length == 0) return;

            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    Vector3 pos = (new Vector3(x, 0, y) * spacing) + (Offsets[x, y] * offsetWeight);
                    switch (Fields[x, y])
                    {
                        case (int)FieldType.Mine:
                            Gizmos.color = Color.black;
                            break;

                        case (int)FieldType.Junk:
                            Gizmos.color = Color.red;
                            break;

                        case (int)FieldType.Empty:
                            Gizmos.color = Color.white;
                            break;

                        case (int)FieldType.Treasure:
                            Gizmos.color = Color.yellow;
                            break;
                    }
                    Gizmos.DrawCube(pos, Vector3.one * 0.2f);
                }
            }
        }
    }
}
