class MessengerUtils
{

	public static function GetCallableAndNonEmptyContacts( journal : JournalManager ) : array< IScriptable >
	{
		var i : Int32;
		var contact : ContactData;
		var contacts : array< IScriptable >;
		contacts = journal.GetContactDataArray( false, true );
		for( i = contacts.Size() - 1; i >= 0; i -= 1 )
		{
			contact = ( ( ContactData )( contacts[ i ] ) );
			if( contact == NULL )
			{
				continue;
			}
			if( ( !( contact.isCallable ) && ( contact.messagesCount == 0 ) ) && ( contact.repliesCount == 0 ) )
			{
				contacts.Erase( i );
			}
		}
		return contacts;
	}

	public static function GetSimpleContactDataArray( journal : JournalManager, includeUnknown : Bool, skipEmpty : Bool, includeWithNoUnread : Bool, optional activeDataSync : weak< MessengerContactSyncData > ) : array< IScriptable >
	{
		var i : Int32;
		var context : JournalRequestContext;
		var entries : array< weak< JournalEntry > >;
		var contactEntry : weak< JournalContact >;
		var trackedEntriesHashList : array< Int32 >;
		var contactDataArray : array< IScriptable >;
		context.stateFilter.active = true;
		journal.GetContacts( context, entries );
		trackedEntriesHashList = FetchTrackedQuestCodexLinks( journal, context );
		for( i = 0; i < entries.Size(); i += 1 )
		{
			contactEntry = ( ( JournalContact )( entries[ i ] ) );
			AddContactData( journal, contactDataArray, contactEntry, trackedEntriesHashList, includeUnknown, skipEmpty, includeWithNoUnread, false, activeDataSync );
		}
		return contactDataArray;
	}

	public static function GetMessageDataArrayForContact( journal : JournalManager, concactHash : Int32, includeUnknown : Bool, skipEmpty : Bool, optional activeDataSync : weak< MessengerContactSyncData > ) : array< IScriptable >
	{
		var context : JournalRequestContext;
		var contactEntry : weak< JournalContact >;
		var contactDataArray : array< IScriptable >;
		var trackedEntriesHashList : array< Int32 >;
		context.stateFilter.active = true;
		contactEntry = ( ( JournalContact )( journal.GetEntry( ( ( Uint32 )( concactHash ) ) ) ) );
		if( contactEntry == NULL )
		{
			return contactDataArray;
		}
		trackedEntriesHashList = FetchTrackedQuestCodexLinks( journal, context );
		AddContactData( journal, contactDataArray, contactEntry, trackedEntriesHashList, includeUnknown, skipEmpty, true, true, activeDataSync );
		return contactDataArray;
	}

	private static function AddContactData( journal : JournalManager, contactDataArray : ref< array< IScriptable > >, contactEntry : JournalContact, const trackedEntriesHashList : ref< array< Int32 > >, includeUnknown : Bool, skipEmpty : Bool, includeWithNoUnread : Bool, includeWithNoReplies : Bool, optional activeDataSync : weak< MessengerContactSyncData > )
	{
		var j, conversationsCount : Int32;
		var messagesReceived : array< weak< JournalEntry > >;
		var playerReplies : array< weak< JournalEntry > >;
		var conversations : array< weak< JournalEntry > >;
		var conversationEntry : weak< JournalPhoneConversation >;
		var singleThreadData : ContactData;
		var multiThreadData : ContactData;
		if( includeUnknown || contactEntry.IsKnown( journal ) )
		{
			messagesReceived.Clear();
			playerReplies.Clear();
			journal.GetConversations( contactEntry, conversations );
			journal.GetFlattenedMessagesAndChoices( contactEntry, messagesReceived, playerReplies );
			if( ( skipEmpty && ( messagesReceived.Size() <= 0 ) ) && ( playerReplies.Size() <= 0 ) )
			{
				return;
			}
			conversationsCount = conversations.Size();
			if( conversationsCount > 1 )
			{
				for( j = 0; j < conversationsCount; j += 1 )
				{
					messagesReceived.Clear();
					playerReplies.Clear();
					conversationEntry = ( ( JournalPhoneConversation )( conversations[ j ] ) );
					journal.GetMessagesAndChoices( conversationEntry, messagesReceived, playerReplies );
					multiThreadData = new ContactData;
					multiThreadData.id = conversationEntry.GetId();
					multiThreadData.hash = journal.GetEntryHash( contactEntry );
					multiThreadData.conversationHash = journal.GetEntryHash( conversationEntry );
					multiThreadData.localizedName = contactEntry.GetLocalizedName( journal );
					multiThreadData.avatarID = contactEntry.GetAvatarID( journal );
					multiThreadData.activeDataSync = activeDataSync;
					multiThreadData.type = MessengerContactType.MultiThread;
					multiThreadData.isCallable = contactEntry.IsCallable( journal );
					multiThreadData.contactId = contactEntry.GetId();
					SetTitle( multiThreadData, conversationEntry );
					GetContactMessageData( multiThreadData, journal, messagesReceived, playerReplies );
					SetTimestamp( multiThreadData, journal, messagesReceived, conversationEntry );
					multiThreadData.questRelated = ContainsQuestRelatedMessage( journal, trackedEntriesHashList, messagesReceived );
					if( includeWithNoUnread || ( multiThreadData.unreadMessages.Size() > 0 ) )
					{
						contactDataArray.PushBack( multiThreadData );
					}
					else if( ( includeWithNoReplies || multiThreadData.hasQuestImportantReply ) || multiThreadData.playerCanReply )
					{
						contactDataArray.PushBack( multiThreadData );
					}
				}
			}
			else
			{
				conversationEntry = ( ( JournalPhoneConversation )( ( ( conversationsCount == 1 ) ? ( conversations[ 0 ] ) : ( NULL ) ) ) );
				singleThreadData = new ContactData;
				singleThreadData.id = contactEntry.GetId();
				singleThreadData.hash = journal.GetEntryHash( contactEntry );
				singleThreadData.conversationHash = GetConversationHash( journal, conversationEntry );
				singleThreadData.localizedName = contactEntry.GetLocalizedName( journal );
				singleThreadData.avatarID = contactEntry.GetAvatarID( journal );
				singleThreadData.activeDataSync = activeDataSync;
				singleThreadData.type = MessengerContactType.SingleThread;
				singleThreadData.isCallable = contactEntry.IsCallable( journal );
				singleThreadData.contactId = contactEntry.GetId();
				SetTitle( singleThreadData, conversationEntry );
				GetContactMessageData( singleThreadData, journal, messagesReceived, playerReplies );
				SetTimestamp( singleThreadData, journal, messagesReceived, conversationEntry );
				singleThreadData.questRelated = ContainsQuestRelatedMessage( journal, trackedEntriesHashList, messagesReceived );
				if( includeWithNoUnread || ( singleThreadData.unreadMessages.Size() > 0 ) )
				{
					contactDataArray.PushBack( singleThreadData );
				}
				else if( ( includeWithNoReplies || singleThreadData.hasQuestImportantReply ) || singleThreadData.playerCanReply )
				{
					contactDataArray.PushBack( singleThreadData );
				}
			}
		}
	}

	private static function GetConversationHash( journal : JournalManager, conversationEntry : weak< JournalPhoneConversation > ) : Int32
	{
		if( conversationEntry )
		{
			return journal.GetEntryHash( conversationEntry );
		}
		return 0;
	}

	private static function SetTitle( out contactData : ContactData, conversationEntry : weak< JournalPhoneConversation > )
	{
		var localizedPreview : String;
		localizedPreview = conversationEntry.GetTitle();
		if( IsStringValid( localizedPreview ) )
		{
			contactData.hasValidTitle = true;
			contactData.localizedPreview = localizedPreview;
		}
		else
		{
			contactData.hasValidTitle = false;
		}
	}

	private static function SetTimestamp( out contactData : ContactData, journal : JournalManager, const messagesReceived : ref< array< weak< JournalEntry > > >, const conversationEntry : weak< JournalPhoneConversation > )
	{
		if( messagesReceived.Size() > 0 )
		{
			contactData.timeStamp = journal.GetEntryTimestamp( messagesReceived.Last() );
		}
		else if( conversationEntry )
		{
			contactData.timeStamp = journal.GetEntryTimestamp( conversationEntry );
		}
	}

	public static function GetContactMessageData( out contactData : ContactData, journal : JournalManager, const messagesReceived : ref< array< weak< JournalEntry > > >, const playerReplies : ref< array< weak< JournalEntry > > > )
	{
		var lastMessegeRecived : weak< JournalPhoneMessage >;
		var lastMessegeSent : weak< JournalPhoneChoiceEntry >;
		var choiceEntry : weak< JournalPhoneChoiceEntry >;
		var j : Int32;
		contactData.playerCanReply = playerReplies.Size() > 0;
		contactData.unreadMessages.Clear();
		for( j = 0; j < messagesReceived.Size(); j += 1 )
		{
			if( !( journal.IsEntryVisited( messagesReceived[ j ] ) ) )
			{
				contactData.unreadMessages.PushBack( journal.GetEntryHash( messagesReceived[ j ] ) );
			}
		}
		if( messagesReceived.Size() > 0 )
		{
			lastMessegeRecived = ( ( JournalPhoneMessage )( messagesReceived.Last() ) );
			if( lastMessegeRecived )
			{
				contactData.lastMesssagePreview = lastMessegeRecived.GetText();
				contactData.playerIsLastSender = false;
			}
			else
			{
				lastMessegeSent = ( ( JournalPhoneChoiceEntry )( messagesReceived.Last() ) );
				contactData.lastMesssagePreview = lastMessegeSent.GetText();
				contactData.playerIsLastSender = true;
			}
		}
		for( j = 0; j < playerReplies.Size(); j += 1 )
		{
			choiceEntry = ( ( JournalPhoneChoiceEntry )( playerReplies[ j ] ) );
			if( choiceEntry.IsQuestImportant() )
			{
				contactData.hasQuestImportantReply = true;
				break;
			}
		}
	}

	public static function RefreshQuestRelatedStatus( out contactData : ContactData, journal : JournalManager, const messagesReceived : ref< array< weak< JournalEntry > > > )
	{
		var context : JournalRequestContext;
		var trackedEntriesHashList : array< Int32 >;
		context.stateFilter.active = true;
		trackedEntriesHashList = FetchTrackedQuestCodexLinks( journal, context );
		contactData.questRelated = ContainsQuestRelatedMessage( journal, trackedEntriesHashList, messagesReceived );
	}

	public static function ContainsQuestRelatedMessage( journal : JournalManager, const trackedEntriesHashList : ref< array< Int32 > >, const messagesReceived : ref< array< weak< JournalEntry > > > ) : Bool
	{
		var j : Int32;
		var hash : Int32;
		for( j = 0; j < messagesReceived.Size(); j += 1 )
		{
			hash = journal.GetEntryHash( messagesReceived[ j ] );
			if( trackedEntriesHashList.Contains( hash ) )
			{
				return true;
			}
		}
		return false;
	}

	public constexpr static function HasPhoneObjective( journal : JournalManager ) : Bool
	{
		return false;
	}

	public static function HasQuestImportantCalls( journal : JournalManager ) : Bool
	{
		var context : JournalRequestContext;
		var entries : array< weak< JournalEntry > >;
		var i : Int32;
		var contactEntry : weak< JournalContact >;
		var hash : Int32;
		var trackedChildEntriesHashList : array< Int32 >;
		context.stateFilter.active = true;
		trackedChildEntriesHashList = FetchTrackedQuestCodexLinks( journal, context );
		if( trackedChildEntriesHashList.Size() == 0 )
		{
			return false;
		}
		journal.GetContacts( context, entries );
		for( i = 0; i < entries.Size(); i += 1 )
		{
			contactEntry = ( ( JournalContact )( entries[ i ] ) );
			if( contactEntry )
			{
				if( !( contactEntry.IsCallable( journal ) ) || !( contactEntry.IsKnown( journal ) ) )
				{
					continue;
				}
				hash = journal.GetEntryHash( contactEntry );
				if( trackedChildEntriesHashList.Contains( hash ) )
				{
					return true;
				}
			}
		}
		return false;
	}

	public static function HasQuestImportantMessages( journal : JournalManager ) : Bool
	{
		var context : JournalRequestContext;
		var entries : array< weak< JournalEntry > >;
		var trackedEntriesHashList : array< Int32 >;
		var contactEntry : weak< JournalContact >;
		var i : Int32;
		var messagesReceived : array< weak< JournalEntry > >;
		var playerReplies : array< weak< JournalEntry > >;
		context.stateFilter.active = true;
		trackedEntriesHashList = FetchTrackedQuestCodexLinks( journal, context );
		if( trackedEntriesHashList.Size() == 0 )
		{
			return false;
		}
		journal.GetContacts( context, entries );
		for( i = 0; i < entries.Size(); i += 1 )
		{
			contactEntry = ( ( JournalContact )( entries[ i ] ) );
			if( contactEntry && contactEntry.IsKnown( journal ) )
			{
				messagesReceived.Clear();
				playerReplies.Clear();
				journal.GetFlattenedMessagesAndChoices( contactEntry, messagesReceived, playerReplies );
				if( ContainsQuestRelatedMessage( journal, trackedEntriesHashList, messagesReceived ) )
				{
					return true;
				}
			}
		}
		return false;
	}

	public static function FetchTrackedQuestCodexLinks( journal : JournalManager, context : JournalRequestContext ) : array< Int32 >
	{
		var trackedObjective : JournalQuestObjective;
		var trackedChildEntriesList : array< weak< JournalEntry > >;
		var trackedChildEntry : weak< JournalQuestCodexLink >;
		var trackedChildEntriesCount : Int32;
		var j : Int32;
		var trackedChildEntriesHashList : array< Int32 >;
		trackedObjective = ( ( JournalQuestObjective )( journal.GetTrackedEntry() ) );
		if( trackedObjective != NULL )
		{
			journal.GetChildren( trackedObjective, context.stateFilter, trackedChildEntriesList );
			trackedChildEntriesCount = trackedChildEntriesList.Size();
			for( j = 0; j < trackedChildEntriesCount; j = j + 1 )
			{
				trackedChildEntry = ( ( JournalQuestCodexLink )( trackedChildEntriesList[ j ] ) );
				if( trackedChildEntry )
				{
					trackedChildEntriesHashList.PushBack( ( ( Int32 )( trackedChildEntry.GetLinkPathHash() ) ) );
				}
			}
		}
		return trackedChildEntriesHashList;
	}

	public static function GetUnreadMessagesCount( journal : JournalManager, contactEntry : weak< JournalContact > ) : Int32
	{
		var messagesReceived : array< weak< JournalEntry > >;
		var playerReplies : array< weak< JournalEntry > >;
		journal.GetFlattenedMessagesAndChoices( contactEntry, messagesReceived, playerReplies );
		return messagesReceived.Size();
	}

	public static function FindConversationHash( journal : JournalManager, entry : weak< JournalEntry > ) : Int32
	{
		var conversation : JournalPhoneConversation;
		while( entry != NULL )
		{
			conversation = ( ( JournalPhoneConversation )( entry ) );
			if( conversation )
			{
				return journal.GetEntryHash( conversation );
			}
			entry = journal.GetParentEntry( entry );
		}
		return 0;
	}

	public static function FindContactHash( journal : JournalManager, entry : weak< JournalEntry > ) : Int32
	{
		var contact : JournalContact;
		while( entry != NULL )
		{
			contact = ( ( JournalContact )( entry ) );
			if( contact )
			{
				return journal.GetEntryHash( contact );
			}
			entry = journal.GetParentEntry( entry );
		}
		return 0;
	}

}

