using System;
using System.Collections.Generic;
using System.Linq;
using Assets.Cities;
using Assets.Common;

namespace Assets.AnnotationPass
{
    public class LocateCitiesPass : IAnnotationPass
    {
        public const string CitiesProperty = "Cities";
        public const string CityProperty = "City";
        public int MaxCitiesCount { get; set; } = 20;
        public int MaxPlacementTries { get; set; } = 30;
        public float RangeOfInfluence { get; set; } = .5f;
        public float CitiesSize { get; set; } = .5f;


        private Graph<Location> _locationGraph;
        private Graph<MapSite> _basicGraph;
        private Random _random;

        public LocateCitiesPass(Random random = null)
        {
            _random = random ?? new Random();
        }

        City CreateCity(int vertex, int size)
        {
            City newCity = new City();

            var site = _basicGraph.Vertices[vertex];
            newCity.Center = site.Center.Clone() as Point;
            site.Tags.Add("City.Center");

            var sites = new List<MapSite> { site };
            var location = site.Metadata.GetProperty<Location>(LandmassPass.SiteLocationProperty);

            for (int i = 0; i < size - 1; i++)
            {
                var neighbours = _basicGraph.Neighbours(site.Index).Select(j => _basicGraph.Vertices[j]).Where(s => s.Metadata.GetProperty<Location>(LandmassPass.SiteLocationProperty) == location).ToList();
                site = neighbours[_random.Next(neighbours.Count)];
                sites.Add(site);
            }

            foreach (var s in sites.Distinct())
            {
                newCity.AddSite(s);
                s.Metadata.SetProperty(CityProperty, newCity);
            }

            newCity.RangeOfInfluence = GetCityRangeOfInfluence(newCity);
            return newCity;
        }

        List<City> CreateCities()
        {
            List<City> cities = new List<City>();
            var vertices = new List<int>();

            int guard = MaxPlacementTries;
            int createdCitiesCount = 0;

            while (createdCitiesCount < MaxCitiesCount && guard-- > 0)
            {
                var randomLocation = _locationGraph.Vertices.Skip(1).RandomElement(_random);
                var randomSite = randomLocation.Sites.RandomElement(_random);

                if (cities.Any(c => Point.Dist(PointUtils.Mean(c.Sites.Select(s => s.Center)), randomSite.Center) < c.RangeOfInfluence))
                    continue;

                cities.Add(CreateCity(randomSite.Index, (int)Math.Ceiling(CitiesSize * _random.Next(0, 30))));
                guard = MaxPlacementTries;
                createdCitiesCount++;
            }

            return cities;

        }

        public float GetCityRangeOfInfluence(City city)
        {
            return RangeOfInfluence*city.Sites.Count;
        }

        public void Annotate(Map map)
        {
            _basicGraph = map.Sites.Clone() as Graph<MapSite>;
            _locationGraph = map.Metadata.GetProperty<Graph<Location>>(LandmassPass.MapLocationsProperty);

            var cities = CreateCities();

            map.Metadata.SetProperty(CitiesProperty, cities);
        }
    }
}