using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Assets.AnnotationPass;
using Assets.Cities;
using Assets.Common;
using Assets.GridGenerator;
using Assets.PointDistribution;
using Assets.Utils;
using UnityEngine;
using UnityEngine.Serialization;
using Random = System.Random;
using Tree = Assets.Common.Tree;

namespace Assets
{
    [Serializable]
    public class GraphGeneratorDebug
    {
        public bool enabled = true;
        
        public bool displayPoints = true;
        public bool displayBoundary = true;
        public bool displayVertices = true;
        public bool displayNeighbours = false;
        public bool displayLabels = false;
        public bool displayEdges = true;
        public bool displayLocations = true;
        public bool displayLocationCells = true;
        public bool displayLocationEdges = true;
        public bool displayCities = true;
        public bool displayLocationPoints = true;
        public bool displayFieldBoundaries = true;
        public bool displayCityRoads = true;
        public bool displayCityFields = true;
        public bool displayTrees = true;

        [NonSerialized] public float generationTime = 0.0f;
    }

    [Serializable]
    public class LocationType
    {
        public string name;
        public Color color;
        public float height = 0.0f;
    }

    [ExecuteInEditMode]
    public class LandmassGenerator : MonoBehaviour
    {
        [NonSerialized] public Map Map;
        [NonSerialized] public Thread GenerationThread;
        
        public GraphGeneratorDebug debug;
        
        public Vector2 size = new Vector2();
        public List<LocationType> types = new List<LocationType>();
        
        [Range(0f, 360f)]
        public float minimumRoadAngle = 30f;
        
        [Range(0f, 4f)]
        public float minimumNudgeDistance = 0.5f;
        [Range(0f, 4f)]
        public float maximumNudgeDistance = 1f;
        [Range(0f, 4f)]
        public float minimumRoadLength = 1f;

        public float treeRadius = 2f;
        public float forestRatio = .4f;
        
        [Range(2.0f, 64.0f)]
        public float radius = 8;
        public int seed = Environment.TickCount;

        public Graph<MapSite> SitesGraph => Map?.Sites;
        public Graph<Point> BoundariesGraph => Map?.Boundaries;
        public Graph<Location> LocationGraph => Map?.Metadata.GetProperty<Graph<Location>>(LandmassPass.MapLocationsProperty);

        public void Generate(Action<Map> onDone)
        {
            GenerationThread?.Abort();
            GenerationThread = new Thread(() =>
            { 
                var generator = CreateMapGenerator();
                var result = generator.Generate(size.x, size.y);
                
                lock (GetType())
                {
                    Map = result;
                    onDone(result);
                }
            });
            
            GenerationThread.Start();
        }

        public void Generate()
        {
            Generate(_ => { });
        }

        public void OnDisable()
        {
            GenerationThread?.Abort();
        }

        private MapGenerator CreateMapGenerator()
        {
            var generator = new MapGenerator(new VoronoiGridGenerator(seed, new PoissonDiskSampler(radius) { Generator = new Random(seed) }));

            generator.AddAnnotationPass(new LandmassPass(types.Prepend(new LocationType { name = "Ocean", height = -1 }).Select(t => new Location { Type = t }), new Random(seed)));
            generator.AddAnnotationPass(new LocateCitiesPass(new Random(seed)));
            generator.AddAnnotationPass(new CityFieldsPass(new Random(seed))
            {
                MinimumAngle = Mathf.Deg2Rad * minimumRoadAngle,
                MaxNudgeDistance = maximumNudgeDistance,
                MinNudgeDistance = minimumNudgeDistance,
                MinimumRoadLength = minimumRoadLength
            });
            generator.AddAnnotationPass(new ForestPass
            {
                TreeRadius = treeRadius,
                ForestRatio = forestRatio
            });

            return generator;
        }

        public void Reset()
        {
            GenerationThread?.Abort();
            Map = null;
        }
        
        private void DisplayGraphCities(IEnumerable<City> cities)
        {
            foreach (City city in cities)
            {
                foreach (var (a, b) in city.Edges)
                {
                    Gizmos.DrawLine(Map.Boundaries[a].ToVector3(), Map.Boundaries[b].ToVector3());
                }

                foreach (var a in city.Sites)
                    Gizmos.DrawSphere(a.Center.ToVector3(), 1);
            }
        }

        void OnDrawGizmos()
        {
            if (!debug.enabled || Map == null) return;
            
            if (debug.displayBoundary)
            {
                Gizmos.color = Color.gray;

                Gizmos.DrawLine(new Vector3(0, 0, 0), new Vector3(size.x, 0, 0));
                Gizmos.DrawLine(new Vector3(0, 0, 0), new Vector3(0, 0, size.y));
                Gizmos.DrawLine(new Vector3(size.x, 0, size.y), new Vector3(size.x, 0, 0));
                Gizmos.DrawLine(new Vector3(size.x, 0, size.y), new Vector3(0, 0, size.y));
            }
            
            if (debug.displayPoints)
            {
                Gizmos.color = Color.white;
                DebugUtils.DisplayGraphVertices(Map.Sites, s => s.Center, displayLabels: debug.displayLabels);
            }

            if (debug.displayNeighbours)
            {
                Gizmos.color = Color.yellow;
                DebugUtils.DisplayGraphEdges(Map.Sites, s => s.Center);
            }

            if (debug.displayVertices)
            {
                Gizmos.color = Color.blue;
                DebugUtils.DisplayGraphVertices(Map.Boundaries, displayLabels: debug.displayLabels);
            }

            if (debug.displayEdges)
            {
                Gizmos.color = Color.white;
                DebugUtils.DisplayGraphEdges(Map.Boundaries);
            }
            
            Gizmos.color = Color.blue;

            if (!Map.Metadata.HasProperty(LandmassPass.MapLocationsProperty)) return;

            var locations = Map.Metadata.GetProperty<Graph<Location>>(LandmassPass.MapLocationsProperty);
            
            if (debug.displayLocationCells)
            {
                var cells = Map.Sites.Vertices
                    .Where(s => s.Metadata.HasProperty(LandmassPass.SiteLocationProperty))
                    .Where(s => s.Metadata.GetProperty<Location>(LandmassPass.SiteLocationProperty).Type.name != "Ocean");
                
                foreach (var site in cells)
                {
                    var location = site.Metadata.GetProperty<Location>(LandmassPass.SiteLocationProperty);
                    
                    Gizmos.color = location.Type.color;
                    Gizmos.DrawSphere(site.Center.ToVector3(), 2);
                }
            }
//
//            if (debug.displayLocations && _locationGraph != null)
//            {
//                DisplayGraphEdges(_locationGraph.Morph(a => a.Item1));
//            }
//
//            if (debug.displayLocationPoints)
//            {
//                foreach (var location in _locations.Skip(1))
//                {
//                    Gizmos.color = location.Type.color;
//                    foreach (var point in location.BoundaryPoints)
//                    {
//                        var v = VoronoiGenerator.Voronoi.Vertices[point].ToVector3();
//                        Gizmos.DrawSphere(v, 1);
//                        if (debug.displayLabels) Handles.Label(v + Vector3.right, $"{point} at {v.x:F2}, {v.y:f2}");
//                    }

//                }
//            }

            if (debug.displayLocationEdges)
            {
                foreach (var location in Map.Metadata.GetProperty<Graph<Location>>(LandmassPass.MapLocationsProperty).Vertices)
                {
                    Gizmos.color = location.Type.color;
                    foreach (var (a, b) in location.BoundaryEdges)
                    {
                        Gizmos.DrawLine(BoundariesGraph[a].ToVector3(), BoundariesGraph[b].ToVector3());
                    }
                }
            }
            
            if (debug.displayCities && Map.Metadata.HasProperty(LocateCitiesPass.CitiesProperty))
            {
                Gizmos.color = Color.magenta;
                DisplayGraphCities(Map.Metadata.GetProperty<IEnumerable<City>>(LocateCitiesPass.CitiesProperty));
            }
            
            var cities = Map.Metadata.GetProperty<List<City>>(LocateCitiesPass.CitiesProperty);
            
            if (debug.displayFieldBoundaries)
            {
                Gizmos.color = Color.white;

                foreach (var city in cities)
                {
                    DebugUtils.DisplayGraphEdges(city.FieldBoundaries);
                    DebugUtils.DisplayGraphVertices(city.Roads, displayLabels: debug.displayLabels);
                }
            }

            if (debug.displayCityRoads)
            {
                Gizmos.color = Color.green;

                foreach (var city in cities)
                {
                    DebugUtils.DisplayGraphEdges(city.Roads);
                    DebugUtils.DisplayGraphVertices(city.Roads, displayLabels: debug.displayLabels);
                }
            }

            if (debug.displayCityFields)
            {
                Gizmos.color = Color.green;

                foreach (var city in cities)
                {
                    foreach (var field in city.Fields)
                    {
                        foreach (var (a, b) in field.Boundary.RotateRight().Zip(field.Boundary, (a, b) => (a, b)))
                            Gizmos.DrawLine(a.ToVector3(), b.ToVector3());   
                        
                        foreach (var tuple in field.Boundary.Zip(PointUtils.CalculateNormals(field.Boundary), (a, b) => (Point: a, Normal: a + b)))
                            Gizmos.DrawLine(tuple.Point.ToVector3(), tuple.Normal.ToVector3());   
                    }
                }
            }

            if (debug.displayTrees)
            {
                Gizmos.color = Color.cyan;
                var trees = Map.GetProperty<IEnumerable<Tree>>(ForestPass.TreesProperty);

                foreach (var tree in trees)
                    Gizmos.DrawCube(
                        tree.Placement.ToVector3() + tree.Site.GetProperty<Location>(LandmassPass.SiteLocationProperty).Type.height * Vector3.up, 
                        new Vector3(1, 1, 1)
                    );
            }
        }
    }
}