using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Tilemaps;
using Random = UnityEngine.Random;

namespace MoreMountains.Tools 
{
	/// <summary>
	/// This class will fill a tilemap with the data generated by the combination of its layers
	/// </summary>
	[ExecuteAlways]  
	public class MMTilemapGenerator : MonoBehaviour
	{
		[Header("Grid")] 
		/// The width of the grid, in cells
		[Tooltip("The width of the grid, in cells")]
		[MMVector("Min","Max")]
		public Vector2Int GridWidth = new Vector2Int(50,50);
		/// the height of the grid, in cells
		[Tooltip("the height of the grid, in cells")]
		[MMVector("Min","Max")]
		public Vector2Int GridHeight = new Vector2Int(50,50);

		[Header("Data")] 
		/// the list of layers that will be used to generate the tilemap
		[Tooltip("the list of layers that will be used to generate the tilemap")]
		public MMTilemapGeneratorLayerList Layers;

		/// a value between 0 and 1 that will be used by all layers as their random seed. If you generate another map using the same seed, it'll look the same 
		[Tooltip("a value between 0 and 1 that will be used by all layers as their random seed. If you generate another map using the same seed, it'll look the same")]
		public int GlobalSeed = 0;
		/// whether or not to randomize the global seed every time a new map is generated
		[Tooltip("whether or not to randomize the global seed every time a new map is generated")]
		public bool RandomizeGlobalSeed = true;
        
		[Header("Slow Render")] 
		/// turning this to true will (at runtime only) draw the map progressively. This is really just for fun.
		[Tooltip("turning this to true will (at runtime only) draw the map progressively. This is really just for fun.")]
		public bool SlowRender = false;
		/// the duration of the slow render, in seconds
		[Tooltip("the duration of the slow render, in seconds")]
		public float SlowRenderDuration = 1f;
		/// the tween to use for the slow render
		[Tooltip("the tween to use for the slow render")]
		public MMTweenType SlowRenderTweenType = new MMTweenType(MMTween.MMTweenCurve.EaseInOutCubic);
        
		protected int[,] _grid;

		protected int _width;
		protected int _height;

		/// <summary>
		/// the possible methods that can be used to generate a random grid
		/// </summary>
		public enum GenerateMethods
		{
			Full,
			Perlin,
			PerlinGround,
			Random,
			RandomWalk,
			RandomWalkAvoider,
			RandomWalkGround,
			Path,
			Copy
		}
        
		/// <summary>
		/// Generates and renders every layer in the data stack
		/// </summary>
		public virtual void Generate()
		{
			Random.InitState((int)System.DateTime.Now.Ticks);
			if (RandomizeGlobalSeed) { GlobalSeed = Mathf.Abs(Random.Range(int.MinValue, int.MaxValue)); }
            
			foreach (MMTilemapGeneratorLayer layer in Layers)
			{
				GenerateLayer(layer);   
			}
		}

		/// <summary>
		/// On reset, we initialize our list
		/// </summary>
		void Reset()
		{
			Layers = new MMTilemapGeneratorLayerList(){
				new MMTilemapGeneratorLayer()
			};
		}

		/// <summary>
		/// Generates a layer grid, and renders it
		/// </summary>
		/// <param name="layer"></param>
		protected virtual void GenerateLayer(MMTilemapGeneratorLayer layer)
		{
			if (!layer.Active)
			{
				return;
			}
            
			if (layer.TargetTilemap == null) { Debug.LogError("Tilemap Generator : you need to specify a Target Tilemap to paint on."); }
			if (layer.Tile == null) { Debug.LogError("Tilemap Generator : you need to specify a Tile to paint with."); }
			if (layer.GridWidth == 0) { Debug.LogError("Tilemap Generator : grid width can't be 0."); }
			if (layer.GridHeight == 0) { Debug.LogError("Tilemap Generator : grid height can't be 0."); }

			float seedFloat = 0f;
			float layerSeedFloat = 0f;
			float globalSeedFloat = 0f;
            
            
			UnityEngine.Random.InitState(GlobalSeed);
			int width = layer.OverrideGridSize ? layer.GridWidth : UnityEngine.Random.Range(GridWidth.x, GridWidth.y);
			int height = layer.OverrideGridSize ? layer.GridHeight : UnityEngine.Random.Range(GridHeight.x, GridHeight.y);
            
			globalSeedFloat = UnityEngine.Random.value;
            
			// random outside of the global seed 
			if (layer.DoNotUseGlobalSeed)
			{
				Random.InitState((int)System.DateTime.Now.Ticks);
				if (layer.RandomizeSeed)
				{
					layer.Seed = Mathf.Abs(Random.Range(int.MinValue, int.MaxValue));
				}
				UnityEngine.Random.InitState(layer.Seed);
				layerSeedFloat = UnityEngine.Random.value;
			}
            
			int seed = layer.DoNotUseGlobalSeed ? layer.Seed : GlobalSeed;
			seedFloat = layer.DoNotUseGlobalSeed ? layerSeedFloat : globalSeedFloat;

			switch (layer.GenerateMethod)
			{
				case GenerateMethods.Full:
					_grid = MMGridGeneratorFull.Generate(width, height, layer.FullGenerationFilled);
					layer.Grid = _grid;
					break;
				case GenerateMethods.Perlin:
					_grid = MMGridGeneratorPerlinNoise.Generate(width, height, seedFloat);
					layer.Grid = _grid;
					break;
				case GenerateMethods.PerlinGround:
					_grid = MMGridGeneratorPerlinNoiseGround.Generate(width, height, seedFloat);
					layer.Grid = _grid;
					break;
				case GenerateMethods.Random:
					_grid = MMGridGeneratorRandom.Generate(width, height, seed, layer.RandomFillPercentage);
					layer.Grid = _grid;
					break;
				case GenerateMethods.RandomWalk:
					_grid = MMGridGeneratorRandomWalk.Generate(width, height, seed, layer.RandomWalkPercent, layer.RandomWalkStartingPoint, layer.RandomWalkMaxIterations);
					layer.Grid = _grid;
					break;
				case GenerateMethods.RandomWalkAvoider:

					int[,] obstacleGrid = MMGridGenerator.TilemapToGrid(layer.RandomWalkAvoiderObstaclesTilemap, width, height);
					_grid = MMGridGeneratorRandomWalkAvoider.Generate(width, height, seed, layer.RandomWalkAvoiderPercent, layer.RandomWalkAvoiderStartingPoint, obstacleGrid, layer.RandomWalkAvoiderObstaclesDistance, layer.RandomWalkAvoiderMaxIterations);
					layer.Grid = _grid;
					break;
				case GenerateMethods.RandomWalkGround:
					_grid = MMGridGeneratorRandomWalkGround.Generate(width, height, seed, 
						layer.RandomWalkGroundMinHeightDifference, layer.RandomWalkGroundMaxHeightDifference, 
						layer.RandomWalkGroundMinFlatDistance, layer.RandomWalkGroundMaxFlatDistance, layer.RandomWalkGroundMaxHeight);
					layer.Grid = _grid;
					break;
				case GenerateMethods.Path:
					_grid = MMGridGeneratorPath.Generate(width, height, seed, layer.PathDirection, layer.PathStartPosition, layer.PathMinWidth,
						layer.PathMaxWidth, layer.PathDirectionChangeDistance, layer.PathWidthChangePercentage,
						layer.PathDirectionChangePercentage);
					layer.Grid = _grid;
					break;
				case GenerateMethods.Copy:
					layer.TargetTilemap.ClearAllTiles();
					DelayedCopy(layer);
					break;
			}

			if (layer.Smooth) { _grid = MMGridGenerator.SmoothenGrid(_grid); }
			if (layer.InvertGrid) { _grid = MMGridGenerator.InvertGrid(_grid); }

			_grid = MMGridGenerator.BindGrid(_grid, layer.BoundsTop, layer.BoundsBottom, layer.BoundsLeft, layer.BoundsRight);
			_grid = MMGridGenerator.ApplySafeSpots(_grid, layer.SafeSpots);

			RenderGrid(layer);
		}

		/// <summary>
		/// Copies the tilemap's content after a delay because Unity.
		/// </summary>
		/// <param name="layer"></param>
		async static void DelayedCopy(MMTilemapGeneratorLayer layer)
		{
			await Task.Delay(500);
			MMTilemapShadow.Copy(layer.CopyTilemap, layer.TargetTilemap);
		}
        
		/// <summary>
		/// Renders the grid with the selected modes to the specified target tilemap
		/// </summary>
		/// <param name="layer"></param>
		protected virtual void RenderGrid(MMTilemapGeneratorLayer layer)
		{
			MMTilemapGridRenderer.RenderGrid(_grid, layer, SlowRender, SlowRenderDuration, SlowRenderTweenType,this);
		}
        
		/// <summary>
		/// Sets default values for all layers
		/// </summary>
		protected virtual void OnValidate()
		{
			if ((Layers == null) || (Layers.Count <= 0))
			{
				return;
			}
			foreach (MMTilemapGeneratorLayer layer in Layers)
			{
				layer.SetDefaults();
			}
		}
	}    
}