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

using Verse.Sound;
using Verse.AI;


namespace RimWorld{
public class Building_Door : Building
{
	//Links
	public CompPowerTrader	powerComp;

	//Working vars - saved
	private bool 			openInt = false;
	private bool			holdOpenInt = false;		//Player has configured door to be/stay open
	private bool			freePassageUntilClosed = false;
	private int				lastFriendlyTouchTick = -9999;

	//Working vars - unsaved
	protected int 			ticksUntilClose = 0;
	protected int 			visualTicksOpen = 0;

	//Constants
	private const float		BaseDoorOpenTicks = 45;
	private const int		AutomaticCloseDelayTicks = 60;
	private const int		ApproachCloseDelayTicks = 300;	//Extra long in case pawn is very slow; don't want door to close before they arrive
	private const float		VisualDoorOffsetStart = 0.0f;
	private const float		VisualDoorOffsetEnd = 0.45f;
	
	private const float		TempEqualizeIntervalOpen = 21;
	private const float		TempEqualizeIntervalClosed = 250;
	private const float		TempEqualizeRate = 0.55f;

	//Properties	
	public bool Open{get{return openInt;}}
	public bool HoldOpen{get{return holdOpenInt;}}
	public bool FreePassage
	{
		get
		{
			return openInt && (holdOpenInt || freePassageUntilClosed);
		}
	}
	public bool CloseBlocked
	{
		get
		{
			var thingList = Position.GetThingList();
			for( int i=0; i<thingList.Count; i++ )
			{
				var t = thingList[i];
				//Don't close on items or pawns
				if(    t.def.category == ThingCategory.Pawn
					|| t.def.category == ThingCategory.Item )
					return true;
			}

			return false;
		}
	}
	public bool DoorPowerOn
	{
		get
		{
			return powerComp != null && powerComp.PowerOn;
		}
	}
	public bool SlowsPawns
	{
		get
		{
			return !DoorPowerOn || TicksToOpenNow > 20;
		}
	}
	public int TicksToOpenNow
	{
		get
		{
			float ticks = BaseDoorOpenTicks / this.GetStatValue( StatDefOf.DoorOpenSpeed );

			if( DoorPowerOn )
				ticks *= 0.25f;

			return Mathf.RoundToInt(ticks);
		}
	}
	private int VisualTicksToOpen
	{
		get
		{
			//return Mathf.RoundToInt(TicksToOpenNow * 0.85f);
			return TicksToOpenNow;
		}
	}


	public override void PostMake()
	{
		base.PostMake();
		powerComp = GetComp<CompPowerTrader>();
	}

	public override void SpawnSetup()
	{
		base.SpawnSetup();
		powerComp = GetComp<CompPowerTrader>();
	}

	public override void ExposeData()
	{
		base.ExposeData();
		Scribe_Values.LookValue(ref openInt, "open", defaultValue: false);
		Scribe_Values.LookValue(ref holdOpenInt, "holdOpen", defaultValue: false);
		Scribe_Values.LookValue(ref freePassageUntilClosed, "freePassageUntilClosed", defaultValue: false );
		Scribe_Values.LookValue(ref lastFriendlyTouchTick, "lastFriendlyTouchTick" );

		if( Scribe.mode == LoadSaveMode.LoadingVars )
			if( openInt )
				visualTicksOpen = VisualTicksToOpen;
	}

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

		if( !openInt )
		{
			//Visual - slide door closed
			if( visualTicksOpen > 0 )
				visualTicksOpen--;

			//Equalize temperatures
			if( (Find.TickManager.TicksGame + thingIDNumber.HashOffset()) % TempEqualizeIntervalClosed == 0 )
				EqualizeTemps();
			
			//If the power is on and we want to hold open, open automatically
		//	if( holdOpenInt && DoorPowerOn )
		//		DoorOpen();
		}
		else if( openInt )
		{
			//Visual - slide door open
			if( visualTicksOpen < VisualTicksToOpen )
				visualTicksOpen++;

			//Count down to closing
			if( !holdOpenInt )
			{
				if( Find.ThingGrid.CellContains( Position, ThingCategory.Pawn ) )
					ticksUntilClose = AutomaticCloseDelayTicks;
				else
				{
					ticksUntilClose--;

					//If the power is on and we were touched recently, close automatically
					if( DoorPowerOn && ticksUntilClose <= 0 && Find.TickManager.TicksGame < lastFriendlyTouchTick + 200 )
						DoorTryClose();
				}
			}

			//Equalize temperatures
			if( (Find.TickManager.TicksGame + thingIDNumber.HashOffset()) % TempEqualizeIntervalOpen == 0 )
				EqualizeTemps();
		}
	}

	public void FriendlyTouched()
	{
		lastFriendlyTouchTick = Find.TickManager.TicksGame;
	}

	private void EqualizeTemps()
	{
		Room[] neighRooms = new Room[4];
		int neighRoomCount = 0;
		float totalTemp = 0;
		for( int i=0; i<4; i++ )
		{
			IntVec3 neigh = Position + GenAdj.CardinalDirections[i];

			if( !neigh.InBounds() )
				continue;
			
			Room r = neigh.GetRoom();
			if( r != null ) 
			{
				totalTemp += r.Temperature;
				neighRooms[neighRoomCount] = r;
				neighRoomCount++;
			}
		}

		if( neighRoomCount == 0 )
		{
			//Door is entirely surrounded by walls
			return;
		}

		//My temp becomes the average of my neighbors' temperatures
		float avgTemp = totalTemp / neighRoomCount;

		Position.GetRoom().Temperature = avgTemp;

		//Push my temperature into my neighbors
		for( int i=0; i<neighRoomCount; i++ )
		{
			float neighTemp = neighRooms[i].Temperature;

			float diff = avgTemp - neighTemp;

			neighRooms[i].PushHeat(diff * TempEqualizeRate);
		}
	}

	public void Notify_PawnApproaching( Pawn p )
	{
		//Open automatically before pawn arrives
		if( PawnCanOpen(p) && !SlowsPawns )
			DoorOpen( ApproachCloseDelayTicks );
	}

	/// <summary>
	/// Returns whether p can physically pass through the door without bashing.
	/// </summary>
	public bool CanPhysicallyPass( Pawn p )
	{
		return FreePassage || PawnCanOpen(p);
	}

	/// <summary>
	/// Returns whether p can open the door without bashing.
	/// </summary>
	public virtual bool PawnCanOpen( Pawn p )
	{
		//Animals *are* allowed to open doors
	//	if( p.RaceProps.Animal )
	//		return false;

		return GenAI.MachinesLike(Faction, p);
	}
	
	public override bool BlocksPawn( Pawn p )
	{
		if( openInt )
			return false;
		else
			return !PawnCanOpen(p);
	}

	protected void DoorOpen(int ticksToClose = AutomaticCloseDelayTicks)
	{
		ticksUntilClose = ticksToClose;

		if( !openInt )
		{
			openInt = true;

			if( holdOpenInt )
			{
				freePassageUntilClosed = true;
				Reachability.ClearCache();
			}

			if( DoorPowerOn )
				def.building.soundDoorOpenPowered.PlayOneShot(Position);
			else
				def.building.soundDoorOpenManual.PlayOneShot(Position);
		}
	}


	protected void DoorTryClose()
	{
		if( holdOpenInt || CloseBlocked )
			return;

		openInt = false;

		if( freePassageUntilClosed )
		{
			freePassageUntilClosed = false;
			Reachability.ClearCache();
		}

		if( DoorPowerOn )
			def.building.soundDoorClosePowered.PlayOneShot(Position);
		else
			def.building.soundDoorCloseManual.PlayOneShot(Position);
	}

		
	public void StartManualOpenBy( Pawn opener )
	{
		DoorOpen();
	}

	public void StartManualCloseBy( Pawn closer )
	{
		DoorTryClose();
	}

	public override void Draw()
	{
		//Note: It's a bit odd that I'm changing game variables in Draw
		//      but this is the easiest way to make this always look right even if
		//      conditions change while the game is paused.
		Rotation = DoorRotationAt(Position);

		//Draw the two moving doors
		float pctOpen = Mathf.Clamp01((float)visualTicksOpen / (float)VisualTicksToOpen);	//Needs clamp for after game load		
		float offsetDist = VisualDoorOffsetStart + (VisualDoorOffsetEnd-VisualDoorOffsetStart)*pctOpen;	

		for( int i=0; i<2; i++ )
		{
			//Left, then right
			Vector3 offsetNormal = new Vector3();
			Mesh mesh;
			if( i == 0 )
			{
				offsetNormal = new Vector3(0,0,-1);
				mesh = MeshPool.plane10;
			}
			else
			{
				offsetNormal = new Vector3(0,0,1);
				mesh = MeshPool.plane10Flip;
			}
			

			//Work out move direction
			Rot4 openDir = Rotation;
			openDir.Rotate(RotationDirection.Clockwise);
			offsetNormal  = openDir.AsQuat * offsetNormal;

			//Position the door
			Vector3 doorPos =  DrawPos;
			doorPos.y = Altitudes.AltitudeFor(AltitudeLayer.DoorMoveable);
			doorPos += offsetNormal * offsetDist;
		
			//Draw!
			Graphics.DrawMesh(mesh, doorPos, Rotation.AsQuat, Graphic.MatAt(Rotation), 0 );
		}
			
		Comps_PostDraw();
	}


	private static int AlignQualityAgainst( IntVec3 c )
	{
		if( !c.InBounds() )
			return 0;

		//We align against anything unwalkthroughable and against blueprints for unwalkthroughable things
		if( !c.Walkable() )
			return 9;
			

		List<Thing> things = Find.ThingGrid.ThingsListAt(c);
		for(int i=0; i<things.Count; i++ )
		{
			Thing t = things[i];

			if( t.def.thingClass == typeof(Building_Door) )
				return 1;

			Thing blue = t as Blueprint;
			if( blue != null )
			{
				if( blue.def.entityDefToBuild.passability == Traversability.Impassable )
					return 9;
				if( blue.def.thingClass == typeof(Building_Door) )
					return 1;
			}
		}
			
		return 0;		
	}


	public static Rot4 DoorRotationAt(IntVec3 loc)
	{
		int horVotes = 0;
		int verVotes = 0;

		horVotes += AlignQualityAgainst( loc + IntVec3.East );
		horVotes += AlignQualityAgainst( loc + IntVec3.West );
		verVotes += AlignQualityAgainst( loc + IntVec3.North );
		verVotes += AlignQualityAgainst( loc + IntVec3.South );

		if( horVotes >= verVotes )
			return Rot4.North;
		else
			return Rot4.East;
	}

	public static readonly Texture2D ButtonIconHoldOpen = ContentFinder<Texture2D>.Get("UI/Commands/HoldOpen");
	public override IEnumerable<Gizmo> GetGizmos()
	{
		foreach( var g in base.GetGizmos() )
		{
			yield return g;
		}

		/*
		if( Faction == Faction.OfColony )
		{
			Command_Toggle l = new Command_Toggle();
			l.defaultLabel = "CommandToggleDoorForbid".Translate();
			l.defaultDesc = "CommandToggleDoorForbidDesc".Translate();
			l.hotKey = KeyBindingDefOf.CommandItemForbid;
			l.icon = CompForbiddable.ButtonIconForbidden;
			l.isActive = ()=>!this.IsForbidden(Faction.OfColony);
			l.toggleAction = ()=>{this.SetForbidden(!this.IsForbidden(Faction.OfColony));};
			l.groupKey = 912515;
			yield return l;
		}*/

		{
			Command_Toggle ro = new Command_Toggle();
			ro.defaultLabel = "CommandToggleDoorHoldOpen".Translate();
			ro.defaultDesc = "CommandToggleDoorHoldOpenDesc".Translate();
			ro.hotKey = KeyBindingDefOf.Misc3;
			ro.icon = ButtonIconHoldOpen;
			ro.isActive = ()=>holdOpenInt;
			ro.toggleAction = ()=>
			{
				holdOpenInt = !holdOpenInt;

				Reachability.ClearCache();

				if( Open && holdOpenInt )
					freePassageUntilClosed = true;

			/*	if( DoorPowerOn )
				{
					if( holdOpenInt )
						DoorOpen();
					else
						DoorTryClose();
				}*/
			};
			yield return ro;
		}
	}
}
}


