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




namespace RimWorld{
public class Fire : AttachableThing, ISizeReporter
{
	//Working vars - gameplay
	public float			fireSize = MinFireSize; //1 is a medium-sized campfire
	private int				ticksSinceSpread; //Unsaved, unimportant
	private float			flammabilityMax = 0.5f; //Updated only periodically


	//Working vars - audiovisual
	private int				ticksUntilSmoke = 0;
	private Sustainer		sustainer = null;

	//Working vars - static
	private static List<Thing> flammableList = new List<Thing>();
	private static int		fireCount;
	private static int		lastFireCountUpdateTick;


	//Constants
	public const float		MinFireSize = 0.1f;
	private const float		MinSizeForSpark = 1f;
	private const float		TicksBetweenSparksBase = 150; //Halves for every fire size
	private const float		TicksBetweenSparksReductionPerFireSize = 40;
	private const float		MinTicksBetweenSparks = 75;
	private const float		MinFireSizeToEmitSpark = 1f;
	private const float		MaxFireSize = 1.75f;

    private const int       ComplexCalcsInterval = 150;

	private const float		CellIgniteChancePerTickPerSize = 0.01f;
	private const float		MinSizeForIgniteMovables = 0.4f;

	private const float		FireBaseGrowthPerTick = 0.00055f;

	private static readonly IntRange SmokeIntervalRange = new IntRange(130,200);
	private const int		SmokeIntervalRandomAddon = 10;

	private const float		BaseSkyExtinguishChance = 0.04f;
	private const int		BaseSkyExtinguishDamage = 10;


	private const float		HeatPerFireSizePerInterval = 160f;
	private const float		HeatFactorWhenDoorPresent = 0.15f;

	private const float		SnowClearRadiusPerFireSize = 3f;
	private const float		SnowClearDepthFactor = 0.1f;

	private const int		FireCountParticlesOff = 15;

	//Properties
	public override string Label
	{
		get
		{
			if( parent != null )
				return "FireOn".Translate( parent.LabelCap);	
			else
				return "Fire".Translate();
		}
	}
	public override string InspectStringAddon
	{
		get
		{
			return "Burning".Translate() + " (" + "FireSizeLower".Translate( (fireSize*100).ToString("F0") ) + ")";	
		}
	}
	private float SpreadInterval
	{
		get
		{
			float ticks = TicksBetweenSparksBase - (fireSize-1)*TicksBetweenSparksReductionPerFireSize;

			if( ticks < MinTicksBetweenSparks )
				ticks = MinTicksBetweenSparks;

			return ticks;
		}
	}



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

		Scribe_Values.LookValue(ref fireSize, "fireSize");
	}

	public override void SpawnSetup()
	{
		base.SpawnSetup();
		RecalcPathsOnAndAroundMe();
		ConceptDecider.TeachOpportunity(ConceptDefOf.HomeArea, this, OpportunityType.Important );

		ticksSinceSpread = (int)(SpreadInterval * Rand.Value);

		LongEventHandler.ExecuteWhenFinished(() =>
			{
				var soundDef = SoundDef.Named("FireBurning");
				SoundInfo info = SoundInfo.InWorld(this, MaintenanceType.PerTick);
				sustainer = SustainerAggregatorUtility.AggregateOrSpawnSustainerFor(this, soundDef, info);
			});
	}

	public float CurrentSize()
	{
		return fireSize;
	}

	public override void DeSpawn()
	{
		if( sustainer.externalParams.sizeAggregator == null )
			sustainer.externalParams.sizeAggregator = new SoundSizeAggregator();
		sustainer.externalParams.sizeAggregator.RemoveReporter(this);

		base.DeSpawn();

		RecalcPathsOnAndAroundMe();
	}

	private void RecalcPathsOnAndAroundMe()
	{
		for( int i=0; i<GenAdj.AdjacentCellsAndInside.Length; i++ )
		{
			IntVec3 c = Position + GenAdj.AdjacentCellsAndInside[i];

			if( !c.InBounds() )
				continue;

			Find.PathGrid.RecalculatePerceivedPathCostAt(c);
		}
	}

	public override void AttachTo(Thing parent)
	{
		base.AttachTo(parent);

		Pawn p = parent as Pawn;
		if( p != null )
		{
			TaleRecorder.RecordTale( TaleDefOf.WasOnFire, p );
		}
	}
	
	public override void Tick()
	{
		if( lastFireCountUpdateTick != Find.TickManager.TicksGame )
		{
			fireCount = Find.ListerThings.ThingsOfDef(def).Count;
			lastFireCountUpdateTick = Find.TickManager.TicksGame;
		}

		if( sustainer != null )
			sustainer.Maintain();
		else
			Log.ErrorOnce("Fire sustainer was null at " + Position, 917321);

        Profiler.BeginSample("Spawn particles");
		{
			//Do smoke and glow
			ticksUntilSmoke--;
			if( ticksUntilSmoke <= 0 )
			{
				if( fireCount < FireCountParticlesOff )
					MoteThrower.ThrowSmoke( DrawPos, fireSize );

				if( fireSize > 0.5f && parent == null )
					MoteThrower.ThrowFireGlow( Position, fireSize );

				float firePct = fireSize / 2;
				if( firePct > 1 )
					firePct = 1;
				firePct = 1f-firePct;
				ticksUntilSmoke = SmokeIntervalRange.Lerped(firePct) + (int)(SmokeIntervalRandomAddon*Rand.Value);
			}

			//Do visual micro sparks
			if( fireCount < FireCountParticlesOff && fireSize > 0.7f && Rand.Value < fireSize * 0.01f )
				MoteThrower.ThrowMicroSparks(DrawPos);
		}
		Profiler.EndSample(); //Spawn particles


		//Spread fire
		Profiler.BeginSample("Spread");
		if( fireSize > MinSizeForSpark )
		{
			ticksSinceSpread++;
			if( ticksSinceSpread >= SpreadInterval )
			{
				TrySpread();
				ticksSinceSpread = 0;
			}
		}
		Profiler.EndSample(); //Spread



		if( Gen.IsHashIntervalTick( this, ComplexCalcsInterval ) )
		{
			bool cellContainsDoor = false;

			Profiler.BeginSample("Determine flammability");
			//Determine list of flammables in my cell
			flammableList.Clear();
			flammabilityMax = 0;
			if( parent == null )
			{
				List<Thing> cellThings = Find.ThingGrid.ThingsListAt(Position);
				for( int i=0; i<cellThings.Count; i++ )
				{
					var ct = cellThings[i];
					if( ct is Building_Door )
						cellContainsDoor = true;

					var thingFlam = ct.GetStatValue( StatDefOf.Flammability );

					if( thingFlam < 0.01f )
						continue;

					//Record its flammability
					flammableList.Add(cellThings[i]);
					if( thingFlam > flammabilityMax )
						flammabilityMax = thingFlam;

					//If I'm a static fire and it's a pawn and I'm big, ignite it
					if( parent == null && fireSize > MinSizeForIgniteMovables && cellThings[i].def.category == ThingCategory.Pawn )
						cellThings[i].TryAttachFire(fireSize*0.2f);
				}
			}
			else
			{
				//Consider only my parent
				flammableList.Add( parent );
				flammabilityMax = parent.GetStatValue( StatDefOf.Flammability );
			}
			Profiler.EndSample(); //Determine flammability

			//Destroy me if I have nothing to burn
			if( flammabilityMax < 0.01f )
			{
				Destroy();
				return;
			}

			Profiler.BeginSample("Do damage");
			{
				//Choose what I'm going to damage
				Thing damagee;
				if( parent != null )
					damagee = parent;						//Damage parent
				else if( flammableList.Count > 0 )
					damagee = flammableList.RandomElement();//Damage random flammable thing in cell
				else
					damagee = null;
		
				//Damage whatever we're supposed to damage
				if( damagee != null )
				{
					//We don't damage the target if it's not our parent, it would attach a fire, and we're too small
					//This is to avoid tiny fires igniting passing pawns
					if( !(fireSize < MinSizeForIgniteMovables && damagee != parent && damagee.def.category == ThingCategory.Pawn) )
						DoFireDamage( damagee );
				}
			}
			Profiler.EndSample(); //Do damage

			Profiler.BeginSample("Room heat");
			//Push some heat
            float fireEnergy = fireSize * HeatPerFireSizePerInterval;
			//Hack to reduce impact on doors, otherwise they hit insane temperatures fast
			if( cellContainsDoor )
				fireEnergy *= HeatFactorWhenDoorPresent;
			GenTemperature.PushHeat(Position, fireEnergy);
			Profiler.EndSample(); //Room heat

			Profiler.BeginSample("Snow clear");
			if( Rand.Value < 0.4f )
			{
				//Clear some snow around the fire
				float snowClearRadius = fireSize * SnowClearRadiusPerFireSize;
				SnowUtility.AddSnowRadial( Position, snowClearRadius, -(fireSize * SnowClearDepthFactor) );
			}
			Profiler.EndSample(); //Snow clear
        

			Profiler.BeginSample("Grow/extinguish");
			//Try to grow the fire
			fireSize += FireBaseGrowthPerTick
					  * flammabilityMax
					  * ComplexCalcsInterval;

			if( fireSize > MaxFireSize )
				fireSize = MaxFireSize;

			//Extinguish from sky (rain etc)
			if( Find.WeatherManager.RainRate > 0.01f )
			{
 				if( VulnerableToRain() )
				{
					if( Rand.Value < BaseSkyExtinguishChance * ComplexCalcsInterval )
						TakeDamage( new DamageInfo(DamageDefOf.Extinguish, BaseSkyExtinguishDamage, null) );
				}
			}
			Profiler.EndSample(); //Grow/extinguish
		}
	}

	private bool VulnerableToRain()
	{
		var roof = Find.RoofGrid.RoofAt(Position);

		//Always affected under no roof
		if( roof == null )
			return true;

		//Never affected under thick roof
		if( roof.isThickRoof )
			return false;

		//Affected under thin roof if and only if a roof holder is here (meaning that I am a wall being rained on).
		Thing building = Position.GetEdifice();
		return building != null && building.def.holdsRoof;
	}

	private void DoFireDamage( Thing targ )
	{
		float damPerTick = 0.0125f + (0.0036f * fireSize);
		damPerTick = Mathf.Clamp( damPerTick, 0.0125f, 0.05f );
		int damAmount = GenMath.RoundRandom(damPerTick * ComplexCalcsInterval);
		if( damAmount < 1 )
			damAmount = 1;

		Pawn p = targ as Pawn;
		if( p != null )
		{
			BodyPartDamageInfo part = new BodyPartDamageInfo(null, BodyPartDepth.Outside);
			targ.TakeDamage( new DamageInfo( DamageDefOf.Flame, damAmount, this, part ) );

			//Damage a random apparel
			if( p.apparel != null )
			{
				Apparel ap;
				if( p.apparel.WornApparel.TryRandomElement(out ap) )
					ap.TakeDamage( new DamageInfo( DamageDefOf.Flame, damAmount, this ) );
			}
		}
		else
		{
			targ.TakeDamage( new DamageInfo( DamageDefOf.Flame, damAmount, this ) );
		}
	}

	public override void PostApplyDamage(DamageInfo dinfo, float totalDamageDealt)
	{
		if( !Destroyed && dinfo.Def == DamageDefOf.Extinguish )
		{
			//One damage reduces fireSize by 0.01f
			fireSize -= dinfo.Amount / 100f;

			if( fireSize <= MinFireSize )
			{
				Destroy();
				return;
			}
		}
	}
	
	protected void TrySpread()
	{
		//This method is optimized as it is a performance bottleneck (as are the sparks it spawns)

		IntVec3 targLoc = Position;
		bool adjacent;
		if( Rand.Value < 0.8f )
		{
			targLoc = Position + GenRadial.ManualRadialPattern[ Rand.RangeInclusive(1,8) ];	//Spark adjacent
			adjacent = true;
		}
		else
		{
			targLoc = Position + GenRadial.ManualRadialPattern[ Rand.RangeInclusive(10,20) ];	//Spark distant
			adjacent = false;
		}
		
		if( !targLoc.InBounds() )
			return;

		if( !adjacent )
		{
			var thingList = targLoc.GetThingList();
			bool foundFlammable = false;
			for( int i=0; i<thingList.Count; i++ )
			{
				if( thingList[i] is Fire )
					return;

				if( thingList[i].FlammableNow )
					foundFlammable = true;
			}

			if( !foundFlammable )
				return;

			if( !FireUtility.FireCanExistIn(targLoc ) )
				return;

			var startRect = CellRect.SingleCell(Position);
			var endRect = CellRect.SingleCell(targLoc);

			// don't create a spark if we'll hit a wall in our way
			if( !GenSight.LineOfSight(Position, targLoc, startRect, endRect) )
				return;

			Spark sp = (Spark)GenSpawn.Spawn( ThingDefOf.Spark, Position );
			sp.Launch( this, targLoc );
		}
		else
		{
			//When adjacent, skip sparks and just magically spawn fires
			FireUtility.TryStartFireIn(targLoc, Fire.MinFireSize);
		}
	}

}}