
// networkJobs provides functions to perform network jobs
// An example is downloading a user's inbox messages
// All of these jobs are run in the background by backgroundJobs (see backgroundJobs.go)

package networkJobs

//TODO: Add all of these jobs to backgroundJobs.go

//TODO: Implement these network jobs:

// Download message funded statuses for hosting
// Download message funded statuses for moderation
// Download message funded statuses for reading (this is to make sure messages we have received are funded)
// Download Host identity funded statuses
// Download Mate identity/profile funded statuses for hosting
// Download Mate identity/profile funded statuses for moderation
// Download Mate identity/profile funded statuses for browsing
// Download Mate identity/profile funded statuses for contacts/chat recipients
// Download report funded statuses for hosting
// Download report funded statuses for moderation
// Broadcast my Mate profile
// Broadcast my Host profile
// Broadcast my Moderator profile/reviews
// Broadcast my Mate messages
// Broadcast my Moderator messages

// Update Existing Mate User Profiles
// 	-We need a way for a client to user know if another user has changed their profile to no longer fulfill our downloads criteria
// 	-We need to download updated profiles for specific identities, without providing a Criteria in the request
// 	-We should download the newest viewable profile for all Mate users whose stored profile fulfills our criteria.
//	-If the user's newer downloaded profile does not fulfill our criteria, we should delete their old profiles.

//TODO: More jobs

//TODO: Add limits to the number of hosts per job, and the number of items per request
// Otherwise, some of these jobs could take a very long time to complete, due to the number of hosts being contacted.

//TODO: The Mate outlier lists can be reduced by removing all of the mate outliers whose profiles/viewable statuses will be downloaded by other jobs.

import "seekia/internal/byteRange"
import "seekia/internal/badgerDatabase"
import "seekia/internal/contentMetadata"
import "seekia/internal/desires/myMateDesires"
import "seekia/internal/helpers"
import "seekia/internal/logger"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/myInbox"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/moderation/myReviews"
import "seekia/internal/myBlockedUsers"
import "seekia/internal/myContacts"
import "seekia/internal/myIdentity"
import "seekia/internal/myLikedUsers"
import "seekia/internal/myRanges"
import "seekia/internal/mySettings"
import "seekia/internal/network/getFundedStatuses"
import "seekia/internal/network/mateCriteria"
import "seekia/internal/network/myMateCriteria"
import "seekia/internal/network/queryHosts"
import "seekia/internal/profiles/calculatedAttributes"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/profiles/viewableProfiles"

import "errors"


// This function will download and update the network-wide parameters files
func DownloadParameters(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadParameters called with invalid networkType: " + networkTypeString)
	}

	err := queryHosts.DownloadParametersFromHosts(true, networkType, 5)
	if (err != nil) { return err }

	return nil
}

// This function will download all of the network's newest viewable host/moderator profiles
//	-This is used by all users to download host profiles
//		-Users will only connect to Viewable hosts, whom are not banned
//	-This is used by moderators/hosts to download all moderator profiles
//		All hosts/moderators must download all newest viewable Moderator profiles
func DownloadAllNewestViewableUserProfiles(profileType string, networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadAllNewestViewableUserProfiles called with invalid networkType: " + networkTypeString)
	}

	// This functions is not used for Mate profiles
	// Mate profiles are always downloaded based on a range, whereas in this function, we download all newest viewable profiles.

	if (profileType != "Host" && profileType != "Moderator"){
		return errors.New("DownloadAllNewestViewableUserProfiles called with invalid profileType: " + profileType)
	}

	checkIfProfileShouldBeDownloaded := func(_ [28]byte, profileAuthor [16]byte, profileCreationTime int64)(bool, error){

		// Check to see if our newest profile is older than the received profile

		newestViewableProfileExists, _, _, _, storedProfileCreationTime, _, err := viewableProfiles.GetNewestViewableUserProfile(profileAuthor, networkType, true, false, true)
		if (err != nil) { return false, err }
		if (newestViewableProfileExists == false){
			// We do not have any viewable profiles for this host
			// Download this profile
			return true, nil
		}

		if (profileCreationTime <= storedProfileCreationTime){
			// Host we are retrieving from has a profile that is not newer than our existing profile
			// We can skip this profile
			return false, nil
		}

		// This profile is newer than our existing newest viewable profile
		// We download it.
		return true, nil
	}

	minimumRange, maximumRange := byteRange.GetMinimumMaximumIdentityHashBounds()

	emptyList := make([][16]byte, 0)

	err := queryHosts.DownloadProfilesFromHosts(false, networkType, profileType, minimumRange, maximumRange, emptyList, nil, true, true, false, checkIfProfileShouldBeDownloaded, 10)
	if (err != nil) { return err }

	return nil
}

// This function will download newest viewable mate profiles that fulfill user's downloads criteria
// It is used by users who are downloading profiles to find matches.
func DownloadMateProfilesToBrowse(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMateProfilesToBrowse called with invalid networkType: " + networkTypeString)
	}

	minimumRange, maximumRange := byteRange.GetMinimumMaximumIdentityHashBounds()

	getMyCriteria := func()([]byte, error){

		exists, myCriteria, err := myMateCriteria.GetMyMateDownloadsCriteria()
		if (err != nil) { return nil, err }
		if (exists == false){
			return nil, nil
		}
		return myCriteria, nil
	}

	myCriteria, err := getMyCriteria()
	if (err != nil) { return err }

	getDownloadAllBool := func()(bool, error){

		//TODO: Fix below
		desiresPruningMode := false

		if (desiresPruningMode == true){
			return true, nil
		}

		return false, nil
	}

	downloadAllBool, err := getDownloadAllBool()
	if (err != nil) { return err }

	emptyList := make([][16]byte, 0)

	checkIfProfileShouldBeDownloaded := func(_ [28]byte, profileAuthor [16]byte, profileCreationTime int64)(bool, error){
		// This function is only called if profile hash is not already downloaded			
		// We check if we have a profile that is newer (and viewable)
		// If we have a newer viewable profile, we will skip downloading this profile

		// We wont check, but if we have a newer profile that is not known to be viewable, we will still download this profile
		// We will delete this older profile if and when the newer profile becomes viewable

		profileExists, _, _, _, storedProfileCreationTime, _, err := viewableProfiles.GetNewestViewableUserProfile(profileAuthor, networkType, true, false, true)
		if (err != nil) { return false, err }
		if (profileExists == false){
			return true, nil
		}

		if (storedProfileCreationTime >= profileCreationTime){
			// The profile the host is offering is not newer than our existing
			// We will skip it

			return false, nil
		}

		// Profile is newer than the profile we already have, or we dont have any profile for this identity
		// We will download it
		return true, nil
	}

	err = queryHosts.DownloadProfilesFromHosts(false, networkType, "Mate", minimumRange, maximumRange, emptyList, myCriteria, true, true, downloadAllBool, checkIfProfileShouldBeDownloaded, 10)
	if (err != nil) { return err }
	
	return nil
}

// This function will download the newest viewable profiles for a user's mate outliers
// Outliers are either contacts, likes, or chat recipients.
// We download them so the user's client will have their profiles downloaded, even if the users do not fulfill our criteria
// This is done so that a user's client has the profiles for users within their contacts and users whom they are chatting with
// These are downloaded one-by-one to prevent hosts from learning a user's contacts/likes/chat recipients
func DownloadMateOutlierProfiles(outlierType string, networkType byte, missingOrExisting string)error{

	if (outlierType != "Contacts" && outlierType != "Likes" && outlierType != "ChatRecipients"){
		return errors.New("DownloadMateOutlierProfiles called with invalid outlierType: " + outlierType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMateOutlierProfiles called with invalid networkType: " + networkTypeString)
	}

	if (missingOrExisting != "Missing" && missingOrExisting != "Existing"){
		return errors.New("DownloadMateOutlierProfiles called with invalid missingOrExisting: " + missingOrExisting)
	}

	//Outputs:
	//	-[][16]byte: List of identities
	//	-error
	getMyOutlierIdentitiesList := func()([][16]byte, error){

		if (outlierType == "Contacts"){

			myContactIdentityHashesList, err := myContacts.GetMyContactsList("Mate")
			if (err != nil) { return nil, err }

			return myContactIdentityHashesList, nil
		}
		if (outlierType == "Likes"){

			myLikedUsersList, err := myLikedUsers.GetMyLikedUsersList()
			if (err != nil) { return nil, err }

			return myLikedUsersList, nil			
		}

		myIdentityExists, chatRecipientIdentityHashesList, err := myChatMessages.GetAllMyNonBlockedChatRecipients("Mate", networkType)
		if (err != nil) { return nil, err }
		if (myIdentityExists == false){
			// Nothing to do, mate identity does not exist
			emptyList := make([][16]byte, 0)
			return emptyList, nil
		}

		return chatRecipientIdentityHashesList, nil
	}

	myOutlierIdentitiesList, err := getMyOutlierIdentitiesList()
	if (err != nil) { return err }
	if (len(myOutlierIdentitiesList) == 0){
		return nil
	}

	helpers.RandomizeListOrder(myOutlierIdentitiesList)

	// We will attempt to retrieve the newest viewable profiles for the identities

	for _, userIdentityHash := range myOutlierIdentitiesList{

		statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(userIdentityHash, networkType)
		if (err != nil) { return err }
		if (statusIsKnown == true && identityIsFunded == false){

			// The user's identity not funded
			// This means that none of their profiles will be hosted by the network
			// We will not try to download their profile, because it will not exist on the network
			continue
		}

		// We check to see if the user's active chat keys exist
		// We do this because even if a user's profile exists, it may not contain the user's newest chat keys

		keysAreMissing, err := peerChatKeys.CheckIfUsersChatKeysAreMissing(userIdentityHash, networkType)
		if (err != nil) { return err }
		if (keysAreMissing == true){
			if (missingOrExisting == "Existing"){
				continue
			}
		}

		profileExists, _, _, _, _, _, err := viewableProfiles.GetNewestViewableUserProfile(userIdentityHash, networkType, true, false, true)
		if (err != nil) { return err }
		if (profileExists == false){

			if (missingOrExisting == "Existing"){
				continue
			}
		} else {
			if (missingOrExisting == "Missing"){
				continue
			}
		}

		checkIfProfileShouldBeDownloaded := func(_ [28]byte, profileAuthor [16]byte, profileCreationTime int64)(bool, error){

			// We check to see if this profile is newer than our stored newest viewable profile for the identity
			// If it is, we download the profile

			profileExists, _, _, _, existingProfileCreationTime, _, err := viewableProfiles.GetNewestViewableUserProfile(profileAuthor, networkType, true, false, true)
			if (err != nil) { return false, err }
			if (profileExists == false){
				return true, nil
			}

			if (profileCreationTime <= existingProfileCreationTime){
				return false, nil
			}

			return true, nil
		}

		identitiesList := [][16]byte{userIdentityHash}

		minimumRange, maximumRange := byteRange.GetMinimumMaximumIdentityHashBounds()

		err = queryHosts.DownloadProfilesFromHosts(true, networkType, "Mate", minimumRange, maximumRange, identitiesList, nil, true, true, false, checkIfProfileShouldBeDownloaded, 1)
		if (err != nil) { return err }
	}

	return nil
}

// This function will download profiles within our host range
// This should be called if a user is in Host mode.
func DownloadProfilesToHost(profileType string, networkType byte)error{

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		return errors.New("DownloadProfilesToHost called with invalid profileType: " + profileType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadProfilesToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAny, myIdentitiesToHostRangeStart, myIdentitiesToHostRangeEnd, err := myRanges.GetMyIdentitiesToHostRange(profileType)
	if (err != nil) { return err }
	if (hostModeEnabled == false){

		// Host mode must have been turned off recently.

		err := logger.AddLogEntry("Network", "DownloadProfilesToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAny == false) {
		// No profiles to host of this profileType. Nothing to do.
		return nil
	}

	checkIfProfileShouldBeDownloaded := func(profileHash [28]byte, profileAuthor [16]byte, profileCreationTime int64)(bool, error){

		//TODO

		// First we check the moderator consensus of the identity
		// If consensus is ban, we will only download profile if time it has been banned is not older than maximum time to store banned content

		// Then we check to see if we have profile metadata
		// If we don't, we download the profile.

		// At this point, we have the profile metadata but not the profile
		// That means we thought it was worth deleting at one point (or it was downloaded for mate/moderator reasons)

		// Then we check the moderator consensus of profile
		// If consensus is ban, we will only download profile if time it has been banned is not older than maximum time to store banned content
		// Otherwise, we will not download profile

		// At this point, consensus for profile is approve/none and identity is not banned
		// We then check to see if we have a newer profile by the same identity
		// If there is no newer profile, we download the profile

		// At this point, there is a newer profile for the identity
		// We only want to download this older profile if it can be used by moderators to determine if the identity should be banned
		// We dont want this to be abused, if an attacker created a report for many old profiles that existed on the network, they could cause hosts to download all those old profiles
		// So, if profile has been approved for a long enough period of time, it does not need to be hosted anymore
		// We cannot trust the creationTimes for reviews, reports, or profiles, so each host keeps track of the time that a consensus has existed based on the reviews it has downloaded
		// We use verdictHistory for this

		// If offered profile is approved, we check to see if profile has been approved for at least the maximum time to store old approved profiles
		// If it has been approved for at least that long, we will not download the profile
		// If it has been approved but not for that long, we will download the profile, just so that moderators can determine if consensus is wrong before profile is dropped from network

		// At this point, consensus for profile is none, and it is not the user's newest profile
		// We check to see if any ban reviews and reports exist for the profile

		// If there are any ban reviews or reports, we check to see how long time that consensus has been none.
		// If that time is older than maximum time to store banned and reported no consensus old content, we will not download it
		// If that time has not passed, we will download the profile

		// At this point, the profile has no ban reviews/reports and consensus is none
		// We will check if profile's no consensus time is older than maximum time to store unreported and unbanned no consensus old content
		// If the profile is older than that time, we will not download it
		// If the profile is newer than that time, we will download it

		// This is quite complicated and could be simplified
		// Basically, we want to profiles on the network for long enough so the moderators can review them and ban any moderators who wrongly banned/approved them

		return true, nil
	}

	getGetViewableProfilesOnlyBool := func()(bool, error){

		if (profileType == "Host"){
			// Hosts must download all peer host profiles, regardless of viewable status
			// This is because (a) malicious moderator(s) could ban all Host profiles and cripple the network
			// Host profiles have no images, so they are much less legally risky to download anyway.
			//TODO: Make it clear in the GUI that hosts will host all unviewable host profiles, even if HostUnviewableProfiles is disabled.
			return false, nil
		}

		exists, hostUnviewableStatus, err := mySettings.GetSetting("HostUnviewableProfilesOnOffStatus")
		if (err != nil) { return false, err }
		if (exists == true && hostUnviewableStatus == "On") {
			return false, nil
		}
		return true, nil
	}

	getViewableProfilesOnlyBool, err := getGetViewableProfilesOnlyBool()
	if (err != nil) { return err }

	emptyList := make([][16]byte, 0)

	err = queryHosts.DownloadProfilesFromHosts(true, networkType, profileType, myIdentitiesToHostRangeStart, myIdentitiesToHostRangeEnd, emptyList, nil, false, getViewableProfilesOnlyBool, false, checkIfProfileShouldBeDownloaded, 10)
	if (err != nil) { return err }

	return nil
}


// This function will download profiles within our moderation range
func DownloadProfilesToModerate(profileType string, networkType byte)error{

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		return errors.New("DownloadProfilesToModerate called with invalid profileType: " + profileType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadProfilesToModerate called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAny, myIdentitiesToModerateRangeStart, myIdentitiesToModerateRangeEnd, err := myRanges.GetMyIdentitiesToModerateRange(profileType)
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){

		// This should not happen unless moderator mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadProfilesToModerate called when moderator mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAny == false){
		// We are not moderating any profiles
		return nil
	}

	checkIfProfileShouldBeDownloaded := func(profileHash [28]byte, profileIdentityHash [16]byte, profileCreationTime int64)(bool, error){

		//TODO: See if we have already reviewed the profile, if yes, then we don't need to download it.
		// Also, if we have the profile's metadata, and it is not the user's newest profile, and it has no ban reviews, we don't need to download it.
		// We must have the profile metadata downloaded because it is the only way we can be sure the profile has no ban reviews

		return true, nil
	}

	emptyList := make([][16]byte, 0)

	err = queryHosts.DownloadProfilesFromHosts(true, networkType, profileType, myIdentitiesToModerateRangeStart, myIdentitiesToModerateRangeEnd, emptyList, nil, false, false, false, checkIfProfileShouldBeDownloaded, 10)
	if (err != nil) { return err }

	return nil
}


// This function will download messages in our inboxes
// Each inbox has to be queried seperately from a different host to prevent linking the inboxes together
func DownloadMyInboxMessages(identityType string, networkType byte)error{

	if (identityType != "Mate" && identityType != "Moderator"){
		return errors.New("DownloadMyInboxMessages called with invalid identityType: " + identityType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMyInboxMessages called with invalid networkType: " + networkTypeString)
	}

	myIdentityFound, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType)
	if (err != nil) { return err }
	if (myIdentityFound == false){
		return nil
	}

	myInboxesList, err := myInbox.GetAllMyActiveInboxes(myIdentityHash, networkType)
	if (err != nil) { return err }

	helpers.RandomizeListOrder(myInboxesList)

	minimumInboxRange, maximumInboxRange := byteRange.GetMinimumMaximumInboxBounds()

	for _, inbox := range myInboxesList{

		inboxAsList := [][10]byte{inbox}

		checkIfMessageShouldBeDownloaded := func(messageHash [26]byte)(bool, error){

			metadataExists, _, messageNetworkType, _, messageInbox, _, err := contentMetadata.GetMessageMetadata(messageHash)
			if (err != nil) { return false, err }
			if (metadataExists == false){
				return true, nil
			}
			// We already downloaded the message at some point
			// First we see if the message inbox/networkType matches what we requested

			if (messageNetworkType != networkType || messageInbox != inbox){
				// The host is offering us a message we know does not belong to the inbox/networkType we requested
				// The host is malicious.

				// We download the message so they do not learn that we downloaded this message at one point (to prevent fingerprinting)

				// We should have already checked for this within queryHosts.DownloadMessagesFromHosts
				// The only way we would not have known is if we downloaded the message recently
				err := logger.AddLogEntry("Network", "checkIfMessageShouldBeDownloaded called when inbox/networkType does not match message we have metadata for.")
				if (err != nil) { return false, err }

				return true, nil
			}

			// Now we check to see if we have already imported, deleted, or failed to decrypt the message

			isDeleted, err := myChatMessages.CheckIfMessageIsDeleted(messageHash)
			if (err != nil) { return false, err }
			if (isDeleted == true){
				return false, nil
			}

			isUndecryptable, err := myChatMessages.CheckIfMessageIsUndecryptable(messageHash)
			if (err != nil) { return false, err }
			if (isUndecryptable == true){
				return false, nil
			}

			isImported, err := myChatMessages.CheckIfMessageIsImported(messageHash, identityType, messageNetworkType)
			if (err != nil) { return false, err }
			if (isImported == true){
				return false, nil
			}

			return true, nil
		}

		err := queryHosts.DownloadMessagesFromHosts(false, networkType, minimumInboxRange, maximumInboxRange, inboxAsList, false, false, checkIfMessageShouldBeDownloaded, 1)
		if (err != nil) { return err }
	}

	return nil
}

// This function will download messages within our hosting range
// This should be called periodically if we are in Host mode
func DownloadMessagesToHost(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessagesToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAny, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToHostRange()
	if (err != nil) { return err }
	if (hostModeEnabled == false){
		// Host mode is disabled. Nothing to do.
		// This should not happen unless host mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadMessagesToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAny == false){
		// Not hosting any messages. Nothing to do.
		return nil
	}

	// We are not retrieving based on a list of inboxes
	inboxesToRetrieveList := make([][10]byte, 0)

	getGetViewableMessagesOnlyBool := func()(bool, error){

		exists, hostUnviewableStatus, err := mySettings.GetSetting("HostUnviewableMessagesOnOffStatus")
		if (err != nil) { return false, err }
		if (exists == true && hostUnviewableStatus == "On") {
			return false, nil
		}

		return true, nil
	}

	getViewableMessagesOnlyBool, err := getGetViewableMessagesOnlyBool()
	if (err != nil) { return err }

	checkIfMessageShouldBeDownloaded := func(inputMessageHash [26]byte)(bool, error){

		//TODO:
		// This should be similar to the function in DownloadProfilesToHost

		return true, nil
	}

	err = queryHosts.DownloadMessagesFromHosts(true, networkType, myInboxRangeStart, myInboxRangeEnd, inboxesToRetrieveList, getViewableMessagesOnlyBool, false, checkIfMessageShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download messages within our moderation range
// This should be called periodically if the user is in Moderator mode.
func DownloadMessagesToModerate(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessagesToModerate called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAny, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToModerateRange()
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){
		// User must have disabled moderator mode recently
		err := logger.AddLogEntry("Network", "DownloadMessagesToModerate called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAny == false){
		// We are not moderating any messages.

		return nil
	}

	checkIfMessageShouldBeDownloaded := func(inputMessageHash [26]byte)(bool, error){

		// This function is only called if we don't already have the message downloaded

		metadataExists, _, messageNetworkType, _, messageInbox, _, err := contentMetadata.GetMessageMetadata(inputMessageHash)
		if (err != nil) { return false, err }
		if (metadataExists == false){
			return true, nil
		}
		if (messageNetworkType != networkType){
			// The host is offering us a message we know does not belong to the networkType we requested
			// The host is malicious.

			// We download the message so they do not learn that we downloaded this message at one point (to prevent fingerprinting)

			return true, nil
		}

		isWithinRequestRange, err := byteRange.CheckIfInboxIsWithinRange(myInboxRangeStart, myInboxRangeEnd, messageInbox)
		if (err != nil) { return false, err }
		if (isWithinRequestRange == false){
			// The host is offering us a message we know does not belong to the inbox range we requested
			// The host is malicious.

			// We download the message so they do not learn that we downloaded this message at one point (to prevent fingerprinting)

			return true, nil
		}

		myIdentityExists, messageMetadataIsKnown, iHaveReviewed, _, err := myReviews.GetMyNewestMessageModerationVerdict(inputMessageHash)
		if (err != nil) { return false, err }
		if (myIdentityExists == false){
			// Without an identity, we would only use moderator mode to browse content
			// We should therefore download the message
			return true, nil
		}
		if (messageMetadataIsKnown == false){
			// We must have just deleted our message metadata
			//TODO: Log this
			return true, nil
		}
		if (iHaveReviewed == true){

			// We can skip messages that the user has already reviewed
			//TODO: Create a mode where this behavior can be disabled
			// Some moderators may want to keep those messages so they can reference them again, until they
			//		expire from the network, or they have been sufficiently banned

			return false, nil
		}

		return true, nil
	}

	emptyList := make([][10]byte, 0)

	err = queryHosts.DownloadMessagesFromHosts(true, networkType, myInboxRangeStart, myInboxRangeEnd, emptyList, false, true, checkIfMessageShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download all reviews banning moderator identities.
// This is necessary if Moderator/Host mode is enabled.
// These reviews determine which moderators are banned, so they are needed to determine the moderation verdict for all identities/content.
// Thus, they must be downloaded by all hosts and moderators.
func DownloadModeratorIdentityBanningReviews(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadModeratorIdentityBanningReviews called with invalid networkType: " + networkTypeString)
	}

	minimumRange, maximumRange := byteRange.GetMinimumMaximumIdentityHashBounds()

	emptyListA := make([][16]byte, 0)
	emptyListB := make([][16]byte, 0)

	checkIfReviewShouldBeDownloadedFunction := func(_ [29]byte, reviewedHash []byte)(bool, error){

		reviewedType, err := helpers.GetReviewedTypeFromReviewedHash(reviewedHash)
		if (err != nil) { return false, err }
		if (reviewedType != "Identity" && reviewedType != "Profile" && reviewedType != "Attribute"){
			return false, errors.New("DownloadIdentityReviewsFromHosts not checking that identity reviewed hash is valid.")
		}
		if (reviewedType == "Identity"){
			return true, nil
		}

		return false, nil
	}

	err := queryHosts.DownloadIdentityReviewsFromHosts(false, networkType, "Moderator", minimumRange, maximumRange, emptyListA, emptyListB, checkIfReviewShouldBeDownloadedFunction, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download reviews for identities/profiles/attributes within our Host range
// It is called periodically if Host mode is enabled.
func DownloadIdentityReviewsToHost(identityTypeToRetrieve string, networkType byte)error{

	if (identityTypeToRetrieve != "Mate" && identityTypeToRetrieve != "Host" && identityTypeToRetrieve != "Moderator"){
		return errors.New("DownloadIdentityReviewsToHost called with invalid identityTypeToRetrieve: " + identityTypeToRetrieve)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadIdentityReviewsToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAnyIdentities, myIdentityRangeStart, myIdentityRangeEnd, err := myRanges.GetMyIdentitiesToHostRange(identityTypeToRetrieve)
	if (err != nil) { return err }
	if (hostModeEnabled == false){
		// Host mode is disabled. Nothing to do.
		// This should not happen unless host mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadIdentityReviewsToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAnyIdentities == false){

		// We are not hosting any identity reviews of the provided identityType

		return nil
	}

	emptyListA := make([][16]byte, 0)
	emptyListB := make([][16]byte, 0)

	checkIfReviewShouldBeDownloadedFunction := func(_ [29]byte, _ []byte)(bool, error){

		//TODO:
		// We want to defend against downloading reviews that we will delete immediately.
		// Don't download reviews by moderators who have been banned for a long enough period of time
		// Don't download reviews for content/identities that have been expired for long enough
		// Any hosts who offers us content that should no longer exist on the network would be a malicious host.

		return true, nil
	}

	err = queryHosts.DownloadIdentityReviewsFromHosts(false, networkType, identityTypeToRetrieve, myIdentityRangeStart, myIdentityRangeEnd, emptyListA, emptyListB, checkIfReviewShouldBeDownloadedFunction, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download reviews for messages within our Host range
// It is called periodically if Host mode is enabled.
func DownloadMessageReviewsToHost(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessageReviewsToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAnyInboxes, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToHostRange()
	if (err != nil) { return err }
	if (hostModeEnabled == false){
		// Host mode is disabled. Nothing to do.
		// This should not happen unless host mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadMessageReviewsToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAnyInboxes == false){

		// We are not hosting any message reviews

		return nil
	}

	emptyListA := make([][26]byte, 0)
	emptyListB := make([][16]byte, 0)
	emptyMap := make(map[[26]byte][10]byte)

	checkIfReviewShouldBeDownloadedFunction := func(reviewHash [29]byte, messageHash [26]byte)(bool, error){

		//TODO:
		// We want to defend against downloading reviews that we will delete immediately.
		// Don't download reviews by moderators who have been banned for a long enough period of time
		// Don't download reviews for messages that have been expired for long enough
		// Any hosts who offers us content that should no longer exist on the network would be a malicious host.

		return true, nil
	}

	err = queryHosts.DownloadMessageReviewsFromHosts(false, networkType, myInboxRangeStart, myInboxRangeEnd, emptyListA, emptyListB, emptyMap, checkIfReviewShouldBeDownloadedFunction, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download reviews for identities/profiles/attributes within our moderation range
// This will be called periodically if Moderator mode is enabled.
func DownloadIdentityReviewsForModeration(identityTypeToRetrieve string, networkType byte)error{

	if (identityTypeToRetrieve != "Mate" && identityTypeToRetrieve != "Host" && identityTypeToRetrieve != "Moderator"){
		return errors.New("DownloadIdentityReviewsForModeration called with invalid identityTypeToRetrieve: " + identityTypeToRetrieve)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadIdentityReviewsForModeration called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAnyIdentities, myIdentityRangeStart, myIdentityRangeEnd, err := myRanges.GetMyIdentitiesToModerateRange(identityTypeToRetrieve)
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){
		// Moderator mode must have been disabled recently

		err := logger.AddLogEntry("Network", "DownloadIdentityReviewsForModeration called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAnyIdentities == false){
		// We are not moderating any identities of the provided identityType
		// We don't need to download any reviews for the identities
		return nil		
	}

	emptyListA := make([][16]byte, 0)
	emptyListB := make([][16]byte, 0)

	checkIfReviewShouldBeDownloadedFunction := func(reviewHash [29]byte, reviewedHash []byte)(bool, error){

		//TODO
		// see DownloadIdentityReviewsToHost for some ideas on what restrictions to add

		return true, nil
	}

	err = queryHosts.DownloadIdentityReviewsFromHosts(false, networkType, identityTypeToRetrieve, myIdentityRangeStart, myIdentityRangeEnd, emptyListA, emptyListB, checkIfReviewShouldBeDownloadedFunction, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download reviews for messages within our moderation range
// This will be called periodically if Moderator mode is enabled.
func DownloadMessageReviewsForModeration(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessageReviewsForModeration called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAnyInboxes, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToModerateRange()
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){
		// Moderator mode must have been disabled recently

		err := logger.AddLogEntry("Network", "DownloadMessageReviewsForModeration called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAnyInboxes == false){
		// We are not moderating any message inboxes
		// We don't need to download any message reviews
		return nil		
	}

	emptyListA := make([][26]byte, 0)
	emptyListB := make([][16]byte, 0)
	emptyMap := make(map[[26]byte][10]byte)

	checkIfReviewShouldBeDownloadedFunction := func(reviewHash [29]byte, reviewedMessageHash [26]byte)(bool, error){

		//TODO
		// see DownloadMessageReviewsToHost for some ideas on what restrictions to add

		return true, nil
	}

	err = queryHosts.DownloadMessageReviewsFromHosts(false, networkType, myInboxRangeStart, myInboxRangeEnd, emptyListA, emptyListB, emptyMap, checkIfReviewShouldBeDownloadedFunction, 5)
	if (err != nil) { return err }

	return nil
}


//	This function will download reports for identities/profiles/attributes within our host range
// It must be called if Host mode is enabled
func DownloadIdentityReportsToHost(identityTypeToRetrieve string, networkType byte)error{

	if (identityTypeToRetrieve != "Mate" && identityTypeToRetrieve != "Host" && identityTypeToRetrieve != "Moderator"){

		return errors.New("DownloadIdentityReportsToHost called with invalid identityTypeToRetrieve: " + identityTypeToRetrieve)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadIdentityReportsToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAnyIdentities, myIdentityRangeStart, myIdentityRangeEnd, err := myRanges.GetMyIdentitiesToHostRange(identityTypeToRetrieve)
	if (err != nil) { return err }
	if (hostModeEnabled == false){
		// Host mode is disabled. Nothing to do.
		// This should not happen unless host mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadIdentityReportsToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAnyIdentities == false){
		return nil
	}

	checkIfReportShouldBeDownloaded := func(reportHash [30]byte, reportedHash []byte)(bool, error){
		
		//TODO
		// see DownloadReviewsToHost for some ideas on what restrictions to add

		return true, nil
	}

	emptyList := make([][16]byte, 0)

	err = queryHosts.DownloadIdentityReportsFromHosts(true, networkType, identityTypeToRetrieve, myIdentityRangeStart, myIdentityRangeEnd, emptyList, checkIfReportShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}


//	This function will download reports for messages within our host range
// It must be called if Host mode is enabled
func DownloadMessageReportsToHost(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessageReportsToHost called with invalid networkType: " + networkTypeString)
	}

	hostModeEnabled, hostingAnyInboxes, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToHostRange()
	if (err != nil) { return err }
	if (hostModeEnabled == false){
		// Host mode is disabled. Nothing to do.
		// This should not happen unless host mode was turned off recently
		err := logger.AddLogEntry("Network", "DownloadMessageReportsToHost called when Host mode is disabled.")
		if (err != nil) { return err }

		return nil
	}
	if (hostingAnyInboxes == false){
		return nil
	}

	checkIfReportShouldBeDownloaded := func(reportHash [30]byte, reportedMessageHash [26]byte)(bool, error){

		//TODO
		// see DownloadMessageReportsToHost for some ideas on what restrictions to add

		return true, nil
	}

	emptyList := make([][26]byte, 0)
	emptyMap := make(map[[26]byte][10]byte)

	err = queryHosts.DownloadMessageReportsFromHosts(true, networkType, myInboxRangeStart, myInboxRangeEnd, emptyList, emptyMap, checkIfReportShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}

// This function will download reports for identities/profiles/attributes within our moderation range
// It is called periodically if Moderator mode is enabled
func DownloadIdentityReportsForModeration(identityTypeToRetrieve string, networkType byte)error{

	if (identityTypeToRetrieve != "Mate" && identityTypeToRetrieve != "Host" && identityTypeToRetrieve != "Moderator"){

		return errors.New("DownloadIdentityReportsForModeration called with invalid identityTypeToRetrieve: " + identityTypeToRetrieve)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadIdentityReportsForModeration called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAny, myRangeStart, myRangeEnd, err := myRanges.GetMyIdentitiesToModerateRange(identityTypeToRetrieve)
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){

		// Moderator mode must have been disabled recently

		err := logger.AddLogEntry("Network", "DownloadIdentityReportsForModeration called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAny == false){

		// Not moderating any identities of provided identityType
		// We don't need to download reports
		return nil
	}

	checkIfReportShouldBeDownloaded := func(reportHash [30]byte, reportedHash []byte)(bool, error){
		
		//TODO
		// see DownloadIdentityReviewsToHost for some ideas on what restrictions to add

		return true, nil
	}

	emptyList := make([][16]byte, 0)

	err = queryHosts.DownloadIdentityReportsFromHosts(true, networkType, identityTypeToRetrieve, myRangeStart, myRangeEnd, emptyList, checkIfReportShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download reports for messages/profiles/identities within our moderation range
// It is called periodically if Moderator mode is enabled
func DownloadMessageReportsForModeration(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMessageReportsForModeration called with invalid networkType: " + networkTypeString)
	}

	moderatorModeEnabled, moderatingAny, myInboxRangeStart, myInboxRangeEnd, err := myRanges.GetMyInboxesToModerateRange()
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){

		// Moderator mode must have been disabled recently

		err := logger.AddLogEntry("Network", "DownloadMessageReportsForModeration called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}
	if (moderatingAny == false){
		// Not moderating any messages
		// We don't need to download any message reports
		return nil
	}

	checkIfReportShouldBeDownloaded := func(reportHash [30]byte, reportedMessageHash [26]byte)(bool, error){
		
		//TODO
		// see DownloadMessageReviewsToHost for some ideas on what restrictions to add

		return true, nil
	}

	emptyList := make([][26]byte, 0)
	emptyMap := make(map[[26]byte][10]byte)

	err = queryHosts.DownloadMessageReportsFromHosts(true, networkType, myInboxRangeStart, myInboxRangeEnd, emptyList, emptyMap, checkIfReportShouldBeDownloaded, 5)
	if (err != nil) { return err }

	return nil
}


// This function will download trusted viewable statuses for downloaded host identities/profiles.
// Downloading these statuses is not needed if a user is able to determine these statuses themselves
//
// Hosts who are hosting host identities will download all host profile reviews, so they can calculate the viewable status themselves
// Moderators who are moderating Host identities can also calculate the viewable status for host identities/profiles within their moderation range
func DownloadHostViewableStatuses(networkType byte, knownOrUnknown string)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadHostViewableStatuses called with invalid networkType: " + networkTypeString)
	}

	if (knownOrUnknown != "Known" && knownOrUnknown != "Unknown"){
		return errors.New("DownloadHostViewableStatuses called with invalid knownOrUnknown: " + knownOrUnknown)
	}

	hostModeEnabled, hostingAny, _, _, err := myRanges.GetMyIdentitiesToHostRange("Host")
	if (err != nil) { return err }
	if (hostModeEnabled == true && hostingAny == true){
		// We are already hosting all reviews for host identities, so we can calculate the viewable status of host profiles ourselves
		// Thus, we don't need to download any trusted viewable statuses for host identitites/profiles 
		return nil
	}

	allHostProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Host")
	if (err != nil) { return err }

	getRelevantHostProfileIdentityHashesList := func()([][16]byte, error){

		moderatorModeEnabled, moderatingAny, identityRangeStart, identityRangeEnd, err := myRanges.GetMyIdentitiesToModerateRange("Host")
		if (err != nil) { return nil, err }
		if (moderatorModeEnabled == false || moderatingAny == false){
			// We are not moderating any host identities
			// We do not need to reduce the list at all
			return allHostProfileIdentityHashesList, nil
		}

		relevantHostProfileIdentityHashesList := make([][16]byte, 0)

		for _, hostIdentityHash := range allHostProfileIdentityHashesList{

			isWithinMyRange, err := byteRange.CheckIfIdentityHashIsWithinRange(identityRangeStart, identityRangeEnd, hostIdentityHash)
			if (err != nil) { return nil, err }
			if (isWithinMyRange == true){
				// We don't need to download this host's viewable status, because we can calculate it ourselves.
				continue
			}

			relevantHostProfileIdentityHashesList = append(relevantHostProfileIdentityHashesList, hostIdentityHash)
		}

		return relevantHostProfileIdentityHashesList, nil
	}

	relevantHostProfileIdentityHashesList, err := getRelevantHostProfileIdentityHashesList()
	if (err != nil) { return err }

	// Map Structure: Profile Hash -> Author identity hash
	profileHashesToRetrieveMap := make(map[[28]byte][16]byte)

	for _, identityHash := range relevantHostProfileIdentityHashesList{

		exists, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(identityHash)
		if (err != nil) { return err }
		if (exists == false){
			// Entry must have been deleted. Will be fixed automatically
			continue
		}
		if (len(profileHashesList) == 0){
			continue
		}

		for _, profileHash := range profileHashesList{

			profileMetadataExists, _, profileNetworkType, profileAuthor, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(profileHash)
			if (err != nil) { return err }
			if (profileMetadataExists == false){
				// Profile is not downloaded, skip it.
				continue
			}
			if (profileNetworkType != networkType){
				continue
			}
			if (profileAuthor != identityHash){
				return errors.New("Database corrupt: Host identity profile hashes list contains profile of different identity")
			}

			if (profileIsDisabled == true){
				continue
			}

			profileHashesToRetrieveMap[profileHash] = identityHash
		}
	}

	err = queryHosts.DownloadViewableStatusesFromHosts(false, networkType, knownOrUnknown, relevantHostProfileIdentityHashesList, profileHashesToRetrieveMap, 5)
	if (err != nil) { return err }

	return nil
}

// This function will download trusted viewable statuses for downloaded moderator profiles whose viewable statuses we cannot calculate ourselves
//
// We only need to do this is Moderator mode is enabled.
// This is only needed so moderators can view Moderator profiles whose viewable status we are not aware of and be sure they are viewable
// If Host mode is enabled, all of the moderator profiles we download, we will be able to determine the viewable status of.
// Mate mode will not download any moderator profiles.
//
// We don't need to download viewable statuses for moderator identities
// This is because in Moderator/Host mode, we should be downloading all moderator identity reviews
// Thus, we should be able to determine their viewable statuses ourselves
//
func DownloadModeratorProfileViewableStatuses(networkType byte, knownOrUnknown string)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadModeratorProfileViewableStatuses called with invalid networkType: " + networkTypeString)
	}

	if (knownOrUnknown != "Known" && knownOrUnknown != "Unknown"){
		return errors.New("DownloadModeratorProfileViewableStatuses called with invalid knownOrUnknown: " + knownOrUnknown)	
	}

	moderatorModeEnabled, moderatingAnyModeratorIdentities, myIdentityRangeStart, myIdentityRangeEnd, err := myRanges.GetMyIdentitiesToModerateRange("Moderator")
	if (err != nil) { return err }
	if (moderatorModeEnabled == false){
		// Moderator mode must have been disabled recently

		err := logger.AddLogEntry("Network", "DownloadModeratorProfileViewableStatuses called when user is not in Moderator mode.")
		if (err != nil) { return err }

		return nil
	}

	if (moderatingAnyModeratorIdentities == true){

		minimumIdentityBound, maximumIdentityBound := byteRange.GetMinimumMaximumIdentityHashBounds()

	 	if (myIdentityRangeStart == minimumIdentityBound && myIdentityRangeEnd == maximumIdentityBound){

			// We are moderating all Moderator identities, so we should be able to determine the viewable status for all moderator profiles
			// We don't need to download any moderator profile trusted viewable statuses
			return nil
		}
	}

	// Now we see if we are hosting all moderator identities

	hostModeEnabled, hostingAny, _, _, err := myRanges.GetMyIdentitiesToHostRange("Moderator")
	if (err != nil) { return err }
	if (hostModeEnabled == true && hostingAny == true){
		// We are hosting all moderator identities
		// Thus, we are downloading all reviews for all moderator profiles
		// We don't need to download the viewable statuses for any moderator profiles
		return nil
	}

	allModeratorProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Moderator")
	if (err != nil) { return err }

	// Map Structure: Profile Hash -> Author identity hash
	profileHashesToRetrieveMap := make(map[[28]byte][16]byte)

	for _, moderatorIdentityHash := range allModeratorProfileIdentityHashesList{

		if (moderatingAnyModeratorIdentities == true){

			// We see if moderator's identity is within our range
			// If it is, we don't have to get the status for profiles authored by this identity, because we can determine the viewable statuses ourselves

			isWithinRange, err := byteRange.CheckIfIdentityHashIsWithinRange(myIdentityRangeStart, myIdentityRangeEnd, moderatorIdentityHash)
			if (err != nil) { return err }
			if (isWithinRange == true){

				continue
			}
		}

		exists, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(moderatorIdentityHash)
		if (err != nil) { return err }
		if (exists == false){
			// Entry must have been deleted. Will be fixed automatically
			continue
		}
		if (len(profileHashesList) == 0){
			continue
		}

		for _, profileHash := range profileHashesList{

			profileMetadataExists, _, profileNetworkType, profileAuthor, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(profileHash)
			if (err != nil) { return err }
			if (profileMetadataExists == false){
				// Profile is not downloaded, skip it.
				continue
			}
			if (profileNetworkType != networkType){
				continue
			}
			if (profileAuthor != moderatorIdentityHash){
				return errors.New("Database corrupt: Moderator identity profile hashes list contains profile of different identity")
			}

			if (profileIsDisabled == true){
				continue
			}

			profileHashesToRetrieveMap[profileHash] = moderatorIdentityHash
		}
	}

	emptyList := make([][16]byte, 0)

	err = queryHosts.DownloadViewableStatusesFromHosts(false, networkType, knownOrUnknown, emptyList, profileHashesToRetrieveMap, 5)
	if (err != nil) { return err }

	return nil
}

// This function will download viewable statuses for mate identities/profiles we are browsing
// If we are not in DesiresPruningMode, we will download the statuses for all downloaded profiles/identities which fulfill our criteria
// If we are in DesiresPruningMode, we have to download the statuses for our matches profiles/identities one-at-a-time
func DownloadMateViewableStatusesForBrowsing(networkType byte, knownOrUnknown string)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMateViewableStatusesForBrowsing called with invalid networkType: " + networkTypeString)
	}

	if (knownOrUnknown != "Known" && knownOrUnknown != "Unknown"){
		return errors.New("DownloadMateViewableStatusesForBrowsing called with invalid knownOrUnknown: " + knownOrUnknown)
	}

	//TODO: Fix
	desiresPruningMode := false

	if (desiresPruningMode == true){

		// We will retrieve consensus statuses for all users who could potentially be matches one-by-one
		// A user could potentially be a match if their newest profile fulfills our desires
		// Their newest profile may not be viewable, so we still need to retrieve statuses for all of their profiles which fulfill our desires

		myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Mate")
		if (err != nil) { return err }

		mateIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Mate")
		if (err != nil) { return err }

		for _, userIdentityHash := range mateIdentityHashesList{

			if (myIdentityExists == true && userIdentityHash == myIdentityHash){
				continue
			}

			userIsBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(userIdentityHash)
			if (err != nil) { return err }
			if (userIsBlocked == true){
				// We don't need to get the viewable statuses for blocked users
				continue
			}

			profileExists, profileVersion, newestProfileHash, _, _, newestRawProfileMap, err := profileStorage.GetNewestUserProfile(userIdentityHash, networkType)
			if (err != nil) { return err }
			if (profileExists == false){
				// This user has no profiles, so we do not need to retrieve any viewable statuses for this user
				// The user's profile was probably deleted in the background
				continue
			}

			userIsDisabled, _, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(newestRawProfileMap, "Disabled")
			if (err != nil) { return err }
			if (userIsDisabled == true){
				// The user will never be a match, because their newest profile is disabled
				// Thus, we do not need to retrieve viewable statuses for the user.
				continue
			}

			getAnyProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, newestRawProfileMap)
			if (err != nil){ return err }

			profilePassesMyDesires, err := myMateDesires.CheckIfMateProfilePassesAllMyDesires(false, "", getAnyProfileAttributeFunction)
			if (err != nil) { return err }
			if (profilePassesMyDesires == false){
				// The user's newest profile does not fulfill our desires
				// We don't need to get this profile's viewable status, because it does not fulfill our desires
				// We don't need to get any viewable statuses for this user, unless they are an outlier
				continue
			}

			anyExist, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(userIdentityHash)
			if (err != nil) { return err }
			if (anyExist == false){
				// This user has no profiles
				continue
			}

			profileHashesToGetStatusesOfList := [][28]byte{newestProfileHash}

			for _, profileHash := range profileHashesList{

				if (profileHash == newestProfileHash){
					// We are already getting this profile's status
					continue
				}

				profileExists, profileBytes, err := badgerDatabase.GetUserProfile("Mate", profileHash)
				if (err != nil) { return err }
				if (profileExists == false){
					continue
				}

				ableToRead, profileVersion, profileNetworkType, profileAuthor, _, profileIsDisabled, rawProfileMap, err := readProfiles.ReadProfile(false, profileBytes)
				if (err != nil) { return err }
				if (ableToRead == false){
					return errors.New("Database corrupt: Contains malformed profile.")
				}
				if (profileNetworkType != networkType){
					// Profile belongs to a different network type.
					continue
				}
				if (profileAuthor != userIdentityHash){
					return errors.New("Database corrupt: Mate identity profile hashes list contains profile of different identity")
				}
				if (profileIsDisabled == true){
					// We don't need to retrieve status for disabled profiles.
					// These profiles will always be viewable, unless their author is banned.
					continue
				}

				getAnyProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, rawProfileMap)
				if (err != nil){ return err }

				profilePassesMyDesires, err := myMateDesires.CheckIfMateProfilePassesAllMyDesires(false, "", getAnyProfileAttributeFunction)
				if (err != nil) { return err }
				if (profilePassesMyDesires == false){
					// We don't need to get this profile's viewable status, because it does not fulfill our desires
					// Thus, we will not show it to the user unless the author is an outlier
					// Outlier viewable statuses are retrieved in a different job
					continue
				}

				profileHashesToGetStatusesOfList = append(profileHashesToGetStatusesOfList, profileHash)
			}

			// Map Structure: Profile Hash -> User Identity Hash
			profileHashesToRetrieveMap := make(map[[28]byte][16]byte)

			for _, profileHash := range profileHashesToGetStatusesOfList{

				profileHashesToRetrieveMap[profileHash] = userIdentityHash
			}

			identityHashList := [][16]byte{userIdentityHash}

			err = queryHosts.DownloadViewableStatusesFromHosts(false, networkType, knownOrUnknown, identityHashList, profileHashesToRetrieveMap, 1)
			if (err != nil) { return err }
		}

		return nil
	}

	// We will retrieve statuses for all identities whose newest profile fulfills our downloads criteria, and their profiles that fulfill our criteria

	myCriteriaExists, myCriteria, err := myMateCriteria.GetMyMateDownloadsCriteria()
	if (err != nil) { return err }

	allMateProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Mate")
	if (err != nil) { return err }

	helpers.RandomizeListOrder(allMateProfileIdentityHashesList)

	identityHashesToRetrieveList := make([][16]byte, 0)

	// Map Structure: Profile Hash -> User Identity Hash
	profileHashesToRetrieveMap := make(map[[28]byte][16]byte)

	for _, userIdentityHash := range allMateProfileIdentityHashesList{

		statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(userIdentityHash, networkType)
		if (err != nil) { return err }
		if (statusIsKnown == true && identityIsFunded == false){

			// The user's identity is not funded
			// This means that none of their profiles will be hosted by the network
			// We won't try to retrieve their viewable status, because their profile/identity will not exist on the network
			continue
		}

		profileExists, profileVersion, newestProfileHash, _, _, newestRawProfileMap, err := profileStorage.GetNewestUserProfile(userIdentityHash, networkType)
		if (err != nil) { return err }
		if (profileExists == false){
			continue
		}

		userIsDisabled, _, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(newestRawProfileMap, "Disabled")
		if (err != nil) { return err }
		if (userIsDisabled == true){
			// The user will never be a match, because their newest profile is disabled
			// Thus, we do not need to retrieve viewable statuses for the user.
			continue
		}

		if (myCriteriaExists == true){

			criteriaIsValid, fulfillsCriteria, err := mateCriteria.CheckIfMateProfileFulfillsCriteria(true, profileVersion, newestRawProfileMap, myCriteria)
			if (err != nil) { return err }
			if (criteriaIsValid == false){
				return errors.New("GetMyMateDownloadsCriteria returning invalid criteria.")
			}

			if (fulfillsCriteria == false){
				// This user's newest profile does not fulfill our criteria. Skip them.
				// DatabaseJobs will delete all of their profiles (unless they are an outlier)
				continue
			}
		}

		anyExist, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(userIdentityHash)
		if (err != nil) { return err }
		if (anyExist == false){
			// User has no profiles
			// They must have been deleted after earlier check
			continue
		}

		for _, profileHash := range profileHashesList{

			if (profileHash == newestProfileHash){
				// We already checked and know that this profile fulfills our criteria
				profileHashesToRetrieveMap[profileHash] = userIdentityHash
				continue
			}

			profileExists, profileBytes, err := badgerDatabase.GetUserProfile("Mate", profileHash)
			if (err != nil) { return err }
			if (profileExists == false){
				continue
			}

			ableToRead, profileVersion, profileNetworkType, profileAuthor, _, profileIsDisabled, rawProfileMap, err := readProfiles.ReadProfile(false, profileBytes)
			if (err != nil) { return err }
			if (ableToRead == false){
				return errors.New("Database corrupt: Contains malformed profile.")
			}
			if (profileNetworkType != networkType){
				// Profile belongs to a different network type.
				continue
			}
			if (profileAuthor != userIdentityHash){
				return errors.New("Database corrupt: Mate identity profile hashes list contains profile of different identity")
			}
			if (profileIsDisabled == true){
				// We don't need to retrieve status for disabled profiles. They are always approved, unless identity is banned.
				continue
			}

			if (myCriteriaExists == true){

				criteriaIsValid, fulfillsCriteria, err := mateCriteria.CheckIfMateProfileFulfillsCriteria(true, profileVersion, rawProfileMap, myCriteria)
				if (err != nil) { return err }
				if (criteriaIsValid == false){
					return errors.New("GetMyMateDownloadsCriteria returning invalid criteria.")
				}

				if (fulfillsCriteria == false){
					// This profile does not fulfill our criteria
					// We don't need to get its viewable status, because it will never be shown to the user
					// The exception is if the profile is an outlier, in which case we will retrieve its status using DownloadMateOutlierViewableStatuses
					// Another reason we skip the profile is that requesting its status would pose a risk of exposing our older criteria to the host
					continue
				}
			}

			profileHashesToRetrieveMap[profileHash] = userIdentityHash
		}

		identityHashesToRetrieveList = append(identityHashesToRetrieveList, userIdentityHash)
	}

	if (len(identityHashesToRetrieveList) == 0){
		// No users fulfill our criteria. Nothing to do.
		return nil
	}

	err = queryHosts.DownloadViewableStatusesFromHosts(false, networkType, knownOrUnknown, identityHashesToRetrieveList, profileHashesToRetrieveMap, 5)
	if (err != nil) { return err }

	return nil
}

// This function will download trusted viewable statuses for Mate users whom are our outliers
// Outliers are either contacts, likes, or chat recipients.
// We download them so the user's client will have their downloaded profiles viewable, even if the users do not fulfill our criteria
// These need to be downloaded one-by-one to prevent any host from learning our contacts/likes/chat recipients
func DownloadMateOutlierViewableStatuses(outlierType string, networkType byte, knownOrUnknown string)error{

	if (outlierType != "Contacts" && outlierType != "Likes" && outlierType != "ChatRecipients"){
		return errors.New("DownloadMateOutlierViewableStatuses called with invalid outlierType: " + outlierType)
	}
	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadMateOutlierViewableStatuses called with invalid networkType: " + networkTypeString)
	}

	if (knownOrUnknown != "Known" && knownOrUnknown != "Unknown"){
		return errors.New("DownloadMateOutlierViewableStatuses called with invalid knownOrUnknown: " + knownOrUnknown)
	}

	//Outputs:
	//	-[]string: List of identities
	//	-error
	getMyOutlierIdentitiesList := func()([][16]byte, error){

		if (outlierType == "Contacts"){

			myContactIdentityHashesList, err := myContacts.GetMyContactsList("Mate")
			if (err != nil) { return nil, err }

			return myContactIdentityHashesList, nil
		}
		if (outlierType == "Likes"){

			myLikedUsersList, err := myLikedUsers.GetMyLikedUsersList()
			if (err != nil) { return nil, err }
			
			return myLikedUsersList, nil			
		}

		myIdentityExists, chatRecipientIdentityHashesList, err := myChatMessages.GetAllMyNonBlockedChatRecipients("Mate", networkType)
		if (err != nil) { return nil, err }
		if (myIdentityExists == false){
			// Nothing to do, mate identity does not exist
			emptyList := make([][16]byte, 0)
			return emptyList, nil
		}

		return chatRecipientIdentityHashesList, nil
	}

	myOutlierIdentityHashesList, err := getMyOutlierIdentitiesList()
	if (err != nil) { return err }
	if (len(myOutlierIdentityHashesList) == 0){
		return nil
	}

	helpers.RandomizeListOrder(myOutlierIdentityHashesList)

	for _, mateIdentityHash := range myOutlierIdentityHashesList{

		statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(mateIdentityHash, networkType)
		if (err != nil) { return err }
		if (statusIsKnown == true && identityIsFunded == false){

			// The user's identity is not funded
			// This means that none of their profiles will be hosted by the network
			// We won't try to retrieve their viewable status, because their profile/identity will not exist on the network
			continue
		}

		identityHashList := [][16]byte{mateIdentityHash}

		// Outputs:
		//	-map[[28]byte][16]byte: Map of profile hashes whose viewable status we want to retrieve (Profile Hash -> User identity Hash)
		//	-error
		getProfileHashesToRetrieveMap := func()(map[[28]byte][16]byte, error){

			profileExists, _, newestProfileHash, _, _, newestRawProfileMap, err := profileStorage.GetNewestUserProfile(mateIdentityHash, networkType)
			if (err != nil) { return nil, err }
			if (profileExists == false){
				// We have no profiles downloaded for this user
				emptyMap := make(map[[28]byte][16]byte)

				return emptyMap, nil
			}

			isDisabled, _, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(newestRawProfileMap, "Disabled")
			if (err != nil) { return nil, err }
			if (isDisabled == true){

				// User's newest profile is disabled.
				// We don't need to download the profile's viewable status, because it cannot be banned

				emptyMap := make(map[[28]byte][16]byte)

				return emptyMap, nil
			}

			// We retrieve the viewable status for all of their downloaded profiles

			profileHashesToRetrieveMap := map[[28]byte][16]byte{
				newestProfileHash: mateIdentityHash,
			}

			anyExist, identityProfileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(mateIdentityHash)
			if (err != nil) { return nil, err }
			if (anyExist == true){

				for _, profileHash := range identityProfileHashesList{
					profileHashesToRetrieveMap[profileHash] = mateIdentityHash
				}
			}

			return profileHashesToRetrieveMap, nil
		}

		profileHashesToRetrieveMap, err := getProfileHashesToRetrieveMap()
		if (err != nil) { return err }

		err = queryHosts.DownloadViewableStatusesFromHosts(false, networkType, knownOrUnknown, identityHashList, profileHashesToRetrieveMap, 1)
		if (err != nil) { return err }
	}

	return nil
}

// This function will download the cryptocurrency deposit history for all downloaded moderator identities
//	We download deposits for all moderators in bulk
//	Because users must download either all or no moderator profiles, there is no fingerprinting risk in downloading them in bulk
//	This should be called if Host/Moderator mode is enabled
func DownloadModeratorIdentityDeposits(networkType byte, unknownOrKnown string)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("DownloadModeratorIdentityDeposits called with invalid networkType: " + networkTypeString)
	}

	err := queryHosts.DownloadModeratorAddressDepositsFromHosts(true, networkType, "Ethereum", unknownOrKnown, 6)
	if (err != nil) { return err }

	err = queryHosts.DownloadModeratorAddressDepositsFromHosts(true, networkType, "Cardano", unknownOrKnown, 6)
	if (err != nil) { return err }

	return nil
}


