-- ===========================================================================
--	Action Panel
--	Main area of game to advance turns and show what is currently blocking.
--	Tabs set to 4 spaces; retaining tab.
-- ===========================================================================

include( "InstanceManager" );
include( "SupportFunctions" );
include("Civ6Common"); -- IsTutorialRunning()

-- ===========================================================================
--	CONSTANTS
-- ===========================================================================
local NO_FLASHING					:number = 0;
local FLASHING_END_TURN				:number = 1;
local FLASHING_SCIENCE				:number = 2;
local FLASHING_PRODUCTION			:number = 3;
local FLASHING_FREE_TECH			:number = 4;
local FLASHING_NEEDS_ORDERS			:number = 5;

local TURN_TIMER_BAR_ACTIVE_COLOR   :number = 0xffffffff;
local TURN_TIMER_BAR_INACTIVE_COLOR :number = 0xff0000ff;

local MAX_BLOCKER_BUTTONS			:number = 4;	-- Number of buttons around big action button
local ERA_DEGREES					:table = { 209,190,171,153,137,122,106,106 };	-- Degrees to place the era indicator
local autoEndTurnOptionHash			:number = DB.MakeHash("AutoEndTurn");

local MAX_BEFORE_TRUNC_TURN_STRING	:number = 150;

local START_TURN_TIMER_TICK_SOUND	:number	= 7;  -- Start making turn timer ticking sounds when the turn timer is lower than this seconds.

-- End Turn Button Strings
local pleaseWaitString 				:string	= Locale.Lookup("LOC_ACTION_PANEL_PLEASE_WAIT");
local pleaseWaitTip					:string	= Locale.Lookup("LOC_ACTION_PANEL_PLEASE_WAIT_TOOLTIP");
local skipTurnString 				:string	= Locale.Lookup("LOC_ACTION_PANEL_SKIP_TURN");
local skipTurnTip 					:string	= Locale.Lookup("LOC_ACTION_PANEL_SKIP_TURN_TOOLTIP");
local moveStackedUnitString 		:string	= Locale.Lookup("LOC_ACTION_PANEL_STACKED_UNIT");
local moveStackedUnitTip 			:string	= Locale.Lookup("LOC_ACTION_PANEL_STACKED_UNIT_TOOLTIP");
local unitNeedsOrdersString 		:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_ORDERS");
local unitNeedsOrdersTip 			:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_ORDERS_TOOLTIP");
local waitForPlayersString 			:string	= Locale.Lookup("LOC_ACTION_PANEL_WAITING_FOR_PLAYERS");
local waitForPlayersTip 			:string	= Locale.Lookup("LOC_ACTION_PANEL_WAITING_FOR_PLAYERS_TOOLTIP");
local waitForPlayerTurnString		:string = "LOC_ACTION_PANEL_WAITING_FOR_PLAYER_TURN";
local nextTurnString 				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEXT_TURN");
local nextTurnTip					:string	= Locale.Lookup("LOC_ACTION_PANEL_NEXT_TURN_TOOLTIP");
local needResearchString			:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_RESEARCH");
local needResearchTip				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_RESEARCH_TOOLTIP");
local needCivicString				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_CIVIC");
local needCivicTip					:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_CIVIC_TOOLTIP");
local fillCivicString				:string	= Locale.Lookup("LOC_ACTION_PANEL_FILL_CIVIC_SLOT");
local fillCivicTip					:string	= Locale.Lookup("LOC_ACTION_PANEL_FILL_CIVIC_SLOT_TOOLTIP");
local considerGovernmentString		:string	= Locale.Lookup("LOC_ACTION_PANEL_CONSIDER_GOVERNMENT_CHANGE");
local considerGovernmentTip			:string	= Locale.Lookup("LOC_ACTION_PANEL_CONSIDER_GOVERNMENT_CHANGE_TOOLTIP");
local considerRazeCityString		:string	= Locale.Lookup("LOC_ACTION_PANEL_CONSIDER_RAZE_CITY");
local considerRazeCityTip			:string	= Locale.Lookup("LOC_ACTION_PANEL_CONSIDER_RAZE_CITY_TOOLTIP");
local needProductionString			:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_PRODUCTION");
local needProductionTip				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_PRODUCTION_TOOLTIP");
local needPantheonString			:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_PANTHEON");
local needPantheonTip				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_PANTHEON_TOOLTIP");
local needReligionString			:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_RELIGION");
local needReligionTip				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_RELIGION_TOOLTIP");
local needBeliefString				:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_BELIEF");
local needBeliefTip					:string	= Locale.Lookup("LOC_ACTION_PANEL_NEEDS_BELIEF_TOOLTIP");
local giveInfluenceTokenString		:string = Locale.Lookup("LOC_ACTION_PANEL_GIVE_INFLUENCE_TOKEN");
local giveInfluenceTokenTip			:string = Locale.Lookup("LOC_ACTION_PANEL_GIVE_INFLUENCE_TOKEN_TOOLTIP");
local claimGreatPersonString		:string = Locale.Lookup("LOC_ACTION_PANEL_CLAIM_GREAT_PERSON");
local claimGreatPersonTip			:string = Locale.Lookup("LOC_ACTION_PANEL_CLAIM_GREAT_PERSON_TOOLTIP");
local unitsHaveMovesString			:string	= Locale.Lookup("LOC_ACTION_PANEL_UNIT_MOVES_REMAINING");
local unitsHaveMovesTip				:string	= Locale.Lookup("LOC_ACTION_PANEL_UNIT_MOVES_REMAINING_TOOLTIP");
local chooseEscapeRouteString		:string = Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_ESCAPE_ROUTE");
local chooseEscapeRouteTip			:string = Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_ESCAPE_ROUTE_TOOLTIP");
local chooseDragnetPriorityString	:string = Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_DRAGNET_PRIORITY");
local chooseDragnetPriorityTip		:string = Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_DRAGNET_PRIORITY_TOOLTIP");
local needArtifactPlayerString		:string	= Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_ARTIFACT_PLAYER");
local needArtifactPlayerTip			:string	= Locale.Lookup("LOC_ACTION_PANEL_CHOOSE_ARTIFACT_PLAYER_TOOLTIP");
local cityRangedAttackString		:string	= Locale.Lookup("LOC_ACTION_PANEL_CITY_RANGED_ATTACK");
local cityRangedAttackTip			:string	= Locale.Lookup("LOC_ACTION_PANEL_CITY_RANGED_ATTACK_TOOLTIP");
local yourTurnToolStr				:string = Locale.Lookup("LOC_KEY_YOUR_TURN_TIME_TOOLTIP");
local estTilTurnToolStr				:string = Locale.Lookup("LOC_KEY_ESTIMATED_TIME_TIL_YOUR_TURN_TIME_TOOLTIP");
local estTimeElapsedToolStr			:string = Locale.Lookup("LOC_KEY_ESTIMATED_TIME_ELAPSED_TOOLTIP");
local canUnreadyTurnTip				:string = Locale.Lookup("LOC_ACTION_PANEL_CAN_UNREADY_TOOLTIP");
													  
-- ===========================================================================
local m_kMessageInfo :table = {};
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_UNITS]						= {Message = unitNeedsOrdersString,			ToolTip = unitNeedsOrdersTip	, Icon="ICON_NOTIFICATION_COMMAND_UNITS"		}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_STACKED_UNITS]				= {Message = moveStackedUnitString,			ToolTip = moveStackedUnitTip	, Icon="ICON_NOTIFICATION_COMMAND_UNITS"		}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_UNIT_NEEDS_ORDERS]			= {Message = unitNeedsOrdersString,			ToolTip = unitNeedsOrdersTip	, Icon="ICON_NOTIFICATION_COMMAND_UNITS"		}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_RESEARCH]					= {Message = needResearchString,			ToolTip = needResearchTip		, Icon="ICON_NOTIFICATION_CHOOSE_TECH"		}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_CIVIC]						= {Message = needCivicString,				ToolTip = needCivicTip			, Icon="ICON_NOTIFICATION_CHOOSE_CIVIC",	Sound="Notification_New_Civic" }
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_FILL_CIVIC_SLOT]			= {Message = fillCivicString ,				ToolTip = fillCivicTip			, Icon="ICON_NOTIFICATION_CHOOSE_CIVIC"		}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_CONSIDER_GOVERNMENT_CHANGE]= {Message = considerGovernmentString,		ToolTip = considerGovernmentTip	, Icon="ICON_NOTIFICATION_CONSIDER_GOVERNMENT_CHANGE"	}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_CONSIDER_RAZE_CITY]		= {Message = considerRazeCityString,		ToolTip = considerRazeCityTip	, Icon="ICON_NOTIFICATION_CONSIDER_RAZE_CITY"	}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_PRODUCTION]				= {Message = needProductionString,			ToolTip = needProductionTip		, Icon="ICON_NOTIFICATION_CHOOSE_CITY_PRODUCTION"	}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_PANTHEON]					= {Message = needPantheonString,			ToolTip = needPantheonTip		, Icon="ICON_NOTIFICATION_CHOOSE_PANTHEON",	Sound="Notification_New_Religion" }
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_RELIGION]					= {Message = needReligionString,			ToolTip = needReligionTip		, Icon="ICON_NOTIFICATION_CHOOSE_RELIGION",	Sound="Notification_New_Religion" }
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_BELIEF]					= {Message = needBeliefString,				ToolTip = needBeliefTip			, Icon="ICON_NOTIFICATION_CHOOSE_RELIGION",	Sound="Notification_New_Religion" }
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_GIVE_INFLUENCE_TOKEN]		= {Message = giveInfluenceTokenString,		ToolTip = giveInfluenceTokenTip	, Icon="ICON_NOTIFICATION_GIVE_INFLUENCE_TOKEN"	}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_CLAIM_GREAT_PERSON]		= {Message = claimGreatPersonString,		ToolTip = claimGreatPersonTip	, Icon="ICON_NOTIFICATION_CLAIM_GREAT_PERSON",	Sound="Notification_Great_Person_Available" }
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_SPY_CHOOSE_ESCAPE_ROUTE]	= {Message = chooseEscapeRouteString,		ToolTip = chooseEscapeRouteTip	, Icon="ICON_NOTIFICATION_SPY_CHOOSE_ESCAPE_ROUTE"	}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_SPY_CHOOSE_DRAGNET_PRIORITY]={Message = chooseDragnetPriorityString,	ToolTip = chooseDragnetPriorityTip	, Icon="ICON_NOTIFICATION_SPY_CHOOSE_DRAGNET_PRIORITY"}
m_kMessageInfo[EndTurnBlockingTypes.ENDTURN_BLOCKING_ARTIFACT]                   ={Message = needArtifactPlayerString,		ToolTip = needArtifactPlayerTip	, Icon="ICON_NOTIFICATION_DISCOVER_ARTIFACT"}


-- ===========================================================================
--	MEMBERS
-- ===========================================================================
local m_overflowIM			: table = InstanceManager:new( "TurnBlockerInstance",  "TurnBlockerButton", Controls.OverflowStack );
local m_shiftsHeld			: number	= 0;
local m_activeBlockerId		: number	= EndTurnBlockingTypes.NO_ENDTURN_BLOCKING;	-- Blocking notification receiving attention
local m_kEras				: table		= {};
local m_kSoundsPlayed		: table		= {};										-- Track which notifications have had their associate sound played
local m_EndTurnId						= Input.GetActionId("EndTurn");				-- Hotkey
local m_lastTurnTickTime	: number	= 0;										-- When did we last make a tick sound for the turn timer?
local m_numberVisibleBlockers	:number	= 0;
local m_visibleBlockerTypes	: table		= {};
local m_isSlowTurnEnable	: boolean	= false;									-- Tutorial: when active slow to allow clicks when turn raises.


-- ===========================================================================
--	UI Event
--	The View()
-- ===========================================================================
function OnRefresh()

	ContextPtr:ClearRequestRefresh();
	
	m_numberVisibleBlockers = 1; -- Start at 1 to account for current main blocker
	m_visibleBlockerTypes = {};

	local pPlayer = Players[Game.GetLocalPlayer()];
	if (pPlayer == nil) then
		return;
	end

	if not pPlayer:IsTurnActiveComplete() or UI.IsProcessingMessages() then
		SetEndTurnWaiting();			
		return;
	end
	
	Controls.EndTurnButton:SetDisabled( false );
	Controls.EndTurnButtonLabel:SetDisabled( false );	
	
	local message				:string;
	local icon					:string;
	local toolTipString			:string;
	local soundName				:string;
	local iFlashingState		:number	= NO_FLASHING;	
	local m_activeBlockerId		:number = NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
	local kAllBlockingTypes		:table	= NotificationManager.GetAllEndTurnBlocking( Game.GetLocalPlayer() );

	-- If there are any blockers shown, there will be at least 1
	table.insert(m_visibleBlockerTypes, m_activeBlockerId);

	-- Loop through all sounds that have just played.
	for _,blockingTypeSoundPlayed in ipairs(m_kSoundsPlayed) do 				
		-- If the blocking type is no longer in the block list
		local isStillInList:boolean = false;
		for _,blockingType in ipairs(kAllBlockingTypes) do
			if blockingType == blockingTypeSoundPlayed then
				isStillInList = true;
				break;
			end
		end
		-- If not found in list, remove it from the just played list.
		if not isStillInList then
			m_kSoundsPlayed[blockingTypeSoundPlayed] = nil;
		end
	end

	-- Play the ticker animation
	Controls.TickerAnim:SetToBeginning();
	Controls.TickerAnim:Play();

	-- Populate current blocker
	local kInfo	:table	= m_kMessageInfo[m_activeBlockerId];
	if kInfo ~= nil then
		message			= kInfo.Message;
		icon			= kInfo.Icon;
		toolTipString	= kInfo.ToolTip;
		iFlashingState	= FLASHING_END_TURN;
		soundName		= kInfo.Sound;
	elseif (CheckUnitsHaveMovesState()) then
		-- Special "Units Have Moves" state for when there are no end turn blockers but 
		-- there are units with partial movement remaining in 'auto end turn mode'.
		icon			= "ICON_NOTIFICATION_COMMAND_UNITS"
		message			= unitsHaveMovesString;
		toolTipString	= unitsHaveMovesTip;
		iFlashingState	= FLASHING_END_TURN;	
	elseif (CheckCityRangeAttackState()) then
		-- Special "City Ranged Attack" state for when there are no end turn blockers but 
		-- there is a city can that perform a ranged attack in 'auto end turn mode'.
		message			= cityRangedAttackString;
		icon            = "ICON_NOTIFICATION_CITY_RANGED_STRIKE";
		toolTipString	= cityRangedAttackTip;
		iFlashingState	= FLASHING_END_TURN;
	else
		message			= nextTurnString;	
		icon			= "ICON_NOTIFICATION_NEXT_TURN";		
		toolTipString	= nextTurnTip;
		iFlashingState	= FLASHING_END_TURN;
	end	

	-- Show controls and setup callbacks based on the notifications.

	local blockersInUIMax	:number = math.min( table.count(kAllBlockingTypes), MAX_BLOCKER_BUTTONS);
	local iControlNum		:number = 2;
	local iBlocker			:number = 0;
	for iBlocker = 1, blockersInUIMax, 1 do
		local endTurnBlockingId:number = kAllBlockingTypes[iBlocker];
		-- We only want to add blocker buttons for blockers that aren't represented already
		if  endTurnBlockingId ~= m_activeBlockerId and (not BlockerIsVisible(endTurnBlockingId)) then			
			local kAlphaControl:table = Controls["TurnBlockerAlpha"..tostring(iControlNum)];
			local kSlideControl:table = Controls["TurnBlockerSlide"..tostring(iControlNum)];
			local kButtonControl:table= Controls["TurnBlockerButton"..tostring(iControlNum)];

			if kAlphaControl:IsHidden() then
				kAlphaControl:SetHide(false);		
				kAlphaControl:SetToBeginning();
				kSlideControl:SetToBeginning();
				kAlphaControl:Play();
				kSlideControl:Play();
			end
			local tooltip:string = m_kMessageInfo[endTurnBlockingId].ToolTip;
			local icon:string	= m_kMessageInfo[endTurnBlockingId].Icon;
			if(icon ~= nil) then
				local textureOffsetX, textureOffsetY, textureSheet = IconManager:FindIconAtlas(icon,40);
				kButtonControl:SetTexture( textureOffsetX, textureOffsetY, textureSheet );
			end
			kButtonControl:SetToolTipString( tooltip );
			kButtonControl:RegisterCallback( Mouse.eLClick, 
				function()					
					DoEndTurn( endTurnBlockingId );
				end
			);
			iControlNum = iControlNum + 1;
			m_numberVisibleBlockers = m_numberVisibleBlockers + 1;
			table.insert(m_visibleBlockerTypes, endTurnBlockingId);
		end		
	end

	-- Go through remaining controls (if any) and hide them if no longer showing.
	for iControlNum = iControlNum, MAX_BLOCKER_BUTTONS, 1 do
		local kAlphaControl:table = Controls["TurnBlockerAlpha"..tostring(iControlNum)];
		if not kAlphaControl:IsHidden() and not kAlphaControl:IsReversing() then
			local kSlideControl:table = Controls["TurnBlockerSlide"..tostring(iControlNum)];
			kAlphaControl:Reverse();
			kSlideControl:Reverse();
			kAlphaControl:Play();
			kSlideControl:Play();
		end
	end


	-- If there are more blockers than room, then add to "+" area:
	if m_numberVisibleBlockers > MAX_BLOCKER_BUTTONS then
		Controls.OverflowCheckboxGroup:SetHide(false);
		m_overflowIM:ResetInstances();
		for iBlocker = MAX_BLOCKER_BUTTONS+1, table.count(kAllBlockingTypes), 1 do
			-- We only want to add blocker buttons for blockers that aren't represented already
			local endTurnBlockingId	:number = kAllBlockingTypes[iBlocker];
			if not BlockerIsVisible(endTurnBlockingId) then
				local title				:string = m_kMessageInfo[endTurnBlockingId].Message;
				local kInst				:table  = m_overflowIM:GetInstance();			
				local tooltip			:string = m_kMessageInfo[endTurnBlockingId].ToolTip;
				local icon				:string = m_kMessageInfo[endTurnBlockingId].Icon;

				if(icon ~= nil) then
					local textureOffsetX, textureOffsetY, textureSheet = IconManager:FindIconAtlas(icon,40);
					kInst.TurnBlockerIcon:SetTexture( textureOffsetX, textureOffsetY, textureSheet );
				end

				kInst.TurnBlockerLabel:SetText( title );			
				kInst.TurnBlockerLabel:SetToolTipString( tooltip );
				kInst.TurnBlockerButton:RegisterCallback( Mouse.eLClick, 
					function()					
						DoEndTurn( endTurnBlockingId );
					end
				);
				table.insert(m_visibleBlockerTypes, endTurnBlockingId);
			end
		end
	else
		Controls.OverflowCheckboxGroup:SetHide(true);
	end

	-- Play associated sound if there is one (and it hasn't played yet; which can happen
	-- if a player chooses another action rather than the immediate blocking action.)
	if soundName ~= nil and soundName ~= "" then
		if m_kSoundsPlayed[m_activeBlockerId] == nil then
			UI.EnqueueNotificationSound( soundName );
			m_kSoundsPlayed[m_activeBlockerId] = true;
		end
	end

	TruncateStringWithTooltip(Controls.EndTurnText, MAX_BEFORE_TRUNC_TURN_STRING, message); 
	Controls.EndTurnButton:SetToolTipString( toolTipString );
	
	-- Set big icon
	if(icon ~= nil) then
		local countActiveType	:number = 0;
		Controls.CurrentTurnBlockerIcon:SetHide(false);
		Controls.CurrentTurnBlockerIcon:SetIcon(icon);
		
		countActiveType = GetNumNotificationsOfActiveBlocker();
		if  countActiveType >= 2 then
			Controls.Count:SetText(countActiveType);
			Controls.CountImage:SetHide(false);
		else
			Controls.CountImage:SetHide(true);
		end
	end

	SetEndTurnFlashing(iFlashingState);

	-- Set the era rotation and tooltip.
	local eraIndex:number = pPlayer:GetEra() + 1;			-- Engine is 0 Based
	Controls.EraIndicator:Rotate( ERA_DEGREES[eraIndex] );

	for _,era in pairs(m_kEras) do
		if era.Index == eraIndex then
			local description:string = Locale.Lookup("LOC_GAME_ERA_DESC", era.Description );
			Controls.EraToolTipArea1:SetToolTipString( description );
			Controls.EraToolTipArea2:SetToolTipString( description );
			break;
		end		
	end
	
end


-- ===========================================================================
function SetEndTurnFlashing( iFlashingState:number )
	
	-- default behavior
	local isHideEndTurnFlash    :boolean = true;
	local isHideScienceFlash    :boolean = true;
	local isHideFreeTechFlash   :boolean = true;
	local isHideProductionFlash :boolean = true;
	local isHideNormalMouseover :boolean = false;
	
	-- set behavior based on flashing type
	if	   iFlashingState == FLASHING_END_TURN then		isHideEndTurnFlash = false;	isHideNormalMouseover = true;
	elseif iFlashingState == FLASHING_SCIENCE then		isHideScienceFlash = false;
	elseif iFlashingState == FLASHING_PRODUCTION then	isHideProductionFlash = false;
	elseif iFlashingState == FLASHING_FREE_TECH then	isHideFreeTechFlash = false;
	elseif iFlashingState == FLASHING_NEEDS_ORDERS then	isHideNormalMouseover = false;
	elseif iFlashingState == NO_FLASHING then	
		-- Stay with defaults if no flashing.
	end
	
	-- realize
	Controls.EndTurnButtonEndTurnAlpha:SetHide(isHideEndTurnFlash);
	Controls.EndTurnButtonScienceAlpha:SetHide(isHideScienceFlash);
	Controls.EndTurnButtonFreeTechAlpha:SetHide(isHideFreeTechFlash);
	Controls.EndTurnButtonProductionAlpha:SetHide(isHideProductionFlash);	
end

-- ===========================================================================
-- utility functions
function GetPlayer ()
	local iPlayerID = Game.GetLocalPlayer();
	if (iPlayerID < 0) then
		return nil;
	end

	if (not Players[iPlayerID]:IsHuman()) then
		return nil;
	end;

	return Players[iPlayerID];
end

-- ===========================================================================
function GetPlayerByID (iPlayerID)
	if (iPlayerID < 0) then
		return nil;
	end

	return Players[iPlayerID];
end

-- ===========================================================================
function UnitsHaveMovesStateEnabled()
	-- When is the "Units Have Moves" end turn button enabled?
	return 	(UserConfiguration.IsAutoEndTurn()) and Game.IsAllowTacticalCommands(Game.GetLocalPlayer());
end

-- ===========================================================================
function HaveCityRangeAttackStateEnabled()
	-- When is the "City Ranged Attack" end turn button enabled?
	return 	(UserConfiguration.IsAutoEndTurn()) and Game.IsAllowTacticalCommands(Game.GetLocalPlayer());
end

-- ===========================================================================
function CheckUnitsHaveMovesState()
	-- Are we in the Units Have Moves state?
	local pPlayer = Players[Game.GetLocalPlayer()];
	if (pPlayer == nil) then
		return false;
	end

	local unitsNeedMovesState :boolean = UnitsHaveMovesStateEnabled() and pPlayer:GetUnits():GetFirstReadyUnit() ~= nil;
	return unitsNeedMovesState;
end

-- ===========================================================================
function CheckCityRangeAttackState()
	-- Are we in the "City Ranged Attack" state?
	local pPlayer = Players[Game.GetLocalPlayer()];
	if (pPlayer == nil) then
		return false;
	end

	local cityAttackState :boolean = HaveCityRangeAttackStateEnabled() and pPlayer:GetCities():GetFirstRangedAttackCity() ~= nil;
	return cityAttackState;
end

-- ===========================================================================
--	Get the number notifications that are the same type as the currently active blocker
-- ===========================================================================
function GetNumNotificationsOfActiveBlocker()
	local count				:number = 0;
	local iBlocker			:number;
	local kAllBlockingTypes	:table = NotificationManager.GetAllEndTurnBlocking( Game.GetLocalPlayer() );
	local m_activeBlockerId	:number = NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());

	for iBlocker = 1, table.count(kAllBlockingTypes), 1 do
		if (kAllBlockingTypes[iBlocker] == m_activeBlockerId) then
			count = count + 1;
		end
	end
	return count;
end

-- ===========================================================================
--	Used to make sure there aren't duplicate blocker notifications
-- ===========================================================================
function BlockerIsVisible(iBlocker:number)
	local currentType:number;

	for currentType=1, table.count(m_visibleBlockerTypes), 1 do
		if m_visibleBlockerTypes[currentType] == iBlocker then
			return true;
		end
	end

	return false;
end

-- ===========================================================================
--	Check if the turn can be automatically ended.
-- ===========================================================================
function CheckAutoEndTurn( eCurrentEndTurnBlockingType:number )
	local pPlayer = Players[Game.GetLocalPlayer()];
	if pPlayer ~= nil then
		if eCurrentEndTurnBlockingType == EndTurnBlockingTypes.NO_ENDTURN_BLOCKING 
			and	(UserConfiguration.IsAutoEndTurn() and not UI.SkipNextAutoEndTurn())
			-- In tactical phases, all units must have orders or used up their movement points.
			and (not CheckUnitsHaveMovesState() and not CheckCityRangeAttackState()) then 
				if not UI.CanEndTurn() then
					error("CheckAutoEndTurn thinks that we can't end turn, but the notification system disagrees");
				end
			UI.RequestAction(ActionTypes.ACTION_ENDTURN);
		end
	end
end

-- ===========================================================================
--	Attempt to end the turn or execute the most current blocking notification
-- ===========================================================================
function DoEndTurn( optionalNewBlocker:number )

	local pPlayer = Players[Game.GetLocalPlayer()];
	if (pPlayer == nil) then
		return;
	end

	-- If the player can unready their turn, request that.
	-- CanUnreadyTurn() is only true when the player should unready their turn instead of turn completing.
	if pPlayer:CanUnreadyTurn() then
		UI.RequestAction(ActionTypes.ACTION_UNREADYTURN);	
		return;
	end

	if UI.IsProcessingMessages() then
		print("ActionPanel:DoEndTurn() The game is busy processing messages");
		return;
	end

	-- If not in selection mode; reset mode before performing the action.
	if UI.GetInterfaceMode() ~= InterfaceModeTypes.SELECTION then
		UI.SetInterfaceMode(InterfaceModeTypes.SELECTION);
	end


	-- Make sure if an active blocker is not set, to do one more check from the engine/authority.
	if optionalNewBlocker ~= nil then
		m_activeBlockerId = optionalNewBlocker;
	else
		m_activeBlockerId = NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
	end
	
	if m_activeBlockerId == EndTurnBlockingTypes.NO_ENDTURN_BLOCKING then
		if (CheckUnitsHaveMovesState()) then
			UI.SelectNextReadyUnit();
		elseif(CheckCityRangeAttackState()) then
			local attackCity = pPlayer:GetCities():GetFirstRangedAttackCity();
			if(attackCity ~= nil) then
				UI.SelectCity(attackCity);
			else
				error( "Unable to find selectable attack city while in CheckCityRangeAttackState()" );
			end
		else
			UI.RequestAction(ActionTypes.ACTION_ENDTURN);		
			UI.PlaySound("Stop_Unit_Movement_Master");
		end
	
	elseif (   m_activeBlockerId == EndTurnBlockingTypes.ENDTURN_BLOCKING_STACKED_UNITS
			or m_activeBlockerId == EndTurnBlockingTypes.ENDTURN_BLOCKING_UNIT_NEEDS_ORDERS
			or m_activeBlockerId == EndTurnBlockingTypes.ENDTURN_BLOCKING_UNITS)	then

		UI.SelectNextReadyUnit();

	else		

		-- generic turn blocker, trigger the notification associated with the turn blocker.
		local pNotification :table = NotificationManager.FindEndTurnBlocking(m_activeBlockerId, Game.GetLocalPlayer());
		
		if pNotification == nil then
			-- Notification is missing.  Use fallback behavior.
			if not UI.CanEndTurn() then
				print("ERROR: ActionPanel UI thinks that we can't end turn, but the notification system disagrees");
				return;
			end				
			UI.RequestAction(ActionTypes.ACTION_ENDTURN);		
			return;
		end

		-- Raise the event across the UI which may be listening for this particular notification.
		LuaEvents.ActionPanel_ActivateNotification( pNotification );
	end

end

-- ===========================================================================
--	UI Callback
-- ===========================================================================
function OnEndTurnClicked()
	DoEndTurn();
end

-- ===========================================================================
--	ENGINE Event
-- ===========================================================================
function OnEndTurnBlockingChanged( ePrevEndTurnBlockingType:number, eNewEndTurnBlockingType:number )
	
	local pPlayer :table = Players[Game.GetLocalPlayer()];
	if pPlayer ~= nil then
		if pPlayer:IsTurnActive() then

			local blockingType:number  = NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer()); 
			if (eNewEndTurnBlockingType ~= blockingType) then
				print("ERROR: ActionPanel received mismatched blocking types.  Event vs engine call: ",eNewEndTurnBlockingType, blockingType);
				return;
			end

			CheckAutoEndTurn( blockingType );
		
			-- If they have auto-unit-cycling off, then don't change the selection.
			if (not UserConfiguration.IsAutoUnitCycle()) then
				return;
			end
			
			local pSelectedUnit = UI.GetHeadSelectedUnit();
			if (pSelectedUnit ~= nil) then
				-- Just exit, the app side UI manager will cycle to the next unit if it is ready to do so.
				return;
			end

			-- Obtain first unit with moves remaining (that isn't automated or about to die).			
			local pUnit:table = pPlayer:GetUnits():GetFirstReadyUnit();
			if pUnit ~= nil then
				SelectUnit(pUnit);
			end
		end
	end
end
	

-- ===========================================================================
function SelectUnit(unit)
	UI.DeselectAllUnits();
	UI.DeselectAllCities();
	UI.SelectUnit( unit );
end


-- ===========================================================================
--	RIGHT CLICK
-- ===========================================================================
function OnEndTurnRightClicked()

	local pPlayer = Players[Game.GetLocalPlayer()];
	if (pPlayer == nil) then
		return;
	end;

	if not pPlayer:IsTurnActive() then
		print("Player's turn not active");
		return;
	end
end

-- ===========================================================================
function OnOverflowClick()
	Controls.OverflowContainer:SetSizeY( Controls.OverflowStack:GetSizeY() + 22 );
	if(Controls.OverflowCheckbox:IsChecked()) then
		Controls.EndTurnButtonLabel:SetHide(true);
		Controls.TurnBlockerContainerAlpha:SetHide(false);
		Controls.TurnBlockerContainerAlpha:SetToBeginning();
		Controls.TurnBlockerContainerSlide:SetToBeginning();
		Controls.TurnBlockerContainerAlpha:Play();
		Controls.TurnBlockerContainerSlide:Play();
	else
		Controls.EndTurnButtonLabel:SetHide(false);
		Controls.TurnBlockerContainerAlpha:Reverse();
		Controls.TurnBlockerContainerSlide:Reverse();
	end
end

function HideOverflowContainer()
	if (Controls.TurnBlockerContainerAlpha:IsReversing()) then 
		Controls.TurnBlockerContainerAlpha:SetHide(true);
	end
end
-- ===========================================================================
function SetEndTurnWaiting()
	-- The local player is waiting for their next turn.  Set the button state based on conditions.
	-- localized tooltip string for end turn button
	local endButtonTooltip : string = waitForPlayersTip; 
	local turnActiveHumanName : string = nil; -- player name of a human player who is turn active.
	local playersWaiting : number = 0;
	local iActivePlayer = Game.GetLocalPlayer();
		
	local players = Game.GetPlayers{Human = true, Major = true};
	for _, player in ipairs(players) do
		local iPlayer = player:GetID();
		if(iPlayer ~= iActivePlayer and player:IsTurnActive()) then
			local pPlayer:table = Players[iPlayer];
			local pPlayerConfig = PlayerConfigurations[iPlayer];
			if(pPlayerConfig ~= nil) then
				local playerName = Locale.Lookup(pPlayerConfig:GetPlayerName());

				if GameConfiguration.IsAnyMultiplayer() and pPlayer:IsHuman() then
					if(iPlayer ~= iActivePlayer and not Players[iActivePlayer]:GetDiplomacy():HasMet(iPlayer)) then
						endButtonTooltip = endButtonTooltip .. "[NEWLINE]" .. Locale.Lookup("LOC_DIPLOPANEL_UNMET_PLAYER") .. " (" .. playerName .. ")";
					else
						endButtonTooltip = endButtonTooltip .. "[NEWLINE]" .. Locale.Lookup(pPlayerConfig:GetCivilizationDescription()) .. " (" .. playerName .. ")";
					end
				else
					endButtonTooltip = endButtonTooltip .. "[NEWLINE]" .. "(" .. playerName .. ") ";
				end

				playersWaiting = playersWaiting + 1;

				-- Remember the name of the first turn active human we find so we can display it on
				-- the end turn button.
				if(turnActiveHumanName == nil) then
					turnActiveHumanName = playerName;
				end
			end
		end
	end	

	-- If players can unready their turn, indicate that in the tooltip. 
	local pLocalPlayer = Players[Game.GetLocalPlayer()];
	if (pLocalPlayer ~= nil and pLocalPlayer:CanUnreadyTurn()) then
		endButtonTooltip = endButtonTooltip .. "[NEWLINE]" .. canUnreadyTurnTip;
	end
		
	Controls.CurrentTurnBlockerIcon:SetHide(true);
	if(playersWaiting == 0) then
		-- Not waiting on other human players.  Just show "Please Wait".
		Controls.EndTurnText:LocalizeAndSetText( pleaseWaitString );
		endButtonTooltip = pleaseWaitTip;
	else
		-- Waiting on human players. 
		if(turnActiveHumanName ~= nil) then
			Controls.EndTurnText:LocalizeAndSetText(waitForPlayerTurnString, string.upper(turnActiveHumanName));
		else
			-- couldn't find the name of the player we're waiting on, use generic message
			Controls.EndTurnText:LocalizeAndSetText(waitForPlayersString);
		end
	end
	Controls.EndTurnButton:SetToolTipString( endButtonTooltip );
	
	SetEndTurnFlashing(NO_FLASHING);
end

-- ===========================================================================
function OnUnitOperationSegmentComplete( player:number, unitID:number, hCommand, iData1)
	-- When using "Units Have Moves" state, having a unit complete a unit operation segment 
	-- can affect your end turn state and auto end turns.
	if UnitsHaveMovesStateEnabled() and player == Game.GetLocalPlayer() then
		local pPlayer		:table = Players[Game.GetLocalPlayer()];
		local blockingType	:number= NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
		
		CheckAutoEndTurn( blockingType );
		ContextPtr:RequestRefresh();
	end
end

-- ===========================================================================
function OnUnitOperationsCleared( player:number, unitID:number, hCommand, iData1)
	-- When using "Units Have Moves" state, having a unit complete all their unit operations 
	-- can affect your end turn state and auto end turns.
	if UnitsHaveMovesStateEnabled() and player == Game.GetLocalPlayer() then
		local pPlayer		:table = Players[Game.GetLocalPlayer()];
		local blockingType	:number= NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
		CheckAutoEndTurn( blockingType );
		ContextPtr:RequestRefresh();
	end
end

-- ===========================================================================
function OnCityCommandStarted( cityOwnerID: number, cityID :number, districtOwnerID :number, districtID :number, commandType :number, iData1 :number )
	-- When the local player starts a city command (ranged attack), that might end the turn for them.
	if(cityOwnerID == Game.GetLocalPlayer() and HaveCityRangeAttackStateEnabled()) then
		local blockingType	:number= NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
		CheckAutoEndTurn( blockingType );
	end
end

-- ===========================================================================
function OnUserOptionChanged(eOptionSet, hOptionKey, iNewOptionValue)
	-- If we enabled AutoEndTurn, we need to check auto end turns because we might already be in a state where we should autoend the turn.
	if(hOptionKey == autoEndTurnOptionHash) then
		if(UserConfiguration.IsAutoEndTurn()) then
			local blockingType	:number= NotificationManager.GetFirstEndTurnBlocking(Game.GetLocalPlayer());
			CheckAutoEndTurn( blockingType );
		end
		ContextPtr:RequestRefresh();  --Refresh incase changing the autoendturn option results in a different end turn state.
	end
end

-- ===========================================================================
function OnLocalPlayerTurnBegin()
	-- Standard disable is set to false in the refresh.
	-- This extra level of input catching is done when tutorial has raised
	-- this boolean and will prevent spam clicking through; as the tutorial
	-- system itself sets ENABLED as it's hiding all controls.
	if m_isSlowTurnEnable then
		Controls.TutorialSlowTurnEnableAnim:SetHide(false);
		Controls.TutorialSlowTurnEnableAnim:SetToBeginning();
		Controls.TutorialSlowTurnEnableAnim:RegisterEndCallback(
			function()
				Controls.TutorialSlowTurnEnableAnim:SetHide(true);
			end
		);
		Controls.TutorialSlowTurnEnableAnim:Play();
	end

	ContextPtr:RequestRefresh();

    -- if auto-cycle is OFF, play this sound to indicate "start of turn"
    if (not UserConfiguration.IsAutoUnitCycle()) then
        UI.PlaySound("SP_Turn_Start");
    end
end

-- ===========================================================================
function OnLocalPlayerTurnEnd()
	
	-- Only disable if not in multi-player, so turns can "unend"...
	if not GameConfiguration.IsAnyMultiplayer() then
		Controls.EndTurnButton:SetDisabled(true);
		Controls.EndTurnButtonLabel:SetDisabled(true);
	end

	SetEndTurnWaiting();
	UI.SetInterfaceMode(InterfaceModeTypes.SELECTION);
	m_kSoundsPlayed = {};
end

-- ===========================================================================
function OnRemotePlayerTurnEnd()
	-- Refresh as the "Waiting for " player might have changed.
	ContextPtr:RequestRefresh();
end

-- ===========================================================================
function OnLocalPlayerChanged( iLocalPlayer:number , iPrevLocalPlayer:number )
	ContextPtr:RequestRefresh();
end

-- ===========================================================================
--	GAME Event
-- ===========================================================================
function OnCityProductionChanged( ePlayer:number, cityID:number )
	if ePlayer == Game.GetLocalPlayer() then
		ContextPtr:RequestRefresh();
	end
end

-- ===========================================================================
--	GAME Event
-- ===========================================================================
function OnEndTurnDirty()
	ContextPtr:RequestRefresh();
end

-- ===========================================================================
--	GAME Event
-- ===========================================================================
function OnNotificationAdded( playerID:number, notificationID:number )
	if playerID == Game.GetLocalPlayer() then
		local pNotification:table = NotificationManager.Find( playerID, notificationID );
		if pNotification == nil then
			-- It is possible, that by the time we get this event, the notification was 'expired' by some other action in the game.
			-- error( "Unable to find player ("..tostring(playerID).." notification ("..tostring(notificationID)..")" );
		end
	end
end

-- ===========================================================================
--	GAME Event
-- ===========================================================================
function OnNotificationDismissed( playerID:number, notificationID:number )
	if playerID == Game.GetLocalPlayer() then
		ContextPtr:RequestRefresh();		
	end
end

-- ===========================================================================
--	Game Event
--	Player just picked a research
-- ===========================================================================
function OnResearchChanged( ePlayer:number, eTech:number )	
	if ePlayer == Game.GetLocalPlayer() then
		ContextPtr:RequestRefresh();
	end
end

-- ===========================================================================
--	Game Engine Event
-- ===========================================================================
function OnInterfaceModeChanged(eOldMode:number, eNewMode:number)
	if eNewMode == InterfaceModeTypes.VIEW_MODAL_LENS then
		ContextPtr:SetHide(true);
	end
	if eOldMode == InterfaceModeTypes.VIEW_MODAL_LENS then
		ContextPtr:SetHide(false);
	end
end

-- ===========================================================================
--	UI Event
-- ===========================================================================
function OnInit( isReload:boolean )
	if isReload then	
		NotificationManager.RestoreVisualState(Game.GetLocalPlayer());	-- Restore the notifications
	end
	ContextPtr:RequestRefresh();
end

-- ===========================================================================
--	UI Event
-- ===========================================================================
function OnInputHandler( pInputStruct:table )
	local uiMsg:number = pInputStruct:GetMessageType();
	if pInputStruct:IsShiftDown() then
				UI.RequestAction(ActionTypes.ACTION_ENDTURN);	-- Shift + Enter = Force End Turn
			end
	if uiMsg == KeyEvents.KeyUp then 
		if pInputStruct:GetKey() == Keys.VK_RETURN then
			if pInputStruct:IsShiftDown() and (not UI.IsFinalRelease()) then
				UI.RequestAction(ActionTypes.ACTION_ENDTURN);	-- Shift + Enter = Force End Turn
			else				
				DoEndTurn();									-- Enter = Normal End Turn
			end
			return true;
		end
	end
	return false;
end


-- ===========================================================================
--	LUA Event
--	When autoplay first starts; kicked off from the Tuner.
-- ===========================================================================
function OnAutoPlayStart()
	Controls.EndTurnText:SetText( Locale.Lookup("LOC_ACTION_PANEL_AUTOPLAY_ACTIVE") );
	Controls.EndTurnButton:SetToolTipString( Locale.Lookup("LOC_ACTION_PANEL_AUTOPLAY_ACTIVE_TOOLTIP") );
end

-- ===========================================================================
--	LUA Event
--	When autoplay completes.
-- ===========================================================================
function OnAutoPlayEnd()
	ContextPtr:RequestRefresh();
end

-- ===========================================================================
--	LUA Event
--	An additional input shield to prevent click-spamming which, can 
--	potentially skip to the next item before the tutorial manager has sent
--	the event to it's handler.
-- ===========================================================================
function OnTutorialSlowTurnEnable( isEnabled:boolean )
	if isEnabled == nil then
		isEnabled = true;
	end
	m_isSlowTurnEnable = isEnabled;
end



-- ===========================================================================
--	Setup and allocate any User Interfaces.
-- ===========================================================================
function AllocateUI()
	-- When an animation finishes playing in reverse; hide it (and it's children)
	function OnAnimEnd( kControl:table ) 
		if kControl:IsReversing() then
			kControl:SetHide( true );
		end
	end
	Controls.TurnBlockerAlpha4:RegisterEndCallback( OnAnimEnd );
	Controls.TurnBlockerAlpha3:RegisterEndCallback( OnAnimEnd );
	Controls.TurnBlockerAlpha2:RegisterEndCallback( OnAnimEnd );	
end


-- ===========================================================================
--	Create a hash table of EraType to its chronological index.
-- ===========================================================================
function PopulateEraData()
	m_kEras = {};
	for row:table in GameInfo.Eras() do
		m_kEras[row.EraType] = { 
			Description	= Locale.Lookup(row.Name),
			Index		= row.ChronologyIndex,
		}
	end	
end

-- ===========================================================================
--	Update turn timer meter
-- ===========================================================================
function SoftRound(x)
	if(x >= 0) then
		return math.floor(x+0.5);
	else
		return math.ceil(x-0.5);
	end
end

function OnTurnTimerUpdated(elapsedTime :number, maxTurnTime :number)
	if(maxTurnTime <= 0) then
		-- We're in a state where there isn't a turn time, hide all the turn timer elements.
		Controls.TurnTimerContainer:SetHide(true);
	else
		local localPlayerID:number;
		if GameConfiguration.IsHotseat() then
			localPlayerID = Game.GetLocalPlayer();
		else
			localPlayerID = Network.GetLocalPlayerID();
		end
		local pPlayer = Players[localPlayerID];

		Controls.TurnTimerContainer:SetHide(false);

		-- Update turn timer bar progress
		local progress : number = 0;
		if(elapsedTime < maxTurnTime) then
			progress = 1 - (elapsedTime/maxTurnTime);
		end
		Controls.TurnTimerMeter:SetPercent(progress);

		local timeRemaining : number = maxTurnTime - elapsedTime;

		-- Update turn timer bar color
		if(pPlayer:IsTurnActive()) then
			Controls.TurnTimerMeter:SetColor(TURN_TIMER_BAR_ACTIVE_COLOR);
			Controls.TurnTimerLabelBG:SetToolTipString(yourTurnToolStr);
		else
			Controls.TurnTimerMeter:SetColor(TURN_TIMER_BAR_INACTIVE_COLOR);
			if(timeRemaining > 0) then
				Controls.TurnTimerLabelBG:SetToolTipString(estTilTurnToolStr);
			else
				Controls.TurnTimerLabelBG:SetToolTipString(estTimeElapsedToolStr);
			end
		end

		-- Update turn timer label 
		if(timeRemaining > 0) then
			-- Update countdown tick sound.
			-- Round the remaining time so the audio syncs up with the numerial countdown.
			local roundedTime = SoftRound(timeRemaining); 
			if( roundedTime <= START_TURN_TIMER_TICK_SOUND) then
				if(roundedTime > m_lastTurnTickTime -- last tick was for previous countdown
					or roundedTime <= (m_lastTurnTickTime-1)) then -- last tick was more than a 1 second ago.
					m_lastTurnTickTime = roundedTime;
					UI.PlaySound("Play_MP_Game_Launch_Timer_Beep");
				end
			end

			-- Format the time remaining string based on how much time we have left.
			-- We manually floor our values using floor and % operations to prevent the localization system 
			-- from rounding the values up.
			local secs = timeRemaining % 60;
			local mins = timeRemaining / 60;
			local hours = timeRemaining / 3600;
			local days = timeRemaining / 86400;
			if(days >= 1) then
				-- Days remaining
				days = math.floor(days);
				hours = hours % 24; -- cap hours
				if(pPlayer:IsTurnActive()) then
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_TIME_DAYS_HOURS", days, hours);
				else
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_EST_TIME_DAYS_HOURS", days, hours);
				end
			elseif(hours >= 1) then
				-- hours left
				hours = math.floor(hours);
				mins = mins % 60; -- cap mins
				if(pPlayer:IsTurnActive()) then
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_TIME_HOURS_MINUTES", hours, mins);
				else
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_EST_TIME_HOURS_MINUTES", hours, mins);
				end
			elseif(mins >= 1) then
				-- mins left
				mins = math.floor(mins);
				if(pPlayer:IsTurnActive()) then
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_TIME_MINS_SECONDS", mins, secs);
				else
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_EST_TIME_MINS_SECONDS", mins, secs);
				end
			else
				-- secs left
				if(pPlayer:IsTurnActive()) then
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_TIME_SECONDS", secs);
				else
					Controls.TurnTimerLabel:LocalizeAndSetText("LOC_KEY_EST_TIME_SECONDS", secs);
				end
			end
		else
			Controls.TurnTimerLabel:LocalizeAndSetText("-");
		end

		Controls.TurnTimerLabelBG:SetSizeX(Controls.TurnTimerLabel:GetSizeX() + 14 );
	end
end

-- ===========================================================================
--	Input Hotkey Event
-- ===========================================================================
function OnInputActionTriggered( actionId )
	if m_EndTurnId ~= nil and actionId == m_EndTurnId then
        UI.PlaySound("Play_UI_Click");
		OnEndTurnClicked();
	end
end


-- ===========================================================================
--	Initialize
-- ===========================================================================
function Initialize()

	AllocateUI();
	PopulateEraData();

	-- It is possible to start with automation already active, test for that
	if Automation.IsActive() then
		OnAutoPlayStart();
	end

	-- UI Events
	ContextPtr:SetInitHandler( OnInit );
	ContextPtr:SetInputHandler( OnInputHandler, true );
	ContextPtr:SetRefreshHandler( OnRefresh );	
	Controls.EndTurnButton:RegisterCallback(		Mouse.eLClick, OnEndTurnClicked );
	Controls.EndTurnButton:RegisterCallback(		Mouse.eRClick, OnEndTurnRightClicked );
	Controls.EndTurnButtonLabel:RegisterCallback(	Mouse.eLClick, OnEndTurnClicked );
	Controls.OverflowCheckbox:RegisterCallback(		Mouse.eLClick, OnOverflowClick);
	Controls.TurnBlockerContainerAlpha:RegisterEndCallback( HideOverflowContainer );

	-- Engine Events
	Events.CityCommandStarted.Add(			OnCityCommandStarted);
	Events.CityProductionChanged.Add(		OnCityProductionChanged );	
	Events.EndTurnBlockingChanged.Add(		OnEndTurnBlockingChanged );
	Events.EndTurnDirty.Add(				OnEndTurnDirty );
	Events.InputActionTriggered.Add(		OnInputActionTriggered );
	Events.InterfaceModeChanged.Add(		OnInterfaceModeChanged );
	Events.LocalPlayerChanged.Add(			OnLocalPlayerChanged );
	Events.LocalPlayerTurnBegin.Add(		OnLocalPlayerTurnBegin );
	Events.LocalPlayerTurnEnd.Add(			OnLocalPlayerTurnEnd );
	Events.RemotePlayerTurnEnd.Add(			OnRemotePlayerTurnEnd );
	Events.NotificationAdded.Add(			OnNotificationAdded );
	Events.NotificationDismissed.Add(		OnNotificationDismissed );
	Events.ResearchChanged.Add(				OnResearchChanged );	
	Events.TurnTimerUpdated.Add(			OnTurnTimerUpdated );
	Events.UnitOperationSegmentComplete.Add(OnUnitOperationSegmentComplete);
	Events.UnitOperationsCleared.Add(		OnUnitOperationsCleared);
	Events.UserOptionChanged.Add(			OnUserOptionChanged);	

	-- LUA Events
	LuaEvents.AutoPlayStart.Add(				OnAutoPlayStart );		-- Raised by engine AutoPlay_Manager!
	LuaEvents.AutoPlayEnd.Add(					OnAutoPlayEnd );		-- Raised by engine AutoPlay_Manager!	
	LuaEvents.Tutorial_SlowNextTurnEnable.Add(	OnTutorialSlowTurnEnable );

end
Initialize();
