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


namespace Verse.AI{
public static class Toils_Recipe
{
	public static Toil MakeUnfinishedThingIfNeeded()
	{
		Toil toil = new Toil();
		toil.initAction = ()=>
			{
				Pawn actor = toil.actor;
				Job curJob = actor.jobs.curJob;

				//Recipe doesn't use unfinished things
				if( !curJob.RecipeDef.UsesUnfinishedThing )
					return;

				//Already working on an unfinished thing
				if( curJob.GetTarget( JobDriver_DoBill.IngredientInd ).Thing is UnfinishedThing )
					return;

				//Create the unfinished thing
				Thing dominantIngredient;
				var ingredients = ProcessedIngredients(curJob, out dominantIngredient);

				//Store the dominant ingredient as the UnfinishedThing's stuff
				ThingDef stuff = curJob.RecipeDef.unfinishedThingDef.MadeFromStuff
					? dominantIngredient.def
					: null;

				//Make the UFT and set its data
				UnfinishedThing uft = (UnfinishedThing)ThingMaker.MakeThing(curJob.RecipeDef.unfinishedThingDef, stuff);
				uft.Creator = actor;
				uft.BoundBill = (Bill_ProductionWithUft)curJob.bill;
				uft.ingredients = ingredients;
				CompColorable cc = uft.TryGetComp<CompColorable>();
				if( cc != null )
					cc.Color = dominantIngredient.DrawColor;

				//Spawn the UFT
				GenSpawn.Spawn(uft, curJob.GetTarget( JobDriver_DoBill.BillGiverInd ).Cell );

				//Set the job to use the unfinished thing as its only ingredient
				curJob.SetTarget( JobDriver_DoBill.IngredientInd, uft);

				//Reserve the unfinished thing
				Find.Reservations.Reserve( actor, uft );
			};
		return toil;
	}



	public static Toil DoRecipeWork()
	{
		const int MinWorkDuration = 3000;
		const int CheckOverrideInterval = 1000;

		Toil toil = new Toil();
		toil.initAction = ()=>
			{
				Pawn actor = toil.actor;
				Job curJob = actor.jobs.curJob;
				JobDriver_DoBill driver = ((JobDriver_DoBill)actor.jobs.curDriver);
				UnfinishedThing uft = curJob.GetTarget(JobDriver_DoBill.IngredientInd).Thing as UnfinishedThing;

				//Set our work left
				//If we're starting from an already-initialized UnfinishedThing, just copy its workLeft into the driver
				//Otherwise, generate a new workLeft and copy it into the UnfinishedThing
				if( uft != null && uft.Initialized)
				{
					driver.workLeft = uft.workLeft;
				}
				else
				{
					driver.workLeft = curJob.bill.recipe.WorkAmountTotal( uft != null ? uft.Stuff : null );

					if( uft != null )
						uft.workLeft = driver.workLeft;
				}

				driver.billStartTick = Find.TickManager.TicksGame;
				driver.ticksSpentDoingRecipeWork = 0;

				curJob.bill.Notify_DoBillStarted();
			};
		toil.tickAction = ()=>
			{
				Pawn actor = toil.actor;
				Job curJob = actor.jobs.curJob;
				JobDriver_DoBill driver = ((JobDriver_DoBill)actor.jobs.curDriver);
				UnfinishedThing uft = curJob.GetTarget(JobDriver_DoBill.IngredientInd).Thing as UnfinishedThing;

				if( uft != null && uft.Destroyed )
				{
					actor.jobs.EndCurrentJob(JobCondition.Incompletable);
					return;
				}

				driver.ticksSpentDoingRecipeWork++;

				curJob.bill.Notify_PawnDidWork(actor);

				//Bill giver gets notification that we're working on it
				IBillGiverWithTickAction bga = toil.actor.CurJob.GetTarget(JobDriver_DoBill.BillGiverInd).Thing as IBillGiverWithTickAction;
				if( bga != null )
					bga.BillTick();

				//Learn (only if the recipe uses unfinished thing to prevent the exploit with drafting/undrafting colonists and getting unlimited xp) 
				if( curJob.RecipeDef.workSkill != null && curJob.RecipeDef.UsesUnfinishedThing )
				{
					actor.skills.GetSkill(curJob.RecipeDef.workSkill).Learn( LearnRates.XpPerTickRecipeBase * curJob.RecipeDef.workSkillLearnFactor );
				}

				//Make some progress
				//Apply it to both the driver's workLeft and, if it exists, the UnfinishedThing's workLeft
				float progress = (curJob.RecipeDef.workSpeedStat==null) ? 1f : actor.GetStatValue( curJob.RecipeDef.workSpeedStat );

                Building_WorkTable workTable = driver.BillGiver as Building_WorkTable;

                if (workTable != null)
                    progress *= workTable.GetStatValue(StatDefOf.WorkTableWorkSpeedFactor);

				if( DebugSettings.fastCrafting )
					progress *= 30;

				driver.workLeft -= progress;
				if( uft != null )
					uft.workLeft = driver.workLeft;

				PawnUtility.GainComfortFromCellIfPossible(actor);

				//End the toil if there is no more work left
				if( driver.workLeft <= 0 )
					driver.ReadyForNextToil();

				//Allow job override periodically
				if( curJob.bill.recipe.UsesUnfinishedThing )
				{
					int billTicks = Find.TickManager.TicksGame - driver.billStartTick;
					if( billTicks >= MinWorkDuration && billTicks%CheckOverrideInterval == 0 )
						actor.jobs.CheckForJobOverride();
				}
			};
		toil.defaultCompleteMode = ToilCompleteMode.Never;
		toil.WithEffect(	()=>toil.actor.CurJob.bill.recipe.effectWorking, TargetIndex.A );
		toil.WithSustainer( ()=>toil.actor.CurJob.bill.recipe.soundWorking );
		toil.WithProgressBar(JobDriver_DoBill.BillGiverInd, () =>
			{
				var actor = toil.actor;
				var curJob = actor.CurJob;
				var uft = curJob.GetTarget(JobDriver_DoBill.IngredientInd).Thing as UnfinishedThing;

				return 1f - ((JobDriver_DoBill)actor.jobs.curDriver).workLeft / curJob.bill.recipe.WorkAmountTotal(uft != null ? uft.Stuff : null);
			});
		toil.FailOn( ()=>toil.actor.CurJob.bill.suspended );

		return toil;
	}




	public static Toil FinishRecipeAndStartStoringProduct()
	{
		Toil toil = new Toil();
		toil.initAction = ()=>
			{
				Pawn actor = toil.actor;
				Job curJob = actor.jobs.curJob;
				JobDriver_DoBill driver = ((JobDriver_DoBill)actor.jobs.curDriver);

				//Learn (if the recipe doesn't use unfinished thing)
				if( curJob.RecipeDef.workSkill != null && !curJob.RecipeDef.UsesUnfinishedThing )
				{
					float xp = driver.ticksSpentDoingRecipeWork * LearnRates.XpPerTickRecipeBase * curJob.RecipeDef.workSkillLearnFactor;
					actor.skills.GetSkill(curJob.RecipeDef.workSkill).Learn(xp);
				}

				//Get ingredients from UnfinishedThing
				Thing dominantIngredient;
				List<Thing> ingredients = ProcessedIngredients(curJob, out dominantIngredient);

				//Make the products
				List<Thing> products = GenRecipe.MakeRecipeProducts( curJob.RecipeDef, actor, ingredients, dominantIngredient ).ToList();

				//Notify bill
                curJob.bill.Notify_IterationCompleted(actor, ingredients);

				//Add records
				RecordsUtility.Notify_BillDone(actor, products);

				//----------------------------------------------------
				//Rearrange the job so the bill doer goes and stores the product
				//----------------------------------------------------

				//Nothing to store? End the job now
				if( products.Count == 0 )
				{
					actor.jobs.EndCurrentJob( JobCondition.Succeeded );
					return;
				}

				//Bill is set to drop-on-floor mode?
				//Drop everything and end the job now
				if( curJob.bill.GetStoreMode() == BillStoreMode.DropOnFloor )
				{
					for( int i=0; i<products.Count; i++ )
					{
						if( !GenPlace.TryPlaceThing(products[i], actor.Position, ThingPlaceMode.Near ) )
							Log.Error( actor + " could not drop recipe product " + products[i] + " near " + actor.Position );
					}

					actor.jobs.EndCurrentJob( JobCondition.Succeeded );
					return;
				}
				
				//Place all products except the first one on the ground
				if( products.Count > 1 )
				{
					for( int i=1; i<products.Count; i++ )
					{
						if( !GenPlace.TryPlaceThing(products[i], actor.Position, ThingPlaceMode.Near ) )
							Log.Error( actor + " could not drop recipe product " + products[i] + " near " + actor.Position );
					}
				}


				//Hack set position to make TryFindBestStoreSquareFor work for a thing that has never been spawned
				products[0].SetPositionDirect( actor.Position );

				//Try find a cell to take the product to
				IntVec3 storeCell;
				if( StoreUtility.TryFindBestBetterStoreCellFor( products[0], actor, StoragePriority.Unstored, actor.Faction, out storeCell ) )
				{
					//Start carrying the first product and proceed to the storage toils
					actor.carrier.TryStartCarry( products[0] );

					curJob.targetB = storeCell;
					curJob.targetA = products[0];
					curJob.maxNumToCarry = 99999;
				}
				else
				{
					//No store cell? Drop the product; we're done.
					if( !GenPlace.TryPlaceThing( products[0], actor.Position, ThingPlaceMode.Near ) )
					{
						Log.Error("Bill doer could not drop product " + products[0] + " near " + actor.Position );
					}

					actor.jobs.EndCurrentJob( JobCondition.Succeeded );
					return;
				}
			};

		return toil;
	}



	//============================================================================================
	//================================= Products creation helpers ================================
	//============================================================================================

	private static Thing GetDominantIngredient( RecipeDef recipe, List<Thing> ingredients )
	{
		// if the recipe uses stuff ingredient (from recipe maker), then the first ingredient is the dominant one (stuff)
		if( recipe.productHasIngredientStuff )
			return ingredients[0];

		if( recipe.products.Any(x => x.thingDef.MadeFromStuff) )
			return ingredients.Where(x => x.def.IsStuff).RandomElementByWeight(x => x.stackCount);
		else
			return ingredients.RandomElementByWeight(x => x.stackCount);
	}

	/// <summary>
	/// Gathers a list of ingredients used by a job, whether they are on the table or in an UnfinishedThing.
	/// Destroys them if necessary and clear the placedTargets so we don't retain useless references to destroyed things.
	/// Also either gets the dominant ingredient from the UnfinishedThing or determines it anew.
	/// </summary>
	private static List<Thing> ProcessedIngredients( Job job, out Thing dominantIngredient )
	{
		//Pull the ingredients from the unfinished thing and destroy it
		UnfinishedThing uft = job.GetTarget( JobDriver_DoBill.IngredientInd).Thing as UnfinishedThing;
		if( uft != null )
		{
			if( uft.def.MadeFromStuff )
				dominantIngredient = uft.ingredients.First(ing => ing.def == uft.Stuff);
			else
				dominantIngredient = null;

			var ufIngs = uft.ingredients;
			uft.Destroy();
			job.placedThings = null;
			return ufIngs;
		}

		//Pull the ingredients from what is currently on the work table
		{
			List<Thing> ingredients = new List<Thing>();
			if( job.placedThings != null )
			{
				for( int i = 0; i < job.placedThings.Count; i++ )
				{
					var ingredient = job.placedThings[i].Split();

					//Error catch
					//Maybe related to double-destroy below
					if( ingredients.Contains(ingredient) )
					{
						Log.Error("Tried to add ingredient from job placed targets twice: " + ingredient );
						continue;
					}

    				ingredients.Add( ingredient );

					//Auto-strip anything
					//Note: This happens even if the strippable is unspawned, and thus only works
					//		because the strippable should still have a last good position.
					IStrippable stripIng = ingredient as IStrippable;
					if( stripIng != null )
						stripIng.Strip();

					if(job.RecipeDef.UsesUnfinishedThing)
					{
						//We only despawn these here because these ingredients can come back out if the UFT is canceled
						//
						//This is potentially buggy and dangerous
						Find.DesignationManager.RemoveAllDesignationsOn(ingredient);

						if( ingredient.Spawned )
							ingredient.DeSpawn();
					}
					else
						ingredient.Destroy();
    			}
			}
			job.placedThings = null;

			if( ingredients.NullOrEmpty() )
				dominantIngredient = null;
			else
				dominantIngredient = GetDominantIngredient( job.RecipeDef, ingredients );

			return ingredients;
		}
	}
}
}

