using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Verse;


namespace RimWorld{

public enum SeedTargFindMode : byte
{
	MapGenCluster,
	Reproduce,
	MapEdge,
	Cave
}

public static class GenPlantReproduction
{
	//Constants
	private const float CavePlantsDensity = 0.5f;

	public static Plant TryReproduceFrom(IntVec3 source, ThingDef plantDef, SeedTargFindMode mode, Map map)
	{
		IntVec3 dest;
		if( !TryFindReproductionDestination(source, plantDef, mode, map, out dest) )
			return null;

		return TryReproduceInto(dest, plantDef, map);
	}

	public static Plant TryReproduceInto(IntVec3 dest, ThingDef plantDef, Map map)
	{
		if( !plantDef.CanEverPlantAt(dest, map) )
			return null;
		
		if( !GenPlant.SnowAllowsPlanting(dest, map) )
			return null;

		return (Plant)GenSpawn.Spawn( plantDef, dest, map );
	}

	public static bool TryFindReproductionDestination(IntVec3 source, ThingDef plantDef, SeedTargFindMode mode, Map map, out IntVec3 foundCell)
	{
		float radius = -1;
		if( mode == SeedTargFindMode.Reproduce )
			radius = plantDef.plant.reproduceRadius;
		else if( mode == SeedTargFindMode.MapGenCluster )
			radius = plantDef.plant.WildClusterRadiusActual;
		else if( mode == SeedTargFindMode.MapEdge )
			radius = 40;
		else if( mode == SeedTargFindMode.Cave )
			radius = plantDef.plant.WildClusterRadiusActual;

		//Gather some data about the area around source
		//Note: This scans a rectangle, though we only reproduce into a circle
		int numFoundPlants = 0;
		int numFoundPlantsMyDef = 0;
		float totalFertility = 0;
		var rect = CellRect.CenteredOn( source, Mathf.RoundToInt(radius) );
		rect.ClipInsideMap(map);
		for (int z = rect.minZ; z <= rect.maxZ; z++)
		{
			for (int x = rect.minX; x <= rect.maxX; x++)
			{
				var c = new IntVec3(x,0,z);
				var p = c.GetPlant(map);
				if( p != null && (mode != SeedTargFindMode.Cave || p.def.plant.cavePlant) ) // if mode == Cave then look at cave plants only
				{
					numFoundPlants++;

					if( p.def == plantDef )
						numFoundPlantsMyDef++;
				}

				totalFertility += c.GetTerrain(map).fertility;
			}
		}

		//Determine theoretical number of desired plants of any type
		float plantDensity = mode == SeedTargFindMode.Cave
			? CavePlantsDensity : map.Biome.plantDensity;
		float numDesiredPlants = totalFertility * plantDensity;
		bool full = numFoundPlants > numDesiredPlants;
		bool overloaded = numFoundPlants > numDesiredPlants * 1.25f;

		if( overloaded )
		{
			foundCell = IntVec3.Invalid;
			return false;
		}

		//This does create a problem where clustering plants will generate nicely on map gen
		//But will not sustain their initial nicely clustered layout, because they're so close together
		//the reproduction code stops them
		if( mode != SeedTargFindMode.MapGenCluster && mode != SeedTargFindMode.Cave )
		{
			//Determine num desired plants of my def
			var curBiome = map.Biome;
			float totalCommonality = curBiome.AllWildPlants.Sum( pd=>curBiome.CommonalityOfPlant(pd) );
			float minProportion = curBiome.CommonalityOfPlant(plantDef) / totalCommonality;
			float maxProportion = (curBiome.CommonalityOfPlant(plantDef) *plantDef.plant.wildCommonalityMaxFraction) / totalCommonality;


			//Too many plants of my type nearby - don't reproduce
			float maxDesiredPlantsMyDef = numDesiredPlants * maxProportion;
			if( numFoundPlantsMyDef > maxDesiredPlantsMyDef )
			{
				foundCell = IntVec3.Invalid;
				return false;
			}

			//Too many plants nearby for the biome/total fertility - don't reproduce
			//UNLESS there are way too few of my kind of plant
			float minDesiredPlantsMyDef = numDesiredPlants * minProportion;
			bool desperateForPlantsMyDef = numFoundPlantsMyDef < minDesiredPlantsMyDef * 0.5f;
			if( full && !desperateForPlantsMyDef )
			{
				foundCell = IntVec3.Invalid;
				return false;
			}
		}

		//We need to plant something
		//Try find a cell to plant into
		Predicate<IntVec3> destValidator = c=>
			{
				if( !plantDef.CanEverPlantAt(c, map) )
					return false;

				if( plantDef.plant.cavePlant && !GoodRoofForCavePlantReproduction(c, map) )
					return false;

				if( !GenPlant.SnowAllowsPlanting(c, map) )
					return false;

				if( !source.InHorDistOf( c, radius) )
					return false;

				if( !GenSight.LineOfSight( source, c, map, skipFirstCell: true ) )
					return false;

				return true;
			};
		return CellFinder.TryFindRandomCellNear( source,
											map,
											Mathf.CeilToInt(radius),
											destValidator,
											out foundCell );
	}

	public static bool GoodRoofForCavePlantReproduction(IntVec3 c, Map map)
	{
		var roof = c.GetRoof(map);
		return roof != null && roof.isNatural;
	}
}}

