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

using Verse.Sound;
using Verse.AI;


namespace RimWorld{

public enum PlantLifeStage : byte
{
	Sowing,
	Growing,
	Mature,
}

public class Plant : Thing
{
	//Working vars
	public float 						growth = 0.05f; //Start in growing phase by default, set to 0 if sowing
	public int							age = 0;
	private int							unlitTicks = 0;
	private int							madeLeaflessTick = -99999;
	public bool							sown = false;

	//Fast vars
	private static Color32[]			workingColors = new Color32[4];

	//Constants and content
	public const float					BaseGrowthPercent = 0.05f;
	private const float					RotDamagePerTick = 1f/200f;
	private static readonly	Graphic		GraphicSowing =  GraphicDatabase.Get<Graphic_Single>("Things/Plant/Plant_Sowing", ShaderDatabase.Cutout, Vector2.one, Color.white);
	private const float					GridPosRandomnessFactor = 0.30f;
	private const int					TicksWithoutLightBeforeRot = GenDate.TicksPerDay * 15;
	private const int					LeaflessMinRecoveryTicks = 60000;	//Minimum time to not show leafless after being made leafless
    public const float					MinGrowthTemperature = 0;			//Min temperature at which plant can grow or reproduce
    public const float					MinOptimalGrowthTemperature = 10f;
    public const float					MaxOptimalGrowthTemperature = 42f;
    public const float					MaxGrowthTemperature = 58;			//Max temperature at which plant can grow or reproduce
	public const float					MaxLeaflessTemperature = -2;
	private const float					MinLeaflessTemperature = -10;
	private const float					MinAnimalEatPlantsTemperature = 0;
	public const float					SeedShootMinGrowthPercent = 0.6f;

	//Properties
	public bool HarvestableNow
	{
		get
		{
			return def.plant.Harvestable && growth > def.plant.harvestMinGrowth;
		}
	}
	public override bool IngestibleNow
	{
		get
		{
			//Trees are always edible
			// This allows alphabeavers completely destroy the tree ecosystem
			if( def.plant.IsTree )
				return true;

			if( growth < def.plant.harvestMinGrowth )
				return false;

			if( LeaflessNow )
				return false;

			if( Position.GetSnowDepth() > def.hideAtSnowDepth )
				return false;

			return true;
		}
	}
	public bool Rotting
	{
		get
		{
			if( def.plant.LimitedLifespan && age > def.plant.LifespanTicks )
				return true;

			if( unlitTicks > TicksWithoutLightBeforeRot )
				return true;

			return false;
		}
	}
	private bool Resting
	{
		get
		{
			return GenDate.CurrentDayPercent < 0.25f || GenDate.CurrentDayPercent > 0.8f;
		}
	}
	private float GrowthPerTick
	{
		get
		{
			if( LifeStage != PlantLifeStage.Growing || Resting )
				return 0;

			float baseRate = 1 / (GenDate.TicksPerDay * def.plant.growDays);
			return baseRate * GrowthRate;
		}
	}
	public float GrowthRate
	{
		get
		{
			return GrowthRateFactor_Fertility * GrowthRateFactor_Temperature * GrowthRateFactor_Light;
		}
	}
	public float GrowthRateFactor_Fertility
	{
		get
		{
			return (LocalFertility * def.plant.fertilityFactorGrowthRate)
				 + (1f-def.plant.fertilityFactorGrowthRate);
		}
	}
	public float GrowthRateFactor_Light
	{
		get
		{
			float raw = Mathf.InverseLerp( def.plant.growMinGlow, def.plant.growOptimalGlow, Find.GlowGrid.GameGlowAt(Position));
			return Mathf.Clamp01(raw);
		}
	}
	public float GrowthRateFactor_Temperature
    {
        get
        {
            float cellTemp;
			if( !GenTemperature.TryGetTemperatureForCell(Position, out cellTemp) )
				return 1;

            if (cellTemp < MinOptimalGrowthTemperature)
				return Mathf.InverseLerp( MinGrowthTemperature, MinOptimalGrowthTemperature, cellTemp );
            else if (cellTemp > MaxOptimalGrowthTemperature)
				return Mathf.InverseLerp( MaxGrowthTemperature, MaxOptimalGrowthTemperature, cellTemp );
			else
				return 1;
        }
    }
	private int TicksUntilFullyGrown
	{
		get
		{
			if( growth > 0.9999f )
				return 0;

			return (int)((1f-growth) / GrowthPerTick);
		}
	}
	private string GrowthPercentString
	{
		get
		{
			return (growth + 0.0001f).ToStringPercent();
		}
	}
	public override string LabelMouseover
	{
		get
		{
			StringBuilder sb = new StringBuilder();
			sb.Append(def.LabelCap);
			sb.Append(" (" + "PercentGrowth".Translate(GrowthPercentString));
			
			if( Rotting )
				sb.Append(", " + "DyingLower".Translate() );
			
			sb.Append(")");
			return sb.ToString();
		}
	}
	private bool HasEnoughLightToGrow
	{
		get
		{
			return GrowthRateFactor_Light > 0.001f;
		}
	}
	private float LocalFertility
	{
		get
		{
			return Find.FertilityGrid.FertilityAt(Position);
		}
	}
	public PlantLifeStage LifeStage
	{
		get
		{
			if( growth < 0.001f )
				return PlantLifeStage.Sowing;

			if( growth > 0.999f )
				return PlantLifeStage.Mature;

			return PlantLifeStage.Growing;
		}
	}

	public override Graphic Graphic
	{
		get
		{
			if( LifeStage == PlantLifeStage.Sowing )
				return GraphicSowing;

			if( def.plant.leaflessGraphic != null && LeaflessNow )
				return def.plant.leaflessGraphic;

			return base.Graphic;
		}
	}
	public bool LeaflessNow
	{
		get
		{
			if( Find.TickManager.TicksGame - madeLeaflessTick < LeaflessMinRecoveryTicks )
				return true;
			else
				return false;
		}
	}


	//temp for debug
	public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
	{
		base.Destroy(mode);
	}


	public override void SpawnSetup()
	{
		base.SpawnSetup();

		//Don't call during init because indoor warm plants will all go leafless if it's cold outside
		if( Game.Mode == GameMode.MapPlaying )
			CheckTemperatureMakeLeafless();
	}
	
	
	public override void ExposeData()
	{
		base.ExposeData();
		
		Scribe_Values.LookValue(ref growth, 	"growth");
		Scribe_Values.LookValue(ref age, 		"age", 0);
		Scribe_Values.LookValue(ref unlitTicks,	"unlitTicks", 0 );
		Scribe_Values.LookValue(ref sown,		"sown", false);
	}

	public override void PostMapInit()
	{
		CheckTemperatureMakeLeafless();
	}

	public override void Ingested( Pawn eater, float nutritionWanted )
	{
        if (def.plant.harvestDestroys)
        {
            Destroy();
        }
        else
        {
            growth -= 0.30f;

            if (growth < 0.08f)
                growth = 0.08f;

            Find.MapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);
        }

		float nut = def.ingestible.nutrition;

		//Take growth into account only for sown plants
		//This controls exploiting sowable plants for animal feed
		if( def.plant.Sowable )
			nut *= growth;
		else
			nut *= Mathf.Lerp(0.5f, 1, growth);

        eater.needs.food.CurLevel += nut; 
	}

	public void PlantCollected()
	{
		if( def.plant.harvestDestroys )
		{
			Destroy();
		}
		else
		{
			growth = 0.08f;
			Find.MapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);
		}
	}
	
	private void CheckTemperatureMakeLeafless()
	{
		float diff = MaxLeaflessTemperature - MinLeaflessTemperature;
		float leaflessThresh = ((this.HashOffset() * 0.01f) % diff)-diff + MaxLeaflessTemperature;

		if( Position.GetTemperature() < leaflessThresh )
			MakeLeafless();
	}

	public void MakeLeafless()
	{
		bool changed = !LeaflessNow;

		madeLeaflessTick = Find.TickManager.TicksGame;

		if( def.plant.dieIfLeafless )
			TakeDamage( new DamageInfo( DamageDefOf.Rotting, 99999, null ) );	

		if( changed )
			Find.MapDrawer.MapMeshDirty( Position, MapMeshFlag.Things );
	}



	public override void TickLong()
	{
		CheckTemperatureMakeLeafless();

		if( GenPlant.GrowthSeasonNow(Position) )
		{
			bool hasLight = HasEnoughLightToGrow;

			//Record light starvation
			if( !hasLight )
				unlitTicks += GenTicks.TickLongInterval;
			else
				unlitTicks = 0;
	
			//Grow
			bool wasMature = LifeStage == PlantLifeStage.Mature;
			growth += GrowthPerTick * GenTicks.TickLongInterval;
			if( !wasMature && LifeStage == PlantLifeStage.Mature )
				NewlyMatured();

			if( def.plant.LimitedLifespan )
			{
				//Age
				age += GenTicks.TickLongInterval;

				//Rot
				if( Rotting)
				{
					int rotDamAmount = Mathf.CeilToInt(RotDamagePerTick * GenTicks.TickLongInterval);
					TakeDamage( new DamageInfo(DamageDefOf.Rotting, rotDamAmount, null) );	
				}
			}
		
			//Reproduce
			if( !Destroyed && def.plant.shootsSeeds && growth >= SeedShootMinGrowthPercent )
			{
				if( Rand.MTBEventOccurs( def.plant.seedEmitMTBDays, GenDate.TicksPerDay, GenTicks.TickLongInterval ) )
				{
					if( !GenPlant.SnowAllowsPlanting(Position) )
						return;

					//Otherwise roofed plants will be not counted from other plants, yet still shoot seeds
					//creating higher densities around themselves
					if( Position.Roofed() )
						return;

					GenPlantReproduction.TrySpawnSeed( Position, def, SeedTargFindMode.ReproduceSeed, this );		
				}
			}
		}
	}

	private void NewlyMatured()
	{
		if( CurrentlyCultivated() )
			Find.MapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);
	}

	private bool CurrentlyCultivated()
	{
		if( !def.plant.Sowable )
			return false;
		
		var z = Find.ZoneManager.ZoneAt(Position);
		if (z != null && z is Zone_Growing)
			return true;

		var ed = Position.GetEdifice();
		if( ed != null && ed.def.building.SupportsPlants )
			return true;

		return false;
	}

	public int YieldNow()
	{
		if( !HarvestableNow )
			return 0;

		//Start with max yield
		float yieldFloat = def.plant.harvestYield;

		//Factor for growth
		float growthFactor = Mathf.InverseLerp( def.plant.harvestMinGrowth, 1, growth);
		growthFactor = 0.5f + growthFactor * 0.5f;	//Scalebias it to 0.5..1 range.
		yieldFloat *= growthFactor;

		//Factor down for health with a 50% lerp factor
		yieldFloat *=  Mathf.Lerp( 0.5f, 1f,  ((float)HitPoints / (float)MaxHitPoints) );

		//Factor for difficulty
		yieldFloat *= Find.Storyteller.difficulty.cropYieldFactor;


		//If yield is 0, handle it
		if( def.plant.harvestYield <= 0 )
			return 0;

		return Gen.RandomRoundToInt(yieldFloat);		
	}


	public override void Print( SectionLayer layer )
	{
		Vector3 trueCenter = this.TrueCenter();

		Rand.Seed = Position.GetHashCode();//So our random generator makes the same numbers every time

		//Determine random local position variance
		float positionVariance;
		if( def.plant.maxMeshCount == 1 )
			positionVariance = 0.05f;
		else
			positionVariance = 0.50f;

		//Determine how many meshes to print
		int meshCount = Mathf.CeilToInt( growth * def.plant.maxMeshCount );
		if( meshCount < 1 )
			meshCount = 1;

		//Grid width is the square root of max mesh count
		int gridWidth = 1;
		switch( def.plant.maxMeshCount )
		{
			case 1: gridWidth = 1; break;
			case 4: gridWidth = 2; break;
			case 9: gridWidth = 3; break;
			case 16: gridWidth = 4; break;
			case 25: gridWidth = 5; break;
			default: Log.Error(def + " must have plant.MaxMeshCount that is a perfect square."); break;
		}
		float gridSpacing = 1f/gridWidth; //This works out to give half-spacings around the edges

		//Shuffle up the position indices and place meshes at them
		//We do this to get even mesh placement by placing them roughly on a grid
		Vector3 adjustedCenter = Vector3.zero;
		Vector2 planeSize = Vector2.zero;
		int meshesYielded = 0;
		var posIndexList = PlantPosIndices.GetPositionIndices(this);
		for(int i=0; i<posIndexList.Length; i++ )
		{		
			int posIndex = posIndexList[i];

			//Determine plane size
			float size = def.plant.visualSizeRange.LerpThroughRange(growth);

			//Determine center position
			if( def.plant.maxMeshCount == 1 )
			{
				adjustedCenter = trueCenter + new Vector3(Rand.Range(-positionVariance, positionVariance),
															 0,
															 Rand.Range(-positionVariance, positionVariance) );

				//Clamp bottom of plant to square bottom
				//So tall plants grow upward
				float squareBottom = Mathf.Floor(trueCenter.z);
				if( (adjustedCenter.z - (size/2f)) <  squareBottom )
					adjustedCenter.z = squareBottom + (size/2f);
			}
			else
			{
				adjustedCenter = Position.ToVector3(); //unshifted
				adjustedCenter.y = def.Altitude;//Set altitude

				//Place this mesh at its randomized position on the submesh grid
				adjustedCenter.x += 0.5f * gridSpacing;
				adjustedCenter.z += 0.5f * gridSpacing;
				int xInd = posIndex / gridWidth;
				int zInd = posIndex % gridWidth;
				adjustedCenter.x += xInd * gridSpacing;
				adjustedCenter.z += zInd * gridSpacing;
				
				//Add a random offset
				float gridPosRandomness = gridSpacing * GridPosRandomnessFactor;
				adjustedCenter += new Vector3(Rand.Range(-gridPosRandomness, gridPosRandomness),
											  0,
											  Rand.Range(-gridPosRandomness, gridPosRandomness) );
			}

			//Randomize horizontal flip
			bool flipped = Rand.Value < 0.5f;		

			//Randomize material
			Material mat = Graphic.MatSingle; //Pulls a random material

			//Set wind exposure value at each vertex by setting vertex color
			workingColors[1].a = workingColors[2].a = (byte)(255 * def.plant.topWindExposure);
			workingColors[0].a = workingColors[3].a = 0;

			size *= def.graphicData.drawSize.x;	//Plants don't support non-square drawSizes
			planeSize = new Vector2( size,size );
			Printer_Plane.PrintPlane( layer, 
									  adjustedCenter, 
									  planeSize,	
									  mat, 
									  flipUv: flipped, 
									  colors:  workingColors );


			meshesYielded++;
			if( meshesYielded >= meshCount )
				break;
		}

		if( def.graphicData.shadowData != null)
		{
			//Brutal shadow positioning hack
			float shadowOffsetFactor = 0.85f;
			if( planeSize.y < 1 )
				shadowOffsetFactor = 0.6f; //for bushes
			else
				shadowOffsetFactor = 0.81f;	//for cacti

			Vector3 sunShadowLoc = adjustedCenter;
			sunShadowLoc.z -= (planeSize.y/2f) * shadowOffsetFactor;
			sunShadowLoc.y -= Altitudes.AltInc;

			Printer_Shadow.PrintShadow( layer, sunShadowLoc, def.graphicData.shadowData );
		}
	}



	
	public override string GetInspectString()
	{
		StringBuilder sb = new StringBuilder();

		sb.Append(base.GetInspectString());
		if( LifeStage == PlantLifeStage.Growing )
		{
			sb.AppendLine("PercentGrowth".Translate(GrowthPercentString));
			sb.AppendLine("GrowthRate".Translate() + ": " + GrowthRate.ToStringPercent());

			//sb.AppendLine("FullyGrownIn".Translate( GrowthRate>0?TicksUntilFullyGrown.TickstoDaysString():"Infinite".Translate() ));

			if( Resting )
				sb.AppendLine("PlantResting".Translate());
			else
			{
				if( !HasEnoughLightToGrow )		
					sb.AppendLine("PlantNeedsLightLevel".Translate() + ": " + def.plant.growMinGlow.ToStringPercent() );

				float tempFactor = GrowthRateFactor_Temperature;
				if( tempFactor < 0.99f )
				{
					if( tempFactor < 0.01f )
						sb.AppendLine("OutOfIdealTemperatureRangeNotGrowing".Translate());
					else
						sb.AppendLine("OutOfIdealTemperatureRange".Translate(Mathf.RoundToInt(tempFactor * 100f).ToString()));
				}
			}
		}
		else if( LifeStage == PlantLifeStage.Mature )
		{
			if( def.plant.Harvestable )
				sb.AppendLine("ReadyToHarvest".Translate() );
			else
				sb.AppendLine("Mature".Translate() );
		}
		
		return sb.ToString();
	}
	
	public void CropBlighted()
	{
        if( Rand.Value < 0.85f )
			Destroy();
	}
}

internal static class PlantPosIndices
{
	//Cached
	//First index - for maxMeshCount
	//Second index - which of the lists for this maxMeshCount
	//Third index - which index in this list
	private static int[][][] rootList = null;

	//Constants
	private const int ListCount = 8;

	static PlantPosIndices()
	{
		rootList = new int[PlantProperties.MaxMaxMeshCount][][];
		for( int i=0; i<PlantProperties.MaxMaxMeshCount; i++ )
		{
			rootList[i] = new int[ListCount][];
			for( int j=0; j<ListCount; j++ )
			{
				int[] newList = new int[i+1];
				for( int k=0; k<i; k++ )
				{
					newList[k] = k;
				}
				newList.Shuffle();

				rootList[i][j] = newList;
			}
		}
	}

	public static int[] GetPositionIndices( Plant p )
	{
		int mmc = p.def.plant.maxMeshCount;
		int index = (p.thingIDNumber^42348528) % ListCount;
		return rootList[mmc-1][ index ];
	}
}

}