-- ===========================================================================
--	CivicsTree
--	Tabs set to 4 spaces retaining tab.
--	
--	Items exist in one of 7 "rows" that span horizontally and within a 
--	"column" based on the era and cost.
--
--	Rows    Start   Eras->
--	-3               _____        _____  
--	-2            /-|_____|----/-|_____| 
--	-1            |  _____     |       Nodes 
--	0        O----%-|_____|----'        
--	1       
--	2
--	3
--
-- ===========================================================================
include( "InstanceManager" );	
include( "SupportFunctions" );
include( "Civ6Common" );			-- Tutorial check support
include( "TechAndCivicSupport" );
include( "TechFilterFunctions" );


-- ===========================================================================
--	DEBUG
--	Toggle these for temporary debugging help.
-- ===========================================================================
local m_debugFilterEraMaxIndex	:number = -1;		-- (-1 default) Only load up to a specific ERA (Value less than 1 to disable)
local m_debugOutputTechInfo		:boolean= false;	-- (false default) Send to console detailed information on tech? 
local m_debugShowIDWithName		:boolean= false;	-- (false default) Show the ID before the name in each node.
local m_debugShowAllMarkers		:boolean= false;	-- (false default) Show all player markers in the timline; even if they haven't been met.


-- ===========================================================================
--	CONSTANTS
-- ===========================================================================

-- Graphic constants
local PIC_BOLT_OFF				:string = "Controls_BoltOff";
local PIC_BOLT_ON				:string = "Controls_BoltOn";
local PIC_BOOST_OFF				:string = "BoostTech";
local PIC_BOOST_ON				:string = "BoostTechOn";
local PIC_DEFAULT_ERA_BACKGROUND:string = "CivicsTree_BGAncient";
local PIC_MARKER_PLAYER			:string = "Tree_TimePipPlayer";
local PIC_MARKER_OTHER			:string = "Controls_TimePip";
local PIC_METER_BACK			:string = "Tree_Meter_GearBack";
local PIC_METER_BACK_DONE		:string = "TechTree_Meter_Done";
local SIZE_ART_ERA_OFFSET_X		:number = 40;			-- How far to push each era marker
local SIZE_ART_ERA_START_X		:number = 40;			-- How far to set the first era marker
local SIZE_GOVTPANEL_HEIGHT     :number = 220;
local SIZE_MARKER_PLAYER_X		:number = 122;			-- Marker of player
local SIZE_MARKER_PLAYER_Y		:number = 42;			-- "
local SIZE_MARKER_OTHER_X		:number = 34;			-- Marker of other players
local SIZE_MARKER_OTHER_Y		:number = 37;			-- "
local SIZE_NODE_X				:number = 460;			-- Item node dimensions
local SIZE_NODE_Y				:number = 84;
local SIZE_NODE_LARGE_Y			:number = 140;				-- "
local SIZE_OPTIONS_X			:number = 200;
local SIZE_OPTIONS_Y			:number = 150;
local SIZE_PATH					:number = 40;
local SIZE_PATH_HALF			:number = 20;
local SIZE_TIMELINE_AREA_Y		:number = 41;
local SIZE_TOP_AREA_Y			:number = 60;
local SIZE_WIDESCREEN_HEIGHT	:number = 768;

local PATH_MARKER_OFFSET_X			:number = 20;
local PATH_MARKER_OFFSET_Y			:number = 50;
local PATH_MARKER_NUMBER_0_9_OFFSET	:number = 20;
local PATH_MARKER_NUMBER_10_OFFSET	:number = 15;

-- Other constants
local COLUMN_WIDTH				:number = 250;			-- Space of node and line(s) after it to the next node
local COLUMNS_NODES_SPAN		:number = 2;			-- How many colunms do the nodes span
local DATA_FIELD_GOVERNMENT     :string = "_GOVERNMENT"; --holds players govt and policies
local DATA_FIELD_LIVEDATA		:string = "_LIVEDATA";	-- The current status of an item.
local DATA_FIELD_PLAYERINFO		:string = "_PLAYERINFO";-- Holds a table with summary information on that player.
local DATA_FIELD_UIOPTIONS		:string = "_UIOPTIONS";	-- What options the player has selected for this screen.
local DATA_ICON_PREFIX			:string = "ICON_";
local DATA_ICON_UNAVAILABLE     :string = "_FOW";
local ITEM_STATUS				:table  = {
									BLOCKED		= 1,
									READY		= 2,
									CURRENT		= 3,
									RESEARCHED	= 4,
								};
local LINE_LENGTH_BEFORE_CURVE		:number = 20;			-- How long to make a line before a node before it curves
local LINE_VERTICAL_OFFSET			:number = 0;
local PADDING_TIMELINE_LEFT			:number = 250;
local PADDING_NODE_STACK_Y			:number = 20;
local PARALLAX_SPEED				:number = 1.1;			-- Speed for how much slower background moves (1.0=regular speed, 0.5=half speed)
local PARALLAX_ART_SPEED			:number = 1.2;			-- Speed for how much slower background moves (1.0=regular speed, 0.5=half speed)
local PREREQ_ID_TREE_START			:string = "_TREESTART";	-- Made up, unique value, to mark a non-node tree start
local ROW_MAX						:number = 3;			-- Highest level row above 0
local ROW_MIN						:number = -3;			-- Lowest level row below 0
local STATUS_ART					:table	= {};
local STATUS_ART_LARGE				:table	= {};
local TREE_START_ROW				:number = -999;			-- Which virtual "row" does tree start on? (or -999 for first node)
local TREE_START_COLUMN				:number = 0;			-- Which virtual "column" does tree start on? (Can be negative!)
local TREE_START_NONE_ID			:number = -999;			-- Special, unique value, to mark no special tree start node.
local TXT_BOOSTED					:string = Locale.Lookup("LOC_BOOST_BOOSTED");
local TXT_TO_BOOST					:string = Locale.Lookup("LOC_BOOST_TO_BOOST");
local VERTICAL_CENTER				:number = (SIZE_NODE_Y) / 2;
local MAX_BEFORE_TRUNC_GOV_TITLE	:number = 165;
local MAX_BEFORE_TRUNC_TO_BOOST		:number = 385;

STATUS_ART[ITEM_STATUS.BLOCKED]		= { Name="BLOCKED",		TextColor0=0xff202726, TextColor1=0x00000000, FillTexture="CivicsTree_GearButtonTile_Disabled.dds",BGU=0,BGV=(SIZE_NODE_Y*3),	HideIcon=true,  IsButton=false,	BoltOn=false,	IconBacking=PIC_METER_BACK };
STATUS_ART[ITEM_STATUS.READY]		= { Name="READY",		TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture=nil,									BGU=0,BGV=0,					HideIcon=true,  IsButton=true,	BoltOn=false,	IconBacking=PIC_METER_BACK  };
STATUS_ART[ITEM_STATUS.CURRENT]		= { Name="CURRENT",		TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture=nil,									BGU=0,BGV=(SIZE_NODE_Y*4),		HideIcon=false,  IsButton=false,	BoltOn=true,	IconBacking=PIC_METER_BACK };
STATUS_ART[ITEM_STATUS.RESEARCHED]	= { Name="RESEARCHED",	TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture="CivicsTree_GearButtonTile_Done.dds",	BGU=0,BGV=(SIZE_NODE_Y*5),		HideIcon=false,  IsButton=false,	BoltOn=true,	IconBacking=PIC_METER_BACK_DONE  };


STATUS_ART_LARGE[ITEM_STATUS.BLOCKED]		= { Name="LARGEBLOCKED",	TextColor0=0xff202726, TextColor1=0x00000000, FillTexture="CivicsTree_GearButton2Tile_Disabled.dds",BGU=0,BGV=(SIZE_NODE_LARGE_Y*3),	HideIcon=true,  IsButton=false,	BoltOn=false,	IconBacking=PIC_METER_BACK };
STATUS_ART_LARGE[ITEM_STATUS.READY]			= { Name="LARGEREADY",		TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture=nil,										BGU=0,BGV=0,						HideIcon=true,  IsButton=true,	BoltOn=false,	IconBacking=PIC_METER_BACK  };
STATUS_ART_LARGE[ITEM_STATUS.CURRENT]		= { Name="LARGECURRENT",	TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture=nil,										BGU=0,BGV=(SIZE_NODE_LARGE_Y*4),	HideIcon=false,  IsButton=false,	BoltOn=true,	IconBacking=PIC_METER_BACK };
STATUS_ART_LARGE[ITEM_STATUS.RESEARCHED]	= { Name="LARGERESEARCHED",	TextColor0=0xaaffffff, TextColor1=0x88000000, FillTexture="CivicsTree_GearButton2Tile_Completed.dds",	BGU=0,BGV=(SIZE_NODE_LARGE_Y*5),HideIcon=false, 	IsButton=false,	BoltOn=true,	IconBacking=PIC_METER_BACK_DONE  };


-- ===========================================================================
--	MEMBERS / VARIABLES
-- ===========================================================================
local m_kNodeIM				:table = InstanceManager:new( "NodeInstance", 			"Top", 		Controls.NodeScroller );
local m_kLargeNodeIM		:table = InstanceManager:new( "LargeNodeInstance", 		"Top", 		Controls.NodeScroller );
local m_kLineIM				:table = InstanceManager:new( "LineImageInstance", 		"LineImage",Controls.NodeScroller );
local m_kEraArtIM			:table = InstanceManager:new( "EraArtInstance", 		"Top", 		Controls.FarBackArtScroller );
local m_kEraLabelIM			:table = InstanceManager:new( "EraLabelInstance", 		"Top", 		Controls.ArtScroller );
local m_kEraDotIM			:table = InstanceManager:new( "EraDotInstance",			"Dot", 		Controls.ScrollbarBackgroundArt );
local m_kMarkerIM			:table = InstanceManager:new( "PlayerMarkerInstance",	"Top",		Controls.TimelineScrollbar );
local m_kSearchResultIM		:table = InstanceManager:new( "SearchResultInstance",   "Root",     Controls.SearchResultsStack);

local m_kDiplomaticPolicyIM	:table = InstanceManager:new( "DiplomaticPolicyInstance", 	"DiplomaticPolicy", 	Controls.DiplomaticStack );
local m_kEconomicPolicyIM	:table = InstanceManager:new( "EconomicPolicyInstance", 	"EconomicPolicy", 	Controls.EconomicStack );
local m_kMilitaryPolicyIM	:table = InstanceManager:new( "MilitaryPolicyInstance", 	"MilitaryPolicy", 	Controls.MilitaryStack );
local m_kReligiousPolicyIM	:table = InstanceManager:new( "ReligiousPolicyInstance", 	"ReligiousPolicy", 	Controls.ReligiousStack ); -- JFD Religious
local m_kWildcardPolicyIM	:table = InstanceManager:new( "WildcardPolicyInstance", 	"WildcardPolicy", 	Controls.WildcardStack );
local m_kPathMarkerIM		:table = InstanceManager:new( "ResearchPathMarker",			"Top",		Controls.NodeScroller);

local m_kUnlocksIM			:table = {};

local m_width				:number= 1024;		-- Screen Width (default / min spec)
local m_height				:number= 768;		-- Screen Height (default / min spec)
local m_scrollWidth			:number= 1024;		-- Width of the scroll bar (default to screen min_spec until set)
local m_kEras				:table = {};		-- type to costs
local m_maxColumns			:number= 0;			-- # of columns (highest column #)
local m_ePlayer				:number= -1;
local m_kAllPlayersTechData	:table = {};		-- All data for local players.
local m_kCurrentData		:table = {};		-- Current set of data.
local m_kItemDefaults		:table = {};		-- Static data about items
local m_kNodeGrid			:table = {};		-- Static data about node location once it's laid out
local m_uiNodes				:table = {};
local m_uiConnectorSets		:table = {};
local m_kFilters			:table = {};
local m_ToggleCivicsTreeId		   = Input.GetActionId("ToggleCivicsTree");		-- Hot key to toggle screen open/close.
local m_kGovernments		:table = {};
local m_kPolicyCatalogData	:table;

local m_shiftDown			:boolean = false;


-- ===========================================================================
-- Return string respresenation of a prereq table
-- ===========================================================================
function GetPrereqsString( prereqs:table )
	local out:string = "";
	for _,prereq in pairs(prereqs) do
		if prereq == PREREQ_ID_TREE_START then
			out = "n/a ";
		else
			out = out .. m_kItemDefaults[prereq].Type .. " ";	-- Add space between techs
		end
	end
	return "[" .. string.sub(out,1,string.len(out)-1) .. "]";	-- Remove trailing space
end

-- ===========================================================================
function SetCurrentNode( hash )
	if hash ~= nil then
		local localPlayerCulture = Players[Game.GetLocalPlayer()]:GetCulture();
		-- Get the complete path to the tech
		local pathToCivic = localPlayerCulture:GetCivicPath( hash );
		local tParameters = {};
		tParameters[PlayerOperations.PARAM_CIVIC_TYPE]	= pathToCivic;
		if m_shiftDown then
			tParameters[PlayerOperations.PARAM_INSERT_MODE] = PlayerOperations.VALUE_APPEND;
		else
			tParameters[PlayerOperations.PARAM_INSERT_MODE] = PlayerOperations.VALUE_EXCLUSIVE;
		end
		UI.RequestPlayerOperation(Game.GetLocalPlayer(), PlayerOperations.PROGRESS_CIVIC, tParameters);
        UI.PlaySound("Confirm_Civic_CivicsTree");
	else
		UI.DataError("Attempt to change current tree item with NIL hash!");
	end

end

-- ===========================================================================
--	If the next item isn't immediate, show a path of #s traversing the tree 
--	to the desired node.
-- ===========================================================================
function RealizePathMarkers()
	
	local pCulture	:table = Players[Game.GetLocalPlayer()]:GetCulture();
	local kNodeIds	:table = pCulture:GetCivicQueue();		-- table: index, IDs
	
	m_kPathMarkerIM:ResetInstances();

	for i,nodeNumber in pairs(kNodeIds) do
		local pathPin = m_kPathMarkerIM:GetInstance();

		if(i < 10) then
			pathPin.NodeNumber:SetOffsetX(PATH_MARKER_NUMBER_0_9_OFFSET);
		else
			pathPin.NodeNumber:SetOffsetX(PATH_MARKER_NUMBER_10_OFFSET);
		end
		pathPin.NodeNumber:SetText(tostring(i));
		for j,node in pairs(m_kItemDefaults) do
			if node.Index == nodeNumber then
				local x:number = m_uiNodes[node.Type].x;
				local y:number = m_uiNodes[node.Type].y;
				pathPin.Top:SetOffsetX(x-PATH_MARKER_OFFSET_X);
				pathPin.Top:SetOffsetY(y-PATH_MARKER_OFFSET_Y);
			end
		end
	end
end

-- ===========================================================================
--	Convert a virtual column # and row # to actual pixels within the
--	scrollable tree area.
-- ===========================================================================
function ColumnRowToPixelXY( column:number, row:number)	
	local horizontal		:number = ((column-1) * COLUMNS_NODES_SPAN * COLUMN_WIDTH) + PADDING_TIMELINE_LEFT;
	local vertical			:number = PADDING_NODE_STACK_Y + (SIZE_WIDESCREEN_HEIGHT / 2) + (row * SIZE_NODE_Y);
	return horizontal, vertical;
end

-- ===========================================================================
--	Get the width of the scroll panel
-- ===========================================================================
function GetMaxScrollWidth()
	return m_maxColumns + (m_maxColumns * COLUMN_WIDTH) + PADDING_TIMELINE_LEFT;
end


-- ===========================================================================
--	Take the default item data and build the nodes that work with it.
--	One time creation, any dynamic pieces should be 
--
--	No state specific data (e.g., selected node) should be set here in order
--	to reuse the nodes across viewing other players' trees for single seat
--	multiplayer or if a (spy) game rule allows looking at another's tree.
-- ===========================================================================
function AllocateUI()	

	m_uiNodes = {};
	m_kNodeIM:ResetInstances();
	m_kLargeNodeIM:ResetInstances();

	m_uiConnectorSets = {};
	m_kLineIM:ResetInstances();

	-- By era, layout all items in single "sort" row
	local eraGrids:table = {};					-- each era has it's own "grid" of items to layout
	for _,item in pairs(m_kItemDefaults) do		

		-- Create any data structures related to this era/row that don't exist:
		if eraGrids[item.EraType] == nil then
			eraGrids[item.EraType] = { rows={}, sortRow={ columns={} } };
			for i= ROW_MIN, ROW_MAX, 1 do
				eraGrids[item.EraType].rows[i] = { columns={} };
			end			
		end

		-- For first placement, ignore row and place everything in the middle (row 0)
		local pos:number = table.count(eraGrids[item.EraType].sortRow.columns) + 1;
		eraGrids[item.EraType].sortRow.columns[pos] = item.Type;
	end

	-- Manually sort based on prereqs, 2(N log N)
	for eraType,grid in pairs(eraGrids) do		
		local numEraItems:number = table.count(grid.sortRow.columns);
		if numEraItems > 1 then			
			for pass=1,2,1 do					-- Make 2 passes so the first swapped item is checked.
				for a=1,numEraItems do
					for b=a,numEraItems do
						if a ~= b then
							for _,prereq in ipairs(m_kItemDefaults[grid.sortRow.columns[a] ].Prereqs) do
								if prereq == grid.sortRow.columns[b] then
									grid.sortRow.columns[a], grid.sortRow.columns[b] = grid.sortRow.columns[b], grid.sortRow.columns[a];	-- swap LUA style
								end
							end					
						end
					end
				end
			end
		end
	end
	
	-- Unflatten single, traversing each era grid from left to right, pushing the item to the right if it hits a prereq
	for eraType,grid in pairs(eraGrids) do
		local maxColumns:number = table.count(grid.sortRow.columns);	-- Worst case, straight line
		while ( table.count(grid.sortRow.columns) > 0 ) do

			local typeName	:string = table.remove( grid.sortRow.columns, 1);				-- Rip off first item in sort row (next item to place)
			local item		:table	= m_kItemDefaults[typeName];
			local pos		:number = 1;
			
			-- No prereqs? Just put at start, otherwise push forward past them all.
			if item.Prereqs ~= nil then
				for _,prereq in ipairs(item.Prereqs) do							-- For every prereq					
					local isPrereqMatched :boolean = false;					
					for x=pos,maxColumns,1 do									-- For every column (from last highest found start position)
						for y=ROW_MIN, ROW_MAX,1 do								-- For every row in the column
							if grid.rows[y].columns[x] ~= nil then				-- Is a prereq in that position of the grid?
								if prereq == grid.rows[y].columns[x] then		-- And is it a prereq for this item?
									pos = x + 1;								-- If so, this item can only exist at least 1 column further over from the prereq
									isPrereqMatched = true;
									break;
								end
							end
						end
						if isPrereqMatched then 							
							-- Ensuring this node wasn't just placed on top of another:
							while( grid.rows[item.UITreeRow].columns[pos] ~= nil ) do
								pos = pos + 1;
							end
							break;
						end
					end
				end
			end
						
			grid.rows[item.UITreeRow].columns[pos] = typeName;	-- Set for future lookups.
			item.Column = pos;									-- Set which column within era item exists.

			if pos > m_kEras[item.EraType].NumColumns then
				m_kEras[item.EraType].NumColumns = pos;
			end
		end	
	end

	-- Determine total # of columns prior to a given era, and max columns overall.
	local priorColumns:number = 0;
	m_maxColumns = 0;
	for i=1,table.count(m_kEras),1 do
		for era,eraData in pairs(m_kEras) do
			if eraData.Index == i then									-- Ensure indexed order
				eraData.PriorColumns = priorColumns;
				priorColumns = priorColumns + eraData.NumColumns + 1;	-- Add one for era art between
				if i==table.count(m_kEras) then							-- Last era determins total # of columns in tree
					m_maxColumns = priorColumns;
				end
				break;
			end
		end
	end
	

	-- Create grid used to route lines, determine maximum number of columns.
	m_kNodeGrid	 = {};
	for i = ROW_MIN,ROW_MAX,1 do
		m_kNodeGrid[i] = {};
	end
	for _,item in pairs(m_kItemDefaults) do	
		local era		:table  = m_kEras[item.EraType];
		local columnNum :number = era.PriorColumns + item.Column;
		m_kNodeGrid[item.UITreeRow][columnNum] = true;
	end

	-- Era divider information
	m_kEraArtIM:ResetInstances();
	m_kEraLabelIM:ResetInstances();
	m_kEraDotIM:ResetInstances();

	for era,eraData in pairs(m_kEras) do		
		
		local instArt :table = m_kEraArtIM:GetInstance();
		if eraData.BGTexture ~= nil then
			instArt.BG:SetTexture( eraData.BGTexture );				
		else
			UI.DataError("Civic tree is unable to find an EraCivicBackgroundTexture entry for era '"..eraData.Description.."'; using a default.");
			instArt.BG:SetTexture(PIC_DEFAULT_ERA_BACKGROUND);
		end

		local startx, _	= ColumnRowToPixelXY( eraData.PriorColumns + 1, 0);
		instArt.Top:SetOffsetX( (startx ) * (1/PARALLAX_ART_SPEED) );
		instArt.Top:SetOffsetY( (SIZE_WIDESCREEN_HEIGHT * 0.5) - (instArt.BG:GetSizeY()*0.5) );	
		instArt.Top:SetSizeVal(eraData.NumColumns*SIZE_NODE_X, 600);

		local inst:table = m_kEraLabelIM:GetInstance();
		local eraMarkerx, _	= ColumnRowToPixelXY( eraData.PriorColumns + 1, 0);
		inst.Top:SetOffsetX( (eraMarkerx - (SIZE_NODE_X*0.5)) * (1/PARALLAX_SPEED) );
		inst.EraTitle:SetText( Locale.Lookup("LOC_GAME_ERA_DESC",eraData.Description) );

		-- Dots on scrollbar
		local markerx:number = (eraData.PriorColumns / m_maxColumns) * Controls.ScrollbarBackgroundArt:GetSizeX();
		if markerx > 0 then
			local inst:table = m_kEraDotIM:GetInstance();
			inst.Dot:SetOffsetX(markerx);	
		end	
	end
	
	local playerId = Game.GetLocalPlayer();
	if (playerId == -1) then
		return;
	end

	-- Actually build UI nodes
	for _,item in pairs(m_kItemDefaults) do
		local civic		= GameInfo.Civics[item.Index];
		local civicType = civic and civic.CivicType;

		local unlockableTypes			= GetUnlockablesForCivic_Cached(civicType, playerId);
		local node				:table;
		local numUnlocks		:number = 0;

		if unlockableTypes ~= nil then
			for _, unlockItem in ipairs(unlockableTypes) do
				if not(unlockItem.Icon) then
					local typeInfo = GameInfo.Types[unlockItem[1]];

					if typeInfo.Kind == "KIND_GOVERNMENT" then
						numUnlocks = numUnlocks + 5;				-- 5 types of policy slots (JFD Religious)
					else
						numUnlocks = numUnlocks + 1;
					end
				else
					numUnlocks = numUnlocks + 1;
				end
			end
		end
			
		-- Create node based on # of unlocks for this civic.	
		if numUnlocks <= 8 then
			node = m_kNodeIM:GetInstance();
		else
			node = m_kLargeNodeIM:GetInstance();
			node.IsLarge = true;
		end
		node.Top:SetTag( item.Hash );	-- Set the hash of the civic to the tag of the node (for tutorial to be able to callout)
		
		local era:table = m_kEras[item.EraType];

		-- Horizontal # = All prior nodes across all previous eras + node position in current era (based on cost vs. other nodes in that era)
		local horizontal, vertical = ColumnRowToPixelXY(era.PriorColumns + item.Column, item.UITreeRow );

		-- Add data fields to UI component
		node.Type	= civicType;	-- Dynamically add "Type" field to UI node for quick look ups in item data table.
		node.x		= horizontal;	-- Granted x,y can be looked up via GetOffset() but caching the values here for
		node.y		= vertical - VERTICAL_CENTER;		-- other LUA functions to use removes the necessity of a slow C++ roundtrip.

		if node["unlockIM"] ~= nil then
			node["unlockIM"]:DestroyInstances()
		end
		node["unlockIM"] = InstanceManager:new( "UnlockInstance", "UnlockIcon", node.UnlockStack );
		
		if node["unlockGOV"] ~= nil then
			node["unlockGOV"]:DestroyInstances()
		end
		node["unlockGOV"] = InstanceManager:new( "GovernmentIcon", "GovernmentInstanceGrid", node.UnlockStack );

		PopulateUnlockablesForCivic(playerId, item.Index, node["unlockIM"], node["unlockGOV"], function() SetCurrentNode(item.Hash); end);

		-- What happens when clicked
		function OpenPedia()	
			LuaEvents.OpenCivilopedia(civicType); 
		end

		node.NodeButton:RegisterCallback( Mouse.eLClick, function() SetCurrentNode(item.Hash); end);		
		node.OtherStates:RegisterCallback( Mouse.eLClick, function() SetCurrentNode(item.Hash); end);
		
		-- Only wire up Civilopedia handlers if not in a on-rails tutorial.
		if IsTutorialRunning()==false then
			node.NodeButton:RegisterCallback( Mouse.eRClick, OpenPedia);
			node.OtherStates:RegisterCallback( Mouse.eRClick, OpenPedia);
		end

		-- Set position and save.		
		node.Top:SetOffsetVal( horizontal, vertical);
		m_uiNodes[item.Type] = node;
	end

	if Controls.TreeStart ~= nil then
		local h,v = ColumnRowToPixelXY( TREE_START_COLUMN, TREE_START_ROW );
		Controls.TreeStart:SetOffsetVal( h,v );
	end

	-- Determine the lines between nodes.
	-- NOTE: Potentially move this to view, since lines are constantly change in look, but
	--		 it makes sense to have at least the routes computed here since they are
	--		 consistent regardless of the look.
	local spaceBetweenColumns:number = COLUMN_WIDTH - SIZE_NODE_X;
	for _,item in pairs(m_kItemDefaults) do
		
		local node:table = m_uiNodes[item.Type];		

		for _,prereqId in pairs(item.Prereqs) do
			
			local previousRow	:number = 0;
			local previousColumn:number = 0;
			if prereqId == PREREQ_ID_TREE_START then
				previousRow		= TREE_START_ROW;
				previousColumn	= TREE_START_COLUMN;
			else
				local prereq :table = m_kItemDefaults[prereqId];
				previousRow		= prereq.UITreeRow;
				previousColumn	= m_kEras[prereq.EraType].PriorColumns + prereq.Column;
			end
			
			local startColumn	:number = m_kEras[item.EraType].PriorColumns + item.Column;				
			local column		:number = startColumn - 1;										-- start one back
			while m_kNodeGrid[item.UITreeRow][column] == nil and column > previousColumn do		-- keep working backwards until hitting a node
				column = column - 1;
			end


			if previousRow == TREE_START_NONE_ID then

				-- Nothing goes before this, not even a fake start area.

			elseif previousRow < item.UITreeRow or previousRow > item.UITreeRow  then				
				
				-- Obtain the line objects
				local inst	:table = m_kLineIM:GetInstance();
				local line1	:table = inst.LineImage; inst = m_kLineIM:GetInstance();
				local line2	:table = inst.LineImage; inst = m_kLineIM:GetInstance();
				local line3	:table = inst.LineImage; inst = m_kLineIM:GetInstance();
				local line4	:table = inst.LineImage; inst = m_kLineIM:GetInstance();
				local line5	:table = inst.LineImage;

				-- Find all the empty space before the node before to make a bend.
				local LineStartX:number = node.x;
				local LineEndX1	:number = (node.x - LINE_LENGTH_BEFORE_CURVE ) ;
				local LineEndX2, _ = ColumnRowToPixelXY( column, item.UITreeRow );
				LineEndX2 = LineEndX2 + SIZE_NODE_X;

				local LineY1	:number;
				local LineY2	:number;
				if previousRow < item.UITreeRow  then
					LineY2 = node.y-((item.UITreeRow-previousRow)*SIZE_NODE_Y)+ LINE_VERTICAL_OFFSET;-- above
					LineY1 = node.y -LINE_VERTICAL_OFFSET;
				else
					LineY2 = node.y+((previousRow-item.UITreeRow)*SIZE_NODE_Y)- LINE_VERTICAL_OFFSET;-- below
					LineY1 = node.y +LINE_VERTICAL_OFFSET;
				end
				
				line1:SetOffsetVal(LineEndX1 + SIZE_PATH_HALF, LineY1 - SIZE_PATH_HALF);				
				line1:SetSizeVal( LineStartX - LineEndX1 - SIZE_PATH_HALF, SIZE_PATH);
				
				line2:SetOffsetVal(LineEndX1 - SIZE_PATH_HALF, LineY1 - SIZE_PATH_HALF);
				line2:SetSizeVal( SIZE_PATH, SIZE_PATH);
				if previousRow < item.UITreeRow  then
					line2:SetTexture("Controls_TreePathDashSE");
				else
					line2:SetTexture("Controls_TreePathDashNE");
				end

				line3:SetOffsetVal(LineEndX1 - SIZE_PATH_HALF, math.min(LineY1 + SIZE_PATH_HALF, LineY2 + SIZE_PATH_HALF) );	
				line3:SetSizeVal( SIZE_PATH, math.abs(LineY1 - LineY2) - SIZE_PATH );
				line3:SetTexture("Controls_TreePathDashNS");
				
				line4:SetOffsetVal(LineEndX1 - SIZE_PATH_HALF, LineY2 - SIZE_PATH_HALF);
				line4:SetSizeVal( SIZE_PATH, SIZE_PATH);
				if previousRow < item.UITreeRow  then
					line4:SetTexture("Controls_TreePathDashES");
				else
					line4:SetTexture("Controls_TreePathDashEN");
				end	

				line5:SetSizeVal(  LineEndX1 - LineEndX2 - SIZE_PATH_HALF, SIZE_PATH );
				line5:SetOffsetVal(LineEndX2, LineY2 - SIZE_PATH_HALF);	

				-- Directly store the line (not instance) with a key name made up of this type and the prereq's type.
				m_uiConnectorSets[item.Type..","..prereqId] = {line1,line2,line3,line4,line5};

			else
				-- Prereq is on the same row
				local inst:table = m_kLineIM:GetInstance();
				local line:table = inst.LineImage;
				local end1, _ = ColumnRowToPixelXY( column, item.UITreeRow );
				end1 = end1 + SIZE_NODE_X;

				line:SetOffsetVal(end1, node.y - SIZE_PATH_HALF);				
				line:SetSizeVal( node.x - end1, SIZE_PATH);	

				-- Directly store the line (not instance) with a key name made up of this type and the prereq's type.
				m_uiConnectorSets[item.Type..","..prereqId] = {line};
			end				
		end
	end

	Controls.NodeScroller:CalculateSize();
	Controls.NodeScroller:ReprocessAnchoring();
	Controls.ArtScroller:CalculateSize();
	Controls.FarBackArtScroller:CalculateSize();

	Controls.NodeScroller:RegisterScrollCallback( OnScroll );

	-- We use a separate BG within the PeopleScroller control since it needs to scroll with the contents
	Controls.ModalBG:SetHide(true);
	Controls.ModalScreenClose:RegisterCallback(Mouse.eLClick, OnClose);
	Controls.ModalScreenTitle:SetText(Locale.ToUpper(Locale.Lookup("LOC_CIVICS_TREE_HEADER")));
end

-- ===========================================================================
--	UI Event
--	Callback when the main scroll panel is scrolled.
-- ===========================================================================
function OnScroll( control:table, percent:number )
	
	-- Parallax 
	-- ??TRON re-examine:
	--Controls.ArtScroller:SetScrollValue( percent * PARALLAX_SPEED);
	--Controls.FarBackArtScroller:SetScrollValue( percent * PARALLAX_ART_SPEED);
	Controls.ArtScroller:SetScrollValue( percent );
	Controls.FarBackArtScroller:SetScrollValue( percent );
	
	-- Audio
	if percent==0 or percent==1.0 then 
		UI.PlaySound("UI_TechTree_ScrollTick_End"); 
	else 
		UI.PlaySound("UI_TechTree_ScrollTick"); 
	end 
end


-- ===========================================================================
--	Display the state of the tree (filter, node display, etc...) based on the 
--	active player's item data. 
--	Viewxx
-- ===========================================================================
function View( playerTechData:table )

	-- Output the node states for the tree
	for _,node in pairs(m_uiNodes) do
		local item		:table = m_kItemDefaults[node.Type];						-- static item data
		local live		:table = playerTechData[DATA_FIELD_LIVEDATA][node.Type];	-- live (changing) data
		local artInfo	:table = (node.IsLarge) and STATUS_ART_LARGE[live.Status] or STATUS_ART[live.Status];

		if(live.Status == ITEM_STATUS.RESEARCHED) then
			for _,prereqId in pairs(item.Prereqs) do
				if(prereqId ~= PREREQ_ID_TREE_START) then
					local prereq		:table = m_kItemDefaults[prereqId];
					local previousRow	:number = prereq.UITreeRow;
					local previousColumn:number = m_kEras[prereq.EraType].PriorColumns;

					for lineNum,line in pairs(m_uiConnectorSets[item.Type..","..prereqId]) do
						if(lineNum == 1 or lineNum == 5) then
							line:SetTexture("Controls_TreePathEW");
						end
						if( lineNum == 3) then
							line:SetTexture("Controls_TreePathNS");
						end

						if(lineNum==2)then
							if previousRow < item.UITreeRow  then
								line:SetTexture("Controls_TreePathSE");
							else
								line:SetTexture("Controls_TreePathNE");
							end
						end

						if(lineNum==4)then
							if previousRow < item.UITreeRow  then
								line:SetTexture("Controls_TreePathES");
							else
								line:SetTexture("Controls_TreePathEN");
							end
						end
					end
				end
			end
		end

		node.NodeName:SetColor( artInfo.TextColor0, 0 );
		node.NodeName:SetColor( artInfo.TextColor1, 1 );
		if m_debugShowIDWithName then
			node.NodeName:SetText( tostring(item.Index).."  "..Locale.Lookup(item.Name) );	-- Debug output
		else
			node.NodeName:SetText( Locale.ToUpper( Locale.Lookup(item.Name) ));				-- Normal output
		end
				
		if live.Turns > 0 then 
			node.Turns:SetHide( false );
			node.Turns:SetColor( artInfo.TextColor0, 0 );
			node.Turns:SetColor( artInfo.TextColor1, 1 );
			node.Turns:SetText( Locale.Lookup("LOC_TECH_TREE_TURNS",live.Turns) );
		else
			node.Turns:SetHide( true );
		end

		if item.IsBoostable and live.Status ~= ITEM_STATUS.RESEARCHED then			
			node.BoostIcon:SetHide( false );
			node.BoostText:SetHide( false );
			node.BoostText:SetColor( artInfo.TextColor0, 0 );
			node.BoostText:SetColor( artInfo.TextColor1, 1 );

			local boostText:string;
			if live.IsBoosted then
				boostText = TXT_BOOSTED.." "..item.BoostText;
				node.BoostIcon:SetTexture( PIC_BOOST_ON );
				node.BoostMeter:SetHide( false );
			else
				boostText = TXT_TO_BOOST.." "..item.BoostText;
				node.BoostedBack:SetHide( true );
				node.BoostIcon:SetTexture( PIC_BOOST_OFF );
				node.BoostMeter:SetHide( false );
				local boostAmount = (item.BoostAmount*.01) + (live.Progress/ live.Cost);
				node.BoostMeter:SetPercent( boostAmount );
			end
			TruncateStringWithTooltip(node.BoostText, MAX_BEFORE_TRUNC_TO_BOOST, boostText); 
		else
			node.BoostIcon:SetHide( true );
			node.BoostText:SetHide( true );
			node.BoostedBack:SetHide( true );
			node.BoostMeter:SetHide( true );
		end
		
		if live.Status == ITEM_STATUS.CURRENT then
			node.GearAnim:SetHide( false );
		else
			node.GearAnim:SetHide( true );
		end

		if live.Progress > 0 then
			node.ProgressMeter:SetHide( false );			
			node.ProgressMeter:SetPercent(live.Progress / live.Cost);
		else
			node.ProgressMeter:SetHide( true );			
		end

		-- Set art for icon area
		if(item.Type ~= nil) then
			local iconName :string = DATA_ICON_PREFIX .. item.Type;
			if (artInfo.Name == "BLOCKED" or artInfo.Name == "LARGEBLOCKED") then
				node.IconBacking:SetHide(true);
				iconName = iconName .. "_FOW";
				node.BoostMeter:SetColor(0x66ffffff);
				node.BoostIcon:SetColor(0x66000000);
			else
				node.IconBacking:SetHide(false);
				iconName = iconName;
				node.BoostMeter:SetColor(0xffffffff);
				node.BoostIcon:SetColor(0xffffffff);
			end
			local textureOffsetX, textureOffsetY, textureSheet = IconManager:FindIconAtlas(iconName,42);
			if (textureOffsetX ~= nil) then
				node.Icon:SetTexture( textureOffsetX, textureOffsetY, textureSheet );
			end
		end

		if artInfo.IsButton then
			node.OtherStates:SetHide( true );
			node.NodeButton:SetTextureOffsetVal( artInfo.BGU, artInfo.BGV );
		else
			node.OtherStates:SetHide( false );
			node.OtherStates:SetTextureOffsetVal( artInfo.BGU, artInfo.BGV );
		end

		if artInfo.FillTexture ~= nil then
			node.FillTexture:SetHide( false );
			node.FillTexture:SetTexture( artInfo.FillTexture );
		else
			node.FillTexture:SetHide( true );
		end

		if artInfo.BoltOn then
			node.Bolt:SetTexture(PIC_BOLT_ON);
		else
			node.Bolt:SetTexture(PIC_BOLT_OFF);
		end

		node.NodeButton:SetToolTipString(ToolTipHelper.GetToolTip(item.Type, Game.GetLocalPlayer()));
		node.IconBacking:SetTexture(artInfo.IconBacking);

		-- Darken items not making it past filter.
		local currentFilter:table = playerTechData[DATA_FIELD_UIOPTIONS].filter;
		if currentFilter == nil or currentFilter.Func == nil or currentFilter.Func( item.Type ) then
			node.FilteredOut:SetHide( true );
		else
			node.FilteredOut:SetHide( false );
		end
	end

	-- Fill in where the markers (representing players) are at:
	m_kMarkerIM:ResetInstances();
	local PADDING		:number = 24;
	local thisPlayerID	:number = Game.GetLocalPlayer();
	local markers		:table	= m_kCurrentData[DATA_FIELD_PLAYERINFO].Markers;
	for _,markerStat in ipairs( markers ) do

		-- Only build a marker if a player has started researching...
		if markerStat.HighestColumn ~= -1 then
			local instance	:table	= m_kMarkerIM:GetInstance();

			if markerStat.IsPlayerHere then
				-- Representing the player viewing the tree			
				instance.Portrait:SetHide( true );
				instance.TurnGrid:SetHide( false );
				instance.TurnLabel:SetText( Locale.Lookup("LOC_TECH_TREE_TURN_NUM" ));
				instance.TurnNumber:SetText( tostring(Game.GetCurrentGameTurn()) );
				local turnLabelWidth = PADDING + instance.TurnLabel:GetSizeX() +  instance.TurnNumber:GetSizeX(); 
				instance.TurnGrid:SetSizeX( turnLabelWidth );
				instance.Marker:SetTexture( PIC_MARKER_PLAYER );
				instance.Marker:SetSizeVal( SIZE_MARKER_PLAYER_X, SIZE_MARKER_PLAYER_Y );
			else
				-- An other player				
				instance.TurnGrid:SetHide( true );
				instance.Marker:SetTexture( PIC_MARKER_OTHER );
				instance.Marker:SetSizeVal( SIZE_MARKER_OTHER_X, SIZE_MARKER_OTHER_Y );
			end

			-- Different content in marker based on if there is just 1 player in the column, or more than 1
			local tooltipString				:string = Locale.Lookup("LOC_TREE_ERA", Locale.Lookup(GameInfo.Eras[markerStat.HighestEra].Name) ).."[NEWLINE]";
			local numOfPlayersAtThisColumn	:number = table.count(markerStat.PlayerNums);
			if numOfPlayersAtThisColumn < 2 then
				instance.Num:SetHide( true );			
				local playerNum		:number = markerStat.PlayerNums[1];
				local pPlayerConfig :table = PlayerConfigurations[playerNum];
				tooltipString = tooltipString.. Locale.Lookup(pPlayerConfig:GetPlayerName());	-- TODO: Temporary using player name until leaderame is fixed

				if not markerStat.IsPlayerHere then
					local iconName:string = "ICON_"..pPlayerConfig:GetLeaderTypeName();
					instance.Portrait:SetHide( false );
					instance.Portrait:SetIcon( iconName );
				end
			else
				instance.Portrait:SetHide( true );
				instance.Num:SetHide( false );
				instance.Num:SetText(tostring(numOfPlayersAtThisColumn));
				for i,playerNum in ipairs(markerStat.PlayerNums) do
					local pPlayerConfig :table = PlayerConfigurations[playerNum];
					--[[ TODO: The human player, player 0, has whack values! No leader name coming from engine!
						local name = pPlayerConfig:GetPlayerName();
						local nick = pPlayerConfig:GetNickName();
						local leader = pPlayerConfig:GetLeaderName();
						local civ = pPlayerConfig:GetCivilizationTypeName();
						local isHuman = pPlayerConfig:IsHuman();
						print("debug info:",name,nick,leader,civ,isHuman);
					]]
					--tooltipString = tooltipString.. Locale.Lookup(pPlayerConfig:GetLeaderName()); 
					tooltipString = tooltipString.. Locale.Lookup(pPlayerConfig:GetPlayerName());	-- TODO:: Temporary using player name until leaderame is fixed
					if i < numOfPlayersAtThisColumn then
						tooltipString = tooltipString.."[NEWLINE]";
					end
				end
			end
			instance.Marker:SetToolTipString( tooltipString );
		
			local MARKER_OFFSET_START:number = 20;
			local markerPercent :number = math.clamp( markerStat.HighestColumn / m_maxColumns, 0, 1 );
			local markerX		:number = MARKER_OFFSET_START + (markerPercent * m_scrollWidth );
			instance.Top:SetOffsetVal(markerX ,0);
		end
	end
	
	RealizePathMarkers();
	RealizeFilterPulldown();
	RealizeKeyPanel();
	RealizeGovernmentPanel();
end

--
--
--
function GetPlayerGovernment(ePlayer:number)
	local kPlayer		:table	= Players[ePlayer];
	local playerCulture	:table	= kPlayer:GetCulture();
	local kCurrentGovernment:table;
	local kGovernmentInfo:table = {};

	local governmentRowId :number = playerCulture:GetCurrentGovernment();
	if governmentRowId ~= -1 then
		kCurrentGovernment = GameInfo.Governments[governmentRowId];
		kGovernmentInfo["NAMES"] = Locale.Lookup(kCurrentGovernment.Name);	
	end

	kGovernmentInfo["DIPLOMATIC"] = 0;
	kGovernmentInfo["ECONOMIC"] = 0;
	kGovernmentInfo["WILDCARD"] = 0;
	kGovernmentInfo["MILITARY"] = 0;
	kGovernmentInfo["RELIGIOUS"] = 0; -- religious 
	kGovernmentInfo["DIPLOMATICPOLICIES"] = {};
	kGovernmentInfo["ECONOMICPOLICIES"] = {};
	kGovernmentInfo["MILITARYPOLICIES"] = {};
	kGovernmentInfo["RELIGIOUSPOLICIES"] = {}; -- religious
	kGovernmentInfo["WILDCARDPOLICIES"] = {};

	local numSlots:number = playerCulture:GetNumPolicySlots();
	for i = 0, numSlots-1, 1 do
		local	iSlotType	:number = playerCulture:GetSlotType(i);
		local	iSlotPolicy	:number= playerCulture:GetSlotPolicy(i);
		local	rowSlotType	:string = GameInfo.GovernmentSlots[iSlotType].GovernmentSlotType;				
				
		if	rowSlotType == "SLOT_DIPLOMATIC" then	
			kGovernmentInfo["DIPLOMATIC"] = kGovernmentInfo["DIPLOMATIC"]+1;
			table.insert(kGovernmentInfo["DIPLOMATICPOLICIES"], iSlotPolicy);
		elseif	rowSlotType == "SLOT_ECONOMIC"	then	
			kGovernmentInfo["ECONOMIC"] = kGovernmentInfo["ECONOMIC"]+1;
			table.insert(kGovernmentInfo["ECONOMICPOLICIES"], iSlotPolicy);
		elseif	rowSlotType == "SLOT_MILITARY"	then	
			kGovernmentInfo["MILITARY"] = kGovernmentInfo["MILITARY"]+1;
			table.insert(kGovernmentInfo["MILITARYPOLICIES"], iSlotPolicy);
		elseif	rowSlotType == "SLOT_JFD_RELIGIOUS"	then	 -- religious
			kGovernmentInfo["RELIGIOUS"] = kGovernmentInfo["RELIGIOUS"]+1;
			table.insert(kGovernmentInfo["RELIGIOUSPOLICIES"], iSlotPolicy);
		elseif	rowSlotType == "SLOT_WILDCARD"	then	
			kGovernmentInfo["WILDCARD"] = kGovernmentInfo["WILDCARD"]+1;
			table.insert(kGovernmentInfo["WILDCARDPOLICIES"], iSlotPolicy);
		else
			UI.DataError("On initialization; unhandled slot type for a policy '"..rowSlotType.."'");
		end		
	end

	return kGovernmentInfo;
end

-- ===========================================================================
--	Load all the 'live' data for a player.
-- ===========================================================================
function GetLivePlayerData( ePlayer:number, eCompletedCivic:number )
	
	-- If first time, initialize player data tables.
	local data	:table = m_kAllPlayersTechData[ePlayer];	
	if data == nil then
		-- Initialize player's top level tables:
		data = {};
		data[DATA_FIELD_LIVEDATA]			= {};
		data[DATA_FIELD_PLAYERINFO]			= {};
		data[DATA_FIELD_UIOPTIONS]			= {};
		data[DATA_FIELD_GOVERNMENT]			= {};
		
		-- Initialize data, and sub tables within the top tables.
		data[DATA_FIELD_PLAYERINFO].Player	= ePlayer;	-- Number of this player
		data[DATA_FIELD_PLAYERINFO].Markers	= {};		-- Hold a condenced, UI-ready version of stats
		data[DATA_FIELD_PLAYERINFO].Stats	= {};		-- Hold stats on where each player is (based on what this player can see)
	end	

	local kPlayer		:table	= Players[ePlayer];
	local playerCulture	:table	= kPlayer:GetCulture();
	local currentTechID	:number = playerCulture:GetProgressingCivic();

	-- DEBUG: Output header to console.
	if m_debugOutputTechInfo then
		print("                          Item Id  Status      Progress   $ Era              Prereqs");
		print("------------------------------ --- ---------- --------- --- ---------------- --------------------------");
	end

	-- Loop through all items and place in appropriate buckets as well
	-- read in the associated information for it.
	for type,item in pairs(m_kItemDefaults) do
		local status	:number = ITEM_STATUS.BLOCKED;
		local turnsLeft	:number = 0;
		if eCompletedCivic == item.Index or playerCulture:HasCivic(item.Index) then
			status = ITEM_STATUS.RESEARCHED;
		elseif item.Index == currentTechID then
			status = ITEM_STATUS.CURRENT;
			turnsLeft = playerCulture:GetTurnsLeft();
		elseif playerCulture:CanProgress(item.Index) then
			status = ITEM_STATUS.READY;
			turnsLeft = playerCulture:GetTurnsToProgressCivic(item.Index);
		else
			turnsLeft = playerCulture:GetTurnsToProgressCivic(item.Index);
		end

		data[DATA_FIELD_LIVEDATA][type] = {
			Cost		= playerCulture:GetCultureCost(item.Index),
			IsBoosted	= playerCulture:HasBoostBeenTriggered(item.Index),
			Progress	= playerCulture:GetCulturalProgress(item.Index),
			Status		= status,
			Turns		= turnsLeft
		}

		-- DEBUG: Output to console detailed information about the tech.
		if m_debugOutputTechInfo then
			local this:table = data[DATA_FIELD_LIVEDATA][type];
			print( string.format("%30s %-3d %-10s %4d/%-4d %3d %-16s %s",
				type,item.Index,
				STATUS_ART[status].Name,
				this.Progress,
				this.Cost,
				this.Turns,
				item.EraType,
				GetPrereqsString(item.Prereqs)
			));
		end
	end

	local players = Game.GetPlayers{Major = true};

	-- Determine where all players are.
	local playerVisibility = PlayersVisibility[ePlayer];
	if playerVisibility ~= nil then
		for i, otherPlayer in ipairs(players) do
			local playerID		:number = otherPlayer:GetID();
			local playerCulture	:table	= players[i]:GetCulture();
			local currentCivic	:number = playerCulture:GetProgressingCivic();

			data[DATA_FIELD_PLAYERINFO].Stats[playerID] = {
				CurrentID		= currentCivic,	
				HasMet			= kPlayer:GetDiplomacy():HasMet(playerID) or playerID==ePlayer or m_debugShowAllMarkers;
				HighestColumn	= -1,				-- where they are in the timeline
				HighestEra		= ""
			};

			-- The latest tech a player may be researching may not be the one
			-- furthest along in time; so go through ALL the techs and track
			-- the highest column of all researched tech.
			local highestColumn :number = -1;
			local highestEra	:string = "";
			for _,item in pairs(m_kItemDefaults) do
				if playerCulture:HasCivic(item.Index) then
					local column:number = item.Column + m_kEras[item.EraType].PriorColumns;
					if column > highestColumn then
						highestColumn	= column;
						highestEra		= item.EraType;
					end
				end
			end
			data[DATA_FIELD_PLAYERINFO].Stats[playerID].HighestColumn	= highestColumn;
			data[DATA_FIELD_PLAYERINFO].Stats[playerID].HighestEra		= highestEra;
		end
	end


	-- All player data is added.. build markers data based on player data.
	local checkedID:table = {};
	data[DATA_FIELD_PLAYERINFO].Markers	= {};
	for playerID:number, targetPlayer:table in pairs(data[DATA_FIELD_PLAYERINFO].Stats) do
		-- Only look for IDs that haven't already been merged into a marker.
		if checkedID[playerID] == nil and targetPlayer.HasMet then
			checkedID[playerID] = true;
			local markerData:table = {};
			if data[DATA_FIELD_PLAYERINFO].Markers[playerID] ~= nil then
				markerData = data[DATA_FIELD_PLAYERINFO].Markers[playerID];
				markerData.HighestColumn = targetPlayer.HighestColumn;
				markerData.HighestEra    = targetPlayer.HighestEra;
				markerData.IsPlayerHere  = (playerID == ePlayer);
			else
				markerData = {
							HighestColumn	= targetPlayer.HighestColumn,	-- Which column this marker should be placed
							HighestEra		= targetPlayer.HighestEra,
							IsPlayerHere	= (playerID == ePlayer),				
							PlayerNums		= {playerID}}									-- All players who share this marker spot
				table.insert( data[DATA_FIELD_PLAYERINFO].Markers, markerData );				
			end
			-- SPECIAL CASE: Current player starts at column 0 so it's immediately visible on timeline:
			if playerID == ePlayer and markerData.HighestColumn == -1 then
				markerData.HighestColumn = 0;		
				local firstEra:table = nil;
				for _,era in pairs(m_kEras) do
					if firstEra == nil or era.Index < firstEra.Index then
						firstEra = era;
					end
				end							
				markerData.HighestEra = firstEra.Index;
			end

			-- Traverse all the IDs and merge them with this one.
			for anotherID:number, anotherPlayer:table in pairs(data[DATA_FIELD_PLAYERINFO].Stats) do				
				-- Don't add if: it's outself, if hasn't researched at least 1 tech, if we haven't met
				if playerID ~= anotherID and anotherPlayer.HighestColumn > -1 and anotherPlayer.HasMet then		
					if markerData.HighestColumn == data[DATA_FIELD_PLAYERINFO].Stats[anotherID].HighestColumn then
						checkedID[anotherID] = true;
						-- Need to do this check if player's ID didn't show up first in the list in creating the marker.						
						if anotherID == ePlayer then
							markerData.IsPlayerHere	= true;
						end
						local foundAnotherID:boolean = false;

						for _, playernumsID in pairs(markerData.PlayerNums) do
							if not foundAnotherID and playernumsID == anotherID then
								foundAnotherID = true;
							end
						end

						if not foundAnotherID then
							table.insert( markerData.PlayerNums, anotherID );
						end
					end
				end
			end

			
		end
	end

	data[DATA_FIELD_GOVERNMENT]	= GetPlayerGovernment(ePlayer);
	
	return data;
end

-- ===========================================================================
function OnGovernmentChanged()
	local ePlayer :number = Game.GetLocalPlayer();
	if ePlayer ~= -1 then
	    if m_ePlayer ~= ePlayer then
		    m_ePlayer = ePlayer;
		    m_kCurrentData = GetLivePlayerData( ePlayer, -1 );
	    end
    end
end

-- ===========================================================================
function OnGovernmentPolicyChanged()
	local ePlayer :number = Game.GetLocalPlayer();
	if ePlayer ~= -1 then
	    if m_ePlayer ~= ePlayer then
		    m_ePlayer = ePlayer;
		    m_kCurrentData = GetLivePlayerData( ePlayer, -1 );
	    end
    end
end

-- ===========================================================================
function OnLocalPlayerTurnBegin()
	local ePlayer :number = Game.GetLocalPlayer();
	if ePlayer ~= -1 then
	    if m_ePlayer ~= ePlayer then
		    m_ePlayer = ePlayer;
		    m_kCurrentData = GetLivePlayerData( ePlayer, -1 );
	    end
    end
end

-- ===========================================================================
--	EVENT
--	Player turn is ending
-- ===========================================================================
function OnLocalPlayerTurnEnd()
	-- If current data set is for the player, save back any changes into
	-- the table of player tables.
	local ePlayer :number = Game.GetLocalPlayer();
	if ePlayer ~= -1 then
		if m_kCurrentData[DATA_FIELD_PLAYERINFO].Player == ePlayer then
			m_kAllPlayersTechData[ePlayer] = m_kCurrentData;
		end
	end
end

-- ===========================================================================
function OnCivicChanged( ePlayer:number, eTech:number )
	if ePlayer == Game.GetLocalPlayer() then
		m_ePlayer = ePlayer;
		m_kCurrentData = GetLivePlayerData( m_ePlayer, -1 );
		if not ContextPtr:IsHidden() then
			View( m_kCurrentData );
		end
	end
end

-- ===========================================================================
function OnCivicComplete( ePlayer:number, eTech:number)
	if ePlayer == Game.GetLocalPlayer() then
		m_ePlayer = ePlayer;
		m_kCurrentData = GetLivePlayerData( m_ePlayer, eTech );
		if not ContextPtr:IsHidden() then
			View( m_kCurrentData );
		end
	end
end

-- ===========================================================================
--	Initially size static UI elements 
--	(or re-size if screen resolution changed)
-- ===========================================================================
function Resize()
	m_width, m_height	= UIManager:GetScreenSizeVal();		-- Cache screen dimensions
	m_scrollWidth		= m_width - 80;						-- Scrollbar area (where markers are placed) slightly smaller than screen width

	local keyHeight:number = SIZE_WIDESCREEN_HEIGHT - (SIZE_OPTIONS_Y + SIZE_TIMELINE_AREA_Y + SIZE_TOP_AREA_Y);
	
	
	if not Controls.GovernmentPanel:IsHidden() then 
		keyHeight = keyHeight - SIZE_GOVTPANEL_HEIGHT;
	end

	Controls.KeyPanel:SetSizeY(keyHeight);
	Controls.KeyScroll:SetSizeY(keyHeight-20);
	if(Controls.KeyPanel:IsHidden()) then
		Controls.GovernmentPanel:SetOffsetY(-87);
	else
		Controls.GovernmentPanel:SetOffsetY(keyHeight - 90);
	end

	-- Determine how far art will span.
	-- First obtain the size of the tree by taking the visible size and multiplying it by the ratio of the full content
	local scrollPanelX:number = (Controls.NodeScroller:GetSizeX() / Controls.NodeScroller:GetRatio());

	local artAndEraScrollWidth:number = scrollPanelX * (1/PARALLAX_SPEED);
	Controls.ArtParchmentDecoTop:SetSizeX( artAndEraScrollWidth );
	Controls.ArtParchmentDecoBottom:SetSizeX( artAndEraScrollWidth );
	Controls.ArtParchmentRippleTop:SetSizeX( artAndEraScrollWidth );
	Controls.ArtParchmentRippleBottom:SetSizeX( artAndEraScrollWidth );
	Controls.ForceSizeX:SetSizeX( artAndEraScrollWidth  );	
	Controls.ArtScroller:CalculateSize();
	Controls.ArtCornerGrungeTR:ReprocessAnchoring();
	Controls.ArtCornerGrungeBR:ReprocessAnchoring();


	local PADDING_DUE_TO_LAST_BACKGROUND_ART_IMAGE:number = 100;
	local backArtScrollWidth:number = scrollPanelX * (1/PARALLAX_ART_SPEED);
	Controls.Background:SetSizeX( backArtScrollWidth + PADDING_DUE_TO_LAST_BACKGROUND_ART_IMAGE );
	Controls.Background:SetSizeY( SIZE_WIDESCREEN_HEIGHT - (SIZE_TIMELINE_AREA_Y-8) );	
	Controls.FarBackArtScroller:CalculateSize();

	Controls.KeyScroll:CalculateSize();
end

-- ===========================================================================
function OnUpdateUI( type:number, tag:string, iData1:number, iData2:number, strData1:string)
	if type == SystemUpdateUI.ScreenResize then
		Resize();
	end
end

-- ===========================================================================
--	Obtain the data from the DB that doesn't change
--	Base costs and relationships (prerequisites)
--	RETURN: A table of node data (techs/civics/etc...) with a prereq for each entry.
-- ===========================================================================
function PopulateItemData( tableName:string, tableColumn:string, prereqTableName:string, itemColumn:string, prereqColumn:string)	
	-- Build main item table.
	m_kItemDefaults = {};
	local index			:number		= 0;
	function GetHash(t)
		local r = GameInfo.Types[t];
		if(r) then
			return r.Hash;
		else
			return 0;
		end
	end

	for row:table in GameInfo[tableName]() do

		local entry:table	= {};
		entry.Type			= row[tableColumn];
		entry.Name			= row.Name;
		entry.BoostText		= "";
		entry.Column		= -1;				-- Dynamically computed during UI layout
		entry.Cost			= row.Cost;
		entry.Description	= row.Description and Locale.Lookup( row.Description );
		entry.EraType		= row.EraType;
		entry.Hash			= GetHash(entry.Type);
		entry.Index			= index;
		entry.IsBoostable	= false;
		entry.Prereqs		= {};
		entry.UITreeRow		= row.UITreeRow;	
		entry.Unlocks		= {};				-- Each unlock has: unlockType, iconUnavail, iconAvail, tooltip

		-- Boost?
		for boostRow in GameInfo.Boosts() do
			if boostRow.CivicType == entry.Type then				
				entry.BoostText = Locale.Lookup( boostRow.TriggerDescription );
				entry.IsBoostable = true;
				entry.BoostAmount = boostRow.Boost;
				break;
			end
		end

		for prereqRow in GameInfo[prereqTableName]() do
			if prereqRow[itemColumn] == entry.Type then
				table.insert( entry.Prereqs, prereqRow[prereqColumn] );
			end
		end
		-- If no prereqs were found, set item to special tree start value
		if table.count(entry.Prereqs) == 0 then
			table.insert(entry.Prereqs, PREREQ_ID_TREE_START);
		end

		-- Warn if DB has an out of bounds entry.
		if entry.UITreeRow < ROW_MIN or entry.UITreeRow > ROW_MAX then
			UI.DataError("UITreeRow for '"..entry.Type.."' has an out of bound UITreeRow="..tostring(entry.UITreeRow).."  MIN="..tostring(ROW_MIN).."  MAX="..tostring(ROW_MAX));
		end

		-- Only build up a limited number of eras if debug information is forcing a subset.
		if m_debugFilterEraMaxIndex < 1 or ( m_debugFilterEraMaxIndex ~= -1 and m_kEras[entry.EraType] ~= nil ) then
			m_kItemDefaults[entry.Type] = entry;
			index = index + 1;
		end
	end
end


-- ===========================================================================
--	Create a hash table of EraType to its chronological index.
-- ===========================================================================
function PopulateEraData()
	m_kEras = {};
	for row:table in GameInfo.Eras() do
		if m_debugFilterEraMaxIndex < 1 or row.ChronologyIndex <= m_debugFilterEraMaxIndex then			
			m_kEras[row.EraType] = { 
				BGTexture	= row.EraCivicBackgroundTexture,
				NumColumns  = 0,
				Description	= Locale.Lookup(row.Name),
				Index		= row.ChronologyIndex,
				PriorColumns= -1					-- Will not be known until UI is laid out
			}
		end
	end	
end


-- ===========================================================================
--
-- ===========================================================================
function PopulateFilterData()

	-- Filters.
	m_kFilters = {};
	table.insert( m_kFilters, { Func=nil,										Description="LOC_TECH_FILTER_NONE",			Icon=nil } );
	
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_FOOD"],			Description="LOC_TECH_FILTER_FOOD",			Icon="[ICON_FOOD]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_SCIENCE"],		Description="LOC_TECH_FILTER_SCIENCE",		Icon="[ICON_SCIENCE]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_PRODUCTION"],	Description="LOC_TECH_FILTER_PRODUCTION",	Icon="[ICON_PRODUCTION]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_CULTURE"],		Description="LOC_TECH_FILTER_CULTURE",		Icon="[ICON_CULTURE]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_GOLD"],			Description="LOC_TECH_FILTER_GOLD",			Icon="[ICON_GOLD]" });	
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_UNITS"],			Description="LOC_TECH_FILTER_UNITS",		Icon="[ICON_UNITS]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_IMPROVEMENTS"],	Description="LOC_TECH_FILTER_IMPROVEMENTS",	Icon="[ICON_IMPROVEMENTS]" });
	table.insert( m_kFilters, { Func=g_TechFilters["TECHFILTER_WONDERS"],		Description="LOC_TECH_FILTER_WONDERS",		Icon="[ICON_WONDERS]" });
	
	for i,filter in ipairs(m_kFilters) do		
		local filterLabel	 = Locale.Lookup( filter.Description );
		local filterIconText = filter.Icon;
		local controlTable	 = {};       
		Controls.FilterPulldown:BuildEntry( "FilterItemInstance", controlTable );
		-- If a text icon exists, use it and bump the label in the button over.
		--[[ TODO: Uncomment if icons are added.
		if filterIconText ~= nil and filterIconText ~= "" then
			controlTable.IconText:SetText( Locale.Lookup(filterIconText) );
			controlTable.DescriptionText:SetOffsetX(24);
		else
			controlTable.IconText:SetText( "" );
			controlTable.DescriptionText:SetOffsetX(4);
		end
		]]
		controlTable.DescriptionText:SetOffsetX(8);
		controlTable.DescriptionText:SetText( filterLabel );

		-- Callback
		controlTable.Button:RegisterCallback( Mouse.eLClick,  function() OnFilterClicked(filter); end );
	end
	Controls.FilterPulldown:CalculateInternals();
end

-- ===========================================================================
-- Populate Full Text Search
-- ===========================================================================
function PopulateSearchData()
	local searchContext = "Civics";
	if(Search.CreateContext(searchContext, "[COLOR_LIGHTBLUE]", "[ENDCOLOR]", "...")) then
		for row in GameInfo.Civics() do
			local description = row.Description and Locale.Lookup(row.Description) or "";
			Search.AddData(searchContext, row.CivicType, Locale.Lookup(row.Name), description);
		end

		for row in GameInfo.Improvements() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		for row in GameInfo.Units() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		for row in GameInfo.Buildings() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		for row in GameInfo.Districts() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		for row in GameInfo.Resources() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		for row in GameInfo.Policies() do
			if(row.PrereqCivic) then
				Search.AddData(searchContext, row.PrereqCivic, Locale.Lookup("LOC_" .. row.PrereqCivic .. "_NAME"), Locale.Lookup(row.Name));
			end
		end

		Search.Optimize(searchContext);
	end
end

-- ===========================================================================
-- Update the Filter text with the current label.
-- ===========================================================================
function RealizeFilterPulldown()
	local pullDownButton = Controls.FilterPulldown:GetButton();	
	if m_kCurrentData[DATA_FIELD_UIOPTIONS].filter == nil or m_kCurrentData[DATA_FIELD_UIOPTIONS].filter.Func== nil then
		pullDownButton:SetText( "  "..Locale.Lookup("LOC_TREE_FILTER_W_DOTS"));
	else
		local description:string = m_kCurrentData[DATA_FIELD_UIOPTIONS].filter.Description;
		pullDownButton:SetText( "  "..Locale.Lookup( description ));
	end
end

-- ===========================================================================
--	filterLabel,	Readable lable of the current filter.
--	filterFunc,		The funciton filter to apply to each node as it's built,
--					nil will reset the filters to none.
-- ===========================================================================
function OnFilterClicked( filter )		  
	m_kCurrentData[DATA_FIELD_UIOPTIONS].filter = filter;
	View( m_kCurrentData )
end

-- ===========================================================================
function OnOpen()
	UI.PlaySound("UI_Screen_Open");
	View( m_kCurrentData );
	ContextPtr:SetHide(false);

	-- From Civ6_styles: FullScreenVignetteConsumer
	Controls.ScreenAnimIn:SetToBeginning();
	Controls.ScreenAnimIn:Play();
	
	LuaEvents.CivicsTree_OpenCivicsTree();
end

-- ===========================================================================
--	Show the Key panel based on the state
-- ===========================================================================
function RealizeKeyPanel()
	if UserConfiguration.GetShowCivicsTreeKey() then
		Controls.KeyPanel:SetHide( false );	
		UI.PlaySound("UI_TechTree_Filter_Open");	        
	else
		if Controls.KeyPanel:IsHidden() then
			UI.PlaySound("UI_TechTree_Filter_Closed");
		end
		Controls.KeyPanel:SetHide( true );
	end

	if Controls.KeyPanel:IsHidden() then
		Controls.ToggleKeyButton:SetText(Locale.Lookup("LOC_TREE_SHOW_KEY"));
		Controls.ToggleKeyButton:SetSelected(false);
	else
		Controls.ToggleKeyButton:SetText(Locale.Lookup("LOC_TREE_HIDE_KEY"));
		Controls.ToggleKeyButton:SetSelected(true);	
	end	
end

-- ===========================================================================
function OnClickCurrentPolicy(clickedPolicy:number)
	local percent:number = 0;
	local prereqCivic:string = GameInfo.Policies[clickedPolicy].PrereqCivic;
	percent = (m_uiNodes[prereqCivic].x - PADDING_TIMELINE_LEFT)/(m_maxColumns * COLUMN_WIDTH);
	Controls.NodeScroller:SetScrollValue(percent);

end

-- ===========================================================================
--	Show the Key panel based on the state
-- ===========================================================================
function RealizeGovernmentPanel()

	m_kDiplomaticPolicyIM:DestroyInstances();
	m_kEconomicPolicyIM:DestroyInstances();
	m_kMilitaryPolicyIM:DestroyInstances();
	m_kReligiousPolicyIM:DestroyInstances(); -- JFD Religious
	m_kWildcardPolicyIM:DestroyInstances();
	m_kDiplomaticPolicyIM = InstanceManager:new( "DiplomaticPolicyInstance", 	"DiplomaticPolicy", 	Controls.DiplomaticStack );
	m_kEconomicPolicyIM	= InstanceManager:new( "EconomicPolicyInstance", 	"EconomicPolicy", 	Controls.EconomicStack );
	m_kMilitaryPolicyIM = InstanceManager:new( "MilitaryPolicyInstance", 	"MilitaryPolicy", 	Controls.MilitaryStack );
	m_kReligiousPolicyIM = InstanceManager:new( "ReligiousPolicyInstance", 	"ReligiousPolicy", 	Controls.ReligiousStack );
	m_kWildcardPolicyIM = InstanceManager:new( "WildcardPolicyInstance", 	"WildcardPolicy", 	Controls.WildcardStack );

	if UserConfiguration.GetShowCivicsTreeGovernment() then
		Controls.GovernmentPanel:SetHide( false );	
		UI.PlaySound("UI_TechTree_Filter_Open");		
	else
		if Controls.GovernmentPanel:IsHidden() then
			UI.PlaySound("UI_TechTree_Filter_Closed");
		end
		Controls.GovernmentPanel:SetHide( true );
	end

	if Controls.GovernmentPanel:IsHidden() then
		Controls.ToggleGovernmentButton:SetText(Locale.Lookup("LOC_TREE_SHOW_GOVERNMENT"));
		Controls.ToggleGovernmentButton:SetSelected(false);
	else
		Controls.ToggleGovernmentButton:SetText(Locale.Lookup("LOC_TREE_HIDE_GOVERNMENT"));
		Controls.ToggleGovernmentButton:SetSelected(true);
	end	

	TruncateStringWithTooltip( Controls.GovernmentTitle, MAX_BEFORE_TRUNC_GOV_TITLE, Locale.ToUpper(m_kCurrentData[DATA_FIELD_GOVERNMENT]["NAMES"]));
	Controls.DiplomaticIconCount:SetText(tostring(m_kCurrentData[DATA_FIELD_GOVERNMENT]["DIPLOMATIC"]));
	Controls.EconomicIconCount:SetText(tostring(m_kCurrentData[DATA_FIELD_GOVERNMENT]["ECONOMIC"]));
	Controls.MilitaryIconCount:SetText(tostring(m_kCurrentData[DATA_FIELD_GOVERNMENT]["MILITARY"]));
	Controls.ReligiousIconCount:SetText(tostring(m_kCurrentData[DATA_FIELD_GOVERNMENT]["RELIGIOUS"])); -- JFD Religious
	Controls.WildcardIconCount:SetText(tostring(m_kCurrentData[DATA_FIELD_GOVERNMENT]["WILDCARD"]));

	local int numDiploResults = #m_kCurrentData[DATA_FIELD_GOVERNMENT]["DIPLOMATICPOLICIES"];
	for i,policy in ipairs(m_kCurrentData[DATA_FIELD_GOVERNMENT]["DIPLOMATICPOLICIES"]) do
		if(policy ~= -1) then
			local diploInst:table = m_kDiplomaticPolicyIM:GetInstance();
			local policyType:string = GameInfo.Policies[policy].Name;
			if numDiploResults > 3 then
				diploInst.DiplomaticPolicy:SetSizeVal(111/numDiploResults, 44 * (3/numDiploResults));
			end
			diploInst.DiplomaticPolicy:SetToolTipString(m_kPolicyCatalogData[policyType].Name .. ": " .. m_kPolicyCatalogData[policyType].Description);
			diploInst.DiplomaticPolicy:RegisterCallback( Mouse.eLClick, function() OnClickCurrentPolicy(policy) end );
		end
	end

	local int numEcoResults = #m_kCurrentData[DATA_FIELD_GOVERNMENT]["ECONOMICPOLICIES"];
	for i,policy in ipairs(m_kCurrentData[DATA_FIELD_GOVERNMENT]["ECONOMICPOLICIES"]) do
		if(policy ~= -1) then
			local ecoInst:table = m_kEconomicPolicyIM:GetInstance();
			local policyType:string = GameInfo.Policies[policy].Name
			if numEcoResults > 3 then
				ecoInst.EconomicPolicy:SetSizeVal(111/numEcoResults, 44 * (3/numEcoResults));
			end
			local description:string = m_kPolicyCatalogData[policyType].Name .. ": " .. m_kPolicyCatalogData[policyType].Description
			ecoInst.EconomicPolicy:SetToolTipString(description);
			ecoInst.EconomicPolicy:RegisterCallback( Mouse.eLClick, function() OnClickCurrentPolicy(policy) end );
		end
	end

	local int numRelResults = #m_kCurrentData[DATA_FIELD_GOVERNMENT]["RELIGIOUSPOLICIES"]; -- JFD Religious
	for i,policy in ipairs(m_kCurrentData[DATA_FIELD_GOVERNMENT]["RELIGIOUSPOLICIES"]) do
		if(policy ~= -1) then
			local relInst:table = m_kReligiousPolicyIM:GetInstance();
			local policyType:string = GameInfo.Policies[policy].Name
			if numRelResults > 3 then
				relInst.ReligiousPolicy:SetSizeVal(111/numRelResults, 44 * (3/numRelResults));
			end
			relInst.ReligiousPolicy:SetToolTipString(m_kPolicyCatalogData[policyType].Name .. ": " .. m_kPolicyCatalogData[policyType].Description);
			relInst.ReligiousPolicy:RegisterCallback( Mouse.eLClick, function() OnClickCurrentPolicy(policy) end );
		end
	end
	
	local int numMilResults = #m_kCurrentData[DATA_FIELD_GOVERNMENT]["MILITARYPOLICIES"];
	for i,policy in ipairs(m_kCurrentData[DATA_FIELD_GOVERNMENT]["MILITARYPOLICIES"]) do
		if(policy ~= -1) then
			local milInst:table = m_kMilitaryPolicyIM:GetInstance();
			local policyType:string = GameInfo.Policies[policy].Name
			if numMilResults > 3 then
				milInst.MilitaryPolicy:SetSizeVal(111/numMilResults, 44 * (3/numMilResults));
			end
			milInst.MilitaryPolicy:SetToolTipString(m_kPolicyCatalogData[policyType].Name .. ": " .. m_kPolicyCatalogData[policyType].Description);
			milInst.MilitaryPolicy:RegisterCallback( Mouse.eLClick, function() OnClickCurrentPolicy(policy) end );
		end
	end

	local int numWildResults = #m_kCurrentData[DATA_FIELD_GOVERNMENT]["WILDCARDPOLICIES"];
	for i,policy in ipairs(m_kCurrentData[DATA_FIELD_GOVERNMENT]["WILDCARDPOLICIES"]) do
		if(policy ~= -1) then
			local wildInst:table = m_kWildcardPolicyIM:GetInstance();
			local policyType:string = GameInfo.Policies[policy].Name;
			local slotType:string =  GameInfo.Policies[policy].GovernmentSlotType;
			if numWildResults > 3 then
				wildInst.WildcardPolicy:SetSizeVal(111/numWildResults, 44 * (3/numWildResults));
			end
			if slotType == "SLOT_DIPLOMATIC" then
				wildInst.WildcardPolicy:SetTexture("Governments_DiplomacyCard_Small");
			else
				if slotType == "SLOT_ECONOMIC" then
					wildInst.WildcardPolicy:SetTexture("Governments_EconomicCard_Small");
				else
					if slotType == "SLOT_MILITARY" then
						wildInst.WildcardPolicy:SetTexture("Governments_MilitaryCard_Small");
					end
				end
			end
			wildInst.WildcardPolicy:SetToolTipString(m_kPolicyCatalogData[policyType].Name .. ": " .. m_kPolicyCatalogData[policyType].Description);
			wildInst.WildcardPolicy:RegisterCallback( Mouse.eLClick, function() OnClickCurrentPolicy(policy) end );
		end
	end

end

-- ===========================================================================
--	Fill the catalog with the static (unchanging) policy data used by
--	all players when viewing the screen.
-- ===========================================================================
function PopulateStaticData()

	-- Fill in the complete catalog of policies.
	m_kPolicyCatalogData = {};
	for row in GameInfo.Policies() do
		local policyTypeRow		:table	= GameInfo.Types[row.PolicyType];
		local policyName		:string = Locale.Lookup(row.Name);
		local policyTypeHash	:number = policyTypeRow.Hash;
		local slotType			:string = row.GovernmentSlotType;
		local description		:string = Locale.Lookup(row.Description);
		--local draftCost			:number = kPlayerCulture:GetEnactPolicyCost(policyTypeHash);	--Move to live data
	
		m_kPolicyCatalogData[row.Name] = {			
			Description = description, 
			Name		= policyName, 
			PolicyHash	= policyTypeHash, 
			SlotType	= slotType,			-- SLOT_MILITARY, SLOT_ECONOMIC, SLOT_DIPLOMATIC, SLOT_WILDCARD, (SLOT_GREAT_PERSON)
			UniqueID	= row.Index			-- the row this policy exists in, is guaranteed to be unique (as-is the house, but these are readable. ;) )
			};
	end	

	-- Fill in governments
	m_kGovernments = {};
	for row in GameInfo.Governments() do
		local government		:table	= GameInfo.Types[row.GovernmentType];
		local slotMilitary		:number = 0;
		local slotEconomic		:number = 0;
		local slotDiplomatic	:number = 0;
		local slotReligious		:number = 0; -- JFD Religious
		local slotWildcard		:number = 0;
		for entry in GameInfo.Government_SlotCounts() do
			if row.GovernmentType == entry.GovernmentType then
				local slotType = entry.GovernmentSlotType;
				for i = 1, entry.NumSlots, 1 do
					if		slotType == "SLOT_MILITARY" then									slotMilitary	= slotMilitary + 1;
					elseif	slotType == "SLOT_ECONOMIC" then									slotEconomic	= slotEconomic + 1;
					elseif	slotType == "SLOT_DIPLOMATIC" then									slotDiplomatic	= slotDiplomatic + 1;
					elseif	slotType == "SLOT_JFD_RELIGIOUS" then								slotReligious	= slotReligious + 1; -- JFD Religious
					elseif	slotType == "SLOT_WILDCARD" or slotType=="SLOT_GREAT_PERSON" then	slotWildcard	= slotWildcard + 1;
					end
				end
			end
		end
		m_kGovernments[row.Name] = {
			BonusAccumlatedText	= row.AccumulatedBonusDesc,
			BonusInherentText	= row.InherentBonusDesc,
			BonusType			= row.BonusType,
			Hash				= government.Hash,
			Index				= row.Index,
			Name				= row.Name,
			NumSlotMilitary		= slotMilitary,
			NumSlotEconomic		= slotEconomic,
			NumSlotDiplomatic	= slotDiplomatic,
			NumSlotReligious	= slotReligious, -- JFD Religious
			NumSlotWildcard		= slotWildcard
		}
	end

end

-- ===========================================================================
--	Show/Hide key panel
-- ===========================================================================
function OnClickToggleKey()
	if Controls.KeyPanel:IsHidden() then
		UserConfiguration.SetShowCivicsTreeKey(true);		
	else
        UI.PlaySound("UI_TechTree_Filter_Closed");
		UserConfiguration.SetShowCivicsTreeKey(false);		
	end
	RealizeKeyPanel();
end
Controls.ToggleKeyButton:RegisterCallback(Mouse.eLClick, OnClickToggleKey);

function OnClickToggleGovernment()
	if Controls.GovernmentPanel:IsHidden() then
		UserConfiguration.SetShowCivicsTreeGovernment(true);		
	else
        UI.PlaySound("UI_TechTree_Filter_Closed");
		UserConfiguration.SetShowCivicsTreeGovernment(false);		
	end
	RealizeGovernmentPanel();
end
Controls.ToggleGovernmentButton:RegisterCallback(Mouse.eLClick, OnClickToggleGovernment);

function OnClickToggleFilter()
    if Controls.FilterPulldown:IsOpen() then
        UI.PlaySound("UI_TechTree_Filter_Open");
    else
        UI.PlaySound("UI_TechTree_Filter_Closed");
    end             
end

-- ===========================================================================
--	Main close function all exit points should call.
-- ===========================================================================
function Close()
	UI.PlaySound("UI_Screen_Close");
	ContextPtr:SetHide(true);
	LuaEvents.CivicsTree_CloseCivicsTree();
	Controls.SearchResultsPanelContainer:SetHide(true);
end
-- ===========================================================================
--	Close via click
-- ===========================================================================
function OnClose()
	Close();
end


-- ===========================================================================
--	Input
--	UI Event Handler
-- ===========================================================================
function KeyDownHandler( key:number )
	if key == Keys.VK_SHIFT then
		m_shiftDown = true;
		-- let it fall through
	end
	return false;
end
function KeyUpHandler( key:number )
	if key == Keys.VK_SHIFT then
		m_shiftDown = false;
		-- let it fall through
	end
    if key == Keys.VK_ESCAPE then
		Close();
		return true;
    end
	if key == Keys.VK_RETURN then
		-- Don't let enter propigate or it will hit action panel which will raise a screen (potentially this one again) tied to the action.
		return true;
	end
    return false;
end
function OnInputHandler( pInputStruct:table )
	local uiMsg = pInputStruct:GetMessageType();
	if uiMsg == KeyEvents.KeyDown then return KeyDownHandler( pInputStruct:GetKey() ); end
	if uiMsg == KeyEvents.KeyUp then return KeyUpHandler( pInputStruct:GetKey() ); end	
	return false;
end

function OnSearchBarGainFocus()
	Controls.SearchEditBox:ClearString();
end

function OnSearchBarLoseFocus()
	Controls.SearchEditBox:SetText(Locale.Lookup("LOC_TREE_SEARCH_W_DOTS"));
end

-- ===========================================================================
--	UI Event Handler
-- ===========================================================================
function OnShutdown()
	-- Clean up events
	LuaEvents.CivicsPanel_RaiseCivicsTree.Remove( OnOpen );
	LuaEvents.LaunchBar_RaiseCivicsTree.Remove( OnOpen );
	LuaEvents.LaunchBar_CloseCivicsTree.Remove( OnClose );

	Events.GovernmentChanged.Remove( OnGovernmentChanged );
	Events.GovernmentPolicyChanged.Remove( OnGovernmentPolicyChanged );
	Events.GovernmentPolicyObsoleted.Remove( OnGovernmentPolicyChanged );
	Events.LocalPlayerTurnBegin.Remove( OnLocalPlayerTurnBegin );
	Events.LocalPlayerTurnEnd.Remove( OnLocalPlayerTurnEnd );
	Events.CivicChanged.Remove( OnCivicChanged );
	Events.CivicCompleted.Remove( OnCivicComplete );
	Events.SystemUpdateUI.Remove( OnUpdateUI );

	Search.DestroyContext("Civics");
end

-- ===========================================================================
--	Centers scroll panel (if possible) on a specfic type.
-- ===========================================================================
function ScrollToNode( typeName:string )
	local percent:number = 0;
	local x		= m_uiNodes[typeName].x - ( m_width * 0.5);
	local size  = (m_width / Controls.NodeScroller:GetRatio()) - m_width;
	percent = math.clamp( x  / size, 0, 1);
	Controls.NodeScroller:SetScrollValue(percent);
	m_kSearchResultIM:DestroyInstances();	
	Controls.SearchResultsPanelContainer:SetHide(true);
end

-- ===========================================================================
--	Input Hotkey Event
-- ===========================================================================
function OnInputActionTriggered( actionId )
	if m_ToggleCivicsTreeId ~= nil and actionId == m_ToggleCivicsTreeId then
        UI.PlaySound("Play_UI_Click");
		if(ContextPtr:IsHidden()) then
			LuaEvents.CivicsChooser_RaiseCivicsTree();
		else
			OnClose();
		end
	end
end

-- ===========================================================================
--	UI Callback
-- ===========================================================================
function OnSearchCharCallback()
	local has_found = {};
	local str = Controls.SearchEditBox:GetText();

	if str ~= nil and str ~= Locale.Lookup("LOC_TREE_SEARCH_W_DOTS") then
		local results = Search.Search("Civics", str);
		if (results and #results > 0) then
			m_kSearchResultIM:DestroyInstances();
			for i, v in ipairs(results) do
				-- v[1]chnologyType
				-- v[2] == Name of Technology w/ search term highlighted.
				-- v[3] == Snippet of Technology description w/ search term highlighted.
				if has_found[v[1]] == nil then
					local instance = m_kSearchResultIM:GetInstance();
					local civicID:number;
					local i:number = 0;
					for row in GameInfo.Civics() do
						if row.CivicType == v[1] then
							civicID = i;
						end
						i = i + 1;
					end

					local iconName :string = DATA_ICON_PREFIX .. v[1];
					instance.SearchIcon:SetIcon(iconName);

					-- Search results already localized.
					instance.Name:SetText(v[2]);
					
					instance.Button:RegisterCallback(Mouse.eLClick, function() ScrollToNode(v[1]); end );
					instance.Button:SetToolTipString(ToolTipHelper.GetToolTip(v[1], Game.GetLocalPlayer()));
					has_found[v[1]] = true;
				end
			end

			Controls.SearchResultsStack:CalculateSize();
			Controls.SearchResultsStack:ReprocessAnchoring();
			Controls.SearchResultsPanel:CalculateSize();
			Controls.SearchResultsPanelContainer:SetHide(false);
		else
			Controls.SearchResultsPanelContainer:SetHide(true);
		end
	end
end

-- ===========================================================================
function OnCityInitialized(owner, ID)
	if (owner == m_ePlayer) then
		m_kCurrentData = GetLivePlayerData( m_ePlayer, -1 );
	end
end

-- ===========================================================================
function OnBuildingChanged( plotX:number, plotY:number, buildingIndex:number, playerID:number, iPercentComplete:number )
	if playerID == m_ePlayer then	
		m_kCurrentData = GetLivePlayerData( m_ePlayer, -1 ); -- Buildings can change culture/science yield which can effect "turns to complete" values
	end
end

-- ===========================================================================
--	Load all static information as well as display information for the
--	current local player.
-- ===========================================================================
function Initialize()

	PopulateStaticData();
	PopulateEraData();
	PopulateItemData("Civics","CivicType","CivicPrereqs","Civic","PrereqCivic");
	PopulateFilterData();
	PopulateSearchData();
	
	AllocateUI();

	-- May be observation mode.
	m_ePlayer = Game.GetLocalPlayer();
	if (m_ePlayer == -1) then
		return;
	end

	Resize();	-- Now that view has been called once, size of tree is known.
	
	m_kCurrentData = GetLivePlayerData( m_ePlayer, -1 );
	View( m_kCurrentData );
	

	-- UI Events
	ContextPtr:SetInputHandler( OnInputHandler, true );
	ContextPtr:SetShutdown( OnShutdown );

	Controls.SearchEditBox:RegisterStringChangedCallback(OnSearchCharCallback);
	Controls.SearchEditBox:RegisterHasFocusCallback( OnSearchBarGainFocus);
	Controls.SearchEditBox:RegisterCommitCallback( OnSearchBarLoseFocus);

	local pullDownButton = Controls.FilterPulldown:GetButton();	
	pullDownButton:RegisterCallback(Mouse.eLClick, OnClickToggleFilter);

	-- LUA Events
	LuaEvents.CivicsChooser_RaiseCivicsTree.Add( OnOpen );
	LuaEvents.LaunchBar_RaiseCivicsTree.Add( OnOpen );
	LuaEvents.LaunchBar_CloseCivicsTree.Add( OnClose );

	-- Game engine Event
	Events.CityInitialized.Add( OnCityInitialized );
	Events.BuildingChanged.Add( OnBuildingChanged );
	Events.CivicChanged.Add( OnCivicChanged );
	Events.CivicCompleted.Add( OnCivicComplete );
	Events.GovernmentChanged.Add( OnGovernmentChanged );
	Events.GovernmentPolicyChanged.Add( OnGovernmentPolicyChanged );
	Events.GovernmentPolicyObsoleted.Add( OnGovernmentPolicyChanged );
	Events.InputActionTriggered.Add( OnInputActionTriggered );	
	Events.LocalPlayerTurnBegin.Add( OnLocalPlayerTurnBegin );
	Events.LocalPlayerTurnEnd.Add( OnLocalPlayerTurnEnd );
	Events.LocalPlayerChanged.Add(AllocateUI);
	Events.SystemUpdateUI.Add( OnUpdateUI );
end
Initialize();
