
// verifiedVerdict provides functions to determine the current moderator consensus verdict for a particular identity/message/profile
// These verdicts are calculated based on the downloaded moderator reviews, and are thus verified

package verifiedVerdict

// We cannot determine the verified consensus verdict of a message/profile without knowing its metadata.
// See package contentMetadata for an explanation.

// Possible verdicts:
//	-Identity: Banned/Not Banned
//	-Profile/Message: Banned/Approved/Undecided

import "seekia/internal/contentMetadata"
import "seekia/internal/identity"
import "seekia/internal/encoding"
import "seekia/internal/moderation/bannedModeratorConsensus"
import "seekia/internal/moderation/enabledModerators"
import "seekia/internal/moderation/moderatorScores"
import "seekia/internal/moderation/reviewStorage"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/backgroundDownloads"
import "seekia/internal/profiles/readProfiles"

import "errors"

//Outputs:
//	-bool: Client is downloading the required reviews/moderator profiles
//	-bool: Parameters Exist
//	-bool: Identity is banned
//	-int: Number of eligible moderators who have banned the identity
//	-float64: Ban Advocates identity score sum (Sum of identity score of all eligible moderators who banned the identity)
//	-[][16]byte: List of all ban advocates (Eligible and banned)
//	-error 
func GetVerifiedIdentityVerdict(identityHash [16]byte, networkType byte)(bool, bool, bool, int, float64, [][16]byte, error){

	clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(identityHash)
	if (err != nil) { return false, false, false, 0, 0, nil, err }
	if (clientIsDownloadingRequiredReviews == false){
	
		return false, false, false, 0, 0, nil, nil
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) { return false, false, false, 0, 0, nil, err }
	if (appNetworkType != networkType){
		// We are not downloading reviews for this network type.
		return false, false, false, 0, 0, nil, nil
	}

	//TODO: Add parameters check
	parametersExist := true
	if (parametersExist == false){
		
		// We do not have moderation parameters. We cannot determine verdict consensus
		return true, false, false, 0, 0, nil, nil
	}

	banAdvocatesMap, err := reviewStorage.GetIdentityBanAdvocatesMap(identityHash, networkType)
	if (err != nil) { return false, false, false, 0, 0, nil, err }

	numberOfEligibleBanAdvocates := 0
	eligibleBanAdvocatesScoreSum := float64(0)
	allEnabledBanAdvocatesList := make([][16]byte, 0)

	for moderatorIdentityHash, _ := range banAdvocatesMap{

		moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, networkType)
		if (err != nil){ return false, false, false, 0, 0, nil, err }
		if (moderatorIsEnabled == false){
			// We will disregard all of this moderators reviews
			continue
		}

		allEnabledBanAdvocatesList = append(allEnabledBanAdvocatesList, moderatorIdentityHash)

		downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, networkType)
		if (err != nil) { return false, false, false, 0, 0, nil, err }
		if (downloadingRequiredData == false){
			return false, true, false, 0, 0, nil, nil
		}
		if (parametersExist == false){
			return true, false, false, 0, 0, nil, nil
		}
		if (moderatorIsBanned == true){
			continue
		}

		scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash)
		if (err != nil) { return false, false, false, 0, 0, nil, err }
		if (scoreIsKnown == false || scoreIsSufficient == false){
			continue
		}

		numberOfEligibleBanAdvocates += 1
		eligibleBanAdvocatesScoreSum += moderatorScore
	}

	//Outputs:
	//	-bool: Downloading required data
	//	-bool: Parameters exist
	//	-bool: Identity is banned
	//	-error
	getBanConsensusVerdict := func()(bool, bool, bool, error){

		identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
		if (err != nil){
			identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
			return false, false, false, errors.New("getBanConsensusVerdict reached with invalid identityHash: " + identityHashHex)
		}
		if (identityType != "Moderator"){

			//TODO: Retrieve this variable from parameters
			minimumBanAdvocatesNeeded := 3

			if (numberOfEligibleBanAdvocates < minimumBanAdvocatesNeeded){
				return true, true, false, nil
			}
			return true, true, true, nil
		}

		// identityType == "Moderator"

		// For moderators, the banning process is more complicated
		// Moderators must have a higher score to ban another moderator
		// We use another package to calculate the result

		downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, identityHash, networkType)
		if (err != nil) { return false, false, false, err }
		if (downloadingRequiredData == false){
			return false, false, false, nil
		}
		if (parametersExist == false){
			return true, false, false, nil
		}

		return true, true, moderatorIsBanned, nil
	}

	downloadingRequiredData, parametersExist, banConsensusVerdict, err := getBanConsensusVerdict()
	if (err != nil) { return false, false, false, 0, 0, nil, err }	
	if (downloadingRequiredData == false){
		return false, true, false, 0, 0, nil, nil
	}
	if (parametersExist == false){
		return true, false, false, 0, 0, nil, nil
	}

	return true, true, banConsensusVerdict, numberOfEligibleBanAdvocates, eligibleBanAdvocatesScoreSum, allEnabledBanAdvocatesList, nil
}

// This function returns if the provided profile is banned.
// This function does not take into account if the profile author is banned.
// A profile with an insufficient number of reviews/reviewers can be "Undecided"
// Host/Moderator "Undecided" profiles are still considered viewable, only Mate profiles must be "Approved" to be viewable
//Outputs:
//	-bool: Profile is disabled
//	-bool: Profile metadata is known
//	-int: Profile version
//	-byte: Profile network type
//	-[16]byte: Profile Identity Hash
//	-map[int][27]byte: Profile attribute hashes map (Attribute identifier -> Attribute hash)
//	-bool: Client is downloading required reviews and moderator profiles
//	-bool: Parameters exist
//	-string: Moderator consensus verdict ("Approved"/"Banned"/"Undecided")
//	-int: Number of eligible moderators who have approved the profile
//	-int: Number of eligible moderators who have banned the profile (including a single attribute)
//	-float64: Approve Score Sum (sum of identity scores for all eligible moderators who approved the entire profile)
//	-float64: Ban Score Sum (sum of identity scores for all eligible moderators who banned the profile, including a single attribute)
//	-map[int]int: Attribute Identifier -> Number of eligible approve advocates
//	-map[int]int: Attribute identifier -> Number of eligible ban advocates
//		-This does not include users who banned the full profile without describing which attribute was unruleful
//	-map[int]float64: Attribute identifier -> Sum of all eligible approve advocate identity scores
//		-This includes users who banned the full profile.
//	-map[int]float64: Attribute identifier -> Sum of all eligible ban advocate identity scores
//		-This includes moderators who banned the entire profile
//	-[][16]byte: List of all moderators who have approved the profile (Eligible and banned)
//		-This does not include moderators who have only approved some attributes.
//		-This only includes moderators who have approved the full profile.
//	-[][16]byte: List of all moderators who have banned the profile (eligible and banned)
//		-This includes moderators who have banned only 1 attribute.
//	-int: Number of moderators who have banned the full profile (eligible and banned)
//		-This only includes moderators who have banned the full profile (not including ones who only banned attributes)
//	-map[int][][16]byte: Map of attribute identifier -> List of moderators who have approved attribute (eligible and banned)
//	-map[int][][16]byte: Map of Attribute Identifier -> List of moderators who have banned attribute (eligible and banned)
//		-This does not include moderators who have banned the entire profile without describing which attribute was unruleful
//	-error
func GetVerifiedProfileVerdict(profileHash [28]byte)(bool, bool, int, byte, [16]byte, map[int][27]byte, bool, bool, string, int, int, float64, float64, map[int]int, map[int]int, map[int]float64, map[int]float64, [][16]byte, [][16]byte, int, map[int][][16]byte, map[int][][16]byte, error){

	_, profileIsDisabled, err := readProfiles.ReadProfileHashMetadata(profileHash)
	if (err != nil){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, errors.New("GetVerifiedProfileVerdict called with invalid profileHash: " + profileHashHex)
	}

	//TODO: Add parameters check
	parametersExist := true

	metadataExists, profileVersion, profileNetworkType, profileIdentityHash, _, profileIsDisabledB, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash)
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }
	if (metadataExists == false){
		// We do not have profile metadata, we cannot determine moderator consensus
		return profileIsDisabled, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
	}
	if (profileIsDisabled != profileIsDisabledB){
		return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, errors.New("GetProfileMetadata returning different profileIsDisabled status than ReadProfileHashMetadata.")
	}
	if (profileIsDisabled == true){

		return true, true, profileVersion, profileNetworkType, profileIdentityHash, nil, true, parametersExist, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
	}

	clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(profileIdentityHash)
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }
	if (clientIsDownloadingRequiredReviews == false){
		// We cannot determine verdict. Required reviews are not being downloaded.

		return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, false, parametersExist, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
	}

	if (parametersExist == false){
		// We dont have parameters. We cannot determine status.
		return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
	}

	// This stores all moderator identity hashes who reviewed the profile/attributes
	// We use a map to avoid duplicates
	// Map Structure: Reviewer identity hash -> Nothing
	allReviewersMap := make(map[[16]byte]struct{})

	type fullProfileVerdictInfo struct{

		// Verdict for full profile ("Approve"/"Ban")
		Verdict string

		// Time of verdict for full profile
		VerdictTime int64
	}

	// Map Structure: Author Identity hash -> fullProfileVerdictInfo
	fullProfileVerdictsInfoMap := make(map[[16]byte]fullProfileVerdictInfo)

	type attributeReviewObject struct{

		AttributeIdentifier int

		// Verdict for the attribute ("Approve"/"Ban")
		Verdict string

		// Time at which the verdict was made
		VerdictTime int64
	}

	// Map Structure: Reviewer identity hash -> List of attribute review objects
	attributeReviewsMap := make(map[[16]byte][]attributeReviewObject)

	// First we add full profile reviews

	fullProfileApproveAdvocatesMap, fullProfileBanAdvocatesMap, err := reviewStorage.GetProfileVerdictMaps(profileHash, profileNetworkType, false, nil)
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

	addFullProfileVerdictsToMaps := func(reviewersMap map[[16]byte]int64, verdict string)error{

		for reviewerIdentityHash, verdictTime := range reviewersMap{

			allReviewersMap[reviewerIdentityHash] = struct{}{}

			newVerdictInfoObject := fullProfileVerdictInfo{
				Verdict: verdict,
				VerdictTime: verdictTime,
			}

			_, exists := fullProfileVerdictsInfoMap[reviewerIdentityHash]
			if (exists == true){
				return errors.New("Trying to add reviewer to fullProfileVerdictsInfoMap and reviewer entry already exists.")
			}
			fullProfileVerdictsInfoMap[reviewerIdentityHash] = newVerdictInfoObject
		}

		return nil
	}

	err = addFullProfileVerdictsToMaps(fullProfileApproveAdvocatesMap, "Approve")
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

	err = addFullProfileVerdictsToMaps(fullProfileBanAdvocatesMap, "Ban")
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

	// Now we add profile attribute reviews

	for attributeIdentifier, attributeHash := range profileAttributeHashesMap{

		addAttributeVerdictsToMap := func(reviewersMap map[[16]byte]int64, verdict string)error{

			for reviewerIdentityHash, verdictTime := range reviewersMap{

				allReviewersMap[reviewerIdentityHash] = struct{}{}

				newAttributeReviewObject := attributeReviewObject{
					AttributeIdentifier: attributeIdentifier,
					Verdict: verdict,
					VerdictTime: verdictTime,
				}

				existingReviewObjectsList, exists := attributeReviewsMap[reviewerIdentityHash]
				if (exists == false){
					newAttributeReviewObjectsList := []attributeReviewObject{newAttributeReviewObject}
					attributeReviewsMap[reviewerIdentityHash] = newAttributeReviewObjectsList
				} else {
					newAttributeReviewObjectsList := append(existingReviewObjectsList, newAttributeReviewObject)
					attributeReviewsMap[reviewerIdentityHash] = newAttributeReviewObjectsList
				}
			}

			return nil
		}

		approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetProfileAttributeVerdictMaps(attributeHash, profileNetworkType, false, nil)
		if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

		err = addAttributeVerdictsToMap(approveAdvocatesMap, "Approve")
		if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

		err = addAttributeVerdictsToMap(banAdvocatesMap, "Ban")
		if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }
	}

	// This keeps track of the number of eligible moderators who approved the full profile
	numberOfEligibleApproveAdvocates := 0
	// This keeps track of the number of eligible moderators who banned the profile
	numberOfEligibleBanAdvocates := 0

	// This is the sum of all identity scores of all eligible moderators who approved the full profile
	eligibleApproveAdvocateScoresSum := float64(0)
	// This is the sum of all identity scores of all eligible moderators who banned the profile
	eligibleBanAdvocateScoresSum := float64(0)

	// We need these maps when determining final verdict
	// Map Structure: Attribute identifier -> Number of eligible approve advocates
	eligibleAttributeApproveAdvocateCountsMap := make(map[int]int)
	// Map Structure: Attribute identifier -> Number of eligible ban advocates (not including full profile bans)
	eligibleAttributeBanAdvocateCountsMap := make(map[int]int)

	// This is a map that keeps track of each attribute's approve and ban weight
	// This is used to determine if the profile is approved or banned
	// Each approve/ban increases attribute weight by the moderator's identity score
	// Approving the entire profile adds to each attribute approve weight
	// Banning the entire profile adds to each attribute ban weight
	// It ignores the reviews of banned/ineligible moderators
	// Map Structure: Attribute identifier -> Attribute weight
	attributeApproveWeightsMap := make(map[int]float64)
	attributeBanWeightsMap := make(map[int]float64)

	// This is a list of all moderators who have approved the profile (Eligible and banned)
	// This only includes moderators who have approved the full profile.
	// This does not include moderators who have only approved some attributes.
	allProfileApproveAdvocatesList := make([][16]byte, 0)

	// This is a list of all moderators who have banned the profile (eligible and banned)
	// This includes moderators who have banned only 1 attribute.
	allProfileBanAdvocatesList := make([][16]byte, 0)

	// This keeps track of the number of moderators who have banned the full profile (eligible and banned)
	numberOfFullProfileBanAdvocates := 0

	// -This includes moderators who have approved the full profile.
	// Map Structure: Attribute identifier -> List of moderators who have approved attribute (eligible and banned)
	allAttributeApproveAdvocatesMap := make(map[int][][16]byte)
	// -This does not include moderators who have only banned the entire profile, but not the specified attribute
	// Map Structure: Attribute Identifier -> List of moderators who have banned attribute (eligible and banned)	
	allAttributeBanAdvocatesMap := make(map[int][][16]byte)

	// Now we iterate through each moderator to populate the variables we just created

	for moderatorIdentityHash, _ := range allReviewersMap{

		moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, profileNetworkType)
		if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }
		if (moderatorIsEnabled == false){
			// We will disregard all of this moderators reviews
			continue
		}

		downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, profileNetworkType)
		if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }
		if (downloadingRequiredData == false){
			// We cannot determine verdict. Required reviews are not being downloaded.
			return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, false, true, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
		}
		if (parametersExist == false){
			// We dont have parameters. We cannot determine status.
			return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil
		}

		//Outputs:
		//	-bool: Moderator is eligible
		//	-float64: Moderator identity score
		//	-error
		checkIfModeratorIsEligible := func()(bool, float64, error){

			if (moderatorIsBanned == true){
				return false, 0, nil
			}

			scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash)
			if (err != nil) { return false, 0, err }
			if (scoreIsKnown == false || scoreIsSufficient == false){
				return false, 0, nil
			}

			return true, moderatorScore, nil
		}

		moderatorIsEligible, moderatorScore, err := checkIfModeratorIsEligible()
		if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

		// We use the function below to deal with the possibility of a conflict between the profile/attribute verdicts
		// Outputs:
		//	-bool: Full profile verdict exists
		//	-string: Full profile verdict ("Approve"/"Ban")
		//	-map[int]string: Attribute identifier -> Attribute verdict ("Approve"/"Ban")
		//	-error
		getModeratorVerdicts := func()(bool, string, map[int]string, error){

			fullProfileReviewInfoObject, fullProfileReviewExists := fullProfileVerdictsInfoMap[moderatorIdentityHash]

			attributeReviewObjectsList, anyAttributeReviewExists := attributeReviewsMap[moderatorIdentityHash]

			if (fullProfileReviewExists == false && anyAttributeReviewExists == false){
				return false, "", nil, errors.New("allReviewersMap contains reviewer without any reviews.")
			}

			if (fullProfileReviewExists == true && anyAttributeReviewExists == false){

				fullProfileReviewVerdict := fullProfileReviewInfoObject.Verdict

				if (fullProfileReviewVerdict == "Approve"){
					// Full profile is approved. All attributes are approved

					// Map Structure: Attribute identifier -> Attribute verdict
					attributeVerdictsMap := make(map[int]string)

					for attributeIdentifier, _ := range profileAttributeHashesMap{
						attributeVerdictsMap[attributeIdentifier] = "Approve"
					}

					return true, "Approve", attributeVerdictsMap, nil
				}

				emptyMap := make(map[int]string)

				return true, "Ban", emptyMap, nil
			}

			if (fullProfileReviewExists == false && anyAttributeReviewExists == true){

				// Map Structure: Attribute identifier -> Attribute verdict
				attributeReviewsMap := make(map[int]string)

				for _, attributeReviewObject := range attributeReviewObjectsList{

					attributeIdentifier := attributeReviewObject.AttributeIdentifier
					attributeVerdict := attributeReviewObject.Verdict

					attributeReviewsMap[attributeIdentifier] = attributeVerdict
				}

				return false, "", attributeReviewsMap, nil
			}

			// The moderator has reviewed the full profile and at least 1 of the profile's attributes
			// We have to take into account that newer reviews will cancel out older reviews
			//
			// These are the two possible conflicts:
			// 1. if a user bans a profile attribute, and then later approves the entire profile, all of their
			//		previous attribute ban reviews for the profile are discarded
			// 2. If a user approves a full profile, and then later bans an attribute, their full profile approval review is discarded

			fullProfileReviewVerdict := fullProfileReviewInfoObject.Verdict
			fullProfileVerdictTime := fullProfileReviewInfoObject.VerdictTime

			// Outputs:
			//	-bool: Full profile verdict exists
			//	-string: Full profile verdict
			//	-error 
			getFullProfileVerdict := func()(bool, string, error){

				if (fullProfileReviewVerdict == "Ban"){
					return true, "Ban", nil
				}

				for _, attributeReviewObject := range attributeReviewObjectsList{

					attributeVerdict := attributeReviewObject.Verdict			

					if (attributeVerdict == "Ban"){

						attributeVerdictTime := attributeReviewObject.VerdictTime

						if (attributeVerdictTime > fullProfileVerdictTime){
							// The moderator banned an attribute after approving the full profile
							// We discard their full profile approval
							return false, "", nil
						}
					}
				}

				return true, "Approve", nil
			}

			fullProfileReviewExists, fullProfileVerdict, err := getFullProfileVerdict()
			if (err != nil) { return false, "", nil, err }

			if (fullProfileReviewExists == true && fullProfileVerdict == "Approve"){
				// Full profile is approved. All attributes are approved

				// Map Structure: Attribute identifier -> Attribute verdict
				attributeVerdictsMap := make(map[int]string)

				for attributeIdentifier, _ := range profileAttributeHashesMap{
					attributeVerdictsMap[attributeIdentifier] = "Approve"
				}

				return true, "Approve", attributeVerdictsMap, nil
			}

			// Map Structure: Attribute identifier -> Attribute verdict
			attributeVerdictsMap := make(map[int]string)

			for _, attributeReviewObject := range attributeReviewObjectsList{

				attributeIdentifier := attributeReviewObject.AttributeIdentifier
				attributeVerdict := attributeReviewObject.Verdict

				attributeVerdictsMap[attributeIdentifier] = attributeVerdict
			}

			return fullProfileReviewExists, fullProfileVerdict, attributeVerdictsMap, nil
		}

		fullProfileReviewExists, fullProfileVerdict, attributeVerdictsMap, err := getModeratorVerdicts()
		if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

		if (fullProfileReviewExists == true && fullProfileVerdict == "Ban"){

			numberOfFullProfileBanAdvocates += 1
		}

		//Outputs:
		//	-bool: Verdict exists
		//	-string: Verdict
		getModeratorProfileVerdict := func()(bool, string){

			if (fullProfileReviewExists == true){

				return true, fullProfileVerdict
			}

			// A moderator who has banned a single attribute is considered to have banned the entire profile

			for _, attributeVerdict := range attributeVerdictsMap{

				if (attributeVerdict == "Ban"){
					return true, "Ban"
				}
			}

			return false, ""
		}

		profileVerdictExists, profileVerdict := getModeratorProfileVerdict()
		if (profileVerdictExists == true){

			if (profileVerdict == "Approve"){
				allProfileApproveAdvocatesList = append(allProfileApproveAdvocatesList, moderatorIdentityHash)

				if (moderatorIsEligible == true){
					numberOfEligibleApproveAdvocates += 1
					eligibleApproveAdvocateScoresSum += moderatorScore
				}

			} else {
				allProfileBanAdvocatesList = append(allProfileBanAdvocatesList, moderatorIdentityHash)

				if (moderatorIsEligible == true){
					numberOfEligibleBanAdvocates += 1
					eligibleBanAdvocateScoresSum += moderatorScore
				}
			}
		}

		for attributeIdentifier, attributeVerdict := range attributeVerdictsMap{

			addIdentityHashToMapEntryList := func(inputMap map[int][][16]byte){

				currentList, exists := inputMap[attributeIdentifier]
				if (exists == false){
					inputMap[attributeIdentifier] = [][16]byte{moderatorIdentityHash}
				} else {
					currentList = append(currentList, moderatorIdentityHash)
					inputMap[attributeIdentifier] = currentList
				}
			}

			if (attributeVerdict == "Approve"){

				addIdentityHashToMapEntryList(allAttributeApproveAdvocatesMap)

				if (moderatorIsEligible == true){
					eligibleAttributeApproveAdvocateCountsMap[attributeIdentifier] += 1
				}

			} else {

				addIdentityHashToMapEntryList(allAttributeBanAdvocatesMap)

				if (moderatorIsEligible == true){
					eligibleAttributeBanAdvocateCountsMap[attributeIdentifier] += 1
				}
			}
		}

		if (moderatorIsEligible == true){

			// Now we add to attributeApproveWeightsMap and attributeBanWeightsMap

			if (fullProfileReviewExists == true && fullProfileVerdict == "Ban"){

				// We add weight for every attribute entry

				for attributeIdentifier, _ := range profileAttributeHashesMap{

					attributeBanWeightsMap[attributeIdentifier] += moderatorScore
				}
			} else {

				// We add to each attribute, only when user approved/banned them

				for attributeIdentifier, attributeVerdict := range attributeVerdictsMap{

					if (attributeVerdict == "Approve"){
						attributeApproveWeightsMap[attributeIdentifier] += moderatorScore
					} else {
						attributeBanWeightsMap[attributeIdentifier] += moderatorScore
					}
				}
			}
		}
	}

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

	if (len(allProfileApproveAdvocatesList) == 0 && len(allProfileBanAdvocatesList) == 0 && len(allAttributeApproveAdvocatesMap) == 0 && len(allAttributeBanAdvocatesMap) == 0){

		// No reviews exist.
		// Profile is undecided.
		emptyMapA := make(map[int]int)
		emptyMapB := make(map[int]int)
		emptyMapC := make(map[int]float64)
		emptyMapD := make(map[int]float64)
		emptyListA := make([][16]byte, 0)
		emptyListB := make([][16]byte, 0)
		emptyMapE := make(map[int][][16]byte)
		emptyMapF := make(map[int][][16]byte)

		return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, true, "Undecided", 0, 0, 0, 0, emptyMapA, emptyMapB, emptyMapC, emptyMapD, emptyListA, emptyListB, 0, emptyMapE, emptyMapF, nil
	}

	//TODO: Retrieve from parameters
	minimumAttributeApproveAdvocates_Mate := 0 // Applies to all non-canonical attributes even if they are not banned
	minimumAttributeApproveAdvocates_Host := 0 // Only used if attribute/profile is banned
	minimumAttributeApproveAdvocates_Moderator := 0 // Only used if attribute/profile is banned

	getMinimumNeededApprovers := func()int{
		if (userIdentityType == "Mate"){
			return minimumAttributeApproveAdvocates_Mate
		}
		if (userIdentityType == "Host"){
			return minimumAttributeApproveAdvocates_Host
		}
		// userIdentityType == "Moderator"
		return minimumAttributeApproveAdvocates_Moderator
	}

	minimumNeededApprovers := getMinimumNeededApprovers()

	//TODO: Retrieve from parameters
	minimumApprovedRatio_Mate := float64(1.5)
	minimumApprovedRatio_Host := float64(1.7)
	minimumApprovedRatio_Moderator := float64(1.8)

	getMinimumApprovedRatio := func()float64{
		if (userIdentityType == "Mate"){
			return minimumApprovedRatio_Mate
		}
		if (userIdentityType == "Host"){
			return minimumApprovedRatio_Host
		}
		// userIdentityType == "Moderator"
		return minimumApprovedRatio_Moderator
	}

	minimumApprovedRatio := getMinimumApprovedRatio()

	getVerdictConsensus := func()(string, error){

		// This will be true if any non-canonical attribute is undecided
		nonCanonicalUndecidedExists := false

		for attributeIdentifier, attributeHash := range profileAttributeHashesMap{

			_, attributeIsCanonical, err := readProfiles.ReadAttributeHashMetadata(attributeHash)
			if (err != nil){
				attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
				return "", errors.New("profileAttributeHashesMap contains invalid attribute hash: " + attributeHashHex)
			}

			getAttributeApproveWeight := func()float64{

				attributeApproveWeight, approveWeightExists := attributeApproveWeightsMap[attributeIdentifier]
				if (approveWeightExists == false){
					return 0
				}
				return attributeApproveWeight
			}

			getAttributeBanWeight := func()float64{

				attributeBanWeight, banWeightExists := attributeBanWeightsMap[attributeIdentifier]
				if (banWeightExists == false){
					return 0
				}
				return attributeBanWeight
			}

			approveWeight := getAttributeApproveWeight()

			banWeight := getAttributeBanWeight()

			if (approveWeight < 0 || banWeight < 0){
				// This should never happen.
				return "", errors.New("Attribute approve/ban weight is negative.")	
			}

			if (approveWeight == 0 && banWeight == 0){

				if (userIdentityType == "Mate" && attributeIsCanonical == false){
					// Attribute status is Undecided
					nonCanonicalUndecidedExists = true
				}
				continue
			}
			if (approveWeight == 0 && banWeight > 0){
				// The attribute is banned, thus the entire profile is banned.
				return "Banned", nil
			}

			getNumberOfAttributeApproveAdvocates := func()int{

				numberOfAttributeApproveAdvocates, exists := eligibleAttributeApproveAdvocateCountsMap[attributeIdentifier]
				if (exists == false){
					return 0
				}
				return numberOfAttributeApproveAdvocates
			}

			if (approveWeight > 0 && banWeight == 0){

				if (userIdentityType == "Mate" && attributeIsCanonical == false){

					// The attribute must be approved by the minimum number of reviewers
					numberOfAttributeApproveAdvocates := getNumberOfAttributeApproveAdvocates()

					if (numberOfAttributeApproveAdvocates < minimumAttributeApproveAdvocates_Mate){
						// Attribute status is Undecided
						// The attribute is not approved by the minimum number of moderators.
						nonCanonicalUndecidedExists = true
					}
				}
				continue
			}
			// approveWeight > 0 && banWeight > 0

			// There is at least 1 ban on the attribute/profile
 			// We must make sure that the minimum number of approvers have approved the attribute

			numberOfAttributeApproveAdvocates := getNumberOfAttributeApproveAdvocates()
			if (numberOfAttributeApproveAdvocates < minimumNeededApprovers){
				// The attribute has at least 1 ban advocate, and does not have the minimum required approve advocates.
				return "Banned", nil
			}

			// We determine what the verdict is

			approveRatio := approveWeight/banWeight

			if (approveRatio < minimumApprovedRatio){
				return "Banned", nil
			}
			// This attribute has passed. We continue to the next attribute
		}

		// All attributes are either approved or undecided

		if (nonCanonicalUndecidedExists == true){
			// Non-mate profiles do not need non-canonical attributes approved, if there are no ban reviews of the attribute
			// For Mate profiles, all non-canonical attributes must be approved for the profile to be approved
			return "Undecided", nil
		}

		// All attributes passed.
		return "Approved", nil
	}

	verdictConsensus, err := getVerdictConsensus()
	if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err }

	return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, true, verdictConsensus, numberOfEligibleApproveAdvocates, numberOfEligibleBanAdvocates, eligibleApproveAdvocateScoresSum, eligibleBanAdvocateScoresSum, eligibleAttributeApproveAdvocateCountsMap, eligibleAttributeBanAdvocateCountsMap, attributeApproveWeightsMap, attributeBanWeightsMap, allProfileApproveAdvocatesList, allProfileBanAdvocatesList, numberOfFullProfileBanAdvocates, allAttributeApproveAdvocatesMap, allAttributeBanAdvocatesMap, nil
}


//Outputs:
//	-bool: Message metadata is known
//	-int: Message version
//	-byte: Message network type
//	-[10]byte: Message inbox
//	-[25]byte: Message cipher key hash
//	-bool: Client is downloading required reviews and moderator profiles
//	-bool: Parameters exist
//	-string: Message verdict ("Approved"/"Banned"/"Undecided")
//	-int: Number of eligible moderators who have approved the message
//	-int: Number of eligible moderators who have banned the message
//	-float64: Approve identity score sum (sum of identity scores of all eligible moderators who approved the message)
//	-float64: Ban identity score sum (sum of identity scores of all eligible moderators who banned the message)
//	-[][16]byte: List of approve advocates (eligible and banned)
//	-[][16]byte: List of ban advocates (eligible and banned)
//	-error
func GetVerifiedMessageVerdict(messageHash [26]byte)(bool, int, byte, [10]byte, [25]byte, bool, bool, string, int, int, float64, float64, [][16]byte, [][16]byte, error){

	messageMetadataExists, messageVersion, messageNetworkType, _, messageInbox, messageCipherKeyHash, err := contentMetadata.GetMessageMetadata(messageHash)
	if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err }
	if (messageMetadataExists == false){

		// We do not know message metadata, so we cannot retrieve message moderated status
		// Message must be downloaded at some point to get the message metadata
		return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, nil
	}

	clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineMessageVerdict(messageNetworkType, messageInbox, true, messageHash)
	if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err }
	if (clientIsDownloadingRequiredReviews == false){

		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil
	}

	//TODO: Add parameters check
	parametersExist := true

	if (parametersExist == false){
		// We do not have parameters downloaded. We cannot determine consensus verdict
		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil
	}

	approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetMessageVerdictMaps(messageHash, messageNetworkType, messageCipherKeyHash)
	if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err }
	if (len(approveAdvocatesMap) == 0 && len(banAdvocatesMap) == 0){

		// There are no valid reviews for this message

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

		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, true, "Undecided", 0, 0, 0, 0, emptyListA, emptyListB, nil
	}

	getEnabledModeratorsFromMap := func(inputModeratorsMap map[[16]byte]int64)([][16]byte, error){

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

		for moderatorIdentityHash, _ := range inputModeratorsMap{

			moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, messageNetworkType)
			if (err != nil){ return nil, err }
			if (moderatorIsEnabled == false){
				// We will disregard all of this moderators reviews
				continue
			}

			enabledModeratorsList = append(enabledModeratorsList, moderatorIdentityHash)
		}

		return enabledModeratorsList, nil
	}

	allEnabledApproveAdvocatesList, err := getEnabledModeratorsFromMap(approveAdvocatesMap)
	if (err != nil){ return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err }

	allEnabledBanAdvocatesList, err := getEnabledModeratorsFromMap(banAdvocatesMap)
	if (err != nil){ return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err }

	//Outputs:
	//	-bool: Downloading required data
	//	-bool: Parameters exist
	//	-int: Number of eligible moderators
	//	-float64: Sum of all moderator scores
	//	-error
	getEligibleModeratorsCountAndScoresSum := func(inputModeratorsList [][16]byte)(bool, bool, int, float64, error){

		numberOfEligibleModerators := 0
		eligibleModeratorScoresSum := float64(0)

		for _, moderatorIdentityHash := range inputModeratorsList{

			downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, messageNetworkType)
			if (err != nil) { return false, false, 0, 0, err }
			if (downloadingRequiredData == false){
				return false, false, 0, 0, nil
			}
			if (parametersExist == false){
				return false, true, 0, 0, nil
			}
			if (moderatorIsBanned == true){
				continue
			}

			scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash)
			if (err != nil) { return false, false, 0, 0, err }
			if (scoreIsKnown == false || scoreIsSufficient == false){
				continue
			}

			numberOfEligibleModerators += 1
			eligibleModeratorScoresSum += moderatorScore
		}

		return true, true, numberOfEligibleModerators, eligibleModeratorScoresSum, nil
	}

	downloadingRequiredData, parametersExist, numberOfEligibleApproveAdvocates, eligibleApproveAdvocatesScoreSum, err := getEligibleModeratorsCountAndScoresSum(allEnabledApproveAdvocatesList)
	if (downloadingRequiredData == false){
		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil
	}
	if (parametersExist == false){
		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil
	}

	downloadingRequiredData, parametersExist, numberOfEligibleBanAdvocates, eligibleBanAdvocatesScoreSum, err := getEligibleModeratorsCountAndScoresSum(allEnabledBanAdvocatesList)
	if (downloadingRequiredData == false){
		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil
	}
	if (parametersExist == false){
		return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil
	}

	getMessageVerdictConsensus := func()string{

		//TODO: Fix this to use parameters
		minimumBanAdvocatesCount := 3
		minimumApproveAdvocatesCount := 3

		if (numberOfEligibleBanAdvocates < minimumBanAdvocatesCount){
			return "Undecided"
		}

		if (eligibleApproveAdvocatesScoreSum < eligibleBanAdvocatesScoreSum){
			return "Banned"
		}
		if (numberOfEligibleApproveAdvocates >= minimumApproveAdvocatesCount){
			return "Approved"
		}

		return "Undecided"
	}

	messageVerdictConsensus := getMessageVerdictConsensus()

	return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, true, messageVerdictConsensus, numberOfEligibleApproveAdvocates, numberOfEligibleBanAdvocates, eligibleApproveAdvocatesScoreSum, eligibleBanAdvocatesScoreSum, allEnabledApproveAdvocatesList, allEnabledBanAdvocatesList, nil
}



