
// manualBroadcasts provides functions to initiate and monitor manual broadcasts
// Manual broadcasts are tasks which run in the background to broadcast profiles, messages, reviews, reports, and parameters
// These broadcasts are initiated by the user through the GUI, and their status can be monitored by the user
// An example of its use is Mate profiles, will will always be manually broadcasted
// This package provides the ability to get the status of each broadcast

// All broadcasted content will be continually broadcasted in the background, regardless of whether it was broadcasted manually or not
// networkJobs.go provides functions to broadcast content automatically in the background to make sure the content is on the network

package manualBroadcasts

//TODO: Call the myReviews.DeleteMyReviewsListCache() function whenever broadcasting a review, or just remove the ability to use
//		this package to broadcast reviews.

import "seekia/internal/byteRange"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/logger"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/moderation/readReports"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/network/eligibleHosts"
import "seekia/internal/network/hostRanges"
import "seekia/internal/network/peerClient"
import "seekia/internal/network/sendRequests"
import "seekia/internal/parameters/readParameters"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/readContent"

import messagepack "github.com/vmihailenco/msgpack/v5"

import "time"
import "sync"
import "errors"
import "slices"

// This object stores data about active processes
type processObject struct{

	// Set to true once the process is complete. Will be true if we encounter an error
	isComplete bool

	// Set to true if we encounter an error
	encounteredError bool

	// The error we encountered
	errorEncountered error

	// Stores the number of hosts that have been successfully broadcasted to
	numberOfSuccessfulBroadcasts int

	// Stores details about process progress, which are shown to user in gui
	progressDetails string
}

var processObjectsMapMutex sync.RWMutex

var processObjectsMap map[[22]byte]processObject = make(map[[22]byte]processObject)

//Outputs:
//	-[22]byte: New process identifier
//	-error
func initializeNewProcessObject()([22]byte, error){

	processIdentifierBytes, err := helpers.GetNewRandomBytes(22)
	if (err != nil) { return [22]byte{}, err }

	processIdentifier := [22]byte(processIdentifierBytes)

	newProcessObject := processObject{
		isComplete: false,
		encounteredError: false,
		errorEncountered: nil,
		numberOfSuccessfulBroadcasts: 0,
		progressDetails: "",
	}

	processObjectsMapMutex.Lock()
	processObjectsMap[processIdentifier] = newProcessObject
	processObjectsMapMutex.Unlock()

	return processIdentifier, nil
}

func increaseProcessSuccessfulBroadcastsCount(processIdentifier [22]byte)error{

	processObjectsMapMutex.Lock()
	defer processObjectsMapMutex.Unlock()

	processObject, exists := processObjectsMap[processIdentifier]
	if (exists == false){
		processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
		return errors.New("increaseProcessSuccessfulBroadcastsCount called with uninitialized process: " + processIdentifierHex)
	}

	processObject.numberOfSuccessfulBroadcasts += 1

	processObjectsMap[processIdentifier] = processObject

	return nil
}

func setProcessProgressDetails(processIdentifier [22]byte, newProgressDetails string)error{

	processObjectsMapMutex.Lock()
	defer processObjectsMapMutex.Unlock()

	processObject, exists := processObjectsMap[processIdentifier]
	if (exists == false){
		processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
		return errors.New("setProcessProgressDetails called with uninitialized process: " + processIdentifierHex)
	}

	processObject.progressDetails = newProgressDetails

	processObjectsMap[processIdentifier] = processObject

	return nil
}

func setProcessEncounteredError(processIdentifier [22]byte, errorEncountered error)error{

	processObjectsMapMutex.Lock()
	defer processObjectsMapMutex.Unlock()

	processObject, exists := processObjectsMap[processIdentifier]
	if (exists == false){
		processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
		return errors.New("setProcessEncounteredError called with uninitialized process: " + processIdentifierHex)
	}

	processObject.isComplete = true
	processObject.encounteredError = true
	processObject.errorEncountered = errorEncountered

	processObjectsMap[processIdentifier] = processObject

	return nil
}

func setProcessIsComplete(processIdentifier [22]byte)error{

	processObjectsMapMutex.Lock()
	defer processObjectsMapMutex.Unlock()

	processObject, exists := processObjectsMap[processIdentifier]
	if (exists == false){
		processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
		return errors.New("setProcessIsComplete called with uninitialized process: " + processIdentifierHex)
	}

	processObject.isComplete = true

	processObjectsMap[processIdentifier] = processObject

	return nil
}


//Outputs:
//	-bool: Process found
//	-bool: Process is complete status
//	-bool: Process encountered error
//	-error: Error that process encountered
//	-int: Number of hosts successfully broadcasted to
//	-string: Process progress details
func GetProcessInfo(processIdentifier [22]byte)(bool, bool, bool, error, int, string){

	processObjectsMapMutex.RLock()
	defer processObjectsMapMutex.RUnlock()

	processObject, exists := processObjectsMap[processIdentifier]
	if (exists == false) {
		return false, false, false, nil, 0, ""
	}

	processIsComplete := processObject.isComplete
	processEncounteredError := processObject.encounteredError
	processError := processObject.errorEncountered
	processNumberOfSuccessfulBroadcasts := processObject.numberOfSuccessfulBroadcasts
	processProgressDetails := processObject.progressDetails

	return true, processIsComplete, processEncounteredError, processError, processNumberOfSuccessfulBroadcasts, processProgressDetails
}

// A broadcast is considered complete when a specified number of hosts have been contacted and have responded with WillHostContent=Yes
//
// Non-Parameters content provided to this function must be of the same profileIdentityHash/messageInbox
// This is because they must be acceptable by the same hosts
//
// Multiple reviews/reports are not allowed. We will only need to broadcast 1 review/report at a time.
//
//Outputs:
//	-bool: At least 1 host found to contact
//	-[22]byte: Process Identifier 
//	-error
func StartContentBroadcast(contentType string, contentNetworkType byte, contentList [][]byte, numberOfHostsToContact int)(bool, [22]byte, error){

	if (len(contentList) == 0){
		return false, [22]byte{}, errors.New("StartContentBroadcast called with empty contentList")
	}

	if (contentType == "Review" || contentType == "Report"){
		if (len(contentList) != 1){
			return false, [22]byte{}, errors.New("StartContentBroadcast does not accept more than 1 review or report")
		}
	}

	isValid := helpers.VerifyNetworkType(contentNetworkType)
	if (isValid == false){
		contentNetworkTypeString := helpers.ConvertByteToString(contentNetworkType)
		return false, [22]byte{}, errors.New("StartContentBroadcast called with invalid contentNetworkType: " + contentNetworkTypeString)
	}

	hostsToContactList, err := GetAvailableHostsToAcceptBroadcastList(contentType, contentNetworkType, contentList)
	if (err != nil) { return false, [22]byte{}, err }

	if (len(hostsToContactList) == 0){
		// No eligible hosts to contact exist. Broadcast is impossible.
		// User should wait for their client to download more hosts and then try again.
		return false, [22]byte{}, nil
	}

	/// Now we get content hashes.

	contentHashesList := make([][]byte, 0, len(contentList))

	for _, contentBytes := range contentList{

		ableToRead, contentHash, err := readContent.GetContentHashFromContentBytes(true, contentType, contentBytes)
		if (err != nil) { return false, [22]byte{}, err }
		if (ableToRead == false){
			return false, [22]byte{}, errors.New("StartContentBroadcast called with invalid " + contentType + " content to broadcast.")
		}

		contentHashesList = append(contentHashesList, contentHash)
	}

	processIdentifier, err := initializeNewProcessObject()
	if (err != nil) { return false, [22]byte{}, err }

	performBroadcasts := func(){

		var contactedHostsListMutex sync.RWMutex	
	
		// We use this list to prevent broadcasting to the same host twice
		contactedHostIdentityHashesList := make([][16]byte, 0)

		checkIfHostHasBeenContacted := func(hostIdentityHash [16]byte)bool{

			contactedHostsListMutex.RLock()
			isContacted := slices.Contains(contactedHostIdentityHashesList, hostIdentityHash)
			contactedHostsListMutex.RUnlock()

			return isContacted
		}

		addContactedHostIdentityHashToList := func(contactedHostIdentityHash [16]byte){
			contactedHostsListMutex.Lock()
			contactedHostIdentityHashesList = append(contactedHostIdentityHashesList, contactedHostIdentityHash)
			contactedHostsListMutex.Unlock()
		}

		var activeBroadcastsMutex sync.RWMutex
		numberOfActiveBroadcasts := 0

		increaseActiveBroadcasts := func(){
			activeBroadcastsMutex.Lock()
			numberOfActiveBroadcasts += 1
			activeBroadcastsMutex.Unlock()
		}
		decreaseActiveBroadcasts := func(){
			activeBroadcastsMutex.Lock()
			numberOfActiveBroadcasts -= 1
			activeBroadcastsMutex.Unlock()
		}
		getNumberOfActiveBroadcasts := func()int{
			activeBroadcastsMutex.RLock()
			result := numberOfActiveBroadcasts
			activeBroadcastsMutex.RUnlock()
			return result
		}

		executeBroadcastToHost := func(hostIdentityHash [16]byte){

			executeBroadcastToHostFunction := func()error{

				processFound, processIsComplete, errorEncountered, _, _, _ := GetProcessInfo(processIdentifier)
				if (processFound == false){
					processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
					return errors.New("executeBroadcastToHostFunction called with uninitialized process: " + processIdentifierHex)
				}
				if (processIsComplete == true || errorEncountered == true){
					// Error may have been encountered from a different broadcast goroutine				
					return nil
				}

				hostIdentityHashString, identityType, err := identity.EncodeIdentityHashBytesToString(hostIdentityHash)
				if (err != nil) { return err }
				if (identityType != "Moderator"){
					return errors.New("executeBroadcastToHost called with non-moderator identity hash.")
				}

				hostIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(hostIdentityHashString, 6)
				if (err != nil) { return err }

				hostProfileFound, connectionEstablished, connectionIdentifier, err := peerClient.EstablishNewConnectionToHost(false, hostIdentityHash, contentNetworkType)
				if (err != nil) { return err }
				if (hostProfileFound == false){
					return nil
				}
				if (connectionEstablished == false){
					err := setProcessProgressDetails(processIdentifier, "Failed to connect to host " + hostIdentityHashTrimmed)
					if (err != nil) { return err }

					return nil
				}

				err = setProcessProgressDetails(processIdentifier, "Broadcasting to host " + hostIdentityHashTrimmed)
				if (err != nil) { return err }

				processFound, processIsComplete, errorEncountered, _, _, _ = GetProcessInfo(processIdentifier)
				if (processFound == false){
					// This should not happen
					return errors.New("Process not found after being found already.")
				}
				if (processIsComplete == true || errorEncountered == true){
					// Error may have been encountered from a different broadcast goroutine	
					return nil
				}

				successfulDownload, contentAcceptedInfoMap, err := sendRequests.BroadcastContentToHost(connectionIdentifier, hostIdentityHash, contentNetworkType, contentType, contentList)
				if (err != nil) { return err }
				if (successfulDownload == false){

					err := setProcessProgressDetails(processIdentifier, "Failed to broadcast to host " + hostIdentityHashTrimmed)
					if (err != nil) { return err }

					return nil
				}

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

					for _, contentHash := range contentHashesList{
						contentAcceptedStatus, exists := contentAcceptedInfoMap[string(contentHash)]
						if (exists == false){
							return false, errors.New("BroadcastContentToHost not verifying contentAcceptedInfoMap contains content hash")
						}
						if (contentAcceptedStatus == false){
							return false, nil
						}
					}
					return true, nil
				}
				contentAcceptedStatus, err := getAllContentAcceptedStatus()
				if (err != nil) { return err }
				if (contentAcceptedStatus == true){

					err := increaseProcessSuccessfulBroadcastsCount(processIdentifier)
					if (err != nil) { return err }

					err = setProcessProgressDetails(processIdentifier, "Successful broadcast to host " + hostIdentityHashTrimmed)
					if (err != nil) { return err }
				}

				return nil
			}

			err := executeBroadcastToHostFunction()
			if (err != nil){				
				logger.AddLogError("General", err)				
				err := setProcessEncounteredError(processIdentifier, err)
				if (err != nil){
					logger.AddLogError("General", err)
				}
			}

			decreaseActiveBroadcasts()
		}

		startTime := time.Now().Unix()

		for {

			processFound, processIsComplete, errorEncountered, _, numberOfSuccessfulBroadcasts, _ := GetProcessInfo(processIdentifier)
			if (processFound == false){
				// This should not happen
				logger.AddLogError("General", errors.New("Process not found during manualBroadcasts loop 1."))
				return
			}
			if (processIsComplete == true || errorEncountered == true){
				return
			}
			if (numberOfSuccessfulBroadcasts >= numberOfHostsToContact){
				// We have completed the required number of broadcasts
				// Nothing left to do
				break
			}

			activeBroadcasts := getNumberOfActiveBroadcasts()

			pendingAndCompletedBroadcasts := numberOfSuccessfulBroadcasts + activeBroadcasts

			if (pendingAndCompletedBroadcasts >= numberOfHostsToContact){

				// We are actively broadcasting to the required number of hosts
				// We wait to see if we need to retry to another host

				currentTime := time.Now().Unix()
				secondsElapsed := currentTime - startTime

				if (secondsElapsed > 150){
					// Something has gone wrong. Broadcasts should timeout before this.					
					err := setProcessEncounteredError(processIdentifier, errors.New("Broadcast failed: Reached timeout."))
					if (err != nil){
						logger.AddLogError("General", err)
					}					
					return
				}

				time.Sleep(time.Second)

				continue
			}

			// We need to start another broadcast
			// We find a host we have not contacted yet

			//Output:
			//	-bool: We found a host
			startNewBroadcast := func()bool{

				for _, hostIdentityHash := range hostsToContactList{

					isContacted := checkIfHostHasBeenContacted(hostIdentityHash)
					if (isContacted == false){

						addContactedHostIdentityHashToList(hostIdentityHash)

						increaseActiveBroadcasts()

						go executeBroadcastToHost(hostIdentityHash)

						return true
					}
				}
				return false
			}

			foundHost := startNewBroadcast()
			if (foundHost == false){
				// There are not enough hosts to contact the requested numberOfHostsToContact
				break
			}
		}

		// We wait for broadcasts to complete
		secondsElapsed := 0
		for {

			processFound, processIsComplete, errorEncountered, _, _, _ := GetProcessInfo(processIdentifier)
			if (processFound == false){
				// This should not happen
				logger.AddLogError("General", errors.New("Process not found while waiting for manualBroadcasts to complete."))
				return
			}
			if (processIsComplete == true || errorEncountered == true){
				// Error may have been encountered from a different broadcast goroutine				
				return
			}

			activeBroadcasts := getNumberOfActiveBroadcasts()
			if (activeBroadcasts <= 0){
				// We are done waiting for broadcasts to complete
				break
			}

			time.Sleep(time.Second)

			secondsElapsed += 1

			if (secondsElapsed > 100){
				// Something has gone wrong.
				err := setProcessEncounteredError(processIdentifier, errors.New("Broadcast failed: Reached timeout."))
				if (err != nil){				
					logger.AddLogError("General", err)
				}	
				return
			}
		}

		err := setProcessIsComplete(processIdentifier)
		if (err != nil){
			logger.AddLogError("General", err)
			return
		}

		processFound, processIsComplete, errorEncountered, _, numberOfSuccessfulBroadcasts, _ := GetProcessInfo(processIdentifier)
		if (processFound == false){
			// This should not happen
			return
		}
		if (processIsComplete == false){
			// This should not happen
			err := setProcessEncounteredError(processIdentifier, errors.New("setProcessIsComplete is not working."))
			if (err != nil) {
				logger.AddLogError("General", err)
			}
			return
		}
		if (errorEncountered == true){
			return
		}

		getFinalBroadcastStatus := func()string{

			if (numberOfSuccessfulBroadcasts != 0){

				return "Broadcast complete!"
			}
			return "Broadcast failed: Hosts unavailable."
		}

		finalBroadcastStatus := getFinalBroadcastStatus()

		err = setProcessProgressDetails(processIdentifier, finalBroadcastStatus)
		if (err != nil){
			logger.AddLogError("General", err)
			return
		}
	}
	
	go performBroadcasts()

	return true, processIdentifier, nil
}


// This function will return a list of all hosts who are available to accept our content, and will host our content
// Non-Parameters content provided to this function must be of the same profileIdentityHash/messageInbox
// This is because they must be acceptable by the same hosts
// Multiple contents are not allowed for reviews and reports

func GetAvailableHostsToAcceptBroadcastList(contentType string, contentNetworkType byte, contentList [][]byte)([][16]byte, error){

	if (contentType != "Parameters" && contentType != "Profile" && contentType != "Message" && contentType != "Review" && contentType != "Report"){
		return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid contentType: " + contentType)
	}

	isValid := helpers.VerifyNetworkType(contentNetworkType)
	if (isValid == false){
		contentNetworkTypeString := helpers.ConvertByteToString(contentNetworkType)
		return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid contentNetworkType: " + contentNetworkTypeString)
	}

	if (len(contentList) == 0){
		return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with empty contentList")
	}

	if (contentType == "Review" || contentType == "Report"){
		if (len(contentList) != 1){
			return nil, errors.New("GetAvailableHostsToAcceptBroadcastList does not accept more than 1 review or report")
		}
	}

	// We use this function to determine if host will host the content we are broadcasting
	//Outputs:
	//	-func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error): Host is hosting my content range
	//	-error
	getCheckIfHostWillHostContentFunction := func()(func(map[int]messagepack.RawMessage)(bool, error), error){

		if (contentType == "Parameters"){

			for _, parametersBytes := range contentList{

				ableToRead, _, parametersNetworkType, _, _, _, _, err := readParameters.ReadParameters(true, parametersBytes)
				if (err != nil) { return nil, err }
				if (ableToRead == false){
					return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid parameters.")
				}
				if (parametersNetworkType != contentNetworkType){
					return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with parameters of different networkType.")
				}
			}

			checkIfHostWillHostParametersFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

				exists, isHostingParameters, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(hostRawProfileMap, "HostingParameters")
				if (err != nil) { return false, err }
				if (exists == false) {
					return false, errors.New("Database corrupt: Contains host profile missing HostingParameters")
				}
				if (isHostingParameters != "Yes"){
					return false, nil
				}

				return true, nil
			}

			return checkIfHostWillHostParametersFunction, nil
		}

		if (contentType == "Profile"){

			var profilesAuthor [16]byte

			for index, profileBytes := range contentList{

				ableToRead, _, profileNetworkType, profileAuthor, _, _, _, err := readProfiles.ReadProfile(true, profileBytes)
				if (err != nil) { return nil, err }
				if (ableToRead == false){
					return nil, errors.New("Trying to broadcast invalid profile.")
				}
				if (profileNetworkType != contentNetworkType){
					return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with profile of different networkType.")
				}

				if (index == 0){
					profilesAuthor = profileAuthor
				} else {
					if (profilesAuthor != profileAuthor){
						return nil, errors.New("Trying to broadcast profiles with different profile authors")			
					}
				}
			}

			identityType, err := identity.GetIdentityTypeFromIdentityHash(profilesAuthor)
			if (err != nil) { return nil, err }

			checkIfHostWillHostProfileFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

				//TODO: Make sure host is hosting unviewable profiles

				hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, identityType)
				if (err != nil) { return false, err }
				if (hostIsHostingIdentityType == false){
					return false, nil				
				}
				if (identityType != "Mate"){
					// Hosts must host either all or none of Host/Moderator profiles
					return true, nil
				}

				identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, profilesAuthor)
				if (err != nil) { return false, err }			
				if (identityIsWithinTheirRange == false){
					return false, nil
				}
				return true, nil
			}

			return checkIfHostWillHostProfileFunction, nil
		}

		if (contentType == "Message"){

			var messagesInbox [10]byte

			for index, messageBytes := range contentList{

				ableToRead, _, messageNetworkType, messageInbox, _, _, _, _, _, _, err := readMessages.ReadChatMessagePublicData(true, messageBytes)
				if (err != nil){ return nil, err }
				if (ableToRead == false){
					return nil, errors.New("Trying to broadcast invalid message.")
				}
				if (messageNetworkType != contentNetworkType){
					return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with message of different networkType.")
				}

				if (index == 0){
					messagesInbox = messageInbox
				} else {
					if (messagesInbox != messageInbox){
						return nil, errors.New("Trying to broadcast messages with different inbox")			
					}
				}
			}

			checkIfHostWillHostMessageFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

				hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
				if (err != nil) { return false, err }
				if (hostIsHostingMessages == false){
					return false, nil
				}

				inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, messagesInbox)
				if (err != nil) { return false, err }
				if (inboxIsWithinTheirRange == false){
					return false, nil
				}

				return true, nil
			}

			return checkIfHostWillHostMessageFunction, nil
		}
		if (contentType == "Review"){

			reviewBytes := contentList[0]

			ableToRead, _, reviewNetworkType, _, _, reviewType, reviewedHash, _, _, err := readReviews.ReadReview(true, reviewBytes)
			if (err != nil) { return nil, err }
			if (ableToRead == false){
				return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid review")
			}
			if (reviewNetworkType != contentNetworkType){
				return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with review of different networkType.")
			}

			if (reviewType == "Message"){

				if (len(reviewedHash) != 26){
					reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
					return nil, errors.New("ReadReview returning invalid message reviewedHash: " + reviewedHashHex)
				}

				reviewedMessageHash := [26]byte(reviewedHash)

				messageMetadataExists, _, messageNetworkType, _, reviewedMessageInbox, _, err := contentMetadata.GetMessageMetadata(reviewedMessageHash)
				if (err != nil) { return nil, err }

				if (messageMetadataExists == true && messageNetworkType != contentNetworkType){
					return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with review which reviews a message on a different networkType.")
				}

				checkIfHostWillHostReviewFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

					hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
					if (err != nil){ return false, err }
					if (hostIsHostingMessages == false){
						return false, nil
					}

					if (messageMetadataExists == true){
					
						inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, reviewedMessageInbox)
						if (err != nil) { return false, err }
						if (inboxIsWithinTheirRange == false){
							return false, nil
						}
						return true, nil
					}
					// messageMetadataExists == false

					// We don't know what inbox the message was sent to
					// This should not happen because we presumably just reviewed this message
					// We will only contact hosts who are hosting all inboxes

					minimumInboxBound, maximumInboxBound := byteRange.GetMinimumMaximumInboxBounds()
					if (theirInboxRangeStart == minimumInboxBound && theirInboxRangeEnd == maximumInboxBound){
						return true, nil
					}
					return false, nil
				}

				return checkIfHostWillHostReviewFunction, nil
			}

			// reviewType = "Identity" or "Profile" or "Attribute"

			//Outputs:
			//	-string: Identity type
			//	-bool: Identity hash is known
			//	-[16]byte: Identity hash
			//	-error
			getReviewedIdentityHash := func()(string, bool, [16]byte, error){

				if (reviewType == "Identity"){

					if (len(reviewedHash) != 16){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						return "", false, [16]byte{}, errors.New("ReadReview returning invalid Identity reviewedHash: " + reviewedHashHex)
					}

					reviewedIdentityHash := [16]byte(reviewedHash)

					reviewedIdentityType, err := identity.GetIdentityTypeFromIdentityHash(reviewedIdentityHash)
					if (err != nil) { return "", false, [16]byte{}, err }

					return reviewedIdentityType, true, reviewedIdentityHash, nil					
				}
				if (reviewType == "Profile"){

					if (len(reviewedHash) != 28){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						return "", false, [16]byte{}, errors.New("ReadReview returning invalid Profile reviewedHash: " + reviewedHashHex)
					}

					reviewedProfileHash := [28]byte(reviewedHash)

					reviewedIdentityType, isDisabled, err := readProfiles.ReadProfileHashMetadata(reviewedProfileHash)
					if (err != nil) {
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						return "", false, [16]byte{}, errors.New("ReadReview returning invalid profile review reviewedHash: " + reviewedHashHex)
					}
					if (isDisabled == true){
						return "", false, [16]byte{}, errors.New("ReadReview returning disabled profile as reviewedHash")
					}

					metadataExists, _, profileNetworkType, profileAuthorIdentityHash, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(reviewedProfileHash)
					if (err != nil) { return "", false, [16]byte{}, err }
					if (metadataExists == false){
						return reviewedIdentityType, false, [16]byte{}, nil
					}
					if (profileNetworkType != contentNetworkType){
						return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing a different network type profile.")
					}
					if (profileIsDisabled == true){
						return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing disabled profile.")
					}

					return reviewedIdentityType, true, profileAuthorIdentityHash, nil
				}
				// reviewType == "Attribute"

				if (len(reviewedHash) != 27){
					reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
					return "", false, [16]byte{}, errors.New("ReadReview returning invalid Attribute reviewedHash: " + reviewedHashHex)
				}

				reviewedAttributeHash := [27]byte(reviewedHash)

				authorIdentityType, _, err := readProfiles.ReadAttributeHashMetadata(reviewedAttributeHash)
				if (err != nil){
					reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
					return "", false, [16]byte{}, errors.New("ReadReview returning invalid Attribute reviewedHash: " + reviewedHashHex)
				}

				metadataExists, _, authorIdentityHash, attributeNetworkType, _, err := profileStorage.GetProfileAttributeMetadata(reviewedAttributeHash)
				if (err != nil) { return "", false, [16]byte{}, err }
				if (metadataExists == false){
					return authorIdentityType, false, [16]byte{}, nil
				}
				if (attributeNetworkType != contentNetworkType){
					return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing a different network type attribute.")
				}

				return authorIdentityType, true, authorIdentityHash, nil
			}

			reviewedIdentityType, reviewedIdentityHashKnown, reviewedIdentityHash, err := getReviewedIdentityHash()
			if (err != nil) { return nil, err }

			checkIfHostWillHostReviewFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

				hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, reviewedIdentityType)
				if (err != nil) { return false, err }
				if (hostIsHostingIdentityType == false){
					return false, nil
				}
				if (reviewedIdentityType != "Mate"){
					// Hosts must host either all or none of Host/Moderator profiles. They should host this profile.
					return true, nil
				}

				if (reviewedIdentityHashKnown == true){

					identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, reviewedIdentityHash)
					if (err != nil) { return false, err }
					if (identityIsWithinTheirRange == false){
						return false, nil
					}
					return true, nil
				}
				// reviewedIdentityHashKnown == false

				// This should not happen, because we have presumably just reviewed this identity's profile
				// We will require the host to be hosting all identities

				minimumIdentityBound, maximumIdentityBound := byteRange.GetMinimumMaximumIdentityHashBounds()
				if (hostRangeStart == minimumIdentityBound && hostRangeEnd == maximumIdentityBound){
					return true, nil
				}

				return false, nil
			}

			return checkIfHostWillHostReviewFunction, nil
		}
		// contentType == "Report"

		reportBytes := contentList[0]

		ableToRead, _, reportNetworkType, _, reportType, reportedHash, _, err := readReports.ReadReport(true, reportBytes)
		if (err != nil) { return nil, err }
		if (ableToRead == false){
			return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid report.")
		}
		if (reportNetworkType != contentNetworkType){
			return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with report of different networkType.")
		}

		if (reportType == "Message"){

			if (len(reportedHash) != 26){
				reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
				return nil, errors.New("ReadReport returning invalid length Message reportedHash: " + reportedHashHex)
			}

			reportedMessageHash := [26]byte(reportedHash)

			messageMetadataExists, _, reportedMessageNetworkType, _, reportedMessageInbox, _, err := contentMetadata.GetMessageMetadata(reportedMessageHash)
			if (err != nil) { return nil, err }

			if (messageMetadataExists == true && reportedMessageNetworkType != contentNetworkType){
				return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with report which reports a message from a different networkType.")
			}

			checkIfHostWillHostReportFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

				hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
				if (err != nil) { return false, err }
				if (hostIsHostingMessages == false){
					return false, nil
				}

				if (messageMetadataExists == true){
					
					inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, reportedMessageInbox)
					if (err != nil) { return false, err }
					if (inboxIsWithinTheirRange == false){
						return false, nil
					}
					return true, nil
				}

				// messageMetadataExists == false

				// This should not happen, because we presumably just reported this message
				// We will only contact hosts who are hosting all inboxes

				minimumInboxBound, maximumInboxBound := byteRange.GetMinimumMaximumInboxBounds()

				if (theirInboxRangeStart == minimumInboxBound && theirInboxRangeEnd == maximumInboxBound){
					return true, nil
				}
				return false, nil
			}

			return checkIfHostWillHostReportFunction, nil
		}

		// reportType = "Identity" or "Profile" or "Attribute"

		//Outputs:
		//	-string: Identity type
		//	-bool: Identity hash is known
		//	-[16]byte: Identity hash
		//	-error
		getReportedIdentityHash := func()(string, bool, [16]byte, error){

			if (reportType == "Identity"){

				if (len(reportedHash) != 16){
					reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
					return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Identity reportedHash: " + reportedHashHex)
				}

				reportedIdentityHash := [16]byte(reportedHash)

				reportedIdentityType, err := identity.GetIdentityTypeFromIdentityHash(reportedIdentityHash)
				if (err != nil) { return "", false, [16]byte{}, err }

				return reportedIdentityType, true, reportedIdentityHash, nil
			}
			if (reportType == "Profile"){

				if (len(reportedHash) != 28){
					reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
					return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Identity reportedHash: " + reportedHashHex)
				}

				reportedProfileHash := [28]byte(reportedHash)

				reportedIdentityType, profileIsDisabled, err := readProfiles.ReadProfileHashMetadata(reportedProfileHash)
				if (err != nil) {
					reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
					return "", false, [16]byte{}, errors.New("ReadReport returning invalid profile report reportedHash: " + reportedHashHex)
				}
				if (profileIsDisabled == true){
					return "", false, [16]byte{}, errors.New("ReadReport returning invalid reportedHash: Profile is disabled.")
				}

				metadataExists, _, profileNetworkType, profileAuthorIdentityHash, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(reportedProfileHash)
				if (err != nil) { return "", false, [16]byte{}, err }
				if (metadataExists == false){
					return reportedIdentityType, false, [16]byte{}, nil
				}
				if (profileNetworkType != contentNetworkType){
					return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting a profile from a different networkType.")
				}
				if (profileIsDisabled == true){
					return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting disabled profile.")
				}

				return reportedIdentityType, true, profileAuthorIdentityHash, nil
			}
			// reportType == "Attribute"

			if (len(reportedHash) != 27){
				reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
				return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Attribute reportedHash: " + reportedHashHex)
			}

			reportedAttributeHash := [27]byte(reportedHash)

			authorIdentityType, _, err := readProfiles.ReadAttributeHashMetadata(reportedAttributeHash)
			if (err != nil) {
				reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
				return "", false, [16]byte{}, errors.New("ReadReport returning invalid Attribute reportedHash: " + reportedHashHex)
			}

			metadataExists, _, authorIdentityHash, attributeNetworkType, _, err := profileStorage.GetProfileAttributeMetadata(reportedAttributeHash)
			if (err != nil) { return "", false, [16]byte{}, err }
			if (metadataExists == false){
				return authorIdentityType, false, [16]byte{}, nil
			}
			if (attributeNetworkType != contentNetworkType){
				return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting an attribute from a different networkType.")
			}

			return authorIdentityType, true, authorIdentityHash, nil
		}

		reportedIdentityType, reportedIdentityHashKnown, reportedIdentityHash, err := getReportedIdentityHash()
		if (err != nil) { return nil, err }

		checkIfHostWillHostReportFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){

			hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, reportedIdentityType)
			if (err != nil) { return false, err }
			if (hostIsHostingIdentityType == false){
				return false, nil
			}
			if (reportedIdentityType != "Mate"){
				// Hosts must host either all or none of Host/Moderator profiles. They should host this profile.
				return true, nil
			}

			if (reportedIdentityHashKnown == true){

				identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, reportedIdentityHash)
				if (err != nil) { return false, err }			
				if (identityIsWithinTheirRange == false){
					return false, nil
				}
				return true, nil
			}
			// reportedIdentityHashKnown == false

			// This should not happen, because we presumably just reported the profile
			// We will only broadcast to hosts whom are hosting all identities

			minimumIdentityBound, maximumIdentityBound := byteRange.GetMinimumMaximumIdentityHashBounds()
			if (hostRangeStart == minimumIdentityBound && hostRangeEnd == maximumIdentityBound){
				return true, nil
			}
			return false, nil
		}

		return checkIfHostWillHostReportFunction, nil
	}

	checkIfHostWillHostContentFunction, err := getCheckIfHostWillHostContentFunction()
	if (err != nil) { return nil, err }

	allEligibleHostIdentityHashesList, err := eligibleHosts.GetEligibleHostsList(contentNetworkType)
	if (err != nil) { return nil, err }

	// We use this list to store hosts who are hosting the content we are broadcasting
	availableHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range allEligibleHostIdentityHashesList{

		exists, _, _, _, _, hostRawProfileMap, err := profileStorage.GetNewestUserProfile(hostIdentityHash, contentNetworkType)
		if (err != nil) { return nil, err }
		if (exists == false){
			continue
		}

		isHostingMyContent, err := checkIfHostWillHostContentFunction(hostRawProfileMap)
		if (err != nil) { return nil, err }
		if (isHostingMyContent == true){
			availableHostsList = append(availableHostsList, hostIdentityHash)	
		}
	}

	return availableHostsList, nil
}



