using System;
using System.Collections.Generic;
using System.Linq;
using Assets.Common;
using Assets.Voronoi;
using Random = System.Random;

namespace Assets.Map
{
    public class LocationGenerator
    {
        private readonly IList<Point> _points;
        private readonly Graph<Site> _sites;
        private readonly Graph<Location> _locations;
        
        private readonly List<(int, Location)> _queue = new List<(int, Location)>();
        
        private readonly Random _random;
        
        public Graph<Site> Sites => _sites;
        public Graph<Location> Result => _locations;
        
        public bool Done => _queue.Count == 0;

        public LocationGenerator(List<Location> locations, Graph<Site> sites, IList<Point> points, Random random = null)
        {
            _points = points;
            _sites     = sites.Morph(s => s);
            _locations = new Graph<Location> { Vertices = new List<Location>(locations) };
            
            _random = random ?? new Random();
        }

        public void SetLocation(int vertex, Location location)
        {
            location.AddSite(_sites.Vertices[vertex]);

            foreach (var neighbour in _sites.Neighbours(vertex))
                if (_sites.Vertices[neighbour].Location == null)
                    _queue.Add((neighbour, location));
                else if (location != _sites.Vertices[neighbour].Location)
                    _locations.AddEdge(
                        _locations.Vertices.IndexOf(location),
                        _locations.Vertices.IndexOf(_sites.Vertices[neighbour].Location)
                    );
        }

        private (int, Location) Dequeue()
        {
            while (_queue.Count > 0)
            {
                var index = _random.Next(_queue.Count);
                var pair  = _queue[index];

                _queue.RemoveAt(index);

                if (_sites.Vertices[pair.Item1].Location == null) return pair;
            }
            
            throw new Exception("No more elements.");
        }
        
        public void Step()
        {
            try
            {
                var (vertex, location) = Dequeue();

                SetLocation(vertex, location);
            }
            catch (Exception ex)
            {
                foreach (var location in _locations.Vertices.Skip(1))
                    FixLocation(location);
            }
        }

        private void FixLocation(Location location)
        {
            var center = location.Center;
            var left   = location.BoundaryEdges;
            
            location.BoundaryEdges = new List<(int, int)>() { EnsureClockwise(center, left[0]) };
            (int, int) last = location.BoundaryEdges[0];
            left.RemoveAt(0);

            while (left.Count > 0)
            {
                var index = left.FindIndex(x => x.Item1 == last.Item2 || x.Item2 == last.Item2);

                if (index == -1) break;
                
                var item = left[index];
                item = item.Item1 == last.Item2 ? item : (item.Item2, item.Item1);
                
                left.RemoveAt(index);
                
                location.BoundaryEdges.Add(item);

                last = item;
            }
        }

        private (int, int) EnsureClockwise(Point center, (int, int) edge)
        {
            return PointUtils.IsClockwise(center, _points[edge.Item1], _points[edge.Item2]) ? (edge.Item2, edge.Item1) : edge;
        }
    }
}