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


namespace Verse{
public class Corpse : ThingWithComps, IThoughtGiver, IStrippable, IBillGiver
{
	//Config
	public Pawn innerPawn = null;

	//Working vars
	private int timeOfDeath = -1000;
    private int vanishAfterTimestamp = -1000;
    private BillStack operationsBillStack = null;
	public bool everBuriedInSarcophagus;
    
    //Constants
    private const int VanishAfterTicks = 50 * GenDate.TicksPerDay;

	//Properties
    public int Age{
        get
        {
            return Find.TickManager.TicksGame - timeOfDeath;
        }
        set
        {
            timeOfDeath = Find.TickManager.TicksGame - value;
        }
    }
	public override string Label
	{
		get
		{
			return "DeadLabel".Translate(innerPawn.LabelCap);
		}
	}
	public override bool IngestibleNow
	{
		get
		{
			if( !innerPawn.RaceProps.IsFlesh )
				return false;

			if( this.GetRotStage() == RotStage.Dessicated )
				return false;

			return true;
		}
	}
	public RotDrawMode CurRotDrawMode
	{
		get
		{
			var rottable = GetComp<CompRottable>();

			if( rottable != null )
			{
				if( rottable.Stage == RotStage.Rotting )
					return RotDrawMode.Rotting;
				else if( rottable.Stage == RotStage.Dessicated )
					return RotDrawMode.Dessicated;
			}

			return RotDrawMode.Fresh;
		}
	}
    private bool ShouldVanish
    {
        get
        {
             return innerPawn.RaceProps.Animal &&
                    vanishAfterTimestamp > 0 &&
                    Age >= vanishAfterTimestamp &&
					holder == null &&
                    this.GetRoom().TouchesMapEdge &&
                    !Find.RoofGrid.Roofed(Position);
        }
    }
	public override IEnumerable<StatDrawEntry> SpecialDisplayStats
	{
		get
		{
			foreach( var s in base.SpecialDisplayStats )
			{
				yield return s;
			}

			yield return new StatDrawEntry(StatCategoryDefOf.Basics, "Nutrition".Translate(), FoodUtility.GetBodyPartNutrition(innerPawn, innerPawn.RaceProps.body.corePart).ToString("0.##"));
		
			var meatAmount = StatDefOf.MeatAmount;
			yield return new StatDrawEntry(meatAmount.category, meatAmount, innerPawn.GetStatValue(meatAmount), StatRequest.For(innerPawn));

			var leatherAmount = StatDefOf.LeatherAmount;
			yield return new StatDrawEntry(leatherAmount.category, leatherAmount, innerPawn.GetStatValue(leatherAmount), StatRequest.For(innerPawn));
		}
	}
    public BillStack BillStack { get { return operationsBillStack; } }
    public IEnumerable <IntVec3> IngredientStackCells { get { yield return InteractionCell; } }
    
    public Corpse()
    {
        operationsBillStack = new BillStack(this);
    }
    
    public bool CurrentlyUsable()
    {
        return InteractionCell.IsValid;
    }
    
    public bool AnythingToStrip()
    {
        return innerPawn.AnythingToStrip();
    }
    



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

		if( timeOfDeath < 0 )
			timeOfDeath = Find.TickManager.TicksGame;

		innerPawn.Rotation = Rot4.South; //Fixes drawing errors

		NotifyColonistBar();
	}

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

		NotifyColonistBar();
	}

	public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
	{
		// unclaim grave if we have any
		if( innerPawn.ownership != null )
			innerPawn.ownership.UnclaimAll();

		// destroy equipment
		if( innerPawn.equipment != null )
			innerPawn.equipment.DestroyAllEquipment();

		// destroy inventory
		innerPawn.inventory.DestroyAll();

		// destroy apparel
		if( innerPawn.apparel != null )
			innerPawn.apparel.DestroyAll();

		innerPawn.corpse = null;

		base.Destroy(mode);

		// recheck if WorldPawns wants to keep the inner pawn now that the corpse has been destroyed
		Find.WorldPawns.DiscardIfUnimportant(innerPawn);

		NotifyColonistBar();
	}

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

		innerPawn.TickRare();

        // reset vanishAfterTimestamp to X days from now if not previously set, or if carcass still fresh
        if (vanishAfterTimestamp < 0 || this.GetRotStage() != RotStage.Dessicated)
            vanishAfterTimestamp = Age + VanishAfterTicks;

        if(ShouldVanish)
            Destroy();
    }

	protected override void PostIngested(Pawn ingester, float nutritionWanted, out int numTaken, out float nutritionIngested)
	{
		int unused;
		float unused2;
		base.PostIngested(ingester, nutritionWanted, out unused, out unused2);

		//Determine part to take
		var part = GetBestBodyPartToEat(ingester, nutritionWanted);
		if( part == null )
		{
			Log.Error(ingester + " ate " + this + " but no body part was found. Replacing with core part.");
			part = innerPawn.RaceProps.body.corePart;
		}

		//Determine the nutrition to gain
		float nut = FoodUtility.GetBodyPartNutrition(innerPawn, part);

		//Affect this thing
		//If ate core part, remove the whole corpse
		//Otherwise, remove the eaten body part
		if( part == innerPawn.RaceProps.body.corePart )
		{
			if( PawnUtility.ShouldSendNotificationAbout(innerPawn) && innerPawn.RaceProps.Humanlike )
				Messages.Message("MessageEatenByPredator".Translate(innerPawn.LabelShort, ingester.LabelIndefinite()).CapitalizeFirst(), ingester, MessageSound.Negative);

			numTaken = 1;
		}
		else
		{
			var missing = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, innerPawn, part);
			missing.lastInjury = HediffDefOf.Bite;
			missing.IsFresh = true;
			innerPawn.health.AddHediff(missing);

			numTaken = 0;
		}

		//Humans have a constant 5% chance of food poisoning from eating unbutchered corpses
		if( ingester.RaceProps.Humanlike && Rand.Value < 0.05f )
			FoodUtility.AddFoodPoisoningHediff(ingester);

		nutritionIngested = nut;
	}

	public override IEnumerable<Thing> ButcherProducts( Pawn butcher, float efficiency )
	{
		foreach( var t in innerPawn.ButcherProducts(butcher, efficiency) )
		{
			yield return t;
		}

		//Spread blood
		if( innerPawn.RaceProps.BloodDef != null )
            FilthMaker.MakeFilth(butcher.Position, innerPawn.RaceProps.BloodDef, innerPawn.LabelIndefinite() );

		//Thought/tale for butchering humanlike
		if( innerPawn.RaceProps.Humanlike )
		{
			butcher.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.ButcheredHumanlikeCorpse);
			foreach( var p in Find.MapPawns.SpawnedPawnsInFaction(butcher.Faction) )
			{
				if( p == butcher || p.needs == null || p.needs.mood == null || p.needs.mood.thoughts == null )
					continue;
				p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowButcheredHumanlikeCorpse);
			}
			TaleRecorder.RecordTale(TaleDefOf.ButcheredHumanlikeCorpse, butcher);
		}
	}


	public override void ExposeData()
	{
		base.ExposeData();
		Scribe_Values.LookValue( ref timeOfDeath, "timeOfDeath" );
        Scribe_Values.LookValue(ref vanishAfterTimestamp, "vanishAfterTimestamp");
		Scribe_Values.LookValue(ref everBuriedInSarcophagus, "everBuriedInSarcophagus");
        Scribe_Deep.LookDeep( ref operationsBillStack, "operationsBillStack", this );
		Scribe_References.LookReference( ref innerPawn, "innerPawn", true );
	}

	public void Strip()
    {
        if(innerPawn.equipment != null)
        {
            innerPawn.equipment.DropAllEquipment(Position, false);
        }
        if(innerPawn.apparel != null)
        {
            innerPawn.apparel.DropAll(Position, false);
        }
        if(innerPawn.inventory != null)
        {
            innerPawn.inventory.DropAllNearPawn(Position);
        }
    }

	public override void DrawAt(Vector3 drawLoc)
	{
		//Don't draw in graves
		Building storeBuilding = this.StoringBuilding();
		if( storeBuilding != null && storeBuilding.def == ThingDefOf.Grave )
			return;

		innerPawn.Drawer.renderer.RenderPawnAt( drawLoc, CurRotDrawMode );
	}

	public Thought_Memory GiveObservedThought()
	{
		//Non-humanlike corpses never give thoughts
		if( !innerPawn.RaceProps.Humanlike )
			return null;

        Thing storingBuilding = this.StoringBuilding();
		if( storingBuilding == null )
		{
			//Laying on the ground
            
			Thought_MemoryObservation obs;
			if( this.IsNotFresh() )
				obs = (Thought_MemoryObservation)ThoughtMaker.MakeThought(ThoughtDefOf.ObservedLayingRottingCorpse);
            else
				obs = (Thought_MemoryObservation)ThoughtMaker.MakeThought(ThoughtDefOf.ObservedLayingCorpse);
			obs.Target = this;
			return obs;
		}
        
		return null;
	}

	public override string GetInspectString()
	{
		System.Text.StringBuilder sb = new System.Text.StringBuilder();
		if( innerPawn.Faction != null )
			sb.AppendLine("Faction".Translate() + ": " + innerPawn.Faction);
		sb.AppendLine("DeadTime".Translate( Age.ToStringTicksToPeriod(false) ) );

		float percentMissing = 1f - innerPawn.health.hediffSet.GetCoverageOfNotMissingNaturalParts(innerPawn.RaceProps.body.corePart);

		if( percentMissing != 0f )
		{
			sb.AppendLine("CorpsePercentMissing".Translate() + ": " + percentMissing.ToStringPercent());
		}

		sb.AppendLine(base.GetInspectString());
		return sb.ToString();
	}

	public void RotStageChanged()
	{
		PortraitsCache.SetDirty(innerPawn);
		NotifyColonistBar();
	}

	private BodyPartRecord GetBestBodyPartToEat(Pawn ingester, float nutritionWanted)
	{
		var candidates = innerPawn.health.hediffSet.GetNotMissingParts()
			.Where(x => x.depth == BodyPartDepth.Outside && FoodUtility.GetBodyPartNutrition(innerPawn, x) > 0.001f);

		if( !candidates.Any() )
			return null;

		// get part which nutrition is the closest to what we want
		return candidates.MinBy(x => Mathf.Abs(FoodUtility.GetBodyPartNutrition(innerPawn, x) - nutritionWanted));
	}

	private void NotifyColonistBar()
	{
		if( innerPawn.Faction == Faction.OfPlayer && Current.ProgramState == ProgramState.MapPlaying )
			Find.ColonistBar.MarkColonistsListDirty();
	}
}}
