class questLogGameController extends gameuiMenuGameController
{
	private editable var m_virtualList : inkWidgetRef;
	private editable var m_detailsPanel : inkWidgetRef;
	private editable var m_buttonHints : inkWidgetRef;
	private editable var m_filtersList : inkWidgetRef;
	private editable var m_questList : inkWidgetRef;
	private var m_game : GameInstance;
	private var m_journalManager : weak< JournalManager >;
	private var m_quests : array< weak< JournalEntry > >;
	private var m_resolvedQuests : array< weak< JournalEntry > >;
	private var m_buttonHintsController : weak< ButtonHints >;
	private var m_menuEventDispatcher : weak< inkMenuEventDispatcher >;
	private var m_trackedQuest : weak< JournalQuest >;
	private var m_curreentQuest : weak< JournalQuest >;
	private var m_externallyOpenedQuestHash : Int32;
	private var m_playerLevel : Int32;
	private var m_recommendedLevel : Int32;
	private var m_entryAnimProxy : inkAnimProxy;
	private var m_canUsePhone : Bool;
	private var m_detailsPanelCtrl : weak< QuestDetailsPanelController >;
	private var m_virtualListController : weak< QuestListVirtualController >;
	private var m_filters : array< weak< QuestListFilterButtonController > >;
	private var m_activeFilter : weak< QuestListFilterButtonController >;
	private var m_currentCustomFilterIndex : Int32;
	private var m_axisDataThreshold : Float;
	default m_axisDataThreshold = 0.5f;
	private var m_mouseDataThreshold : Float;
	default m_mouseDataThreshold = 7.0f;
	private var m_delayedShowDuration : Float;
	default m_delayedShowDuration = 0.2f;
	private var m_delayedShow : DelayID;
	private var m_listPanelHoverd : Bool;
	private var m_isDelayTicking : Bool;
	private var m_firstInit : Bool;
	private var m_filterSwich : Bool;
	private var m_questData : weak< JournalQuest >;
	private var m_appliedQuestData : weak< JournalQuest >;
	private var m_skipAnimation : Bool;
	var m_listData : array< QuestListItemData >;
	var m_questTypeList : array< QuestListItemType >;
	var m_questToOpen : weak< JournalQuest >;

	protected event OnInitialize()
	{
		SetupFilterButtons();
		RegisterToGlobalInputCallback( 'OnPostOnAxis', this, 'OnAxisInput' );
		RegisterToGlobalInputCallback( 'OnPostOnRelative', this, 'OnAxisInput' );
		RegisterToGlobalInputCallback( 'OnPostOnRelease', this, 'OnReleaseInput' );
		inkWidgetRef.RegisterToCallback( m_questList, 'OnEnter', this, 'OnQuestListPanelEnter' );
		inkWidgetRef.RegisterToCallback( m_questList, 'OnLeave', this, 'OnQuestListLeave' );
		m_detailsPanelCtrl = ( ( QuestDetailsPanelController )( inkWidgetRef.GetController( m_detailsPanel ) ) );
		m_virtualListController = ( ( QuestListVirtualController )( inkWidgetRef.GetController( m_virtualList ) ) );
		m_virtualListController.RegisterToCallback( 'OnAllElementsSpawned', this, 'OnAllElementsSpawned' );
		m_game = ( ( GameObject )( GetPlayerControlledObject() ) ).GetGame();
		m_journalManager = GameInstance.GetJournalManager( m_game );
		m_journalManager.RegisterScriptCallback( this, 'OnJournalReady', gameJournalListenerType.State );
		m_playerLevel = RoundMath( GameInstance.GetStatsSystem( m_game ).GetStatValue( GetPlayerControlledObject().GetEntityID(), gamedataStatType.Level ) );
		OnJournalReady( 0, '', JournalNotifyOption.Notify, JournalChangeType.Undefined );
		m_virtualListController.SortQuests( false );
		m_buttonHintsController = ( ( ButtonHints )( SpawnFromExternal( inkWidgetRef.Get( m_buttonHints ), R"base\gameplay\gui\common\buttonhints.inkwidget", 'Root' ).GetController() ) );
		m_buttonHintsController.AddButtonHint( 'back', GetLocalizedText( "Common-Access-Close" ) );
		m_buttonHintsController.AddButtonHint( 'sort_list', GetSortTypeName( m_virtualListController.GetQuestSortType() ) );
		m_buttonHintsController.AddButtonHint( 'track', GetLocalizedText( "UI-UserActions-TrackQuest" ) );
		PlayLibraryAnimation( 'journal_intro' );
		m_canUsePhone = IsPhoneAvailable();
		m_firstInit = true;
		m_filterSwich = false;
	}

	protected event OnUninitialize()
	{
		UnregisterFromGlobalInputCallback( 'OnPostOnAxis', this, 'OnAxisInput' );
		UnregisterFromGlobalInputCallback( 'OnPostOnRelative', this, 'OnAxisInput' );
		UnregisterFromGlobalInputCallback( 'OnPostOnRelease', this, 'OnReleaseInput' );
		inkWidgetRef.UnregisterFromCallback( m_questList, 'OnEnter', this, 'OnQuestListPanelEnter' );
		inkWidgetRef.UnregisterFromCallback( m_questList, 'OnLeave', this, 'OnQuestListLeave' );
		m_virtualListController.UnregisterFromCallback( 'OnAllElementsSpawned', this, 'OnAllElementsSpawned' );
		m_menuEventDispatcher.UnregisterFromEvent( 'OnBack', this, 'OnBack' );
	}

	protected event OnAllElementsSpawned()
	{
		var entryToSelectHash : Int32;
		if( m_firstInit )
		{
			entryToSelectHash = m_journalManager.GetEntryHash( ( ( m_questToOpen ) ? ( m_questToOpen ) : ( m_trackedQuest ) ) );
			m_firstInit = false;
			m_questToOpen = NULL;
			m_virtualListController.SelectItemByHash( entryToSelectHash );
		}
		else if( m_filterSwich )
		{
			m_filterSwich = false;
			m_virtualListController.SelectItemByHash( m_journalManager.GetEntryHash( m_questData ) );
		}
	}

	protected event OnQuestListPanelEnter( evt : inkPointerEvent )
	{
		m_listPanelHoverd = true;
	}

	protected event OnQuestListLeave( evt : inkPointerEvent )
	{
		m_listPanelHoverd = false;
	}

	protected event OnAxisInput( evt : inkPointerEvent ) {}

	protected event OnSetMenuEventDispatcher( menuEventDispatcher : weak< inkMenuEventDispatcher > )
	{
		m_menuEventDispatcher = menuEventDispatcher;
		m_menuEventDispatcher.RegisterToEvent( 'OnBack', this, 'OnBack' );
	}

	protected event OnBack( userData : IScriptable )
	{
		if( !( StatusEffectSystem.ObjectHasStatusEffectWithTag( GetPlayerControlledObject(), 'LockInHubMenu' ) ) )
		{
			m_menuEventDispatcher.SpawnEvent( 'OnCloseHubMenu' );
		}
	}

	protected event OnCodexPopupRequest( evt : OpenCodexPopupEvent )
	{
		GetRootWidget().SetVisible( false );
	}

	protected event OnCodexPopupClosedEvent( evt : CodexPopupClosedEvent )
	{
		GetRootWidget().SetVisible( true );
	}

	protected event OnJournalReady( entryHash : Uint32, className : CName, notifyOption : JournalNotifyOption, changeType : JournalChangeType )
	{
		var context : JournalRequestContext;
		var contextFilter : JournalRequestStateFilter;
		var resolvedContext : JournalRequestContext;
		var resolvedEntriesBuffer : array< weak< JournalEntry > >;
		var i : Int32;
		if( notifyOption == JournalNotifyOption.DoNotNotify )
		{
			return true;
		}
		contextFilter.active = true;
		contextFilter.inactive = false;
		contextFilter.succeeded = false;
		contextFilter.failed = false;
		context.stateFilter = contextFilter;
		m_quests.Clear();
		m_journalManager.GetQuests( context, m_quests );
		resolvedContext.stateFilter = QuestLogUtils.GetSuccessFilter();
		m_resolvedQuests.Clear();
		m_journalManager.GetQuests( resolvedContext, resolvedEntriesBuffer );
		for( i = 0; i < resolvedEntriesBuffer.Size(); i += 1 )
		{
			m_resolvedQuests.PushBack( resolvedEntriesBuffer[ i ] );
		}
		resolvedContext.stateFilter = QuestLogUtils.GetFailedFilter();
		m_journalManager.GetQuests( resolvedContext, resolvedEntriesBuffer );
		for( i = 0; i < resolvedEntriesBuffer.Size(); i += 1 )
		{
			m_resolvedQuests.PushBack( resolvedEntriesBuffer[ i ] );
		}
		m_trackedQuest = GetTopQuestEntry( m_journalManager, m_journalManager.GetTrackedEntry() );
		BuildQuestList();
	}

	private function SetupFilterButtons()
	{
		RequestSpawnFilterButton( ( ( Int32 )( QuestListItemType.All ) ) );
		RequestSpawnFilterButton( ( ( Int32 )( QuestListItemType.MainQuest ) ) );
		RequestSpawnFilterButton( ( ( Int32 )( QuestListItemType.Gig ) ) );
		RequestSpawnFilterButton( ( ( Int32 )( QuestListItemType.Finished ) ) );
	}

	private function RequestSpawnFilterButton( type : Int32 )
	{
		var spawnedData : FilterButtonSpawnedData;
		spawnedData = new FilterButtonSpawnedData;
		spawnedData.m_type = type;
		AsyncSpawnFromLocal( inkWidgetRef.Get( m_filtersList ), 'filterButton', this, 'OnFilterButtonSpawned', spawnedData );
	}

	protected event OnFilterButtonSpawned( widget : inkWidget, userData : IScriptable )
	{
		var controller : QuestListFilterButtonController;
		var spawnedData : FilterButtonSpawnedData;
		var filterType : QuestListItemType;
		var questCount, finishedQuestCount : Int32;
		controller = ( ( QuestListFilterButtonController )( widget.GetController() ) );
		m_filters.PushBack( controller );
		widget.SetHAlign( inkEHorizontalAlign.Left );
		widget.SetVAlign( inkEVerticalAlign.Top );
		widget.RegisterToCallback( 'OnRelease', this, 'OnFilterReleased' );
		spawnedData = ( ( FilterButtonSpawnedData )( userData ) );
		filterType = ( ( QuestListItemType )( spawnedData.m_type ) );
		controller.SetData( filterType );
		if( filterType == QuestListItemType.All )
		{
			m_activeFilter = controller;
			controller.SetActive( true );
			m_virtualListController.SetFilter( m_activeFilter.GetType() );
			finishedQuestCount = m_questTypeList.Count( QuestListItemType.Finished );
			questCount = m_questTypeList.Size() - finishedQuestCount;
		}
		else if( filterType == QuestListItemType.MainQuest )
		{
			questCount = m_questTypeList.Count( QuestListItemType.MainQuest ) + m_questTypeList.Count( QuestListItemType.SideQuest );
		}
		else if( filterType == QuestListItemType.Gig )
		{
			questCount = ( ( ( m_questTypeList.Count( QuestListItemType.Apartment ) + m_questTypeList.Count( QuestListItemType.Gig ) ) + m_questTypeList.Count( QuestListItemType.Cyberpsycho ) ) + m_questTypeList.Count( QuestListItemType.NCPDQuest ) ) + m_questTypeList.Count( QuestListItemType.Courier );
		}
		else
		{
			questCount = m_questTypeList.Count( filterType );
		}
		controller.SetCounter( questCount );
	}

	protected event OnFilterReleased( evt : inkPointerEvent )
	{
		var controller : weak< QuestListFilterButtonController >;
		var i : Int32;
		if( evt.IsAction( 'click' ) )
		{
			controller = ( ( QuestListFilterButtonController )( evt.GetTarget().GetController() ) );
			if( m_activeFilter != controller )
			{
				if( m_activeFilter )
				{
					m_activeFilter.SetActive( false );
				}
				m_activeFilter = controller;
				m_activeFilter.SetActive( true );
				m_virtualListController.SetFilter( m_activeFilter.GetType() );
				m_filterSwich = true;
				for( i = 0; i < m_filters.Size(); i += 1 )
				{
					if( m_filters[ i ] == m_activeFilter )
					{
						m_currentCustomFilterIndex = i;
					}
				}
			}
		}
	}

	private function IsPhoneAvailable() : Bool
	{
		var phoneSystem : weak< PhoneSystem >;
		phoneSystem = ( ( PhoneSystem )( GameInstance.GetScriptableSystemsContainer( m_game ).Get( 'PhoneSystem' ) ) );
		if( phoneSystem )
		{
			return phoneSystem.IsPhoneEnabled();
		}
		return false;
	}

	private function GetListedCategories() : array< gameJournalQuestType >
	{
		var result : array< gameJournalQuestType >;
		result.PushBack( gameJournalQuestType.MainQuest );
		result.PushBack( gameJournalQuestType.SideQuest );
		result.PushBack( gameJournalQuestType.StreetStory );
		result.PushBack( gameJournalQuestType.Contract );
		result.PushBack( gameJournalQuestType.CyberPsycho );
		result.PushBack( gameJournalQuestType.VehicleQuest );
		result.PushBack( gameJournalQuestType.ApartmentQuest );
		result.PushBack( gameJournalQuestType.CourierQuest );
		result.PushBack( gameJournalQuestType.CourierSideQuest );
		return result;
	}

	private function GetDisplayedCategory( category : gameJournalQuestType ) : QuestListItemType
	{
		switch( category )
		{
			case gameJournalQuestType.MainQuest:
				return QuestListItemType.MainQuest;
			case gameJournalQuestType.SideQuest:
			case gameJournalQuestType.MinorQuest:
			case gameJournalQuestType.CourierSideQuest:
				return QuestListItemType.SideQuest;
			case gameJournalQuestType.StreetStory:
				return QuestListItemType.Gig;
			case gameJournalQuestType.CyberPsycho:
				return QuestListItemType.Cyberpsycho;
			case gameJournalQuestType.Contract:
				return QuestListItemType.NCPDQuest;
			case gameJournalQuestType.ApartmentQuest:
				return QuestListItemType.Apartment;
			case gameJournalQuestType.CourierQuest:
				return QuestListItemType.Courier;
		}
		return QuestListItemType.Invalid;
	}

	protected event OnSetUserData( userData : IScriptable )
	{
		var attachment : MessageMenuAttachmentData;
		attachment = ( ( MessageMenuAttachmentData )( userData ) );
		if( attachment )
		{
			m_externallyOpenedQuestHash = attachment.m_entryHash;
		}
	}

	protected event OnReleaseInput( e : inkPointerEvent )
	{
		var trackEvt : RequestChangeTrackedObjective;
		var linkEvt : ActivateLink;
		var mapEvt : ActivateMapLink;
		if( e.IsAction( 'option_switch_next_settings' ) )
		{
			NavigateCustomFilters( ECustomFilterDPadNavigationOption.SelectNext );
			PlaySound( 'Button', 'OnPress' );
		}
		else if( e.IsAction( 'option_switch_prev_settings' ) )
		{
			PlaySound( 'Button', 'OnPress' );
			NavigateCustomFilters( ECustomFilterDPadNavigationOption.SelectPrev );
		}
		else if( e.IsAction( 'sort_list' ) )
		{
			PlaySound( 'Button', 'OnPress' );
			m_virtualListController.SortQuests( true );
			m_buttonHintsController.AddButtonHint( 'sort_list', GetSortTypeName( m_virtualListController.GetQuestSortType() ) );
		}
		else if( e.IsAction( 'toggle_map' ) || e.IsAction( 'open_map_link' ) )
		{
			if( ( !( e.IsHandled() ) && !( e.IsConsumed() ) ) && !( m_detailsPanelCtrl.HasMultipleActionLinks() ) )
			{
				e.Handle();
				e.Consume();
				PlaySound( 'Button', 'OnPress' );
				mapEvt = new ActivateMapLink;
				QueueEvent( mapEvt );
			}
		}
		else if( e.IsAction( 'open_codex_link' ) )
		{
			if( ( !( e.IsHandled() ) && !( e.IsConsumed() ) ) && !( m_detailsPanelCtrl.HasMultipleActionLinks() ) )
			{
				e.Handle();
				e.Consume();
				PlaySound( ' Button', 'OnPress' );
				linkEvt = new ActivateLink;
				QueueEvent( linkEvt );
			}
		}
		else if( e.IsAction( 'track' ) )
		{
			if( ( m_curreentQuest && !( e.IsHandled() ) ) && !( m_listPanelHoverd ) )
			{
				trackEvt = new RequestChangeTrackedObjective;
				trackEvt.m_quest = m_curreentQuest;
				QueueEvent( trackEvt );
			}
		}
	}

	private function NavigateCustomFilters( option : ECustomFilterDPadNavigationOption )
	{
		var filtersAmount : Int32;
		var newCustomFilterIndex : Int32;
		var controller : weak< QuestListFilterButtonController >;
		filtersAmount = m_filters.Size();
		if( filtersAmount == 0 )
		{
			return;
		}
		newCustomFilterIndex = m_currentCustomFilterIndex;
		do
		{
			if( option == ECustomFilterDPadNavigationOption.SelectNext )
			{
				newCustomFilterIndex = ( ( newCustomFilterIndex < ( filtersAmount - 1 ) ) ? ( newCustomFilterIndex + 1 ) : ( 0 ) );
			}
			else if( option == ECustomFilterDPadNavigationOption.SelectPrev )
			{
				newCustomFilterIndex = ( ( newCustomFilterIndex > 0 ) ? ( newCustomFilterIndex - 1 ) : ( filtersAmount - 1 ) );
			}
			controller = ( ( QuestListFilterButtonController )( m_filters[ newCustomFilterIndex ].GetController() ) );
		}
		while( !( controller.IsVisible() || ( newCustomFilterIndex == m_currentCustomFilterIndex ) ) );
		m_currentCustomFilterIndex = newCustomFilterIndex;
		if( m_activeFilter )
		{
			m_activeFilter.SetActive( false );
		}
		m_activeFilter = controller;
		m_activeFilter.SetActive( true );
		m_virtualListController.SetFilter( m_activeFilter.GetType() );
	}

	private function BuildQuestList()
	{
		var itemData : QuestListItemData;
		var targetQuestEntry : weak< JournalEntry >;
		var trackedQuestType : Int32;
		var scriptableListData : array< IScriptable >;
		var i, limit : Int32;
		var questEntry : weak< JournalQuest >;
		m_listData.Clear();
		if( m_externallyOpenedQuestHash != 0 )
		{
			targetQuestEntry = m_journalManager.GetEntry( ( ( Uint32 )( m_externallyOpenedQuestHash ) ) );
			if( ( ( JournalQuestMapPinBase )( targetQuestEntry ) ) )
			{
				targetQuestEntry = ( ( JournalEntry )( GetTopQuestEntry( m_journalManager, targetQuestEntry ) ) );
				if( targetQuestEntry )
				{
					m_externallyOpenedQuestHash = m_journalManager.GetEntryHash( targetQuestEntry );
				}
			}
		}
		for( i = 0; i < m_quests.Size(); i += 1 )
		{
			questEntry = ( ( JournalQuest )( m_quests[ i ] ) );
			itemData = GetQuestListItemData( questEntry, GetDisplayedCategory( questEntry.GetType() ), m_trackedQuest );
			if( m_externallyOpenedQuestHash != 0 )
			{
				if( m_journalManager.GetEntryHash( questEntry ) == m_externallyOpenedQuestHash )
				{
					m_questToOpen = questEntry;
				}
			}
			else if( ( m_questToOpen == NULL ) || ( questEntry == m_trackedQuest ) )
			{
				m_questToOpen = questEntry;
			}
			m_listData.PushBack( itemData );
		}
		for( i = 0; i < m_resolvedQuests.Size(); i += 1 )
		{
			questEntry = ( ( JournalQuest )( m_resolvedQuests[ i ] ) );
			itemData = GetQuestListItemData( questEntry, QuestListItemType.Finished, m_trackedQuest );
			m_listData.PushBack( itemData );
		}
		if( m_questToOpen )
		{
			trackedQuestType = ( ( Int32 )( GetDisplayedCategory( m_questToOpen.GetType() ) ) );
			if( trackedQuestType != ( ( Int32 )( gameJournalQuestType.MainQuest ) ) )
			{
			}
		}
		for( i = 0, limit = m_listData.Size(); i < limit; i += 1 )
		{
			m_questTypeList.PushBack( m_listData[ i ].GetQuestType() );
			scriptableListData.PushBack( ( ( IScriptable )( m_listData[ i ] ) ) );
		}
		m_virtualListController.SetData( scriptableListData );
	}

	public static function GetTopQuestEntry( journalManager : JournalManager, entry : weak< JournalEntry > ) : weak< JournalQuest >
	{
		var lastValidQuestEntry : weak< JournalQuest >;
		var tempEntry : weak< JournalEntry >;
		tempEntry = entry;
		while( tempEntry != NULL )
		{
			tempEntry = journalManager.GetParentEntry( tempEntry );
			if( ( ( JournalQuest )( tempEntry ) ) )
			{
				lastValidQuestEntry = ( ( JournalQuest )( tempEntry ) );
			}
		}
		return lastValidQuestEntry;
	}

	private function GetQuestListItemData( questEntry : JournalQuest, questType : QuestListItemType, optional trackedQuest : JournalQuest ) : QuestListItemData
	{
		var result : QuestListItemData;
		var recommendedLevel : Int32;
		recommendedLevel = GameInstance.GetLevelAssignmentSystem( m_game ).GetLevelAssignment( m_journalManager.GetRecommendedLevelID( questEntry ) );
		result = new QuestListItemData;
		result.m_questType = questType;
		result.m_journalManager = m_journalManager;
		result.m_questData = questEntry;
		result.m_playerLevel = m_playerLevel;
		result.m_recommendedLevel = recommendedLevel;
		result.m_State = m_journalManager.GetEntryState( questEntry );
		result.QuestLastUpdateTime();
		if( trackedQuest != NULL )
		{
			result.m_isTrackedQuest = trackedQuest == questEntry;
		}
		return result;
	}

	protected event OnQuestListItemSelected( e : QuestlListItemSelected )
	{
		m_questData = e.m_questData;
		m_skipAnimation = e.m_skipAnimation;
		m_appliedQuestData = m_questData;
		DisplayQuestData( m_appliedQuestData, m_skipAnimation );
		UpdateTrackingInputHint();
	}

	protected event OnQuestlListItemDelayedHover( evt : QuestlListItemDelayedHover )
	{
		m_isDelayTicking = false;
		if( ( m_questData && ( m_appliedQuestData != m_questData ) ) && m_listPanelHoverd )
		{
			m_appliedQuestData = m_questData;
			DisplayQuestData( m_appliedQuestData, m_skipAnimation );
		}
	}

	private function DisplayQuestData( questData : weak< JournalQuest >, skipAnimation : Bool )
	{
		var i : Int32;
		var data : QuestListItemData;
		var updateEvent : UpdateOpenedQuestEvent;
		if( m_curreentQuest != questData )
		{
			updateEvent = new UpdateOpenedQuestEvent;
			updateEvent.m_openedQuest = questData;
			m_curreentQuest = questData;
			QueueEvent( updateEvent );
			for( i = 0; i < m_listData.Size(); i += 1 )
			{
				data = ( ( QuestListItemData )( m_listData[ i ] ) );
				if( data )
				{
					data.m_isOpenedQuest = updateEvent.m_openedQuest == data.m_questData;
				}
			}
			m_detailsPanelCtrl.SetPhoneAvailable( m_canUsePhone );
			m_detailsPanelCtrl.Setup( questData, m_journalManager, ( ( PhoneSystem )( GameInstance.GetScriptableSystemsContainer( m_game ).Get( 'PhoneSystem' ) ) ), GameInstance.GetMappinSystem( m_game ), GameInstance.GetUISystem( m_game ), m_game, skipAnimation );
			if( m_entryAnimProxy.IsPlaying() )
			{
				m_entryAnimProxy.Stop();
			}
			m_entryAnimProxy = PlayLibraryAnimation( 'entry_fade_in' );
		}
	}

	private function GetFirstObjectiveFromQuest( journalQuest : weak< JournalQuest > ) : weak< JournalQuestObjective >
	{
		var i : Int32;
		var unpackedData : array< weak< JournalEntry > >;
		QuestLogUtils.UnpackRecursive( m_journalManager, journalQuest, unpackedData );
		for( i = 0; i < unpackedData.Size(); i += 1 )
		{
			if( ( ( JournalQuestObjective )( unpackedData[ i ] ) ) )
			{
				return ( ( JournalQuestObjective )( unpackedData[ i ] ) );
			}
		}
		return NULL;
	}

	private function GetSortTypeName( currentQuestSortType : QuestListSortType ) : String
	{
		var sortTypeName : String;
		sortTypeName = ( GetLocalizedText( "UI-Quests-Sorting-Sort" ) + GetLocalizedText( "Common-Characters-Semicolon" ) ) + " ";
		if( currentQuestSortType == QuestListSortType.Distance )
		{
			sortTypeName += GetLocalizedText( "UI-Quests-Sorting-Distance" );
		}
		else
		{
			sortTypeName += GetLocalizedText( "UI-Quests-Sorting-Update" );
		}
		return sortTypeName;
	}

	protected event OnRequestChangeTrackedObjective( e : RequestChangeTrackedObjective )
	{
		var i : Int32;
		var data : QuestListItemData;
		var updateEvent : UpdateTrackedObjectiveEvent;
		var trackedEntry : weak< JournalEntry >;
		if( m_journalManager.GetEntryState( e.m_quest ) != gameJournalEntryState.Failed && m_journalManager.GetEntryState( e.m_quest ) != gameJournalEntryState.Succeeded )
		{
			if( e.m_forceSelectEntry != 0 )
			{
				m_virtualListController.SelectItemByHash( e.m_forceSelectEntry );
			}
			if( e.m_objective == NULL )
			{
				e.m_objective = GetFirstObjectiveFromQuest( e.m_quest );
			}
			trackedEntry = m_journalManager.GetTrackedEntry();
			if( ( ( m_trackedQuest == e.m_quest ) && ( m_trackedQuest != NULL ) ) || ( ( trackedEntry == e.m_objective ) && ( trackedEntry != NULL ) ) )
			{
				m_journalManager.UntrackEntry();
				updateEvent = new UpdateTrackedObjectiveEvent;
				updateEvent.m_trackedObjective = NULL;
				updateEvent.m_trackedQuest = NULL;
				m_trackedQuest = NULL;
			}
			else
			{
				m_journalManager.TrackEntry( e.m_objective );
				updateEvent = new UpdateTrackedObjectiveEvent;
				updateEvent.m_trackedObjective = e.m_objective;
				updateEvent.m_trackedQuest = GetTopQuestEntry( m_journalManager, e.m_objective );
				m_trackedQuest = updateEvent.m_trackedQuest;
			}
			QueueEvent( updateEvent );
			PlaySound( 'MapPin', 'OnCreate' );
			PlayRumble( RumbleStrength.SuperLight, RumbleType.Fast, RumblePosition.Right );
			for( i = 0; i < m_listData.Size(); i += 1 )
			{
				data = ( ( QuestListItemData )( m_listData[ i ] ) );
				if( data )
				{
					data.m_isTrackedQuest = updateEvent.m_trackedQuest == data.m_questData;
				}
			}
			UpdateTrackingInputHint();
		}
	}

	private function UpdateTrackingInputHint()
	{
		if( m_trackedQuest == m_questData )
		{
			m_buttonHintsController.AddButtonHint( 'track', GetLocalizedText( "UI-Quests-Untrack" ) );
		}
		else
		{
			m_buttonHintsController.AddButtonHint( 'track', GetLocalizedText( "UI-UserActions-TrackQuest" ) );
		}
	}

}

class QuestListItemHoverOverEvent extends Event
{
	var m_isQuestResolved : Bool;
}

class QuestObjectiveHoverOverEvent extends Event
{
}

class QuestListItemHoverOutEvent extends Event
{
}

class QuestObjectiveHoverOutEvent extends Event
{
}

class QuestListHeaderData
{
	var m_type : Int32;
	var m_nameLocKey : CName;
}

class QuestListDistanceData
{
	var m_objective : weak< JournalQuestObjective >;
	var m_distance : Float;
}

class QuestListItemData
{
	var m_questType : QuestListItemType;
	var m_lastUpdateTimestamp : GameTime;
	var m_isTrackedQuest : Bool;
	var m_isOpenedQuest : Bool;
	var m_questData : weak< JournalQuest >;
	var m_journalManager : weak< JournalManager >;
	var m_playerLevel : Int32;
	var m_recommendedLevel : Int32;
	var m_State : gameJournalEntryState;
	var m_distance : Float;
	private var m_distancesFetched : Bool;
	private var m_objectivesDistances : array< QuestListDistanceData >;

	public function GetFirstObjective() : weak< JournalQuestObjective >
	{
		var i : Int32;
		var unpackedData : array< weak< JournalEntry > >;
		QuestLogUtils.UnpackRecursive( m_journalManager, m_questData, unpackedData );
		for( i = 0; i < unpackedData.Size(); i += 1 )
		{
			if( ( ( JournalQuestObjective )( unpackedData[ i ] ) ) )
			{
				return ( ( JournalQuestObjective )( unpackedData[ i ] ) );
			}
		}
		return NULL;
	}

	public function isVisited() : Bool
	{
		return m_journalManager.IsEntryVisited( m_questData );
	}

	public function isDone() : Bool
	{
		return m_State == gameJournalEntryState.Succeeded || m_State == gameJournalEntryState.Failed;
	}

	public function SetVisited()
	{
		if( !( m_journalManager.IsEntryVisited( m_questData ) ) )
		{
			m_journalManager.SetEntryVisited( m_questData, true );
		}
	}

	public function GetDistances() : array< QuestListDistanceData >
	{
		var i : Int32;
		var unpackedData : array< weak< JournalEntry > >;
		var distanceData : QuestListDistanceData;
		if( !( m_distancesFetched ) )
		{
			QuestLogUtils.UnpackRecursive( m_journalManager, m_questData, unpackedData );
			for( i = 0; i < unpackedData.Size(); i += 1 )
			{
				if( ( ( JournalQuestObjective )( unpackedData[ i ] ) ) )
				{
					distanceData = new QuestListDistanceData;
					distanceData.m_objective = ( ( weak< weak< JournalQuestObjective > > )( unpackedData[ i ] ) );
					distanceData.m_distance = m_journalManager.GetDistanceToNearestMappin( ( ( JournalQuestObjective )( unpackedData[ i ] ) ), QuestLogUtils.GetDefaultFilter() );
					m_objectivesDistances.PushBack( distanceData );
				}
			}
			m_distancesFetched = true;
		}
		return m_objectivesDistances;
	}

	public function QuestLastUpdateTime()
	{
		var i : Int32;
		var time : GameTime;
		var unpackedData : array< weak< JournalEntry > >;
		time = GameTime.MakeGameTime( 0, 0, 0, 0 );
		QuestLogUtils.UnpackRecursive( m_journalManager, m_questData, unpackedData );
		for( i = 0; i < unpackedData.Size(); i += 1 )
		{
			time = m_journalManager.GetEntryTimestamp( unpackedData[ i ] );
			if( m_lastUpdateTimestamp < time )
			{
				m_lastUpdateTimestamp = time;
			}
		}
	}

	public function GetQuestType() : QuestListItemType
	{
		return m_questType;
	}

	public function GetEntryHash() : Int32
	{
		if( m_journalManager )
		{
			return m_journalManager.GetEntryHash( m_questData );
		}
		return -1;
	}

	public function GetNearestDistance() : QuestListDistanceData
	{
		var i : Int32;
		var result : QuestListDistanceData;
		var distances : array< QuestListDistanceData >;
		distances = GetDistances();
		for( i = 0; i < distances.Size(); i += 1 )
		{
			if( result == NULL )
			{
				result = distances[ i ];
				continue;
			}
			if( ( result.m_distance <= 0.0 ) || ( distances[ i ].m_distance < result.m_distance ) )
			{
				result = distances[ i ];
			}
		}
		m_distance = result.m_distance;
		return result;
	}

	public function GetTrackedOrNearest() : QuestListDistanceData
	{
		var i : Int32;
		var unpackedData : array< weak< JournalEntry > >;
		var result : QuestListDistanceData;
		var trackedObjective : weak< JournalEntry >;
		trackedObjective = m_journalManager.GetTrackedEntry();
		if( m_isTrackedQuest )
		{
			QuestLogUtils.UnpackRecursive( m_journalManager, m_questData, unpackedData );
			for( i = 0; i < unpackedData.Size(); i += 1 )
			{
				if( ( ( JournalQuestObjective )( unpackedData[ i ] ) ) )
				{
					if( unpackedData[ i ] == trackedObjective )
					{
						result = new QuestListDistanceData;
						result.m_objective = ( ( JournalQuestObjective )( unpackedData[ i ] ) );
						result.m_distance = m_journalManager.GetDistanceToNearestMappin( ( ( JournalQuestObjective )( unpackedData[ i ] ) ) );
						return result;
					}
				}
			}
		}
		else
		{
			return GetNearestDistance();
		}
		return NULL;
	}

}

class QuestListHeaderClicked extends Event
{
	var m_questType : Int32;
}

class QuestlListItemSelected extends Event
{
	var m_questData : weak< JournalQuest >;
	var m_skipAnimation : Bool;
	var m_hash : Int32;
	var m_selectionIndex : Uint32;
}

class QuestlListItemHover extends Event
{
	var m_hash : Int32;
}

class QuestlListItemDelayedHover extends Event
{
}

class ActivateLink extends Event
{
}

class ActivateMapLink extends Event
{
}

class QuestListVirtualTemplateClassifier extends inkVirtualItemTemplateClassifier
{

	public virtual function ClassifyItem( data : Variant ) : Uint32
	{
		return 0;
	}

}

class QuestListVirtualDataView extends ScriptableDataView
{
	private var m_filterType : QuestListItemType;
	private var m_compareBuilder : CompareBuilder;
	private var m_currentQuestSortType : QuestListSortType;
	default m_currentQuestSortType = QuestListSortType.Updated;

	public function Setup()
	{
		m_compareBuilder = CompareBuilder.Make();
	}

	public function SetFilter( type : QuestListItemType )
	{
		m_filterType = type;
		Filter();
	}

	public override function FilterItem( data : IScriptable ) : Bool
	{
		var questData : QuestListItemData;
		questData = ( ( QuestListItemData )( data ) );
		if( !( questData ) )
		{
			return false;
		}
		if( m_filterType == QuestListItemType.All )
		{
			return questData.m_questType != QuestListItemType.Finished;
		}
		if( m_filterType == QuestListItemType.MainQuest )
		{
			return questData.m_questType == QuestListItemType.SideQuest || questData.m_questType == QuestListItemType.MainQuest;
		}
		if( m_filterType == QuestListItemType.Gig )
		{
			return ( ( ( questData.m_questType == QuestListItemType.Apartment || questData.m_questType == QuestListItemType.Gig ) || questData.m_questType == QuestListItemType.Cyberpsycho ) || questData.m_questType == QuestListItemType.NCPDQuest ) || questData.m_questType == QuestListItemType.Courier;
		}
		return questData.m_questType == m_filterType;
	}

	public function SetSortType( type : QuestListSortType )
	{
		m_currentQuestSortType = type;
	}

	protected override function SortItem( left : IScriptable, right : IScriptable ) : Bool
	{
		var leftData, rightData : QuestListItemData;
		leftData = ( ( QuestListItemData )( left ) );
		rightData = ( ( QuestListItemData )( right ) );
		m_compareBuilder.Reset();
		if( leftData && rightData )
		{
			m_compareBuilder.IntAsc( ( ( Int32 )( leftData.m_questType ) ), ( ( Int32 )( rightData.m_questType ) ) );
			if( m_currentQuestSortType == QuestListSortType.Distance )
			{
				m_compareBuilder.FloatAsc( leftData.m_distance, rightData.m_distance );
			}
			else
			{
				m_compareBuilder.GameTimeDesc( leftData.m_lastUpdateTimestamp, rightData.m_lastUpdateTimestamp );
			}
		}
		return m_compareBuilder.GetBool();
	}

}

class ScrollToJournalEntryEvent extends Event
{
	var m_hash : Int32;
}

class QuestListVirtualController extends inkVirtualListController
{
	protected var m_dataView : QuestListVirtualDataView;
	protected var m_dataSource : ScriptableDataSource;
	protected var m_classifier : QuestListVirtualTemplateClassifier;
	private var m_controller : weak< QuestMissionLinkController >;
	private var m_uiScriptableSystem : weak< UIScriptableSystem >;
	private var m_questSortType : QuestListSortType;

	protected event OnInitialize()
	{
		m_dataView = new QuestListVirtualDataView;
		m_dataSource = new ScriptableDataSource;
		m_classifier = new QuestListVirtualTemplateClassifier;
		m_questSortType = QuestListSortType.Updated;
		m_dataView.Setup();
		m_dataView.EnableSorting();
		m_dataView.SetSource( m_dataSource );
		SetSource( m_dataView );
		SetClassifier( m_classifier );
	}

	protected event OnUninitialize()
	{
		m_dataView.SetSource( NULL );
		SetSource( NULL );
		SetClassifier( NULL );
		m_dataSource = NULL;
	}

	protected event OnScrollToJournalEntry( evt : ScrollToJournalEntryEvent )
	{
		SetFilter( QuestListItemType.All );
		SelectItemByHash( evt.m_hash );
	}

	public virtual function SetData( const data : ref< array< IScriptable > >, optional sortOnce : Bool )
	{
		m_dataSource.Reset( data );
		EnableSorting();
		if( sortOnce )
		{
			DisableSorting();
		}
	}

	public virtual function GetDataSize() : Int32
	{
		return ( ( Int32 )( m_dataView.Size() ) );
	}

	public virtual function EnableSorting()
	{
		m_dataView.EnableSorting();
	}

	public virtual function DisableSorting()
	{
		m_dataView.DisableSorting();
	}

	public virtual function SortQuests( cycleSortType : Bool )
	{
		m_dataView.EnableSorting();
		if( cycleSortType )
		{
			m_questSortType = ( ( ( ( Int32 )( m_questSortType ) ) < ( ( ( Int32 )( QuestListSortType.Size ) ) - 1 ) ) ? ( ( ( QuestListSortType )( ( ( Int32 )( m_questSortType ) ) + 1 ) ) ) : ( QuestListSortType.Updated ) );
		}
		m_dataView.SetSortType( m_questSortType );
		m_dataView.Sort();
		m_dataView.DisableSorting();
	}

	public function GetQuestSortType() : QuestListSortType
	{
		return m_questSortType;
	}

	public virtual function IsSortingEnabled() : Bool
	{
		return m_dataView.IsSortingEnabled();
	}

	public function SetFilter( type : QuestListItemType )
	{
		m_dataView.SetFilter( type );
		SortQuests( false );
	}

	public function SelectItemByHash( questHash : Int32 )
	{
		var i, targetIndex, size : Int32;
		var currentData : weak< QuestListItemData >;
		targetIndex = -1;
		size = ( ( Int32 )( m_dataView.Size() ) );
		for( i = 0; i < size; i += 1 )
		{
			currentData = ( ( QuestListItemData )( m_dataView.GetItem( ( ( Uint32 )( i ) ) ) ) );
			if( currentData.GetEntryHash() == ( ( Int32 )( questHash ) ) )
			{
				targetIndex = i;
				break;
			}
		}
		if( targetIndex != -1 )
		{
			ForceSelectIndex( ( ( Uint32 )( targetIndex ) ), currentData.m_questData );
		}
	}

	private function ForceSelectIndex( idx : Uint32, questRecord : weak< JournalQuest > )
	{
		var evt : QuestlListItemSelected;
		ToggleItem( idx );
		ScrollToIndex( idx );
		evt = new QuestlListItemSelected;
		evt.m_selectionIndex = idx;
		evt.m_questData = questRecord;
		QueueEvent( evt );
	}

}

class QuestDetailsObjectiveController extends inkLogicController
{
	private editable var m_objectiveName : inkTextRef;
	private editable var m_trackingMarker : inkWidgetRef;
	private editable var m_root : inkWidgetRef;
	private var m_objective : weak< JournalQuestObjective >;
	private var m_journalManager : weak< JournalManager >;
	private var m_hovered : Bool;
	private var m_isTracked : Bool;

	protected event OnInitialize()
	{
		GetRootWidget().RegisterToCallback( 'OnRelease', this, 'OnRelease' );
		GetRootWidget().RegisterToCallback( 'OnHoverOver', this, 'OnHoverOver' );
		GetRootWidget().RegisterToCallback( 'OnHoverOut', this, 'OnHoverOut' );
	}

	public function Setup( objective : weak< JournalQuestObjective >, journalManager : weak< JournalManager >, currentCounter, totalCounter : Int32, optional isTracked : Bool )
	{
		var finalTitle : String;
		m_journalManager = journalManager;
		m_objective = objective;
		finalTitle = objective.GetDescription();
		if( totalCounter > 0 )
		{
			finalTitle = ( ( ( ( GetLocalizedText( finalTitle ) + " [" ) + IntToString( currentCounter ) ) + "/" ) + IntToString( totalCounter ) ) + "]";
		}
		if( m_journalManager.GetIsObjectiveOptional( objective ) )
		{
			finalTitle = ( ( GetLocalizedText( finalTitle ) + " [" ) + GetLocalizedText( "UI-ScriptExports-Optional0" ) ) + "]";
		}
		inkTextRef.SetText( m_objectiveName, finalTitle );
		m_isTracked = isTracked;
		inkWidgetRef.SetState( m_trackingMarker, ( ( m_isTracked ) ? ( 'Tracked' ) : ( 'Default' ) ) );
		m_hovered = false;
		UpdateState();
	}

	protected event OnUpdateTrackedObjectiveEvent( e : UpdateTrackedObjectiveEvent )
	{
		m_isTracked = m_objective == e.m_trackedObjective;
		inkWidgetRef.SetState( m_trackingMarker, ( ( m_isTracked ) ? ( 'Tracked' ) : ( 'Default' ) ) );
	}

	protected event OnRelease( e : inkPointerEvent )
	{
		var evt : RequestChangeTrackedObjective;
		if( e.IsAction( 'click' ) || ( e.IsAction( 'track' ) && !( m_isTracked ) ) )
		{
			evt = new RequestChangeTrackedObjective;
			evt.m_objective = m_objective;
			e.Handle();
			QueueEvent( evt );
		}
	}

	public function UpdateState()
	{
		var targetState : CName;
		targetState = 'Default';
		if( m_hovered )
		{
			targetState = 'Hover';
		}
		inkWidgetRef.SetState( m_root, targetState );
	}

	protected event OnHoverOver( e : inkPointerEvent )
	{
		var evt : QuestObjectiveHoverOverEvent;
		evt = new QuestObjectiveHoverOverEvent;
		QueueEvent( evt );
		m_hovered = true;
		UpdateState();
	}

	protected event OnHoverOut( e : inkPointerEvent )
	{
		var evt : QuestObjectiveHoverOutEvent;
		evt = new QuestObjectiveHoverOutEvent;
		QueueEvent( evt );
		m_hovered = false;
		UpdateState();
	}

}

class UpdateTrackedObjectiveEvent extends Event
{
	var m_trackedObjective : weak< JournalQuestObjective >;
	var m_trackedQuest : weak< JournalQuest >;
}

class UpdateOpenedQuestEvent extends Event
{
	var m_openedQuest : weak< JournalQuest >;
}

class RequestChangeTrackedObjective extends Event
{
	var m_objective : weak< JournalQuestObjective >;
	var m_quest : weak< JournalQuest >;
	var m_forceSelectEntry : Int32;
}

class QuestListFilterButtonController extends inkLogicController
{
	editable var m_icon : inkImageRef;
	editable var m_counter : inkTextRef;
	var m_filterType : QuestListItemType;
	var m_hovered : Bool;
	var m_active : Bool;

	protected event OnInitialize()
	{
		GetRootWidget().RegisterToCallback( 'OnHoverOver', this, 'OnHoverOver' );
		GetRootWidget().RegisterToCallback( 'OnHoverOut', this, 'OnHoverOut' );
	}

	protected event OnHoverOver( evt : inkPointerEvent )
	{
		m_hovered = true;
		UpdateState();
	}

	protected event OnHoverOut( evt : inkPointerEvent )
	{
		m_hovered = false;
		UpdateState();
	}

	public function SetData( filterType : QuestListItemType )
	{
		m_filterType = filterType;
		if( m_filterType == QuestListItemType.Apartment )
		{
			inkImageRef.SetTexturePart( m_icon, 'apartment_car_buy' );
		}
		else
		{
			inkImageRef.SetTexturePart( m_icon, QuestTypeIconUtils.GetFilterIcon( m_filterType ) );
		}
		inkImageRef.SetState( m_icon, QuestTypeIconUtils.GetIconState( m_filterType ) );
	}

	public function SetCounter( count : Int32 )
	{
		inkTextRef.SetState( m_counter, QuestTypeIconUtils.GetIconState( m_filterType ) );
		inkTextRef.SetText( m_counter, ( ( String )( count ) ) );
		GetRootWidget().SetVisible( count );
	}

	public function GetType() : QuestListItemType
	{
		return m_filterType;
	}

	public function SetActive( active : Bool )
	{
		m_active = active;
		UpdateState();
	}

	public function IsVisible() : Bool
	{
		return GetRootWidget() && GetRootWidget().IsVisible();
	}

	private function UpdateState()
	{
		if( m_active )
		{
			GetRootWidget().SetState( 'Active' );
			return;
		}
		GetRootWidget().SetState( ( ( m_hovered ) ? ( 'Hover' ) : ( 'Default' ) ) );
	}

}

class FilterButtonSpawnedData
{
	var m_type : Int32;
}

enum QuestListItemType
{
	MainQuest = 0,
	SideQuest = 1,
	Gig = 2,
	Cyberpsycho = 3,
	NCPDQuest = 4,
	Apartment = 5,
	Courier = 6,
	Finished = 7,
	Count = 8,
	All = 9,
	Invalid = -1,
}

enum QuestListSortType
{
	Updated = 0,
	Distance = 1,
	Size = 2,
}

class CodexPopupClosedEvent extends Event
{
}

