using System.Collections.Generic;
using System.Linq;
using Assets.Commo;
using Assets.Common;
using Assets.Generators;
using Assets.Map;
using Assets.Voronoi;
using UnityEngine;
using Random = System.Random;

namespace Assets
{
    [RequireComponent(typeof(MeshFilter))]
    [RequireComponent(typeof(MeshRenderer))]
    [RequireComponent(typeof(GraphGenerator))]
    public class ForestGenerator : MonoBehaviour
    {
        public GameObject[] trees;
        [Range(1, 15)]
        public float radius = 3.0f;
        public GameObject forest;
        
        private Random _random = new Random();
        private List<Site> _sites;
        
        private GraphGenerator Generator => GetComponent<GraphGenerator>();
        
        public void Generate()
        {
            Cleanup();
            
            Generator.EnsureGenerated();
            
            FindForestSites();
            PlaceTrees();
            
            _random = new Random(Generator.seed);
        }

        private void PlaceTrees()
        {
            foreach (Site site in _sites)
            {
                var sampler = new PoissonDiskSampler(radius) { Generator = _random };
                
                Bounding bounding =
                    BoundingTools.GetBounding(site.Vertices.Select(i => Generator.BoundariesGraph.Vertices[i]));

                var offset = Vector3.up * site.Location.Type.height;
                var points = sampler.Generate((float)bounding.Width, (float)bounding.Height);

                foreach (var point in points.Select(point => point + bounding.Min).Where(point => IsPointInSite(site, point)))
                {
                    PlaceRandomTree(point.ToVector3() + offset);
                }
            }
        }

        public void Cleanup()
        {
            var destroy = new List<GameObject>();
            
            foreach (var child in forest.transform)
                if (child is Transform t)
                    destroy.Add(t.gameObject);

            foreach (var @object in destroy)
                DestroyImmediate(@object);
        }

        private void FindForestSites()
        {
            _sites = new List<Site>();

            foreach (var location in Generator.LocationGraph.Vertices.Skip(1))
            {
                var count = _random.Next(10);
                
                for (int i = 0; i < count; i++)
                    _sites.Add(location.Sites[_random.Next(location.Sites.Count)]);
            }
        }

        private void PlaceRandomTree(Vector3 pos)
        {
            GameObject tree = Instantiate(trees[_random.Next(trees.Length)], forest.transform);

            tree.transform.position = pos - Vector3.down * 0.2f;
        }

        private bool IsPointInSite(Site site, Point point)
        {
            var polygon = site.Vertices.Select(i => Generator.BoundariesGraph[i]).ToArray();

            return PointUtils.IsPointInside(point, polygon);
        }
    }
}