package gui

//broadcastGui.go implements pages for a user to broadcast, disable, and enable their profiles, and to monitor manual broadcasts

//TODO: Add review replacement functionality
// When broadcasting a review, check if the existing conflicting review exists for the same reviewedhash
// If the review is reviewing an identity that the user has already reviewed and broadcast, we should show this in the GUI
// The user should confirm to overwrite their previous verdict
// Once they agree, the application should delete their old broadcasted review
// We should also add the ability to update existing identity reviews with more errant profiles, if more unruleful profiles are discovered

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/canvas"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/data/binding"
import "fyne.io/fyne/v2/dialog"
import "fyne.io/fyne/v2/layout"
import "fyne.io/fyne/v2/theme"
import "fyne.io/fyne/v2/widget"

import "seekia/internal/appMemory"
import "seekia/internal/currencies"
import "seekia/internal/encoding"
import "seekia/internal/genetics/myPeople"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/imagery"
import "seekia/internal/mateQuestionnaire"
import "seekia/internal/moderation/myIdentityScore"
import "seekia/internal/myIdentity"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/manualBroadcasts"
import "seekia/internal/network/myBroadcasts"
import "seekia/internal/network/myIdentityBalance"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/myLocalProfiles"
import "seekia/internal/profiles/myProfileExports"
import "seekia/internal/profiles/myProfileStatus"
import "seekia/internal/profiles/profileFormat"
import "seekia/internal/profiles/readProfiles"

import "strings"
import "image"
import "time"
import "errors"

func setBroadcastPage(window fyne.Window, profileType string, previousPage func()){

	setLoadingScreen(window, "Broadcast " + profileType + " Profile", "Loading Broadcast Page...")

	currentPage := func(){setBroadcastPage(window, profileType, previousPage)}

	appMemory.SetMemoryEntry("CurrentViewedPage", "Broadcast")

	if (profileType != "Mate" && profileType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("setBroadcastPage called with invalid profile type: " + profileType), previousPage)
		return
	}

	title := getPageTitleCentered("Broadcast " + profileType + " Profile")

	backButton := getBackButtonCentered(previousPage)

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(profileType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == false){

		profileIcon, err := getFyneImageIcon("Profile")
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		iconSize := getCustomFyneSize(10)
		profileIcon.SetMinSize(iconSize)
		profileIconCentered := container.NewHBox(layout.NewSpacer(), profileIcon, layout.NewSpacer())

		description1 := getBoldLabelCentered("Your " + profileType + " identity does not exist.")
		description2 := getLabelCentered("You must create your user identity hash.")
		description3 := getLabelCentered("This hash is how other users will identify you.")

		createButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){
				setChooseNewIdentityHashPage(window, profileType, currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), profileIconCentered, description1, description2, description3, createButton)

		setPageContent(page, window)
		return
	}

	description := getLabelCentered("Broadcast your " + profileType + " profile to the world.")

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	identityExists, myProfileIsActiveStatus, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, appNetworkType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (identityExists == false) {
		setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage)
		return
	}

	myProfileStatusLabel := getBoldLabelCentered("My Profile Status:")

	getProfileStatusSection := func()(*fyne.Container, error){

		if (myProfileIsActiveStatus == true){

			activeIcon, err := getFyneImageIcon("ToggleOn")
			if (err != nil) { return nil, err }

			activeLabel := getBoldLabel("Active")
			activeLabelWithIcon := container.NewGridWithRows(2, activeIcon, activeLabel)

			updateProfileButton := widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), func(){
					setBroadcastMyProfilePage(window, profileType, currentPage, currentPage)
				})

			disableButton := widget.NewButtonWithIcon("Disable", theme.CancelIcon(), func(){
					setDisableMyProfilePage(window, profileType, currentPage)
				})

			actionButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, updateProfileButton, disableButton))

			profileStatusSection := container.NewVBox(activeLabelWithIcon, widget.NewSeparator(), actionButtonsGrid)

			return profileStatusSection, nil
		}

		// myProfileIsActiveStatus == false

		// Profile is not active.
		// Possible reasons:
		//	-Profile was never broadcast
		// 	-Identity is not funded
		//	-Profile is disabled
		//	-Profile has expired from network (For Mate profiles only)

		inactiveIcon, err := getFyneImageIcon("ToggleOff")
		if (err != nil) { return nil, err }

		inactiveLabel := getBoldLabel("Inactive")
		inactiveLabelWithIcon := container.NewGridWithRows(2, inactiveIcon, inactiveLabel)

		exists, localIsDisabled, err := myLocalProfiles.GetProfileData(profileType, "Disabled")
		if (err != nil) { return nil, err }
		if (exists == true && localIsDisabled == "Yes"){

			enableProfileButton := getWidgetCentered(widget.NewButton("Enable", func(){
					setEnableMyProfilePage(window, profileType, currentPage, currentPage)
				}))

			profileStatusSection := container.NewVBox(inactiveLabelWithIcon, widget.NewSeparator(), enableProfileButton)

			return profileStatusSection, nil
		}

		broadcastProfileFunction := func()error{

			if (profileType == "Mate"){

				myIdentityFound, myBalanceIsSufficient, _, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
				if (err != nil){ return err }
				if (myIdentityFound == false){
					return errors.New("My identity not found after being found already.")
				}
				if (myBalanceIsSufficient == false) {
					dialogTitle := translate("Identity Balance Is Insufficient.")
					dialogMessageA := getLabelCentered(translate("Your identity balance is insufficient."))
					dialogMessageB := getLabelCentered(translate("You must spend funds to fund your identity and broadcast a profile."))
					dialogMessageC := getLabelCentered(translate("Visit the Manage Identity Balance page to fund your identity."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return nil
				}

			} else if (profileType == "Moderator"){

				myIdentityFound, _, scoreIsSufficient, _, _, err := myIdentityScore.GetMyIdentityScore()
				if (err != nil) { return err }
				if (myIdentityFound == false){
					return errors.New("My moderator identity not found after being found already.")
				}
				if (scoreIsSufficient == false){

					dialogTitle := translate("Identity Score Is Insufficient.")
					dialogMessageA := getLabelCentered(translate("Your identity score is insufficient."))
					dialogMessageB := getLabelCentered(translate("You must send cryptocurrency to fund your identity."))
					dialogMessageC := getLabelCentered(translate("Visit the Manage Identity Score page to fund your identity."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return nil
				}
			}

			setBroadcastMyProfilePage(window, profileType, currentPage, currentPage)
			return nil
		}

		broadcastButton := getWidgetCentered(widget.NewButtonWithIcon("Broadcast", theme.RadioButtonCheckedIcon(), func(){
				err := broadcastProfileFunction()
				if (err != nil){
					setErrorEncounteredPage(window, err, previousPage)
					return
				}
			}))

		profileStatusSection := container.NewVBox(inactiveLabelWithIcon, broadcastButton)

		return profileStatusSection, nil
	}

	profileStatusSection, err := getProfileStatusSection()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), myProfileStatusLabel, widget.NewSeparator(), profileStatusSection, widget.NewSeparator())

	if (myProfileIsActiveStatus == true && profileType == "Mate"){

		addProfileExpirationTimeSection := func()error{

			profileExpirationTimeLabel := getItalicLabelCentered("My Profile Expiration Time:")

			page.Add(profileExpirationTimeLabel)

			// All mate profiles will expire after an expiration duration, which is defined in the network parameters
			// They must be updated, or else they will be dropped by the network
			// We will determine how long this current profile has until it is expired
			// The user has to broadcast a profile again to reset this countdown

			myIdentityExists, myProfileExists, _, myProfileAttributeExists, myProfileCreationTime, err := myBroadcasts.GetAnyAttributeFromMyBroadcastProfile(myIdentityHash, appNetworkType, "CreationTime")
			if (err != nil) { return err }
			if (myIdentityExists == false) {
				return errors.New("My identity not found after being found already.")
			}
			if (myProfileExists == false){
				return errors.New("My broadcast profile not found after myProfileStatus has already been determined to be active.")
			}
			if (myProfileAttributeExists == false){
				return errors.New("My Broadcast profile malformed: Missing CreationTime")
			}

			myProfileCreationTimeInt64, err := helpers.ConvertCreationTimeStringToInt64(myProfileCreationTime)
			if (err != nil){
				return errors.New("My Broadcast profile malformed: Contains invalid creationTime: " + myProfileCreationTime)
			}

			_, mateProfileMaximumExistenceDuration, err := getParameters.GetMateProfileMaximumExistenceDuration(appNetworkType)
			if (err != nil) { return err }

			currentTime := time.Now().Unix()
			profileExistenceTime := currentTime - myProfileCreationTimeInt64

			if (profileExistenceTime >= mateProfileMaximumExistenceDuration){

				return errors.New("My Broadcast profile is expired, but myProfileStatus says it is active")
			}

			timeUntilExpiration := mateProfileMaximumExistenceDuration - profileExistenceTime

			timeUntilExpirationString, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeUntilExpiration, true)
			if (err != nil) { return err }

			timeUntilExpirationLabel := getBoldLabel("Expires in " + timeUntilExpirationString)

			profileExpirationTimeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setMateProfileExpirationExplainerPage(window, currentPage)
				})

			timeUntilExpirationRow := container.NewHBox(layout.NewSpacer(), timeUntilExpirationLabel, profileExpirationTimeHelpButton, layout.NewSpacer())

			page.Add(timeUntilExpirationRow)
			page.Add(widget.NewSeparator())

			return nil
		}

		err := addProfileExpirationTimeSection()
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

	if (profileType == "Mate"){

		getMyIdentityBalanceSection := func()(*fyne.Container, error){

			myIdentityBalanceLabel := getItalicLabelCentered("My Identity Balance:")

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

				myIdentityFound, myBalanceIsSufficient, myBalanceExpirationTime, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
				if (err != nil){ return "", err }
				if (myIdentityFound == false){
					return "", errors.New("My identity not found after being found already.")
				}

				if (myBalanceIsSufficient == false){
					result := translate("Insufficient")
					return result, nil
				}

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

				timeLeft := myBalanceExpirationTime - currentTime

				timeTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeLeft, true)
				if (err != nil) { return "", err }

				identityBalanceExpirationTime := "Expires in " + timeTranslated

				return identityBalanceExpirationTime, nil
			}

			myIdentityBalanceStatus, err := getMyIdentityBalanceStatus()
			if (err != nil){ return nil, err }

			myIdentityBalanceStatusLabel := getBoldLabelCentered(myIdentityBalanceStatus)

			manageBalanceButton := getWidgetCentered(widget.NewButtonWithIcon("Manage", theme.VisibilityIcon(), func(){
					setViewMyIdentityBalancePage(window, profileType, currentPage)
				}))

			identityBalanceSection := container.NewVBox(myIdentityBalanceLabel, myIdentityBalanceStatusLabel, manageBalanceButton)

			return identityBalanceSection, nil
		}

		myIdentityBalanceSection, err := getMyIdentityBalanceSection()
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		page.Add(myIdentityBalanceSection)

	} else if (profileType == "Moderator"){

		getIdentityScoreSection := func()(*fyne.Container, error){

			myIdentityScoreLabel := getItalicLabelCentered("My Identity Score:")

			myIdentityFound, _, scoreIsSufficient, _, _, err := myIdentityScore.GetMyIdentityScore()
			if (err != nil) { return nil, err }
			if (myIdentityFound == false){
				return nil, errors.New("My identity not found after being found already.")
			}

			getIdentityScoreStatus := func()string{
				if (scoreIsSufficient == false){
					result := translate("Insufficient")
					return result
				}

				result := translate("Sufficient")
				return result
			}

			identityScoreStatus := getIdentityScoreStatus()

			identityScoreStatusLabel := getBoldLabelCentered(identityScoreStatus)

			manageIdentityScoreButton := getWidgetCentered(widget.NewButtonWithIcon("View Identity Score", theme.VisibilityIcon(), func(){
					setViewMyModeratorScorePage(window, currentPage)
				}))

			identityScoreSection := container.NewVBox(myIdentityScoreLabel, identityScoreStatusLabel, manageIdentityScoreButton)

			return identityScoreSection, nil
		}

		identityScoreSection, err := getIdentityScoreSection()
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		page.Add(identityScoreSection)
	}

	setPageContent(page, window)
}

func setBroadcastMyProfilePage(window fyne.Window, profileType string, previousPage func(), pageToVisitAfter func()) {

	currentPage := func(){setBroadcastMyProfilePage(window, profileType, previousPage, pageToVisitAfter)}

	title := getPageTitleCentered(translate("Broadcast " + profileType + " Profile"))

	backButton := getBackButtonCentered(previousPage)

	if (profileType == "Mate"){

		myGenomePersonIdentifierExists, myGenomePersonIdentifier, err := myLocalProfiles.GetProfileData("Mate", "GenomePersonIdentifier")
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		if (myGenomePersonIdentifierExists == true){

			anyGenomesExist, personAnalysisIsReady, _, err := myPeople.CheckIfPersonAnalysisIsReady(myGenomePersonIdentifier)
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}
			if (anyGenomesExist == true && personAnalysisIsReady == false){

				description1 := getBoldLabelCentered(translate("Your profile contains a linked genome person."))
				description2 := getLabelCentered(translate("You need to perform your genetic analysis."))
				description3 := getLabelCentered(translate("Only the information you choose will be shared in your profile."))

				performAnalysisButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Perform Analysis"), theme.NavigateNextIcon(), func(){
						setConfirmPerformPersonAnalysisPage(window, myGenomePersonIdentifier, currentPage, currentPage)
					}))

				page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, performAnalysisButton)

				setPageContent(page, window)
				return
			}
		}
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	err = myProfileExports.UpdateMyExportedProfile(profileType, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(profileType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}
	if (myIdentityExists == false){
		setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage) 
		return
	}

	existingBroadcastProfileExists, _, _, _, existingBroadcastProfileRawMap, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator())

	if (existingBroadcastProfileExists == true){
		description1 := getBoldLabelCentered("Are you sure you want to update your " + profileType + " profile?")
		page.Add(description1)
	} else {
		description1 := getBoldLabelCentered("Are you sure you want to broadcast your " + profileType + " profile?")
		page.Add(description1)
	}

	viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), func(){
			setViewMyProfilePage(window, profileType, "Local", currentPage)
		}))
	page.Add(viewProfileButton)

	page.Add(widget.NewSeparator())

	if (existingBroadcastProfileExists == true){

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

			exists, lastCreationTimeString, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(existingBroadcastProfileRawMap, "CreationTime")
			if (err != nil){ return "", err }
			if (exists == false){
				return "", errors.New("My broadcast profile missing CreationTime")
			}

			lastCreationTimeInt64, err := helpers.ConvertStringToInt64(lastCreationTimeString)
			if (err != nil){
				return "", errors.New("My broadcast profile contains invalid CreationTime: " + lastCreationTimeString)
			}

			lastUpdatedTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(lastCreationTimeInt64, true)
			if (err != nil){ return "", err }

			return lastUpdatedTimeAgo, nil
		}

		lastUpdatedTimeAgo, err := getLastUpdatedTimeAgo()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return	
		}

		lastUpdatedTitle := widget.NewLabel("Last Updated:")
		lastUpdatedLabel := getBoldLabel(lastUpdatedTimeAgo)

		lastUpdatedRow := container.NewHBox(layout.NewSpacer(), lastUpdatedTitle, lastUpdatedLabel, layout.NewSpacer())

		page.Add(lastUpdatedRow)

		getNumberOfChangesMade := func()(int, error){

			profileFound, _, _, exportProfileRawMap, err := myProfileExports.GetMyExportedProfile(profileType, appNetworkType)
			if (err != nil) { return 0, err }
			if (profileFound == false){
				return 0, errors.New("My exported profile not found after profile was exported.")		
			}

			allAttributesMap := make(map[int]struct{})

			for attributeIdentifier, _ := range existingBroadcastProfileRawMap{
				allAttributesMap[attributeIdentifier] = struct{}{}
			}

			for attributeIdentifier, _ := range exportProfileRawMap{
				allAttributesMap[attributeIdentifier] = struct{}{}
			}

			changesMade := 0

			// This will add the number of changed fields
			for attributeIdentifier, _ := range allAttributesMap{

				attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
				if (err != nil) { return 0, err }

				if (attributeName == "CreationTime" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "MlkemKey"){
					continue
				}

				existingValueExists, existingValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(existingBroadcastProfileRawMap, attributeName)
				if (err != nil) { return 0, err }

				exportValueExists, exportValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(exportProfileRawMap, attributeName)
				if (err != nil) { return 0, err }

				if (existingValueExists != exportValueExists || existingValue != exportValue){

					// The value has changed between the old and new profile 

					changesMade += 1
				}
			}

			return changesMade, nil
		}

		numberOfChangesMade, err := getNumberOfChangesMade()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return	
		}

		numberOfChangesMadeString := helpers.ConvertIntToString(numberOfChangesMade)

		changesMadeTitle := getItalicLabel("Changes Made:")

		numberOfChangesMadeLabel := getBoldLabel(numberOfChangesMadeString)

		numberOfChangesMadeRow := container.NewHBox(layout.NewSpacer(), changesMadeTitle, numberOfChangesMadeLabel, layout.NewSpacer())
		page.Add(numberOfChangesMadeRow)

		if (numberOfChangesMade != 0){

			viewChangesButton := getWidgetCentered(widget.NewButtonWithIcon("View Changes", theme.VisibilityIcon(), func(){
					setViewNewProfileChangesPage(window, profileType, currentPage)
				}))

			page.Add(viewChangesButton)
		}	

		page.Add(widget.NewSeparator())
	}

	if (profileType == "Mate"){

		// Mate profiles must be funded with each broadcast

		getCurrentAppCurrency := func()(string, error){
			exists, currentAppCurrency, err := globalSettings.GetSetting("Currency")
			if (err != nil) { return "", err }
			if (exists == false){
				return "USD", nil
			}
			return currentAppCurrency, nil
		}

		currentAppCurrencyCode, err := getCurrentAppCurrency()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		_, appCurrencySymbol, err := currencies.GetCurrencyInfoFromCurrencyCode(currentAppCurrencyCode)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		costTitle := widget.NewLabel("Cost:")

		appCurrencySymbolButton := widget.NewButton(appCurrencySymbol, func(){
				setChangeAppCurrencyPage(window, currentPage)	
			})

		//TODO: Fix this to actually calculate cost (get cost from parameters)
		costLabel := getBoldLabel("0.1 " + currentAppCurrencyCode)

		costRow := container.NewHBox(layout.NewSpacer(), costTitle, appCurrencySymbolButton, costLabel, layout.NewSpacer())
		page.Add(costRow)

		page.Add(widget.NewSeparator())
	}

	broadcastIcon, err := getFyneImageIcon("Broadcast")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	confirmButton := widget.NewButtonWithIcon("Broadcast", theme.ConfirmIcon(), func(){

			//TODO: Make sure funds are available

			setConfirmBroadcastMyProfilePage(window, profileType, currentPage)
		})

	confirmButtonWithIcon := getContainerCentered(container.NewGridWithRows(2, broadcastIcon, confirmButton))

	page.Add(confirmButtonWithIcon)
	page.Add(widget.NewSeparator())		

	setPageContent(page, window)
}

func setConfirmBroadcastMyProfilePage(window fyne.Window, profileType string, previousPage func()){

	if (profileType != "Mate" && profileType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("setConfirmBroadcastMyProfilePage called with invalid profileType: " + profileType), previousPage)
		return
	}

	title := getPageTitleCentered("Confirm Broadcast Profile")

	backButton := getBackButtonCentered(previousPage)

	//TODO: Improve wording of below:

	description1 := getBoldLabelCentered("Confirm broadcast your " + profileType + " profile?")
	description2 := getLabelCentered("This will upload your profile to the Seekia network.")
	description3 := getLabelCentered("The whole world will be able to see it.")
	description4 := getLabelCentered("Make sure you are comfortable sharing your profile with the world.")

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){

			profilePage := func(){setProfilePage(window, true, profileType, false, nil)}
			afterCompletionPage := func(){setBroadcastPage(window, profileType, profilePage)}

			if (profileType == "Mate"){

				setStartAndMonitorMateProfileFundingAndBroadcastPage(window, afterCompletionPage)

			} else if (profileType == "Moderator"){

				setStartAndMonitorMyModeratorProfileBroadcastPage(window, afterCompletionPage)
			}
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, confirmButton)

	setPageContent(page, window)
}

// This function will show the changes between the existing broadcast profile and the new exported profile
func setViewNewProfileChangesPage(window fyne.Window, myProfileType string, previousPage func()){

	setLoadingScreen(window, "View Profile Changes", "Loading profile changes...")

	currentPage := func(){setViewNewProfileChangesPage(window, myProfileType, previousPage)}

	title := getPageTitleCentered("View Profile Changes")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below are the changes between your old and new profile.")

	getChangesGrid := func()(*fyne.Container, error){

		myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
		if (err != nil){ return nil, err }
		if (myIdentityExists == false){
			return nil, errors.New("setViewNewProfileChangesPage called with missing identity.")
		}

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return nil, err }

		oldProfileExists, _, _, _, oldProfileRawMap, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
		if (err != nil) { return nil, err }
		if (oldProfileExists == false){
			return nil, errors.New("setViewNewProfileChangesPage called when no broadcast profile exists.")
		}

		profileFound, _, _, newProfileRawMap, err := myProfileExports.GetMyExportedProfile(myProfileType, appNetworkType)
		if (err != nil) { return nil, err }
		if (profileFound == false){
			return nil, errors.New("setViewNewProfileChangesPage called when exportedProfile is missing.")
		}

		// We use this map to avoid duplicates
		allAttributesIdentifiersMap := make(map[int]struct{})

		for attributeIdentifier, _ := range oldProfileRawMap{
			allAttributesIdentifiersMap[attributeIdentifier] = struct{}{}
		}

		for attributeIdentifier, _ := range newProfileRawMap{
			allAttributesIdentifiersMap[attributeIdentifier] = struct{}{}
		}

		allAttributeNamesList := make([]string, 0, len(allAttributesIdentifiersMap))

		for attributeIdentifier, _ := range allAttributesIdentifiersMap{

			attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
			if (err != nil) { return nil, err }

			allAttributeNamesList = append(allAttributeNamesList, attributeName)
		}

		// We sort attributes so they show up in the same order each time
		helpers.SortStringListToUnicodeOrder(allAttributeNamesList)

		attributeLabel := getItalicLabelCentered("Attribute")
		oldProfileTitle := getItalicLabelCentered("Old Profile")
		newProfileTitle := getItalicLabelCentered("New Profile")

		attributeTitleColumn := container.NewVBox(attributeLabel, widget.NewSeparator())
		oldProfileColumn := container.NewVBox(oldProfileTitle, widget.NewSeparator())
		newProfileColumn := container.NewVBox(newProfileTitle, widget.NewSeparator())

		for _, attributeName := range allAttributeNamesList{

			if (attributeName == "CreationTime" || attributeName == "Disabled" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "MlkemKey"){
				continue
			}

			oldValueExists, oldValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(oldProfileRawMap, attributeName)
			if (err != nil) { return nil, err }

			newValueExists, newValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(newProfileRawMap, attributeName)
			if (err != nil) { return nil, err }

			if (oldValueExists == false && newValueExists == false){
				// This should never happen. Probably cosmic ray bit flip or faulty hardware.
				return nil, errors.New("oldProfileRawMap and newProfileRawMap are missing the attribute.")
			}
			if (oldValueExists == true && newValueExists == true && oldValue == newValue){
				// The value is the same between both profiles
				continue
			}

			attributeTitle, _, formatValueFunction, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
			if (err != nil){ return nil, err }

			attributeTitleLabel := getBoldLabelCentered(attributeTitle)

			getProfileValueCell := func(valueExists bool, profileValue string)(*fyne.Container, error){

				if (valueExists == false){
					noneLabel := getItalicLabelCentered(translate("None"))
					return noneLabel, nil
				}

				switch attributeName{

					case "Photos":{

						photosBase64List := strings.Split(profileValue, "+")

						photosList := make([]image.Image, 0, len(photosBase64List))

						for _, photoBase64 := range photosBase64List{

							goImage, err := imagery.ConvertJPEGBase64StringToCroppedDownsizedImageObject(photoBase64)
							if (err != nil){
								return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Photos attribute: " + profileValue)
							}

							photosList = append(photosList, goImage)
						}

						if (len(photosList) > 5){
							return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Photos attribute: Too many photos: " + profileValue)
						}

						viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
								setViewFullpageImagesWithNavigationPage(window, photosList, 0, currentPage)
							}))

						return viewAttributeButton, nil
					}

					case "Avatar":{

						avatarIdentifier, err := helpers.ConvertStringToInt(profileValue)
						if (err != nil) {
							return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Avatar attribute: " + profileValue)
						}

						emojiImage, err := getEmojiImageObject(avatarIdentifier)
						if (err != nil){
							return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Avatar attribute: " + profileValue)
						}

						viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
								setViewFullpageImagePage(window, emojiImage, currentPage)
							}))

						return viewAttributeButton, nil
					}

					case "23andMe_AncestryComposition":{

						viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
								setViewUser23andMeAncestryCompositionPage(window, profileValue, currentPage)		
							}))

						return viewAttributeButton, nil
					}

					case "Questionnaire":{

						questionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(profileValue)
						if (err != nil) {
							return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Questionnaire attribute: " + profileValue + ". Reason: " + err.Error())
						}

						myResponsesMap := make(map[string]string)

						submitQuestionnairePage := func(_ string, _ func()){
							currentPage()
						}

						viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
								setTakeQuestionnairePage(window, questionnaireObject, 0, myResponsesMap, currentPage, submitQuestionnairePage)
							}))

						return viewAttributeButton, nil
					}

					//TODO: Format more values that cannot be displayed as a string in their raw form (Tags, Location, Language, etc...)
				}

				valueFormatted, err := formatValueFunction(profileValue)
				if (err != nil) { return nil, err }

				valueTrimmed, anyChangesOccurred, err := helpers.TrimAndFlattenString(valueFormatted, 20)
				if (err != nil) { return nil, err }
				if (anyChangesOccurred == false){

					valueLabel := getBoldLabelCentered(valueFormatted)

					return valueLabel, nil
				}

				trimmedValueLabel := getBoldLabel(valueTrimmed)

				viewFullValueButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewTextPage(window, "Viewing Attribute", valueFormatted, false, currentPage)
					})

				profileValueCell := container.NewHBox(layout.NewSpacer(), trimmedValueLabel, viewFullValueButton, layout.NewSpacer())

				return profileValueCell, nil
			}

			oldValueCell, err := getProfileValueCell(oldValueExists, oldValue)
			if (err != nil) { return nil, err }

			newValueCell, err := getProfileValueCell(newValueExists, newValue)
			if (err != nil) { return nil, err }

			attributeTitleColumn.Add(attributeTitleLabel)
			oldProfileColumn.Add(oldValueCell)
			newProfileColumn.Add(newValueCell)

			attributeTitleColumn.Add(widget.NewSeparator())
			oldProfileColumn.Add(widget.NewSeparator())
			newProfileColumn.Add(widget.NewSeparator())
		}

		displayGrid := container.NewHBox(layout.NewSpacer(), attributeTitleColumn, oldProfileColumn, newProfileColumn, layout.NewSpacer())

		return displayGrid, nil	
	}

	displayGrid, err := getChangesGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), displayGrid)

	setPageContent(page, window)
}


func setEnableMyProfilePage(window fyne.Window, myProfileType string, previousPage func(), pageToVisitAfter func()){

	if (myProfileType != "Mate" && myProfileType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("setEnableMyProfilePage called with invalid profileType: " + myProfileType), previousPage)
		return
	}

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == false){
		// This should not happen, this page should only be reached if identity exists
		setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage)
		return
	}

	attributeExists, localProfileIsDisabled, err := myLocalProfiles.GetProfileData(myProfileType, "Disabled")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}
	if (attributeExists == false || localProfileIsDisabled != "Yes"){
		// This should not happen, as this page should only be viewed if profile is disabled	
		setErrorEncounteredPage(window, errors.New("setEnableMyProfilePage called when your profile is not disabled."), previousPage)
		return
	}

	title := getPageTitleCentered("Enable " + myProfileType + " Profile")

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Are you sure you want to re-enable your profile?")
	description2 := getLabelCentered("You must broadcast your profile after this step.")

	confirmFunction := func(){

		err = myBroadcasts.DeleteMyBroadcastProfiles(myIdentityHash)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		err := myLocalProfiles.SetProfileData(myProfileType, "Disabled", "No")
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		pageToVisitAfter()
	}

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), confirmFunction))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, confirmButton)

	setPageContent(page, window)
}

func setDisableMyProfilePage(window fyne.Window, myProfileType string, previousPage func()){

	if (myProfileType != "Mate" && myProfileType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("setDisableMyProfilePage called with invalid profile type: " + myProfileType), previousPage)
		return
	}

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == false){
		// This should not happen, this page should only be reached if identity exists
		setErrorEncounteredPage(window, errors.New("My identity not found on setDisableMyProfilePage."), previousPage)
		return
	}

	currentPage := func(){setDisableMyProfilePage(window, myProfileType, previousPage)}

	title := getPageTitleCentered("Disable My " + myProfileType + " Profile")
	
	backButton := getBackButtonCentered(previousPage)

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

		attributeExists, localProfileIsDisabled, err := myLocalProfiles.GetProfileData(myProfileType, "Disabled")
		if (err != nil){ return false, err }
		if (attributeExists == true && localProfileIsDisabled == "Yes"){
			return true, nil
		}

		return false, nil
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

		identityExists, profileExists, _, attributeExists, broadcastProfileIsDisabled, err := myBroadcasts.GetAnyAttributeFromMyBroadcastProfile(myIdentityHash, appNetworkType, "Disabled")
		if (err != nil) { return false, err }
		if (identityExists == false) {
			return false, errors.New("My identity not found after being found already.")
		}
		if (profileExists == false){
			return false, nil
		}
		if (attributeExists == true && broadcastProfileIsDisabled == "Yes"){
			return true, nil
		}
		return false, nil
	}

	localProfileIsDisabled, err := getLocalProfileIsDisabledStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	broadcastProfileIsDisabled, err := getBroadcastProfileIsDisabledStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (localProfileIsDisabled == true && broadcastProfileIsDisabled == true){
		// This should not happen, as this page should only be viewed if profile is enabled	
		setErrorEncounteredPage(window, errors.New("setDisableMyProfilePage accessed with enabled/missing profile."), previousPage)
		return
	}

	description1 := getBoldLabelCentered("Confirm to disable your " + myProfileType + " Profile?")
	description2 := getLabelCentered("Other users will see your profile as disabled.")
	description3 := getLabelCentered("You will stop receiving " + myProfileType + " messages.")
	description4 := getLabelCentered("You can always enable your profile later.")

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4)

	if (myProfileType == "Mate"){
		description5 := getLabelCentered("Your identity balance will be retained, but will continue to expire.")
		page.Add(description5)	
	} else if (myProfileType == "Moderator"){
		description5 := getLabelCentered("Your identity score will be retained.")
		page.Add(description5)	
	}

	if (myProfileType == "Mate"){

		// Mate profiles must be funded with each broadcast

		page.Add(widget.NewSeparator())

		getCurrentAppCurrency := func()(string, error){
			exists, currentAppCurrency, err := globalSettings.GetSetting("Currency")
			if (err != nil) { return "", err }
			if (exists == false){
				return "USD", nil
			}
			return currentAppCurrency, nil
		}

		currentAppCurrencyCode, err := getCurrentAppCurrency()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		_, appCurrencySymbol, err := currencies.GetCurrencyInfoFromCurrencyCode(currentAppCurrencyCode)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		costTitle := widget.NewLabel("Cost:")

		appCurrencySymbolButton := widget.NewButton(appCurrencySymbol, func(){
				setChangeAppCurrencyPage(window, currentPage)	
			})

		//TODO: Fix this to actually calculate cost (get cost from parameters)
		costLabel := getBoldLabel("0.10 " + currentAppCurrencyCode)

		costRow := container.NewHBox(layout.NewSpacer(), costTitle, appCurrencySymbolButton, costLabel, layout.NewSpacer())
		page.Add(costRow)

		page.Add(widget.NewSeparator())
	}

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){

			//TODO: Make sure funds are sufficient (if profileType == "Mate")

			err := myLocalProfiles.SetProfileData(myProfileType, "Disabled", "Yes")
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage)			
				return
			}
			err = myProfileExports.UpdateMyExportedProfile(myProfileType, appNetworkType)
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			if (myProfileType == "Mate"){

				setStartAndMonitorMateProfileFundingAndBroadcastPage(window, previousPage)

			} else if (myProfileType == "Moderator"){

				setStartAndMonitorMyModeratorProfileBroadcastPage(window, previousPage)
			}
		}))

	// TODO: Add a warning that you should not disable multiple identity's profiles at the same time
	// Correlation between the identities is possible

	page.Add(confirmButton)

	setPageContent(page, window)
}

// This page will first attempt to fund a mate profile, and if it succeeds, it will initiate a manual profile broadcast
func setStartAndMonitorMateProfileFundingAndBroadcastPage(window fyne.Window, afterCompletionPage func()){

	pageIdentifier, err := helpers.GetNewRandomHexString(16)
	if (err != nil) {
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

	checkIfPageHasChangedFunction := func()bool{
		exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage")
		if (exists == true && currentViewedPage == pageIdentifier){
			return false
		}
		return true
	}

	title := getPageTitleCentered("Funding Mate Profile")

	progressBinding := binding.NewString()

	updateProgressBindings := func(){

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

		for {

			//TODO: Add details that describe the steps (Example: contacting server, making transaction)

			currentTime := time.Now().Unix()
			secondsElapsed := currentTime - startTime
			if (secondsElapsed % 3 == 0){
				progressBinding.Set("Funding profile.")
			} else if (secondsElapsed % 3 == 1){
				progressBinding.Set("Funding profile..")
			} else {
				progressBinding.Set("Funding profile...")		
			}

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			time.Sleep(time.Second/2)
		}
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}

	fundProfileFunction := func(){

		// We fund the export profile before we broadcast it
		newProfileFound, newProfileHash, _, _, err := myProfileExports.GetMyExportedProfile("Mate", appNetworkType)
		if (err != nil) {
			fyne.Do(func(){setErrorEncounteredPage(window, err, afterCompletionPage)})
			return
		}
		if (newProfileFound == false){
			fyne.Do(func(){setErrorEncounteredPage(window, errors.New("setStartAndMonitorMateProfileFundingAndBroadcastPage called when export profile is missing."), afterCompletionPage)})
			return
		}

		//Outputs:
		//	-bool: Fund successful
		//	-error
		fundProfile := func(profileHashToFund [28]byte)(bool, error){
			//TODO: Add function to fund profile
			time.Sleep(time.Second * 3)
		
			return true, nil
		}

		fundSuccessful, err := fundProfile(newProfileHash)
		if (err != nil){
			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			fyne.Do(func(){setErrorEncounteredPage(window, errors.New("Profile fund encountered error: " + err.Error()), afterCompletionPage)})
			return
		}
		if (fundSuccessful == false){

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			description1 := getBoldLabelCentered("Seekia failed to fund the profile.")
			description2 := getLabelCentered("The payment proof provider we contacted may be down.")
			description3 := getLabelCentered("Your internet connection may also be broken.")

			retryFunction := func(){setStartAndMonitorMateProfileFundingAndBroadcastPage(window, afterCompletionPage)}

			retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))	
			exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))

			manageConnectionDescription := getLabelCentered("Check if your internet connection is working below.")

			manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
					setManageNetworkConnectionPage(window, retryFunction)
				}))

			page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), retryButton, exitButton, widget.NewSeparator(), manageConnectionDescription, manageConnectionButton)

			fyne.Do(func(){setPageContent(page, window)})
			return
		}

		// Funding is complete. We update the broadcast profile

		myIdentityExists, newBroadcastProfileHash, err := myBroadcasts.UpdateMyBroadcastProfile("Mate", appNetworkType)
		if (err != nil) {

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			fyne.Do(func(){setErrorEncounteredPage(window, err, afterCompletionPage)})
			return
		}
		if (myIdentityExists == false){
			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			fyne.Do(func(){setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), afterCompletionPage)})
			return
		}
		if (newBroadcastProfileHash != newProfileHash){
			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}

			// This should not happen. This means we funded the wrong profile.
			// MyBroadcasts broadcasts the newest exported profile
			fyne.Do(func(){setErrorEncounteredPage(window, errors.New("Exported profile is not the same as broadcasted profile"), afterCompletionPage)})
			return
		}

		// Now we start a new broadcast

		//Outputs:
		//	-bool: Any hosts found
		//	-[22]byte: New process identifier
		//	-error
		startNewBroadcastFunction := func()(bool, [22]byte, error){

			myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Mate")
			if (err != nil){ return false, [22]byte{}, err }
			if (myIdentityExists == false){
				return false, [22]byte{}, errors.New("My identity not found after being found already.")
			}

			// We get the user's newest profile each time
			// It is unlikely, but the user could update their broadcast profile before this manual broadcast process completes
			// In this case, we will just be broadcasting the user's newest profile in multiple manual processes

			profileExists, _, profileHash, profileBytes, _, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
			if (err != nil) { return false, [22]byte{}, err }
			if (profileExists == false){
				return false, [22]byte{}, errors.New("Cannot get profile to broadcast on setStartAndMonitorMateProfileFundingAndBroadcastPage")
			}
			if (profileHash != newProfileHash){
				return false, [22]byte{}, errors.New("GetMyNewestBroadcastProfile returning different profile during Mate profile broadcast")
			}

			profileToBroadcastList := [][]byte{profileBytes}

			anyHostsFound, processIdentifier, err := manualBroadcasts.StartContentBroadcast("Profile", appNetworkType, profileToBroadcastList, 3)
			if (err != nil) { return false, [22]byte{}, err }
			if (anyHostsFound == false){
				return false, [22]byte{}, nil
			}

			return true, processIdentifier, nil
		}

		anyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
		if (err != nil){
			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){
				return
			}
			fyne.Do(func(){setErrorEncounteredPage(window, err, afterCompletionPage)})
			return
		}
		pageHasChanged := checkIfPageHasChangedFunction()
		if (pageHasChanged == true){

			// Whether the process started or not, we will not show the user in the GUI
			// If no hosts were found, their profile should still be broadcast in the background
			// Seekia will automatically download enough hosts to contact
			return
		}

		noHostsFound := !anyHostsFound

		nextPageTitle := "Broadcasting Mate Profile"
		nextPageDescription := "Seekia is broadcasting your Mate profile."

		fyne.Do(func(){setMonitorManualBroadcastPage(window, nextPageTitle, "Profile", nextPageDescription, noHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)})
	}

	description1 := getBoldLabelCentered("Seekia is funding your new Mate profile.")
	description2 := getLabelCentered("You can leave this page.")

	progressLabel := widget.NewLabelWithData(progressBinding)
	progressLabel.TextStyle = getFyneTextStyle_Bold()

	progressLabelCentered := getWidgetCentered(progressLabel)

	exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))

	page := container.NewVBox(title, widget.NewSeparator(), description1, description2, widget.NewSeparator(), progressLabelCentered, widget.NewSeparator(), exitButton)

	setPageContent(page, window)

	go updateProgressBindings()
	go fundProfileFunction()
}

// Starts broadcast and allows user to monitor progress
func setStartAndMonitorMyModeratorProfileBroadcastPage(window fyne.Window, afterCompletionPage func()){

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Moderator")
	if (err != nil){
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}
	if (myIdentityExists == false){
		setErrorEncounteredPage(window, errors.New("Identity does not exist on setStartAndMonitorMyModeratorProfileBroadcastPage"), afterCompletionPage)
		return
	}

	// We have to update the broadcast profile

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}

	identityExists, _, err := myBroadcasts.UpdateMyBroadcastProfile("Moderator", appNetworkType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}
	if (identityExists == false){
		setErrorEncounteredPage(window, errors.New("Identity not found after being found already."), afterCompletionPage)
		return
	}

	//Outputs:
	//	-bool: Any hosts found
	//	-[22]byte: New process identifier
	//	-error
	startNewBroadcastFunction := func()(bool, [22]byte, error){

		// We get the user's newest profile each time
		// It is unlikely, but the user could update their broadcast profile before this manual broadcast process completes
		// In this case, we will just be broadcasting the user's newest profile in multiple manual processes

		profileExists, _, _, profileBytes, _, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
		if (err != nil) { return false, [22]byte{}, err }
		if (profileExists == false){
			return false, [22]byte{}, errors.New("Cannot get profile to broadcast on setStartAndMonitorMyModeratorProfileBroadcastPage")
		}

		profileToBroadcastList := [][]byte{profileBytes}

		anyHostsFound, processIdentifier, err := manualBroadcasts.StartContentBroadcast("Profile", appNetworkType, profileToBroadcastList, 3)
		if (err != nil) { return false, [22]byte{}, err }
		if (anyHostsFound == false){
			return false, [22]byte{}, nil
		}

		return true, processIdentifier, nil
	}

	anyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
	if (err != nil){
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}

	noHostsFound := !anyHostsFound

	nextPageTitle := "Broadcasting Moderator Profile"
	nextPageDescription := "Seekia is broadcasting your Moderator profile."

	setMonitorManualBroadcastPage(window, nextPageTitle, "Profile", nextPageDescription, noHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)
}


func setMonitorManualBroadcastPage(window fyne.Window, pageTitleText string, broadcastType string, description1Text string, noHostsFound bool, processIdentifier [22]byte, startNewBroadcastFunction func()(bool, [22]byte, error), afterCompletionPage func()){

	currentPage := func(){setMonitorManualBroadcastPage(window, pageTitleText, broadcastType, description1Text, noHostsFound, processIdentifier, startNewBroadcastFunction, afterCompletionPage)}

	pageIdentifier, err := helpers.GetNewRandomHexString(16)
	if (err != nil) {
		setErrorEncounteredPage(window, err, afterCompletionPage)
		return
	}

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

	checkIfPageHasChangedFunction := func()bool{
		exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage")
		if (exists == true && currentViewedPage == pageIdentifier){
			return false
		}
		return true
	}

	title := getPageTitleCentered(pageTitleText)

	retryFunction := func(){
		newBroadcastAnyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
		if (err != nil){
			setErrorEncounteredPage(window, err, afterCompletionPage)
			return
		}

		newBroadcastNoHostsFound := !newBroadcastAnyHostsFound

		setMonitorManualBroadcastPage(window, pageTitleText, broadcastType, description1Text, newBroadcastNoHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)
	}

	if (noHostsFound == true){

		description1 := getLabelCentered("No available hosts were found.")
		description2 := getLabelCentered("Please wait for Seekia to find more hosts.")
		description3 := getLabelCentered("This should take less than 1 minute.")
		description4 := getLabelCentered("You can leave this page and the broadcast will still happen automatically.")

		retryingInSecondsBinding := binding.NewString()

		startRetryCountdownFunction := func(){

			secondsRemaining := 30
			for {

				secondsRemainingString := helpers.ConvertIntToString(secondsRemaining)

				if (secondsRemaining != 1){
					retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " seconds...")
				} else {
					retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " second...")
				}

				time.Sleep(time.Second)
				secondsRemaining -= 1

				if (secondsRemaining <= 0){

					pageHasChanged := checkIfPageHasChangedFunction()
					if (pageHasChanged == true){
						return
					}
					fyne.Do(retryFunction)
					return
				}
			}
		}

		retryingInLabel := widget.NewLabelWithData(retryingInSecondsBinding)
		retryingInLabel.TextStyle = getFyneTextStyle_Bold()
		retryingInLabelCentered := getWidgetCentered(retryingInLabel)

		retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))	
		exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))

		descriptionD := getLabelCentered("Check if your internet connection is working below.")

		manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
				setManageNetworkConnectionPage(window, currentPage)
			}))

		page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), retryingInLabelCentered, widget.NewSeparator(), retryButton, exitButton, widget.NewSeparator(), descriptionD, manageConnectionButton)

		setPageContent(page, window)

		go startRetryCountdownFunction()

		return
	}

	broadcastProgressStatusBinding := binding.NewString()
	broadcastProgressDetailsBinding := binding.NewString()

	updateBindingsFunction := func(){

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

		setBroadcastProgressStatus := func(processComplete bool, newStatus string){

			getProgressEllipsis := func()string{
				if (processComplete == true){
					return ""			
				}

				currentTime := time.Now().Unix()
				secondsElapsed := currentTime - startTime
				if (secondsElapsed % 3 == 0){
					return "."
				}
				if (secondsElapsed % 3 == 1){
					return ".."
				}
				return "..."
			}
			progressEllipsis := getProgressEllipsis()
			broadcastProgressStatusBinding.Set(newStatus + progressEllipsis)
		}

		for {

			processFound, processIsCompleteBool, processEncounteredError, processError, numberOfCompletedBroadcasts, processProgressDetails := manualBroadcasts.GetProcessInfo(processIdentifier)
			if (processFound == false){
				// This should not happen
				processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
				setBroadcastProgressStatus(true, "ERROR: manualBroadcasts process not found: " + processIdentifierHex)
				broadcastProgressDetailsBinding.Set("Report this error to the Seekia developers.")

				return
			}

			numberOfCompletedBroadcastsString := helpers.ConvertIntToString(numberOfCompletedBroadcasts)

			if (processIsCompleteBool == true){

				if (processEncounteredError == true){
					setBroadcastProgressStatus(true, "ERROR:" + processError.Error())
					broadcastProgressDetailsBinding.Set("Report this error to the Seekia developers.")
					return
				}

				pageHasChanged := checkIfPageHasChangedFunction()
				if (pageHasChanged == true){
					return
				}

				if (numberOfCompletedBroadcasts == 3){
					// We broadcasted to all 3 hosts. Broadcast is complete.
					fyne.Do(afterCompletionPage)
					return
				}

				// Broadcast did not complete all required hosts.
				// We will show user option to retry.

				retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))

				exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))

				if (numberOfCompletedBroadcasts != 0){
					// Process is complete, and at least 1 host was broadcasted to, but not all hosts.
					// This means that we ran out of hosts.
					// Seekia will keep broadcasting the content(s) in the background, so nothing needs to be done by the user.

					description1 := getBoldLabelCentered("The broadcast was successful to " + numberOfCompletedBroadcastsString + "/3 hosts.")
					description2 := getLabelCentered("We ran out of hosts to contact.")
					description3 := getLabelCentered("You can exit or wait for more hosts to be found and retry.")
					description4 := getLabelCentered("Seekia will broadcast the " + broadcastType + " in the background either way.")

					page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, retryButton, exitButton)
					fyne.Do(func(){setPageContent(page, window)})
					return
				}

				// Broadcast completed, but 0 hosts were successfully contacted
				// Now we will show them a "Broadcast Failed" page and an option to retry.

				description1 := getBoldLabelCentered("The broadcast was unsuccessful.")
				description2 := getLabelCentered("Retry the broadcast?")

				description3 := getLabelCentered("Check if your internet connection is working below.")

				manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
						setManageNetworkConnectionPage(window, currentPage)
					}))

				page := container.NewVBox(title, widget.NewSeparator(), description1, description2, retryButton, exitButton, widget.NewSeparator(), description3, manageConnectionButton)

				fyne.Do(func(){setPageContent(page, window)})
				return
			}

			// Broadcast is not complete

			progressProgressStatusString := "Broadcasted to " + numberOfCompletedBroadcastsString + "/3 hosts."
			setBroadcastProgressStatus(processIsCompleteBool, progressProgressStatusString)
			broadcastProgressDetailsBinding.Set(processProgressDetails)

			time.Sleep(100 * time.Millisecond)
		}
	}

	description1 := getBoldLabelCentered(description1Text)
	description2 := getLabelCentered("This process will run in the background.")
	description3 := getLabelCentered("You can leave this page.")

	broadcastProgressStatusLabel := widget.NewLabelWithData(broadcastProgressStatusBinding)
	broadcastProgressStatusLabel.TextStyle = getFyneTextStyle_Bold()
	broadcastProgressStatusLabelCentered := getWidgetCentered(broadcastProgressStatusLabel)

	broadcastProgressDetailsLabel := getWidgetCentered(widget.NewLabelWithData(broadcastProgressDetailsBinding))

	exitPageButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.MediaSkipNextIcon(), func(){
			appMemory.DeleteMemoryEntry("CurrentViewedPage")
			afterCompletionPage()
		}))

	page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), broadcastProgressStatusLabelCentered, broadcastProgressDetailsLabel, widget.NewSeparator(), exitPageButton)

	setPageContent(page, window)

	go updateBindingsFunction()
}

func setViewMyIdentityBalancePage(window fyne.Window, myIdentityType string, previousPage func()){

	if (myIdentityType != "Mate" && myIdentityType != "Host"){
		setErrorEncounteredPage(window, errors.New("setViewMyIdentityBalancePage called with invalid identity type: " + myIdentityType), previousPage)
		return
	}

	currentPage := func(){setViewMyIdentityBalancePage(window, myIdentityType, previousPage)}

	title := getPageTitleCentered("My " + myIdentityType + " Identity Balance")
	backButton := getBackButtonCentered(previousPage)

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myIdentityType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == false){
		// This should not occur, this page should only be reached if identity exists.
		setErrorEncounteredPage(window, errors.New("Identity not found."), previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (myIdentityType == "Mate"){
			description1 := getLabelCentered("Below is your Mate identity balance.")
			description2 := getLabelCentered("It must be sufficient for your profile to be broadcast.")
			description3 := getLabelCentered("Spend funds to increase your identity balance.")
			descriptionSection := container.NewVBox(description1, description2, description3)
			return descriptionSection
		}

		// myIdentityType == "Host"
		description1 := getLabelCentered("Below is your Host identity balance.")
		description2 := getLabelCentered("It must be sufficient for you to be a Seekia host.")
		description3 := getLabelCentered("Spend funds to increase your identity balance.")

		descriptionSection := container.NewVBox(description1, description2, description3)
		return descriptionSection
	}

	descriptionSection := getDescriptionSection()

	getMyIdentityExpirationTimeDisplaySection := func()(*fyne.Container, error){

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return nil, err }

		identityFound, balanceIsSufficient, balanceExpirationTime, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
		if (err != nil) { return nil, err }
		if (identityFound == false) {
			return nil, errors.New("Identity found not found after being found already.")
		}

		currentBalanceStatusText := getBoldLabelCentered("My Balance Status:")

		getBalanceStatusIcon := func()(*canvas.Image, error){

			iconSize := getCustomFyneSize(0)

			if (balanceIsSufficient == false){

				insufficientIcon, err := getFyneImageIcon("Insufficient")
				if (err != nil) { return nil, err }

				insufficientIcon.SetMinSize(iconSize)
				return insufficientIcon, nil
			}	
			sufficientIcon, err := getFyneImageIcon("Sufficient")
			if (err != nil) { return nil, err }
			sufficientIcon.SetMinSize(iconSize)

			return sufficientIcon, nil
		}

		balanceStatusIcon, err := getBalanceStatusIcon()
		if (err != nil){ return nil, err }

		balanceStatusIconCentered := getFyneImageCentered(balanceStatusIcon)

		getBalanceStatusText := func()string{
			if (balanceIsSufficient == true){
				return "Sufficient"		
			}
			return "Insufficient"
		}

		balanceStatusText := getBalanceStatusText()
		balanceStatusLabel := getBoldLabelCentered(balanceStatusText)

		displaySection := container.NewVBox(currentBalanceStatusText, balanceStatusIconCentered, balanceStatusLabel, widget.NewSeparator())

		if (balanceIsSufficient == true){

			timeRemainingTitle := getLabelCentered("Time Until Expiration:")

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

			if (currentTime > balanceExpirationTime){
				return nil, errors.New("Balance expiration time is less than current time while Balance is sufficient = true")
			}

			timeLeft := balanceExpirationTime - currentTime

			timeTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeLeft, true)
			if (err != nil) { return nil, err }

			timeRemainingLabel := getBoldLabelCentered(timeTranslated)
		
			displaySection.Add(timeRemainingTitle)
			displaySection.Add(timeRemainingLabel)
		}

		refreshBalanceButton := widget.NewButtonWithIcon(translate("Refresh"), theme.ViewRefreshIcon(), func(){
				//TODO: Add manualDownloads download and page to monitor it
				showUnderConstructionDialog(window)
			})

		addTimeButton := widget.NewButtonWithIcon(translate("Add Time"), theme.MoveUpIcon(), func(){
				setIncreaseMyIdentityBalancePage(window, myIdentityType, 30, currentPage)
			})

		buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, refreshBalanceButton, addTimeButton))

		displaySection.Add(buttonsGrid)

		return displaySection, nil
	}

	identityExpirationTimeSection, err := getMyIdentityExpirationTimeDisplaySection()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), identityExpirationTimeSection)

	setPageContent(page, window)
}


