using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Assets.Common;
using Assets.Generators;
using Assets.Map;
using Assets.Voronoi;
using UnityEditor;
using UnityEngine;
using Random = System.Random;

namespace Assets
{
    [Serializable]
    public class GraphGeneratorDebug
    {
        public bool enabled = true;
        public bool displayPoints = true;
        public bool displayBoundary = true;
        public bool displayBeachLine = true;
        public bool displayParabolas = false;
        public bool displayVertices = true;
        public bool displayNeighbours = false;
        public bool displayLabels = false;
        public bool displayHalfEdges = false;
        public bool displayEdges = true;
        public bool displayLocations = true;
        public bool displayLocationCells = true;
        public bool displayLocationEdges = true;
        public bool displayLocationPoints = true;

        [NonSerialized] public float generationTime = 0.0f;
    }

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

    public enum GenerationStage
    {
        Start,
        Voronoi,
        Location,
        Done
    };

    [ExecuteInEditMode]
    public class GraphGenerator : MonoBehaviour
    {
        private PoissonDiskSampler _sampler;
        private Random _random;
        
        private VoronoiGenerator  _voronoiGenerator;
        private LocationGenerator _locationGenerator;
        
        private List<Vector3> _points = new List<Vector3>();
        private List<Location> _locations = new List<Location>();
        private Graph<(Point, Location)> _locationGraph;
        
        public List<LocationType> types = new List<LocationType>();
        
        [Range(2.0f, 64.0f)]
        public float radius = 8;
        public int seed = Environment.TickCount;
        public Vector2 size = new Vector2();
        
        public GraphGeneratorDebug debug;
        
        public GenerationStage Stage { get; private set; } = GenerationStage.Start;

        public VoronoiGenerator VoronoiGenerator => _voronoiGenerator;
        public LocationGenerator LocationGenerator => _locationGenerator;

        void Start()
        {
            Reset();
        }

        public void Reset()
        {
            _random = new System.Random(seed);
            
            _sampler = new PoissonDiskSampler(radius);
            _sampler.Generator = _random;
        
            _points = new List<Vector3>();
            _points.Add(new Vector3(-size.x/8, size.y / 2 + 0.1f));
            _points.Add(new Vector3(size.x * 1.125f, size.y / 2 - 0.1f));
            _points.Add(new Vector3(size.x / 2 - 0.1f, -size.y/8));
            _points.Add(new Vector3(size.x / 2 + 0.1f, size.y * 1.125f));
            
            _points.AddRange(_sampler.Generate(size.x, size.y));

            Stage = GenerationStage.Voronoi;
            
            _voronoiGenerator  = new VoronoiGenerator(_points.Select(x => new Point(x.x, x.y)).ToList());
            _locationGenerator = null;
            _locationGraph     = null;

            _locations = new List<Location>() { new Location() { Type = new LocationType() { color = Color.clear, name = "Ocean", height = -1 } } };
            _locations.AddRange(types.Select(type => new Location { Type = type }));
        }

        public void Generate()
        {
            var stopwatch = new Stopwatch();
            
            stopwatch.Start();
            
            debug.generationTime = 0;

            while (Stage != GenerationStage.Done)
            {
                Step();
                debug.generationTime = stopwatch.ElapsedMilliseconds / 1000.0f;
            }

            stopwatch.Stop();
            debug.generationTime = stopwatch.ElapsedMilliseconds / 1000.0f;
        }
    
        public void Step()
        {
            if (Stage == GenerationStage.Voronoi)
            {
                _voronoiGenerator.Step();

                if (_voronoiGenerator.Done)
                {
                    Stage = GenerationStage.Location;
                    
                    _locationGenerator = new LocationGenerator(_locations, _voronoiGenerator.Delaunay, _voronoiGenerator.Voronoi.Vertices, _random);

                    var initial =
                        from location in _locations
                        select (_random.Next(_voronoiGenerator.Delaunay.Vertices.Count), location);

                    initial = initial.Concat(new List<int> { 0, 1, 2, 3 }.Select(i => (i, _locations[0])));

                    foreach (var (vertex, location) in initial)
                        _locationGenerator.SetLocation(vertex, location);
                }
            }
            else if (Stage == GenerationStage.Location)
            {
                _locationGenerator.Step();

                if (_locationGenerator.Done)
                {
                    Stage = GenerationStage.Done;
                    RecalculateLocationGraph();
                }
            }
        }

        void OnDrawGizmos()
        {

            if (debug.displayBoundary)
            {
                Gizmos.color = Color.gray;
                
                Gizmos.DrawLine(new Vector3(0, 0), new Vector3(size.x, 0));
                Gizmos.DrawLine(new Vector3(0, 0), new Vector3(0, size.y));
                Gizmos.DrawLine(new Vector3(size.x, size.y), new Vector3(size.x, 0));
                Gizmos.DrawLine(new Vector3(size.x, size.y), new Vector3(0, size.y));
            }
            
            if (!debug.enabled || _voronoiGenerator == null) return;

            if (debug.displayPoints)
            {
                Gizmos.color = Color.white;
                DisplayGraphVertices(_voronoiGenerator.Delaunay.Morph(x => x.Point));
            }

            if (debug.displayNeighbours)
            {
                Gizmos.color = Color.yellow;
                DisplayGraphEdges(_voronoiGenerator.Delaunay.Morph(x => x.Point));
            }

            if (debug.displayVertices)
            {
                Gizmos.color = Color.blue;
                DisplayGraphVertices(_voronoiGenerator.Voronoi);
            }
            
            if (debug.displayHalfEdges)
            {
                Gizmos.color = Color.green;
                foreach (var edge in _voronoiGenerator.HalfEdges)
                {
                    Gizmos.DrawLine(edge.Start.ToVector3(), edge.End.ToVector3());
                }
            }

            if (debug.displayEdges)
            {
                Gizmos.color = Color.white;
                DisplayGraphEdges(_voronoiGenerator.Voronoi);
            }

            Gizmos.color = Color.red;
            Gizmos.DrawLine(new Vector3(0, (float)_voronoiGenerator.Line.Directrix), new Vector3(size.x, (float)_voronoiGenerator.Line.Directrix));
            Gizmos.color = Color.blue;

            Vector3 start;
            
            if (debug.displayParabolas)
            {
                foreach (var parabola in _voronoiGenerator.Line.Parabolas)
                {
                    start = new Vector3(-size.x, (float)_voronoiGenerator.Line.EvalParabola(parabola.Site.Point, -size.x));
                    for (var x = -size.x + 0.1f; x < size.x * 2; x += 0.1f)
                    {
                        var point = new Vector3(x, (float)_voronoiGenerator.Line.EvalParabola(parabola.Site.Point, x));
                        Gizmos.DrawLine(start, point);

                        start = point;
                    }
                }
            }
        
            if (debug.displayBeachLine)
            {
                Gizmos.color = Color.white;
                start = new Vector3(-size.x, (float)_voronoiGenerator.Line.Eval(-size.x));
                for (var x = -size.x + 0.1f; x < size.x * 2; x += 0.1f)
                {
                    var point = new Vector3(x, (float)_voronoiGenerator.Line.Eval(x));
                    Gizmos.DrawLine(start, point);

                    start = point;
                }
            }

            if (LocationGenerator == null) return;

            if (debug.displayLocationCells)
            {
                foreach (var location in LocationGenerator.Sites.Vertices.Where(l => l.Location != null && l.Location != _locations[0]))
                {
                    Gizmos.color = location.Location.Type.color;
                    Gizmos.DrawSphere(location.Site.Point.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 _locations.Skip(1))
                {
                    Gizmos.color = location.Type.color;
                    foreach (var (a, b) in location.BoundaryEdges)
                    {
                        Gizmos.DrawLine(VoronoiGenerator.Voronoi.Vertices[a].ToVector3(), VoronoiGenerator.Voronoi.Vertices[b].ToVector3());
                    }
                }
            }
        }

        public void Rewind(float y)
        {
            while (_voronoiGenerator.Line.Directrix < y && !_voronoiGenerator.Done)
            {
                _voronoiGenerator.Step();
            }
        }

        private void DisplayGraphVertices(Graph<Point> graph)
        {
            var vertices = graph.Vertices.Select(p => p.ToVector3());
            var offset   = Vector3.right;
            
            foreach (var (v, i) in vertices.Select((x, i) => (x, i)))
            {
                Gizmos.DrawSphere(v, 1);
                if (debug.displayLabels) Handles.Label(v + offset, $"{i} at {v.x:F2}, {v.y:f2}");
            }
        }
        
        private void DisplayGraphEdges(Graph<Point> graph)
        {
            var edges = graph.Edges
                .Select(edge => (graph.Vertices[edge.Item1], graph.Vertices[edge.Item2]))
                .Select(edge => (edge.Item1.ToVector3(), edge.Item2.ToVector3()));
            
            foreach (var (s, e) in edges) Gizmos.DrawLine(s, e);
        }

        private void RecalculateLocationGraph()
        {
            _locationGraph = LocationGenerator.Result.Morph(location => (location.Center, location));
        }
    }

}