package gui

// viewProfileGui.go implements pages to view user profiles

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/canvas"
import "fyne.io/fyne/v2/container"
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/encoding"
import "seekia/internal/genetics/companyAnalysis"
import "seekia/internal/genetics/createCoupleGeneticAnalysis"
import "seekia/internal/genetics/createPersonGeneticAnalysis"
import "seekia/internal/genetics/locusValue"
import "seekia/internal/genetics/myChosenAnalysis"
import "seekia/internal/genetics/myPeople"
import "seekia/internal/genetics/readGeneticAnalysis"
import "seekia/internal/genetics/trainedPredictionModels"
import "seekia/internal/genetics/geneticReferences/monogenicDiseases"
import "seekia/internal/genetics/geneticReferences/polygenicDiseases"
import "seekia/internal/genetics/geneticReferences/traits"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/imagery"
import "seekia/internal/mateQuestionnaire"
import "seekia/internal/moderation/moderatorRanking"
import "seekia/internal/moderation/trustedViewableStatus"
import "seekia/internal/moderation/verifiedStickyStatus"
import "seekia/internal/myIdentity"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/getFundedStatuses"
import "seekia/internal/network/myBroadcasts"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/calculatedAttributes"
import "seekia/internal/profiles/myLocalProfiles"
import "seekia/internal/profiles/myProfileExports"
import "seekia/internal/profiles/myProfileStatus"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/profiles/viewableProfiles"
import "seekia/internal/worldLanguages"
import "seekia/internal/worldLocations"

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

// This page will show user the difference between local/public and let them choose
func setViewMyLocalOrPublicProfilePage(window fyne.Window, myProfileType string, previousPage func()){

	setLoadingScreen(window, "Loading My Profile", "Loading...")

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

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

	title := getPageTitleCentered(translate("View My Profile"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("View My " + myProfileType + " Profile")

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

		description1 := getBoldLabelCentered("Your " + myProfileType + " identity does not exist.")
		description2 := getLabelCentered("Create your " + myProfileType + " identity below.")

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

		page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, createIdentityButton)
		setPageContent(page, window)
		return	
	}

	description1 := getLabelCentered("Choose which profile to view.")
	description2 := getLabelCentered("Your Local profile is the profile stored on your computer.")
	description3 := getLabelCentered("Your Public profile is the most recent profile that you have broadcasted.")

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

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

	viewLocalProfileButton := widget.NewButton("Local", func(){
			setViewMyProfilePage(window, myProfileType, "Local", currentPage)
		})

	viewPublicProfileButton := widget.NewButton("Public", func(){
		setViewMyProfilePage(window, myProfileType, "Public", currentPage)
	})

	viewLocalButtonWithIcon := container.NewGridWithColumns(1, localIcon, viewLocalProfileButton)
	viewPublicButtonWithIcon := container.NewGridWithColumns(1, publicIcon, viewPublicProfileButton)	

	buttonsRow := container.NewHBox(layout.NewSpacer(), viewLocalButtonWithIcon, viewPublicButtonWithIcon, layout.NewSpacer())	

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

	setPageContent(page, window)
}

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

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

	if (localOrPublic != "Local" && localOrPublic != "Public"){
		setErrorEncounteredPage(window, errors.New("setViewMyProfilePage called with invalid localOrPublic: " + localOrPublic), previousPage)
		return
	}

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

	title := getPageTitleCentered("View My Profile")

	backButton := getBackButtonCentered(previousPage)

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

		description1 := getBoldLabelCentered("Your " + myProfileType + " identity does not exist.")
		description2 := getLabelCentered("Create your " + myProfileType + " identity below.")

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

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, createIdentityButton)
		setPageContent(page, window)
		return	
	}

	if (myProfileType == "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(myProfileType, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	profileFound, profileHash, _, rawProfileMap, err := myProfileExports.GetMyExportedProfile(myProfileType, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (profileFound == false){
		setErrorEncounteredPage(window, errors.New("My exported profile not found after exporting."), previousPage)
		return
	}

	getAnyLocalProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(1, rawProfileMap)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	exists, _, iAmDisabled, err := getAnyLocalProfileAttributeFunction("Disabled")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (exists == true && iAmDisabled == "Yes"){

		description1 := getBoldLabelCentered("Your " + myProfileType + " profile is disabled.")
		description2 := getLabelCentered("Re-enable it on the Broadcast page.")

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

		setPageContent(page, window)
		return
	}

	if (localOrPublic == "Local"){
		setViewUserProfilePage(window, true, profileHash, 0, getAnyLocalProfileAttributeFunction, previousPage)
		return
	}

	// localOrPublic == "Public"

	identityExists, broadcastProfileExists, publicProfileHash, getAnyPublicProfileAttributeFunction, err := myBroadcasts.GetRetrieveAnyAttributeFromMyBroadcastProfileFunction(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
	}
	if (broadcastProfileExists == false){

		description1 := getBoldLabelCentered("Your public profile does not exist.")
		description2 := getLabelCentered("You have not broadcast your profile.")
		description3 := getLabelCentered("Broadcast your profile on the Profile - Broadcast page.")

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

		setPageContent(page, window)
		return
	}

	myIdentityExists, myProfileIsActive, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, appNetworkType)
	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
	}
	if (myProfileIsActive == false){

		description1 := getBoldLabelCentered("Your public profile has expired.")
		description2 := getLabelCentered("You must broadcast your profile again.")
		description3 := getLabelCentered("Broadcast your profile on the Profile - Broadcast page.")

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

		setPageContent(page, window)
		return
	}

	setViewUserProfilePage(window, true, publicProfileHash, 0, getAnyPublicProfileAttributeFunction, previousPage)
}

func setViewPeerProfilePageFromIdentityHash(window fyne.Window, peerIdentityHash [16]byte, previousPage func()){

	setLoadingScreen(window, "View Profile", "Loading Profile...")

	currentPage := func(){setViewPeerProfilePageFromIdentityHash(window, peerIdentityHash, previousPage)}

	title := getPageTitleCentered(translate("View Profile"))

	backButton := getBackButtonCentered(previousPage)

	userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(peerIdentityHash)
	if (err != nil) {
		peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
		setErrorEncounteredPage(window, errors.New("setViewPeerProfilePageFromIdentityHash called with invalid peerIdentityHash: " + peerIdentityHashHex), previousPage)
		return
	}

	// We only allow unknown viewable status profiles to be viewed if the user profile type is host/moderator
	getAllowUnknownStatusBool := func()bool{
		if (userIdentityType == "Mate"){
			return false
		}
		return true
	}

	allowUnknownStatusBool := getAllowUnknownStatusBool()

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

	viewableProfileExists, profileHash, getAnyAttributeFromViewableProfileFunction, err := viewableProfiles.GetRetrieveAnyNewestViewableUserProfileAttributeFunction(peerIdentityHash, appNetworkType, true, allowUnknownStatusBool, true)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (viewableProfileExists == true){

		// The user has a viewable profile.
		// We display it.
		setViewUserProfilePage(window, false, profileHash, 0, getAnyAttributeFromViewableProfileFunction, previousPage) 
		return
	}

	// Viewable profile does not exist
	// We check to see if any profile exists

	//Outputs:
	//	-bool: Viewable status is known
	//	-bool: Identity Is Viewable status
	//	-error
	getIdentityIsViewableStickyStatus := func()(bool, bool, error){

		// First try verified status, then try trusted if verified is not available

		downloadingRequiredReviews, networkParametersExist, stickyConsensusEstablished, identityIsViewableStatus, err := verifiedStickyStatus.GetVerifiedIdentityIsViewableStickyStatus(peerIdentityHash, appNetworkType)
		if (err != nil) { return false, false, err }
		if (downloadingRequiredReviews == true && networkParametersExist == true && stickyConsensusEstablished == true){
			return true, identityIsViewableStatus, nil
		}

		statusIsKnown, identityIsViewable, _, err := trustedViewableStatus.GetTrustedIdentityIsViewableStatus(peerIdentityHash, appNetworkType)
		if (err != nil) { return false, false, err }
		if (statusIsKnown == true){
			return true, identityIsViewable, nil
		}

		return false, false, nil
	}

	identityViewableStatusIsKnown, identityIsViewableStatus, err := getIdentityIsViewableStickyStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	theirNewestProfileExists, newestProfileVersion, newestProfileHash, _, _, newestProfileRawMap, err := profileStorage.GetNewestUserProfile(peerIdentityHash, appNetworkType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}
	if (theirNewestProfileExists == false){

		// Their profile does not exist.

		downloadProfileFunction := func(){setDownloadMissingUserProfilePage(window, peerIdentityHash, true, true, previousPage, currentPage, previousPage)}

		if (identityViewableStatusIsKnown == true && identityIsViewableStatus == false){

			description1 := getLabelCentered("This user is banned.")
			description2 := getLabelCentered("You do not have their profile downloaded.")
			description3 := getLabelCentered("Do you want to try to download their profile?")

			downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.ConfirmIcon(), downloadProfileFunction))

			page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, downloadButton)
			setPageContent(page, window)
			return
		}

		// Identity is not banned. Profile does not exist
		// We check if user's identity is known to be expired.

		statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(peerIdentityHash, appNetworkType)
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		if (statusIsKnown == true && identityIsFunded == false){

			// Identity is known to be expired

			description1 := getBoldLabelCentered("This user's identity is expired.")
			description2 := getLabelCentered("Their profile is expired from the network.")
			description3 := getLabelCentered("You can try to update this status to see if the user's identity is funded.")

			updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), func(){
					//TODO:
					// Use manualDownloads to update the user identity's funded status
					showUnderConstructionDialog(window)
				}))

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

			setPageContent(page, window)
			return
		}

		// We will commence profile download without any prompt.	

		downloadProfileFunction()
		return
	}

	// We know their profile exists, but it is not viewable
	// For host/moderator profiles, this must mean that the profile/identity is banned
	// For mate profiles, it could also mean that we have not downloaded the profile's sticky viewable status

	getAnyNewestProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(newestProfileVersion, newestProfileRawMap)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	viewNewestProfileFunction := func(){setViewUserProfilePage(window, false, newestProfileHash, 0, getAnyNewestProfileAttributeFunction, previousPage)}

	_, newestProfileIsDisabled, err := readProfiles.ReadProfileHashMetadata(newestProfileHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (newestProfileIsDisabled == true){

		// Their newest profile is disabled.
		// The user must be banned.
		// We can show it to the user.

		viewNewestProfileFunction()
		return
	}

	if (identityViewableStatusIsKnown == true && identityIsViewableStatus == false){

		description1 := getBoldLabelCentered("This user is banned.")
		description2 := getLabelCentered("Do you want to view their profile anyway?")

		viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, viewProfileButton)
		setPageContent(page, window)
		return
	}

	if (userIdentityType == "Host" || userIdentityType == "Moderator"){

		// Host/moderator profiles are not considered unviewable until they are banned, or their author is banned.
		// An unreviewed host/moderator profile is considered viewable.
		// We already checked to see if the user's identity is banned
		// Therefore, we know that this profile is banned.

		description1 := getBoldLabelCentered("This user's profile is banned.")
		description2 := getLabelCentered("Do you want to view their profile anyway?")

		viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, viewProfileButton)
		setPageContent(page, window)
		return
	}

	// userIdentityType == "Mate"

	// We know the user's identity is either viewable or unknown
	// We check to see if mate profile is viewable

	//Outputs:
	//	-bool: Profile metadata found
	//	-bool: Viewable Status is known
	//	-bool: Profile is viewable
	//	-error
	getNewestProfileIsViewableStatus := func()(bool, bool, bool, error){

		profileIsDisabled, profileMetadataIsKnown, profileNetworkType, profileIdentityHash, downloadingRequiredReviews, networkParametersExist, stickyConsensusEstablished, profileIsViewableStatus, err := verifiedStickyStatus.GetVerifiedProfileIsViewableStickyStatus(newestProfileHash)
		if (err != nil) { return false, false, false, err }
		if (profileIsDisabled == true){
			return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different profileIsDisabled status than ReadProfileHashMetadata.")
		}
		if (profileMetadataIsKnown == false){
			// Profile metadata must have been deleted after earlier check.
			return false, false, false, nil
		}
		if (profileNetworkType != appNetworkType){
			return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different networkType than requested from GetNewestUserProfile.")
		}

		if (profileIdentityHash != peerIdentityHash){
			return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different profileIdentityHash")
		}

		if (downloadingRequiredReviews == true && networkParametersExist == true && stickyConsensusEstablished == true){
			return true, true, profileIsViewableStatus, nil
		}

		return true, false, false, nil
	}

	newestProfileMetadataFound, profileViewableStatusIsKnown, profileIsViewableStatus, err := getNewestProfileIsViewableStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}

	if (newestProfileMetadataFound == false){
		// This should only happen if the profile metadata was deleted after our earlier check
		// We show a loading screen so this issue can be identified if it is because of an error
		setLoadingScreen(window, "Loading Profile", "Looking for profile...")
		time.Sleep(time.Second)

		currentPage()
		return
	}

	if (profileViewableStatusIsKnown == true && profileIsViewableStatus == false){

		description1 := getBoldLabelCentered("This user's profile is not viewable.")
		description2 := getLabelCentered("It may be banned, or not yet approved.")
		description3 := getLabelCentered("Do you still want to view it?")

		viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction))

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

		setPageContent(page, window)	
		return
	}

	// We know the profile is not considered viewable
	// We know the identity is not banned, or its status is unknown
	// We know the profile is not known to be unviewable
	// Therefore, the profile viewable status must be unknown

	description1 := getBoldLabelCentered("This user's profile has an unknown moderation status.")
	description2 := getLabelCentered("Do you still want to view their profile?")

	description3 := getLabelCentered("If not, you must wait for Seekia to download the profile's moderation status.")
	description4 := getLabelCentered("After the download, you will know if the profile is banned or viewable.")
	description5 := getLabelCentered("This will happen automatically in the background.")
	description6 := getLabelCentered("Refresh the page to check if the status is downloaded.")

	refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

	viewProfileButton := widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction)

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

	setPageContent(page, window)
}


func setViewUserProfilePage(window fyne.Window, profileIsMine bool, profileHash [28]byte, currentImageIndex int, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) {

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

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

	getTitleText := func()string{
		if (profileIsMine == true){
			return "Viewing My Profile"
		}
		return "Viewing Profile"
	}

	titleText := getTitleText()

	setLoadingScreen(window, titleText, "Loading Profile...")

	currentPage := func(){setViewUserProfilePage(window, profileIsMine, profileHash, currentImageIndex, getAnyUserProfileAttributeFunction, previousPage)}

	userIdentityHashExists, _, userIdentityHashString, err := getAnyUserProfileAttributeFunction("IdentityHash")
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (userIdentityHashExists == false) {
		setErrorEncounteredPage(window, errors.New("Failed to get user profile IdentityHash"), previousPage)
		return
	}

	userIdentityHash, userProfileType, err := identity.ReadIdentityHashString(userIdentityHashString)
	if (err != nil){
		setErrorEncounteredPage(window, errors.New("getAnyUserProfileAttributeFunction returning invalid IdentityHash: " + userIdentityHashString), previousPage)
		return
	}

	title := getPageTitleCentered(titleText)

	backButton := getBackButtonCentered(previousPage)

	getProfileIsDisabledStatus := func()(bool, error){
			
		exists, _, disabledAttribute, err := getAnyUserProfileAttributeFunction("Disabled")
		if (err != nil) { return false, err }
		if (exists == true && disabledAttribute == "Yes") {
			return true, nil
		}
		return false, nil
	}

	userIsDisabled, err := getProfileIsDisabledStatus()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (userIsDisabled == true){

		description1 := getBoldLabelCentered("User Is Disabled.")

		getDescription2Text := func()string{

			if (profileIsMine == true){
				result := translate("You have disabled your profile.")
				return result
			}
			result := translate("This user has disabled their profile.")
			return result
		}

		description2Text := getDescription2Text()

		description2 := getLabelCentered(description2Text)

		theirIdentityHashLabel := getItalicLabelCentered("User Identity Hash:")
		theirIdentityHashText := getBoldLabelCentered(userIdentityHashString)

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

		if (profileIsMine == false){

			page.Add(widget.NewSeparator())

			actionsButton := getWidgetCentered(widget.NewButtonWithIcon("Actions", theme.ContentRedoIcon(), func(){ 
					setViewPeerActionsPage(window, userIdentityHash, currentPage)
				}))

			page.Add(actionsButton)
		}

		setPageContent(page, window)
		return
	}

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

		chatButton := widget.NewButtonWithIcon("Chat", theme.MailComposeIcon(), func(){
				if (profileIsMine == true){
					dialogTitle := translate("Chat Inaccessible")
					dialogMessageA := getLabelCentered(translate("You are viewing your own profile."))
					dialogMessageB := getLabelCentered(translate("You cannot chat with yourself."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}
				setViewAConversationPage(window, userIdentityHash, true, currentPage)
			})
		actionsButton := widget.NewButtonWithIcon("Actions", theme.ContentRedoIcon(), func(){
				if (profileIsMine == true){
					dialogTitle := translate("Peer Actions Inaccessible")
					dialogMessageA := getLabelCentered(translate("You are viewing your own profile."))
					dialogMessageB := getLabelCentered(translate("You cannot access the peer actions page for your own identity."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}

				setViewPeerActionsPage(window, userIdentityHash, currentPage)
			})

		topRowLeftButtonsColumn := container.NewGridWithColumns(1, chatButton, actionsButton)

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

			imageObjectsList := make([]image.Image, 0)

			if (userProfileType == "Mate"){

				exists, _, jpegPhotosAttribute, err := getAnyUserProfileAttributeFunction("Photos")
				if (err != nil) { return nil, err }
				if (exists == true){
					jpegPhotosList := strings.Split(jpegPhotosAttribute, "+")

					for _, base64Image := range jpegPhotosList{

						imageObject, err := imagery.ConvertJPEGBase64StringToCroppedDownsizedImageObject(base64Image)
						if (err != nil) { return nil, err }

						imageObjectsList = append(imageObjectsList, imageObject)
					}
				}
			}

			getAvatarEmojiIdentifier := func()(int, error){
				exists, _, emojiIdentifier, err := getAnyUserProfileAttributeFunction("Avatar")
				if (err != nil){ return 0, err }
				if (exists == false){
					// This is the avatar for users who have not selected an avatar
					return 2929, nil
				}

				emojiIdentifierInt, err := helpers.ConvertStringToInt(emojiIdentifier)
				if (err != nil) {
					return 0, errors.New("User profile is malformed: Invalid avatar: " + emojiIdentifier)
				}

				return emojiIdentifierInt, nil				
			}

			avatarEmojiIdentifier, err := getAvatarEmojiIdentifier()
			if (err != nil) { return nil, err }

			avatarImage, err := getEmojiImageObject(avatarEmojiIdentifier)
			if (err != nil){ return nil, err }

			imageObjectsList = append(imageObjectsList, avatarImage)

			numberOfImages := len(imageObjectsList)

			getImageIndex := func()int{
				if (currentImageIndex <= 0){
					return 0			
				}

				finalIndex := numberOfImages-1

				if (currentImageIndex >= finalIndex){
					return finalIndex
				}
				return currentImageIndex
			}

			imageIndex := getImageIndex()

			currentImage := imageObjectsList[imageIndex]

			currentFyneImage := canvas.NewImageFromImage(currentImage)
			currentFyneImage.FillMode = canvas.ImageFillContain
			currentFyneImage.SetMinSize(getCustomFyneSize(30))

			getNavigationWithZoomRow := func()*fyne.Container{

				zoomButton := widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
						setViewFullpageImagesWithNavigationPage(window, imageObjectsList, imageIndex, currentPage)
					})

				if (numberOfImages == 1){
					zoomButtonRow := getWidgetCentered(zoomButton)

					return zoomButtonRow
				}

				getBackButton := func()fyne.Widget{
					if (imageIndex <= 0) {
						button := widget.NewButton("", nil)
						return button
					}
					button := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){
							newImageIndex := imageIndex-1
							setViewUserProfilePage(window, profileIsMine, profileHash, newImageIndex, getAnyUserProfileAttributeFunction, previousPage)
						})
					return button
				}

				getNextButton := func()fyne.Widget{
					if (imageIndex >= numberOfImages-1) {
						button := widget.NewButton("", nil)
						return button
					}
					newImageIndex := imageIndex+1
					button := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){
							setViewUserProfilePage(window, profileIsMine, profileHash, newImageIndex, getAnyUserProfileAttributeFunction, previousPage)
						})
					return button
				}

				backButton := getBackButton()
				nextButton := getNextButton()

				navigationWithZoomRow := getContainerCentered(container.NewGridWithRows(1, backButton, zoomButton, nextButton))

				return navigationWithZoomRow	
			}

			navigationWithZoomRow := getNavigationWithZoomRow()

			currentFyneImageBoxed := container.NewHBox(layout.NewSpacer(), currentFyneImage, layout.NewSpacer())
			imagesColumn := container.NewVBox(currentFyneImageBoxed, navigationWithZoomRow)

			return imagesColumn, nil
		}

		photosColumnWithNavButtons, err := getPhotosColumnWithNavigationButtons()
		if (err != nil) { return nil, err }

		profileDetailsButton := widget.NewButtonWithIcon("Details", theme.InfoIcon(), func(){
				setViewUserProfilePage_ProfileDetails(window, profileHash, getAnyUserProfileAttributeFunction, currentPage)
			})

		reportProfileButton := widget.NewButtonWithIcon("Report", theme.WarningIcon(), func(){
				if (profileIsMine == true){
					dialogTitle := translate("Reporting Inaccessible")
					dialogMessageA := getLabelCentered(translate("You are viewing your own profile."))
					dialogMessageB := getLabelCentered(translate("You cannot report your own profile."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}
				//TODO
				showUnderConstructionDialog(window)
			})

		topRowRightButtonsColumn := container.NewGridWithColumns(1, profileDetailsButton, reportProfileButton)

		topRow := container.NewHBox(layout.NewSpacer(), topRowLeftButtonsColumn, widget.NewSeparator(), photosColumnWithNavButtons,  widget.NewSeparator(), topRowRightButtonsColumn, layout.NewSpacer())

		identityHashTitle := widget.NewLabel("Identity Hash:")
		identityHashLabel := getBoldLabel(userIdentityHashString)
		viewIdentityHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewIdentityHashPage(window, userIdentityHash, currentPage)
			})
		identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashTitle, identityHashLabel, viewIdentityHashButton, layout.NewSpacer())

		profileContentContainer := container.NewVBox(topRow, widget.NewSeparator(), identityHashRow)

		usernameTitle := widget.NewLabel("Username:")

		getUsernameLabel := func()(fyne.Widget, error){

			exists, _, usernameString, err := getAnyUserProfileAttributeFunction("Username")
			if (err != nil) { return nil, err }
			if (exists == false){
				anonymousLabel := getBoldItalicLabel(translate("Anonymous"))
				
				return anonymousLabel, nil
			}

			usernameLabel := getBoldLabel(usernameString)
			
			return usernameLabel, nil
		}

		usernameLabel, err := getUsernameLabel()
		if (err != nil) { return nil, err }

		usernameRow := container.NewHBox(layout.NewSpacer(), usernameTitle, usernameLabel, layout.NewSpacer())

		profileContentContainer.Add(usernameRow)

		if (profileIsMine == false && userProfileType == "Mate"){

			exists, _, matchScore, err := getAnyUserProfileAttributeFunction("MatchScore")
			if (err != nil) { return nil, err }
			if (exists == false){
				return nil, errors.New("Unable to retrieve mate user match score.")
			}

			matchScoreTitle := widget.NewLabel("Match Score:")
			matchScoreLabel := getBoldLabel(matchScore)
			viewMatchScoreDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					// This page describes which desires a user fulfills
					setViewUserMatchScoreBreakdownPage(window, userIdentityHash, currentPage)
				})
			matchScoreRow := container.NewHBox(layout.NewSpacer(), matchScoreTitle, matchScoreLabel, viewMatchScoreDetailsButton, layout.NewSpacer())

			profileContentContainer.Add(matchScoreRow)

			distanceTranslated, _, formatDistanceFunction, distanceUnits, unavailableDistanceText, err := attributeDisplay.GetProfileAttributeDisplayInfo("Distance")
			if (err != nil) { return nil, err }

			distanceTitle := getLabelCentered(distanceTranslated + ":")

			getDistanceLabel := func()(fyne.Widget, error){

				exists, _, distanceKilometersString, err := getAnyUserProfileAttributeFunction("Distance")
				if (err != nil) { return nil, err }
				if (exists == false){
					distanceLabel := getBoldItalicLabel(unavailableDistanceText)
					return distanceLabel, nil
				}

				distanceFormatted, err := formatDistanceFunction(distanceKilometersString)
				if (err != nil) { return nil, err }

				distanceLabel := getBoldLabel(distanceFormatted + distanceUnits)

				return distanceLabel, nil
			}

			distanceLabel, err := getDistanceLabel()
			if (err != nil) { return nil, err }

			distanceRow := container.NewHBox(layout.NewSpacer(), distanceTitle, distanceLabel, layout.NewSpacer())

			profileContentContainer.Add(distanceRow)
		}

		if (userProfileType == "Moderator"){

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

			rankingKnown, moderatorRank, totalNumberOfModerators, err := moderatorRanking.GetModeratorRanking(userIdentityHash, appNetworkType)
			if (err != nil) { return nil, err }

			getModeratorRankText := func()string{
				if (rankingKnown == false){
					result := translate("Unknown")
					return result		
				}

				moderatorRankString := helpers.ConvertIntToString(moderatorRank)
				totalNumberOfModeratorsString := helpers.ConvertIntToString(totalNumberOfModerators)

				moderatorRankText := moderatorRankString + "/" + totalNumberOfModeratorsString

				return moderatorRankText
			}

			moderatorRankText := getModeratorRankText()

			moderatorRankTitle := widget.NewLabel(translate("Moderator Rank") + ":")
			moderatorRankLabel := getBoldLabel(moderatorRankText)

			moderatorRankRow := container.NewHBox(layout.NewSpacer(), moderatorRankTitle, moderatorRankLabel, layout.NewSpacer())

			profileContentContainer.Add(moderatorRankRow)
		}

		if (userProfileType == "Mate"){

			ageTitle := widget.NewLabel("Age:")

			getAgeLabel := func()(fyne.Widget, error){

				exists, _, ageString, err := getAnyUserProfileAttributeFunction("Age")
				if (err != nil) { return nil, err }
				if (exists == false){
					noResponseLabel := getBoldItalicLabel(translate("No Response"))
					return noResponseLabel, nil
				}

				ageLabel := getBoldLabel(ageString)

				return ageLabel, nil
			}

			ageLabel, err := getAgeLabel()
			if (err != nil) { return nil, err }

			ageRow := container.NewHBox(layout.NewSpacer(), ageTitle, ageLabel, layout.NewSpacer())

			profileContentContainer.Add(ageRow)
		}

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

			generalIcon, err := getFyneImageIcon("General")
			if (err != nil) { return nil, err }
			generalButton := widget.NewButton("General", func(){
					setViewUserProfilePage_Category(window, profileIsMine, "General", getAnyUserProfileAttributeFunction, currentPage)
				})

			generalButtonWithIcon := container.NewGridWithRows(2, generalIcon, generalButton)

			if (userProfileType == "Host" || userProfileType == "Moderator"){
				categorySelectButtonsRow := getContainerCentered(generalButtonWithIcon)
				return categorySelectButtonsRow, nil
			}

			physicalIcon, err := getFyneImageIcon("Person")
			if (err != nil) { return nil, err }
			physicalButton := widget.NewButton("Physical", func(){
					setViewUserProfilePage_Category(window, profileIsMine, "Physical", getAnyUserProfileAttributeFunction, currentPage)
				})

			physicalButtonWithIcon := container.NewGridWithRows(2, physicalIcon, physicalButton)		

			lifestyleIcon, err := getFyneImageIcon("Lifestyle")
			if (err != nil) { return nil, err }
			lifestyleButton := widget.NewButton("Lifestyle", func(){
					setViewUserProfilePage_Category(window, profileIsMine, "Lifestyle", getAnyUserProfileAttributeFunction, currentPage)
				})

			lifestyleButtonWithIcon := container.NewGridWithRows(2, lifestyleIcon, lifestyleButton)

			mentalIcon, err := getFyneImageIcon("Mental")
			if (err != nil) { return nil, err }
			mentalButton := widget.NewButton("Mental", func(){
					setViewUserProfilePage_Category(window, profileIsMine, "Mental", getAnyUserProfileAttributeFunction, currentPage)
				})

			mentalButtonWithIcon := container.NewGridWithRows(2, mentalIcon, mentalButton)

			categoriesRow := getContainerCentered(container.NewGridWithRows(1, generalButtonWithIcon, physicalButtonWithIcon, lifestyleButtonWithIcon, mentalButtonWithIcon))

			return categoriesRow, nil
		}

		selectProfileCategoryButtonsRow, err := getProfileCategorySelectButtons()
		if (err != nil) { return nil, err }

		profileContentContainer.Add(widget.NewSeparator())
		profileContentContainer.Add(selectProfileCategoryButtonsRow)
		profileContentContainer.Add(widget.NewSeparator())

		return profileContentContainer, nil
	}
	
	profileContent, err := getProfileContainer()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), profileContent)
	
	setPageContent(page, window)
}


func setViewUserProfilePage_ProfileDetails(window fyne.Window, profileHash [28]byte, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) {

	currentPage := func(){setViewUserProfilePage_ProfileDetails(window, profileHash, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile Details"))

	backButton := getBackButtonCentered(previousPage)

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

		profileHashLabel := widget.NewLabel("Profile Hash:")

		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

		profileHashTrimmed, _, err := helpers.TrimAndFlattenString(profileHashHex, 20)
		if (err != nil){ return nil, err }

		profileHashTrimmedLabel := getBoldLabel(profileHashTrimmed)
		viewProfileHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewContentHashPage(window, "Profile", profileHash[:], currentPage)	
			})

		profileHashRow := container.NewHBox(layout.NewSpacer(), profileHashLabel, profileHashTrimmedLabel, viewProfileHashButton, layout.NewSpacer())

		exists, _, profileCreationTimeString, err := getAnyUserProfileAttributeFunction("CreationTime")
		if (err != nil){ return nil, err }
		if (exists == false){
			return nil, errors.New("setViewUserProfilePage_ProfileDetails called with profile missing CreationTime")
		}

		profileCreationTime, err := helpers.ConvertCreationTimeStringToInt64(profileCreationTimeString)
		if (err != nil){
			return nil, errors.New("setViewUserProfilePage_ProfileDetails called with profile with invalid CreationTime: " + profileCreationTimeString)
		}

		creationTimeLabel := widget.NewLabel("Creation Time:")

		creationTimeString := helpers.ConvertUnixTimeToTranslatedTime(profileCreationTime)

		creationTimeAgoString, err := helpers.ConvertUnixTimeToTimeFromNowTranslated(profileCreationTime, true)
		if (err != nil) { return nil, err }

		creationTimeText := getBoldLabel(creationTimeString + " (" + creationTimeAgoString + ")")

		creationTimeWarningButton := widget.NewButtonWithIcon("", theme.WarningIcon(), func(){
				title := translate("Creation Time Warning")
				dialogMessageA := getLabelCentered("Profile creation times are not verified.")
				dialogMessageB := getLabelCentered("They can be faked by the profile author.")
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(title, translate("Close"), dialogContent, window)
			})

		creationTimeRow := container.NewHBox(layout.NewSpacer(), creationTimeLabel, creationTimeText, creationTimeWarningButton, layout.NewSpacer())

		exists, profileVersion, profileType, err := getAnyUserProfileAttributeFunction("ProfileType")
		if (err != nil) { return nil, err }
		if (exists == false){
			return nil, errors.New("getAnyUserProfileAttributeFunction called and ProfileType not found")
		}
		profileTypeLabel := widget.NewLabel("Profile Type:")
		profileTypeText := getBoldLabel(profileType)
		profileTypeRow := container.NewHBox(layout.NewSpacer(), profileTypeLabel, profileTypeText, layout.NewSpacer())	

		profileVersionLabel := widget.NewLabel("Profile Version:")
		profileVersionString := helpers.ConvertIntToString(profileVersion)
		profileVersionText := getBoldLabel(profileVersionString)
		profileVersionRow := container.NewHBox(layout.NewSpacer(), profileVersionLabel, profileVersionText, layout.NewSpacer())

		//TODO: Add moderation details (if profile/identity is banned)

		//TODO: Show when identity/profile will expire, and allow user to send funds to increase the user's identity balance/score

		//TODO: Save raw profile button (will save it as a txt file)

		pageContent := container.NewVBox(profileHashRow, widget.NewSeparator(), creationTimeRow, widget.NewSeparator(), profileTypeRow, widget.NewSeparator(), profileVersionRow)

		return pageContent, nil
	}

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

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

	setPageContent(page, window)
}


func setViewUserProfilePage_Category(window fyne.Window, profileIsMine bool, categoryName string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) {

	if (categoryName != "General" && categoryName != "Physical" && categoryName != "Lifestyle" && categoryName != "Mental"){
		setErrorEncounteredPage(window, errors.New("setViewUserProfilePage_Category called with invalid profile category: " + categoryName), previousPage)
		return
	}

	currentPage := func(){setViewUserProfilePage_Category(window, profileIsMine, categoryName, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered("View Profile - " + categoryName)

	backButton := getBackButtonCentered(previousPage)

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

	addAttributeRow := func(attributeName string)error{

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

		attributeTitleLabel := widget.NewLabel(attributeTitle + ":")

		exists, _, attributeValue, err := getAnyUserProfileAttributeFunction(attributeName)
		if (err != nil) { return err }
		if (exists == false) {

			attributeValueLabel := getBoldItalicLabel(missingValueText)
			attributeRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueLabel, layout.NewSpacer())
			page.Add(attributeRow)

			return nil
		}

		attributeValueFormatted, err := formatAttributeValue(attributeValue)
		if (err != nil) { return err }

		attributeValueFormattedWithUnits := attributeValueFormatted + attributeUnits

		attributeValueTrimmed, anyChangesOccurred, err := helpers.TrimAndFlattenString(attributeValueFormattedWithUnits, 25)
		if (err != nil) { return err }
		if (anyChangesOccurred == false){

			attributeValueLabel := getBoldLabel(attributeValueFormattedWithUnits)

			attributeRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueLabel, layout.NewSpacer())
			page.Add(attributeRow)

			return nil
		}

		attributeValueTrimmedLabel := getBoldLabel(attributeValueTrimmed)

		viewAttributeValueButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewTextPage(window, "Viewing " + attributeTitle, attributeValueFormattedWithUnits, false, currentPage)	
			})

		attributeValueRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueTrimmedLabel, viewAttributeValueButton, layout.NewSpacer())
		page.Add(attributeValueRow)

		return nil
	}

	addAttributesFunction := func()error{

		if (categoryName == "General"){

			exists, _, userProfileType, err := getAnyUserProfileAttributeFunction("ProfileType")
			if (err != nil) { return err }
			if (exists == false){
				return errors.New("getAnyUserProfileAttributeFunction not able to find ProfileType")
			}

			if (userProfileType == "Mate"){

				locationButton := widget.NewButton("Location", func(){
						setViewMateProfilePage_Location(window, getAnyUserProfileAttributeFunction, currentPage)
					})

				questionnaireButton := widget.NewButton("Questionnaire", func(){
						setViewMateProfilePage_Questionnaire(window, profileIsMine, getAnyUserProfileAttributeFunction, currentPage)			
					})

				tagsButton := widget.NewButton("Tags", func(){
						setViewMateProfilePage_Tags(window, getAnyUserProfileAttributeFunction, currentPage)
					})

				buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, locationButton, questionnaireButton, tagsButton))
				page.Add(buttonsGrid)

				page.Add(widget.NewSeparator())
			}

			err = addAttributeRow("Description")
			if (err != nil) { return err }
			
			err = addAttributeRow("Username")
			if (err != nil) { return err }

			if (userProfileType == "Mate"){

				err := addAttributeRow("Age")
				if (err != nil) { return err }

				err = addAttributeRow("Sexuality")
				if (err != nil) { return err }			
			}

			return nil
		}
		if (categoryName == "Physical"){
			
			deepAppearanceSimilarityButton := widget.NewButton("Deep Appearance Similarity", func(){
					setViewMateProfilePage_DeepAppearanceSimilarity(window, getAnyUserProfileAttributeFunction, currentPage)
				})

			ancestryCompositionButton := widget.NewButton("Ancestry Composition", func(){
					setViewMateProfilePage_AncestryComposition(window, "User", getAnyUserProfileAttributeFunction, currentPage)
				})

			haplogroupsButton := widget.NewButton("Haplogroups", func(){
					setViewMateProfilePage_Haplogroups(window, getAnyUserProfileAttributeFunction, currentPage)
				})
			
			neanderthalVariantsButton := widget.NewButton("Neanderthal Variants", func(){
					setViewMateProfilePage_NeanderthalVariants(window, getAnyUserProfileAttributeFunction, currentPage)
				})

			totalDiseaseRiskButton := widget.NewButton("Total Disease Risk", func(){
					setViewMateProfilePage_TotalDiseaseRisk(window, getAnyUserProfileAttributeFunction, currentPage)
				})

			monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){
					setViewMateProfilePage_MonogenicDiseases(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
				})

			polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){
					setViewMateProfilePage_PolygenicDiseases(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
				})

			geneticTraitsButton := widget.NewButton("Genetic Traits", func(){
					setViewMateProfilePage_GeneticTraits(window, getAnyUserProfileAttributeFunction, currentPage)
				})

			buttonsGrid := getContainerCentered(container.NewGridWithColumns(2, deepAppearanceSimilarityButton, totalDiseaseRiskButton, ancestryCompositionButton, monogenicDiseasesButton, haplogroupsButton, polygenicDiseasesButton, neanderthalVariantsButton, geneticTraitsButton))
			page.Add(buttonsGrid)

			page.Add(widget.NewSeparator())	

			err := addAttributeRow("Sex")
			if (err != nil) { return err }

			err = addAttributeRow("Height")
			if (err != nil) { return err }

			page.Add(widget.NewSeparator())

			err = addAttributeRow("EyeColor")
			if (err != nil) { return err }

			err = addAttributeRow("SkinColor")
			if (err != nil) { return err }

			err = addAttributeRow("HairColor")
			if (err != nil) { return err }

			err = addAttributeRow("HairTexture")
			if (err != nil) { return err }

			page.Add(widget.NewSeparator())
			
			err = addAttributeRow("BodyFat")
			if (err != nil) { return err }

			err = addAttributeRow("BodyMuscle")
			if (err != nil) { return err }

			page.Add(widget.NewSeparator())

			err = addAttributeRow("HasHIV")
			if (err != nil) { return err }

			err = addAttributeRow("HasGenitalHerpes")
			if (err != nil) { return err }

			return nil
		}
		if (categoryName == "Lifestyle"){

			dietButton := getWidgetCentered(widget.NewButton("Diet", func(){
					setViewMateProfilePage_Diet(window, getAnyUserProfileAttributeFunction, currentPage) 
				}))
			page.Add(dietButton)
			page.Add(widget.NewSeparator())

			err := addAttributeRow("Hobbies")
			if (err != nil) { return err }		

			err = addAttributeRow("WealthInGold")
			if (err != nil) { return err }

			err = addAttributeRow("Job")
			if (err != nil) { return err }

			err = addAttributeRow("Fame")
			if (err != nil) { return err }

			page.Add(widget.NewSeparator())

			err = addAttributeRow("AlcoholFrequency")
			if (err != nil) { return err }

			err = addAttributeRow("TobaccoFrequency")
			if (err != nil) { return err }

			err = addAttributeRow("CannabisFrequency")
			if (err != nil) { return err }	

			return nil
		}
		if (categoryName == "Mental"){

			languageButton := getWidgetCentered(widget.NewButton("Language", func(){
					setViewMateProfilePage_Language(window, getAnyUserProfileAttributeFunction, currentPage)
				}))
			page.Add(languageButton)
			page.Add(widget.NewSeparator())			

			err := addAttributeRow("Beliefs")
			if (err != nil) { return err }

			err = addAttributeRow("GenderIdentity")
			if (err != nil) { return err }

			page.Add(widget.NewSeparator())

			err = addAttributeRow("PetsRating")
			if (err != nil) { return err }

			err = addAttributeRow("DogsRating")
			if (err != nil) { return err }

			err = addAttributeRow("CatsRating")
			if (err != nil) { return err }

			return nil
		}
		return errors.New("setViewUserProfilePage_Category called with invalid profile category: " + categoryName)
	}

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

	page.Add(widget.NewSeparator())	

	setPageContent(page, window)
}

func setViewMateProfilePage_Location(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	setLoadingScreen(window, "View Profile - General", "Loading user location...")

	title := getPageTitleCentered("View Profile - General")

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Location"))

	//Outputs:
	//	-bool: Location exists
	//	-*fyne.Container
	//	-error
	getLocationColumn := func(locationRank string)(bool, *fyne.Container, error){

		if (locationRank != "Primary" && locationRank != "Secondary"){
			return false, nil, errors.New("getLocationColumn called with invalid locationRank: " + locationRank)
		}

		exists, _, locationLatitudeString, err := getAnyUserProfileAttributeFunction(locationRank + "LocationLatitude")
		if (err != nil) { return false, nil, err }
		if (exists == false){
			return false, nil, nil
		}

		exists, _, locationLongitudeString, err := getAnyUserProfileAttributeFunction(locationRank + "LocationLongitude")
		if (err != nil) { return false, nil, err }
		if (exists == false){
			return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing " + locationRank + "LocationLatitude but missing " + locationRank + "LocationLongitude")
		}

		locationLatitudeFloat64, err := helpers.ConvertStringToFloat64(locationLatitudeString)
		if (err != nil){
			return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing invalid LocationLatitude: " + locationLatitudeString)
		}

		locationLongitudeFloat64, err := helpers.ConvertStringToFloat64(locationLongitudeString)
		if (err != nil){
			return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing invalid LocationLongitude: " + locationLongitudeString)
		}

		locationRankLabel := getBoldLabelCentered(locationRank)

		locationCountryExists, _, locationCountryIdentifier, err := getAnyUserProfileAttributeFunction(locationRank + "LocationCountry")
		if (err != nil) { return false, nil, err }

		getCountryTextLabel := func()(fyne.Widget, error){
			
			if (locationCountryExists == false){
				noneLabel := getBoldItalicLabel(translate("None"))
				return noneLabel, nil
			}

			locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier)
			if (err != nil) { return nil, err }			

			countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(locationCountryIdentifierInt)
			if (err != nil){ return nil, err }

			countryNamesList := countryObject.NamesList

			countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/")

			countryTextLabel := getBoldLabel(translate(countryDescription))
			return countryTextLabel, nil
		}

		countryTextLabel, err := getCountryTextLabel()
		if (err != nil) { return false, nil, err }

		countryLabel := getLabelCentered("Country:")

		coordinatesLabel := getLabelCentered("Coordinates:")

		locationCoordinatesLabel := getBoldLabel(locationLatitudeString + "°, " + locationLongitudeString + "°")

		cityLabel := getLabelCentered("City:")

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

			// We will either find the exact city or the closest city

			cityName, cityState, cityCountryIdentifier, cityDistanceKilometers, err := worldLocations.GetClosestCityFromCoordinates(locationLatitudeFloat64, locationLongitudeFloat64)
			if (err != nil) { return nil, err }

			if (cityDistanceKilometers == 0){

				locationCityFormatted := cityName + ", " + cityState

				locationCityLabel := getBoldLabelCentered(locationCityFormatted)

				return locationCityLabel, nil
			}

			currentUnitsExist, currentUnits, err := globalSettings.GetSetting("MetricOrImperial")
			if (err != nil){ return nil, err }

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

				if (currentUnitsExist == true && currentUnits == "Imperial"){

					distanceMiles, err := helpers.ConvertKilometersToMiles(cityDistanceKilometers)
					if (err != nil) { return "", err }

					distanceMilesString := helpers.ConvertFloat64ToStringRounded(distanceMiles, 1)
					result := distanceMilesString + " miles"
					return result, nil
				}

				distanceKilometersString := helpers.ConvertFloat64ToStringRounded(cityDistanceKilometers, 1)

				result := distanceKilometersString + " kilometers"
				
				return result, nil
			}

			nearbyCityDistanceFormattedString, err := getNearbyCityDistanceFormattedString()
			if (err != nil) { return nil, err }

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

				if (locationCountryExists == true){

					locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier)
					if (err != nil) { return "", err }	

					if (cityCountryIdentifier == locationCountryIdentifierInt){
						result := cityName + ", " + cityState
						return result, nil
					}
				}

				countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(cityCountryIdentifier)
				if (err != nil){ return "", err }

				countryNamesList := countryObject.NamesList

				countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/")

				result := cityName + ", " + cityState + ", " + countryDescription
				return result, nil
			}

			nearbyCityNameFormatted, err := getNearbyCityNameFormatted()
			if (err != nil) { return nil, err }

			nearbyCityNameFormattedAndTrimmed, _, err := helpers.TrimAndFlattenString(nearbyCityNameFormatted, 40)
			if (err != nil) { return nil, err }

			locationDistanceLabel := getBoldLabelCentered(nearbyCityDistanceFormattedString + " from")

			locationCityNameLabel := getBoldLabelCentered(nearbyCityNameFormattedAndTrimmed)
		
			cityInfoContainer := container.NewVBox(locationDistanceLabel, locationCityNameLabel)		

			return cityInfoContainer, nil
		}

		cityContent, err := getCityContent()
		if (err != nil){ return false, nil, err }

		locationColumn := container.NewVBox(locationRankLabel, widget.NewSeparator(), countryLabel, countryTextLabel, widget.NewSeparator(), coordinatesLabel, locationCoordinatesLabel, widget.NewSeparator(), cityLabel, cityContent)

		return true, locationColumn, nil
	}

	primaryLocationExists, primaryLocationColumn, err := getLocationColumn("Primary")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (primaryLocationExists == false){

		noLocationsLabel := getBoldLabelCentered("This user has no locations.")
		page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), noLocationsLabel)

		setPageContent(page, window)
		return
	}

	locationColumnsGrid := container.NewGridWithRows(1, primaryLocationColumn)

	secondaryLocationExists, secondaryLocationColumn, err := getLocationColumn("Secondary")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (secondaryLocationExists == true){

		locationColumnsGrid.Add(secondaryLocationColumn)
	}
	locationColumnsGridCentered := getContainerCentered(locationColumnsGrid)

	getDescriptionText := func()string{
		if (secondaryLocationExists == false){
			result := translate("Below is the user's location.")
			return result
		}

		result := translate("Below are the user's locations.")
		return result
	}
	
	descriptionText := getDescriptionText()

	description := getLabelCentered(descriptionText)

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

	setPageContent(page, window)
}

func setViewMateProfilePage_Questionnaire(window fyne.Window, profileIsMine bool, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	currentPage := func(){setViewMateProfilePage_Questionnaire(window, profileIsMine, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - General"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Questionnaire"))

	questionnaireExists, _, questionnaireString, err := getAnyUserProfileAttributeFunction("Questionnaire")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (questionnaireExists == false){
		description := getBoldLabelCentered("This user does not have a questionnaire.")

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

		setPageContent(page, window)
		return
	}

	description1 := getLabelCentered("This user has a questionnaire.")
	description2 := getLabelCentered("You can take their questionnaire and send them your responses.")

	questionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(questionnaireString)
	if (err != nil){
		setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_Questionnaire called with profile containing invalid questionnaire."), previousPage)
		return
	}

	numberOfQuestions := len(questionnaireObject)
	numberOfQuestionsString := helpers.ConvertIntToString(numberOfQuestions)

	numberOfQuestionsLabel := widget.NewLabel("Number of questions:")
	numberOfQuestionsText := getBoldLabel(numberOfQuestionsString)

	numberOfQuestionsRow := container.NewHBox(layout.NewSpacer(), numberOfQuestionsLabel, numberOfQuestionsText, layout.NewSpacer())

	exists, _, userIdentityHashString, err := getAnyUserProfileAttributeFunction("IdentityHash")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (exists == false){
		setErrorEncounteredPage(window, errors.New("getAnyUserProfileAttributeFunction failed to get IdentityHash"), previousPage)
		return
	}

	userIdentityHash, _, err := identity.ReadIdentityHashString(userIdentityHashString)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	//TODO: Show if user has already taken the questionnaire

	submitQuestionnairePage := func(questionnaireResponse string, prevPage func()){
		if (profileIsMine == true){

			dialogTitle := translate("Cannot Submit Questionnaire")
			dialogMessageA := getLabelCentered(translate("You are viewing your own profile."))
			dialogMessageB := getLabelCentered(translate("You cannot submit a response to your own questionnaire."))
			dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		}
		setSubmitQuestionnairePage(window, userIdentityHash, questionnaireResponse, prevPage, currentPage)
	}

	takeQuestionnaireButton := getWidgetCentered(widget.NewButtonWithIcon("Take Questionnaire", theme.DocumentCreateIcon(), func(){
			emptyMap := make(map[string]string)
			setTakeQuestionnairePage(window, questionnaireObject, 0, emptyMap, currentPage, submitQuestionnairePage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, numberOfQuestionsRow, takeQuestionnaireButton)
	
	setPageContent(page, window)
}

func setViewMateProfilePage_Tags(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - General"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Tags"))

	anyTagsExist, _, tagsAttribute, err := getAnyUserProfileAttributeFunction("Tags")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (anyTagsExist == false){

		description := getBoldLabelCentered("This user has no tags.")

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

		setPageContent(page, window)
		return
	}

	description := getLabelCentered("Below are the user's tags.")

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

	tagsList := strings.Split(tagsAttribute, "+&")

	for _, tagName := range tagsList{

		tagLabel := getBoldLabelCentered(tagName)

		page.Add(tagLabel)
	}
	
	setPageContent(page, window)
}

func setViewMateProfilePage_Haplogroups(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Haplogroups"))

	description1 := getLabelCentered(translate("Below are the user's haplogroups."))
	description2 := getLabelCentered("These are reported by 23andMe. More companies will be added.")

	userMaternalHaplogroupExists, _, userMaternalHaplogroup_23andMe, err := getAnyUserProfileAttributeFunction("23andMe_MaternalHaplogroup")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	maternalHaplogroupLabel := widget.NewLabel("Maternal Haplogroup:")

	getMaternalHalogroupText := func()fyne.Widget{

		if (userMaternalHaplogroupExists == false){
			noResponseLabel := getBoldItalicLabel("No Response")
			return noResponseLabel
		}

		maternalHaplogroupLabel := getBoldLabel(userMaternalHaplogroup_23andMe)

		return maternalHaplogroupLabel
	}

	maternalHaplogroupText := getMaternalHalogroupText()

	userMaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), maternalHaplogroupLabel, maternalHaplogroupText, layout.NewSpacer())

	userPaternalHaplogroupExists, _, userPaternalHaplogroup_23andMe, err := getAnyUserProfileAttributeFunction("23andMe_PaternalHaplogroup")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	paternalHaplogroupLabel := widget.NewLabel("Paternal Haplogroup:")

	getPaternalHalogroupText := func()fyne.Widget{

		if (userPaternalHaplogroupExists == false){
			noResponseLabel := getBoldItalicLabel("No Response")
			return noResponseLabel
		}

		paternalHaplogroupLabel := getBoldLabel(userPaternalHaplogroup_23andMe)

		return paternalHaplogroupLabel
	}

	paternalHaplogroupText := getPaternalHalogroupText()

	userPaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), paternalHaplogroupLabel, paternalHaplogroupText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), userMaternalHaplogroupRow, userPaternalHaplogroupRow)

	//Outputs:
	// -bool: Offspring maternal haplogroup is known
	// -string: Offspring maternal haplogroup
	// -bool: Offspring paternal haplogroup is known
	// -string: Offspring paternal haplogroup is known
	// -error
	getOffspringHaplogroups := func()(bool, string, bool, string, error){

		isKnown, _, isSameSex, err := getAnyUserProfileAttributeFunction("IsSameSex")
		if (err != nil){ return false, "", false, "", err }
		if (isKnown == true && isSameSex == "Yes"){
			return false, "", false, "", nil
		}

		mySexExists, mySex, err := myLocalProfiles.GetProfileData("Mate", "Sex")
		if (err != nil) { return false, "", false, "", err }

		userSexExists, _, userSex, err := getAnyUserProfileAttributeFunction("Sex")
		if (err != nil) { return false, "", false, "", err }

		if (mySexExists == false && userSexExists == false){
			return false, "", false, "", nil
		}

		//Outputs:
		// -bool: Haplogroup is known
		// -string: Offspring maternal haplogroup
		// -error
		getOffspringMaternalHaplogroup := func()(bool, string, error){

			if (mySexExists == true){
				if (mySex == "Female" || mySex == "Intersex Female"){

					exists, myMaternalHaplogroup, err := myLocalProfiles.GetProfileData("Mate", "23andMe_MaternalHaplogroup")
					if (err != nil){ return false, "", err }
					if (exists == false){
						return false, "", nil
					}
					return true, myMaternalHaplogroup, nil
				}
			}
			if (userSexExists == true){
				if (userSex == "Female" || userSex == "Intersex Female"){
					if (userMaternalHaplogroupExists == true){
						return true, userMaternalHaplogroup_23andMe, nil
					}
				}
			}
			return false, "", nil
		}

		offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, err := getOffspringMaternalHaplogroup()
		if (err != nil) { return false, "", false, "", err }

		//Outputs:
		// -bool: Haplogroup is known
		// -string: Offspring paternal haplogroup
		// -error
		getOffspringPaternalHaplogroup := func()(bool, string, error){
			if (mySexExists == true){
				if (mySex == "Male" || mySex == "Intersex Male"){

					exists, myPaternalHaplogroup, err := myLocalProfiles.GetProfileData("Mate", "23andMe_PaternalHaplogroup")
					if (err != nil){ return false, "", err }
					if (exists == false){
						return false, "", nil
					}
					return true, myPaternalHaplogroup, nil
				}
			}
			if (userSexExists == true){

				if (userSex == "Male" || userSex == "Intersex Male"){
					if (userPaternalHaplogroupExists == true){
						return true, userPaternalHaplogroup_23andMe, nil
					}
				}
			}
			
			return false, "", nil
		}

		offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, err := getOffspringPaternalHaplogroup()
		if (err != nil) { return false, "", false, "", err }

		return offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, nil
	}

	offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, err := getOffspringHaplogroups()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (offspringMaternalHaplogroupIsKnown == true || offspringPaternalHaplogroupIsKnown == true){

		page.Add(widget.NewSeparator())
	
		offspringDescription := getLabelCentered("Below are the haplogroups of an offspring created from you and the user.")
		page.Add(offspringDescription)
		page.Add(widget.NewSeparator())

		offspringMaternalHaplogroupLabel := widget.NewLabel("Offspring Maternal Haplogroup:")

		getOffspringMaternalHaplogroupText := func()fyne.Widget{

			if (offspringMaternalHaplogroupIsKnown == false){
				unknownLabel := getBoldItalicLabel("Unknown")
				return unknownLabel
			}
			haplogroupLabel := getBoldLabel(offspringMaternalHaplogroup)
			return haplogroupLabel
		}

		offspringMaternalHaplogroupText := getOffspringMaternalHaplogroupText()

		offspringMaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), offspringMaternalHaplogroupLabel, offspringMaternalHaplogroupText, layout.NewSpacer())
		page.Add(offspringMaternalHaplogroupRow)

		offspringPaternalHaplogroupLabel := widget.NewLabel("Offspring Paternal Haplogroup:")

		getOffspringPaternalHaplogroupText := func()fyne.Widget{

			if (offspringPaternalHaplogroupIsKnown == false){
				unknownLabel := getBoldItalicLabel("Unknown")
				return unknownLabel
			}
			haplogroupLabel := getBoldLabel(offspringPaternalHaplogroup)
			return haplogroupLabel
		}

		offspringPaternalHaplogroupText := getOffspringPaternalHaplogroupText()

		offspringPaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), offspringPaternalHaplogroupLabel, offspringPaternalHaplogroupText, layout.NewSpacer())

		page.Add(offspringPaternalHaplogroupRow)
	}

	setPageContent(page, window)
}

func setViewMateProfilePage_NeanderthalVariants(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - Physical"))
	
	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Neanderthal Variants"))

	description1 := getLabelCentered("Below is the user's neanderthal variant count.")
	description2 := getLabelCentered("The information comes from 23andMe. More companies will be added.")

	userResponseExists, _, userNeanderthalVariants, err := getAnyUserProfileAttributeFunction("23andMe_NeanderthalVariants")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}

	getUserNeanderthalVariantsLabel := func()fyne.Widget{

		if (userResponseExists == false){
			noResponseLabel := getBoldItalicLabel(translate("No Response"))

			return noResponseLabel
		}

		userNeanderthalVariantsLabel := getBoldLabel(userNeanderthalVariants + " variants")
		return userNeanderthalVariantsLabel
	}

	userNeanderthalVariantsLabel := getUserNeanderthalVariantsLabel()

	neanderthalVariantsLabel := widget.NewLabel("Neanderthal Variants:")

	neanderthalVariantsRow := container.NewHBox(layout.NewSpacer(), neanderthalVariantsLabel, userNeanderthalVariantsLabel, layout.NewSpacer())

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

	isSameSexIsKnown, _, isSameSex, err := getAnyUserProfileAttributeFunction("IsSameSex")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (isSameSexIsKnown == true && isSameSex == "Yes"){

		setPageContent(page, window)
		return
	}

	offspringNeanderthalVariantsKnown, _, offspringNeanderthalVariants, err := getAnyUserProfileAttributeFunction("23andMe_OffspringNeanderthalVariants")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (offspringNeanderthalVariantsKnown == false){
		setPageContent(page, window)
		return
	}

	page.Add(widget.NewSeparator())
	offspringDescription := getLabelCentered("Below is the estimated neanderthal variant count of an offspring created from you and this user.")
	page.Add(offspringDescription)

	offspringNeanderthalVariantsLabel := widget.NewLabel("Offspring Neanderthal Variants:")
	offspringNeanderthalVariantsText := getBoldLabel(offspringNeanderthalVariants + " variants")

	offspringNeanderthalVariantsRow := container.NewHBox(layout.NewSpacer(), offspringNeanderthalVariantsLabel, offspringNeanderthalVariantsText, layout.NewSpacer())
	page.Add(offspringNeanderthalVariantsRow)

	setPageContent(page, window)
}

func setViewMateProfilePage_DeepAppearanceSimilarity(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Deep Appearance Similarity"))

	description := getLabelCentered("This page describes the deep appearance similarity between you and the user.")

	//TODO: Add help pages to explain results

	deepAppearanceSimilarityTitle := widget.NewLabel("Deep Appearance Similarity:")

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

		similarityIsKnown, _, deepAppearanceSimilarity, err := getAnyUserProfileAttributeFunction("DeepAppearanceSimilarity")
		if (err != nil) { return "", err }
		if (similarityIsKnown == false){

			result := translate("Unknown")
			return result, nil
		}

		return deepAppearanceSimilarity, nil
	}

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

	deepAppearanceSimilarityLabel := getBoldLabel(deepAppearanceSimilarityText)

	deepAppearanceSimilarityHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			//TODO
			showUnderConstructionDialog(window)
		})

	deepAppearanceSimilarityRow := container.NewHBox(layout.NewSpacer(), deepAppearanceSimilarityTitle, deepAppearanceSimilarityLabel, deepAppearanceSimilarityHelpButton, layout.NewSpacer())

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

		//TODO: A button for each trait to see what the our and the user's trait value is

		traitNameHeader := getItalicLabelCentered("Trait Name")
		traitSimilarityHeader := getItalicLabelCentered("Trait Similarity")
		geneticSimilarityHeader := getItalicLabelCentered("Genetic Similarity")
		numberOfTestedLociHeader := getItalicLabelCentered("Number Of Tested Loci")

		traitNamesColumn := container.NewVBox(traitNameHeader, widget.NewSeparator())
		traitSimilarityColumn := container.NewVBox(traitSimilarityHeader, widget.NewSeparator())
		geneticSimilarityColumn := container.NewVBox(geneticSimilarityHeader, widget.NewSeparator())
		numberOfTestedLociColumn := container.NewVBox(numberOfTestedLociHeader, widget.NewSeparator())

		addSimilarityRow := func(traitName string, traitSimilarityAttributeName string, geneticSimilarityAttributeName string, numberOfSimilarAllelesAttributeName string)error{

			traitTitleLabel := getBoldLabelCentered(translate(traitName))

			traitNamesColumn.Add(traitTitleLabel)

			if (traitName == "Facial Structure"){

				// This trait's similarity cannot be calculated yet
				// This feature is planned to be added - it will compare the faces in user profile photos.

				dashLabel := getItalicLabelCentered("-")

				traitSimilarityColumn.Add(dashLabel)

			} else {

				traitSimilarityIsKnown, _, attributeValue, err := getAnyUserProfileAttributeFunction(traitSimilarityAttributeName)
				if (err != nil) { return err }
				if (traitSimilarityIsKnown == false){

					unknownLabel := getItalicLabelCentered("Unknown")

					traitSimilarityColumn.Add(unknownLabel)
				} else {

					similarityFormatted := attributeValue + "%"

					similarityLabel := getBoldLabelCentered(similarityFormatted)

					traitSimilarityColumn.Add(similarityLabel)
				}
			}

			// We figure out how many loci exist for this trait

			traitObject, err := traits.GetTraitObject(traitName)
			if (err != nil) { return err }

			traitLociList := traitObject.LociList
			quantityOfTraitLoci := len(traitLociList)

			quantityOfTraitLociString := helpers.ConvertIntToString(quantityOfTraitLoci)

			geneticSimilarityIsKnown, _, attributeValue, err := getAnyUserProfileAttributeFunction(geneticSimilarityAttributeName)
			if (err != nil) { return err }
			if (geneticSimilarityIsKnown == false){
				unknownLabel := getItalicLabelCentered("Unknown")

				geneticSimilarityColumn.Add(unknownLabel)

				quantityOfTestedLociText := "0/" + quantityOfTraitLociString

				quantityOfTestedLociLabel := getBoldLabelCentered(quantityOfTestedLociText)

				numberOfTestedLociColumn.Add(quantityOfTestedLociLabel)
			} else {

				similarityFormatted := attributeValue + "%"

				similarityLabel := getBoldLabelCentered(similarityFormatted)

				geneticSimilarityColumn.Add(similarityLabel)

				attributeIsKnown, _, numberOfSimilarAllelesValue, err := getAnyUserProfileAttributeFunction(numberOfSimilarAllelesAttributeName)
				if (err != nil) { return err }
				if (attributeIsKnown == false){
					return errors.New("User profile contains " + geneticSimilarityAttributeName + " but is missing " + numberOfSimilarAllelesAttributeName)
				}

				// numberOfSimilarAllelesValue is represented by (number of shared alleles)/(number of tested alleles)
				// Example: "52/60"

				// We extract the denominator

				_, numberOfTestedAllelesString, delimeterFound := strings.Cut(numberOfSimilarAllelesValue, "/")
				if (delimeterFound == false){
					return errors.New("User profile contains invalid " + numberOfSimilarAllelesAttributeName + ": " + numberOfSimilarAllelesValue)
				}

				numberOfTestedAlleles, err := helpers.ConvertStringToInt(numberOfTestedAllelesString)
				if (err != nil){
					return errors.New("User profile contains invalid " + numberOfSimilarAllelesAttributeName + ": " + numberOfSimilarAllelesValue)
				}

				// Each locus has 2 alleles (this will change once we include sex chromosome locations)
				numberOfTestedLoci := numberOfTestedAlleles/2

				numberOfTestedLociString := helpers.ConvertIntToString(numberOfTestedLoci)

				numberOfTestedLociLabelText := numberOfTestedLociString + "/" + quantityOfTraitLociString

				numberOfTestedLociLabel := getBoldLabelCentered(numberOfTestedLociLabelText)

				numberOfTestedLociColumn.Add(numberOfTestedLociLabel)
			}

			traitNamesColumn.Add(widget.NewSeparator())
			traitSimilarityColumn.Add(widget.NewSeparator())
			geneticSimilarityColumn.Add(widget.NewSeparator())
			numberOfTestedLociColumn.Add(widget.NewSeparator())

			return nil
		}

		err = addSimilarityRow("Eye Color", "EyeColorSimilarity", "EyeColorGenesSimilarity", "EyeColorGenesSimilarity_NumberOfSimilarAlleles")
		if (err != nil) { return nil, err }

		err = addSimilarityRow("Hair Color", "HairColorSimilarity", "HairColorGenesSimilarity", "HairColorGenesSimilarity_NumberOfSimilarAlleles")
		if (err != nil) { return nil, err }

		err = addSimilarityRow("Skin Color", "SkinColorSimilarity", "SkinColorGenesSimilarity", "SkinColorGenesSimilarity_NumberOfSimilarAlleles")
		if (err != nil) { return nil, err }

		err = addSimilarityRow("Hair Texture", "HairTextureSimilarity", "HairTextureGenesSimilarity", "HairTextureGenesSimilarity_NumberOfSimilarAlleles")
		if (err != nil) { return nil, err }

		err = addSimilarityRow("Facial Structure", "", "FacialStructureGenesSimilarity", "FacialStructureGenesSimilarity_NumberOfSimilarAlleles")
		if (err != nil) { return nil, err }

		similarityGrid := container.NewHBox(layout.NewSpacer(), traitNamesColumn, traitSimilarityColumn, geneticSimilarityColumn, numberOfTestedLociColumn, layout.NewSpacer())

		return similarityGrid, nil
	}

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), deepAppearanceSimilarityRow, widget.NewSeparator(), deepAppearanceSimilarityBreakdownGrid)

	setPageContent(page, window)
}

func setViewMateProfilePage_AncestryComposition(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	if (userOrOffspring != "User" && userOrOffspring != "Offspring"){
		setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_AncestryComposition called with invalid userOrOffspring: " + userOrOffspring), previousPage)
		return
	}

	currentPage := func(){setViewMateProfilePage_AncestryComposition(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Ancestry Composition"))

	description1 := getLabelCentered("Below is the ancestry location composition for this user.")
	description2 := getLabelCentered("The information is provided by 23andMe. More companies will be added.")

	userAncestryCompositionExists, _, userAncestryCompositionAttribute, err := getAnyUserProfileAttributeFunction("23andMe_AncestryComposition")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (userAncestryCompositionExists == false){
		noAncestryComposition := getBoldLabelCentered("This user has no ancestry composition.")

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

		setPageContent(page, window)
		return
	}

	description3Label := getLabelCentered("Choose if you want to view the composition of the user or the offspring between you and the user.")
	offspringAncestryCompositionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setOffspringAncestryCompositionExplainerPage(window, currentPage)
		})

	description3Row := container.NewHBox(layout.NewSpacer(), description3Label, offspringAncestryCompositionHelpButton, layout.NewSpacer())

	handleSelectFunction := func(newUserOrOffspring string){
			if (newUserOrOffspring == userOrOffspring){
				return
			}
			setViewMateProfilePage_AncestryComposition(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage)
		}

	userOrOffspringList := []string{"User", "Offspring"}

	userOrOffspringSelector := widget.NewSelect(userOrOffspringList, handleSelectFunction)

	userOrOffspringSelector.Selected = userOrOffspring

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

	header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), description3Row, userOrOffspringSelectorCentered, widget.NewSeparator())

	if (userOrOffspring == "User"){

		attributeIsValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, userAncestryCompositionAttribute)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		if (attributeIsValid == false){
			setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_AncestryComposition called with getAnyUserProfileAttributeFunction returning invalid userAncestryCompositionAttribute"), previousPage)
			return
		}

		userCompositionDisplay, err := get23andMeAncestryCompositionDisplay(continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		page := container.NewBorder(header, nil, nil, nil, userCompositionDisplay)
		setPageContent(page, window)
		return
	}

	//userOrOffspring == "Offspring"

	myAncestryCompositionExists, myAncestryCompositionAttribute, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myAncestryCompositionExists == false){

		description4 := getBoldLabelCentered("Your ancestry location composition does not exist.")
		description5 := getLabelCentered("Add your ancestry composition to view your offspring's ancestry composition.")
		description6 := getLabelCentered("You can do this on the Build Profile - Physical - Ancestry Composition page.")

		page := container.NewVBox(header, description4, description5, description6)

		setPageContent(page, window)
		return
	}

	offspringContinentPercentagesMap, offspringRegionPercentagesMap, offspringSubregionPercentagesMap, err := companyAnalysis.GetOffspringAncestryComposition_23andMe(myAncestryCompositionAttribute, userAncestryCompositionAttribute)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	offspringAncestryCompositionDisplay, err := get23andMeAncestryCompositionDisplay(offspringContinentPercentagesMap, offspringRegionPercentagesMap, offspringSubregionPercentagesMap)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewBorder(header, nil, nil, nil, offspringAncestryCompositionDisplay)

	setPageContent(page, window)
}


// This is a page to view the total monogenic disease risk for a user's offspring
func setViewMateProfilePage_TotalDiseaseRisk(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Total Disease Risk"))

	description1 := getLabelCentered("This page describes the total disease risk for this user.")
	description2 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring disease information.")

	offspringProbabilityOfAnyMonogenicDiseaseTitle := widget.NewLabel("Offspring Probability Of Any Monogenic Disease:")

	getOffspringProbabilityOfAnyMonogenicDiseaseLabel := func()(fyne.Widget, error){

		probabilityIsKnown, _, offspringProbabilityOfAnyMonogenicDisease, err := getAnyUserProfileAttributeFunction("OffspringProbabilityOfAnyMonogenicDisease")
		if (err != nil) { return nil, err }
		if (probabilityIsKnown == false){
			unknownLabel := getItalicLabel(translate("Unknown"))
			return unknownLabel, nil
		}

		probabilityFormatted := offspringProbabilityOfAnyMonogenicDisease + "%"

		probabilityLabel := getBoldLabel(probabilityFormatted)

		return probabilityLabel, nil
	}

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

	offspringProbabilityOfAnyMonogenicDiseaseRow := container.NewHBox(layout.NewSpacer(), offspringProbabilityOfAnyMonogenicDiseaseTitle, offspringProbabilityOfAnyMonogenicDiseaseLabel, layout.NewSpacer())

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

		attributeIsKnown, _, totalNumberOfMonogenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested")
		if (err != nil) { return 0, err }
		if (attributeIsKnown == false){
			return 0, nil
		}
		totalNumberOfMonogenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfMonogenicDiseasesTestedString)
		if (err != nil){
			return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested value: " + totalNumberOfMonogenicDiseasesTestedString)
		}

		return totalNumberOfMonogenicDiseasesTested, nil
	}

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

	allMonogenicDiseaseNamesList, err := monogenicDiseases.GetMonogenicDiseaseNamesList()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	totalNumberOfMonogenicDiseases := len(allMonogenicDiseaseNamesList)

	totalNumberOfMonogenicDiseasesString := helpers.ConvertIntToString(totalNumberOfMonogenicDiseases)

	numberOfOffspringMonogenicDiseasesTestedString := helpers.ConvertIntToString(numberOfOffspringMonogenicDiseasesTested)

	numberOfOffspringMonogenicDiseasesTestedLabelText := numberOfOffspringMonogenicDiseasesTestedString + "/" + totalNumberOfMonogenicDiseasesString

	numberOfOffspringMonogenicDiseasesTestedTitle := widget.NewLabel("Number Of Offspring Monogenic Diseases Tested:")
	numberOfOffspringMonogenicDiseasesTestedLabel := getBoldLabel(numberOfOffspringMonogenicDiseasesTestedLabelText)

	numberOfOffspringMonogenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfOffspringMonogenicDiseasesTestedTitle, numberOfOffspringMonogenicDiseasesTestedLabel, layout.NewSpacer())


	userTotalPolygenicDiseaseRiskScoreTitle := widget.NewLabel("User Total Polygenic Disease Risk Score:")

	getUserTotalPolygenicDiseaseRiskScoreLabel := func()(fyne.Widget, error){

		riskScoreIsKnown, _, userTotalPolygenicDiseaseRiskScore, err := getAnyUserProfileAttributeFunction("TotalPolygenicDiseaseRiskScore")
		if (err != nil) { return nil, err }
		if (riskScoreIsKnown == false){
			unknownLabel := getItalicLabel(translate("Unknown"))
			return unknownLabel, nil
		}

		riskScoreFormatted := userTotalPolygenicDiseaseRiskScore + "/100"

		riskScoreLabel := getBoldLabel(riskScoreFormatted)

		return riskScoreLabel, nil
	}

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

	userTotalPolygenicDiseaseRiskScoreRow := container.NewHBox(layout.NewSpacer(), userTotalPolygenicDiseaseRiskScoreTitle, userTotalPolygenicDiseaseRiskScoreLabel, layout.NewSpacer())

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

		attributeIsKnown, _, totalNumberOfPolygenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested")
		if (err != nil) { return 0, err }
		if (attributeIsKnown == false){
			return 0, nil
		}
		totalNumberOfPolygenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfPolygenicDiseasesTestedString)
		if (err != nil){
			return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested value: " + totalNumberOfPolygenicDiseasesTestedString)
		}

		return totalNumberOfPolygenicDiseasesTested, nil
	}

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

	allPolygenicDiseaseNamesList, err := polygenicDiseases.GetPolygenicDiseaseNamesList()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	// We count up every disease that has a prediction model
	totalNumberOfPolygenicDiseases := 0

	for _, diseaseName := range allPolygenicDiseaseNamesList{
		predictionModelExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName)
		if (predictionModelExists == true){
			totalNumberOfPolygenicDiseases += 1
		}
	}

	totalNumberOfPolygenicDiseasesString := helpers.ConvertIntToString(totalNumberOfPolygenicDiseases)

	numberOfUserPolygenicDiseasesTestedString := helpers.ConvertIntToString(numberOfUserPolygenicDiseasesTested)

	numberOfUserPolygenicDiseasesTestedLabelText := numberOfUserPolygenicDiseasesTestedString + "/" + totalNumberOfPolygenicDiseasesString

	numberOfUserPolygenicDiseasesTestedTitle := widget.NewLabel("Number Of User Polygenic Diseases Tested:")
	numberOfUserPolygenicDiseasesTestedLabel := getBoldLabel(numberOfUserPolygenicDiseasesTestedLabelText)

	numberOfUserPolygenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfUserPolygenicDiseasesTestedTitle, numberOfUserPolygenicDiseasesTestedLabel, layout.NewSpacer())


	offspringTotalPolygenicDiseaseRiskScoreTitle := widget.NewLabel("Offspring Total Polygenic Disease Risk Score:")

	getOffspringTotalPolygenicDiseaseRiskScoreLabel := func()(fyne.Widget, error){

		riskScoreIsKnown, _, offspringTotalPolygenicDiseaseRiskScore, err := getAnyUserProfileAttributeFunction("OffspringTotalPolygenicDiseaseRiskScore")
		if (err != nil) { return nil, err }
		if (riskScoreIsKnown == false){
			unknownLabel := getItalicLabel(translate("Unknown"))
			return unknownLabel, nil
		}

		riskScoreFormatted := offspringTotalPolygenicDiseaseRiskScore + "/100"

		riskScoreLabel := getBoldLabel(riskScoreFormatted)

		return riskScoreLabel, nil
	}

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

	offspringTotalPolygenicDiseaseRiskScoreRow := container.NewHBox(layout.NewSpacer(), offspringTotalPolygenicDiseaseRiskScoreTitle, offspringTotalPolygenicDiseaseRiskScoreLabel, layout.NewSpacer())

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

		attributeIsKnown, _, totalNumberOfPolygenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested")
		if (err != nil) { return 0, err }
		if (attributeIsKnown == false){
			return 0, nil
		}
		totalNumberOfPolygenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfPolygenicDiseasesTestedString)
		if (err != nil){
			return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested value: " + totalNumberOfPolygenicDiseasesTestedString)
		}

		return totalNumberOfPolygenicDiseasesTested, nil
	}

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

	numberOfOffspringPolygenicDiseasesTestedString := helpers.ConvertIntToString(numberOfOffspringPolygenicDiseasesTested)

	numberOfOffspringPolygenicDiseasesTestedLabelText := numberOfOffspringPolygenicDiseasesTestedString + "/" + totalNumberOfPolygenicDiseasesString

	numberOfOffspringPolygenicDiseasesTestedTitle := widget.NewLabel("Number Of Offspring Polygenic Diseases Tested:")
	numberOfOffspringPolygenicDiseasesTestedLabel := getBoldLabel(numberOfOffspringPolygenicDiseasesTestedLabelText)

	numberOfOffspringPolygenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfOffspringPolygenicDiseasesTestedTitle, numberOfOffspringPolygenicDiseasesTestedLabel, layout.NewSpacer())

	//TODO: Add help buttons

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), offspringProbabilityOfAnyMonogenicDiseaseRow, numberOfOffspringMonogenicDiseasesTestedRow, widget.NewSeparator(), userTotalPolygenicDiseaseRiskScoreRow, numberOfUserPolygenicDiseasesTestedRow, widget.NewSeparator(), offspringTotalPolygenicDiseaseRiskScoreRow, numberOfOffspringPolygenicDiseasesTestedRow)

	setPageContent(page, window)
}

func setViewMateProfilePage_MonogenicDiseases(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	currentPage := func(){setViewMateProfilePage_MonogenicDiseases(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Monogenic Diseases"))

	description1 := getLabelCentered("Below is the monogenic disease analysis for this user.")
	description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
	description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see full offspring information.")

	handleSelectButton := func(newUserOrOffspring string){
			if (userOrOffspring == newUserOrOffspring){
				return
			}
			setViewMateProfilePage_MonogenicDiseases(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) 
		}

	userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
	userOrOffspringSelector.Selected = userOrOffspring	

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

	//TODO: Sort to show highest risk diseases first. All other diseases should be in normal order

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

		emptyLabel1 := widget.NewLabel("")
		emptyLabel2 := widget.NewLabel("")

		emptyLabel3 := widget.NewLabel("")
		diseaseNameLabel := getItalicLabelCentered("Disease Name")

		emptyLabel4 := widget.NewLabel("")
		userHasDiseaseLabel := getItalicLabelCentered("User Has Disease")

		userProbabilityOfLabelA := getItalicLabelCentered("User Probability Of")
		passingVariantLabel := getItalicLabelCentered("Passing Variant")

		userNumberOfLabel := getItalicLabelCentered("User Number Of")
		variantsTestedLabelA := getItalicLabelCentered("Variants Tested")

		offspringProbabilityOfLabelA := getItalicLabelCentered("Offspring Probability Of")
		havingDiseaseLabel := getItalicLabelCentered("Having Disease")

		offspringProbabilityOfLabelB := getItalicLabelCentered("Offspring Probability Of")
		havingVariantLabel := getItalicLabelCentered("Having Variant")

		offspringNumberOfLabel := getItalicLabelCentered("Offspring Number Of")
		variantsTestedLabelB := getItalicLabelCentered("Variants Tested")

		diseaseInfoButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
		diseaseNameColumn := container.NewVBox(emptyLabel3, diseaseNameLabel, widget.NewSeparator())
		userHasDiseaseColumn := container.NewVBox(emptyLabel4, userHasDiseaseLabel, widget.NewSeparator())
		userProbabilityOfPassingVariantColumn := container.NewVBox(userProbabilityOfLabelA, passingVariantLabel, widget.NewSeparator())
		userNumberOfVariantsTestedColumn := container.NewVBox(userNumberOfLabel, variantsTestedLabelA, widget.NewSeparator())
		offspringProbabilityOfHavingDiseaseColumn := container.NewVBox(offspringProbabilityOfLabelA, havingDiseaseLabel, widget.NewSeparator())
		offspringProbabilityOfHavingAVariantColumn := container.NewVBox(offspringProbabilityOfLabelB, havingVariantLabel, widget.NewSeparator())
		offspringNumberOfVariantsTestedColumn := container.NewVBox(offspringNumberOfLabel, variantsTestedLabelB, widget.NewSeparator())

		myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
		if (err != nil) { return nil, err }

		monogenicDiseaseObjectsList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList()
		if (err != nil) { return nil, err }		

		for _, diseaseObject := range monogenicDiseaseObjectsList{

			monogenicDiseaseName := diseaseObject.DiseaseName	
			diseaseIsDominantOrRecessive := diseaseObject.DominantOrRecessive

			diseaseVariantsList := diseaseObject.VariantsList
			numberOfDiseaseVariants := len(diseaseVariantsList)

			//Outputs:
			//	-bool: User disease info exists
			//	-bool: User has the disease
			//	-int: User probability of passing a disease variant (0-100)
			//	-int: User number of variants tested
			//	-error
			getUserDiseaseInfo := func()(bool, bool, int, int, error){

				diseaseNameWithUnderscores := strings.ReplaceAll(monogenicDiseaseName, " ", "_")

				probabilityOfPassingAVariantAttributeName := "MonogenicDisease_" + diseaseNameWithUnderscores + "_ProbabilityOfPassingAVariant"
				numberOfVariantsTestedAttributeName := "MonogenicDisease_" + diseaseNameWithUnderscores + "_NumberOfVariantsTested"

				userDiseaseInfoExists, _, userProbabilityOfPassingDiseaseVariantString, err := getAnyUserProfileAttributeFunction(probabilityOfPassingAVariantAttributeName)
				if (err != nil) { return false, false, 0, 0, err }
				if (userDiseaseInfoExists == false){
					return false, false, 0, 0, nil
				}

				userProbabilityOfPassingDiseaseVariant, err := helpers.ConvertStringToInt(userProbabilityOfPassingDiseaseVariantString)
				if (err != nil) {
					return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with profile containing invalid probabilityOfPassingAVariant: " + userProbabilityOfPassingDiseaseVariantString)
				}

				getUserHasDiseaseBool := func()bool{

					if (diseaseIsDominantOrRecessive == "Dominant"){
						if (userProbabilityOfPassingDiseaseVariant != 0){
							return true
						}
						return false
					}
					// diseaseIsDominantOrRecessive == "Recessive"
					if (userProbabilityOfPassingDiseaseVariant == 100){
						return true
					}
					return false
				}

				userHasDisease := getUserHasDiseaseBool()

				userVariantsTestedExists, _, userNumberOfVariantsTested, err := getAnyUserProfileAttributeFunction(numberOfVariantsTestedAttributeName)
				if (err != nil) { return false, false, 0, 0, err }
				if (userVariantsTestedExists == false){
					return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with user profile containing probabilityOfPassingAVariant but not numberOfVariantsTested")
				}

				userNumberOfVariantsTestedInt, err := helpers.ConvertStringToInt(userNumberOfVariantsTested)
				if (err != nil) {
					return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with profile containing invalid numberOfVariantsTested: " + userNumberOfVariantsTested)
				}

				return true, userHasDisease, userProbabilityOfPassingDiseaseVariant, userNumberOfVariantsTestedInt, nil
			}

			userDiseaseInfoExists, userHasDisease, userProbabilityOfPassingAVariant, userNumberOfVariantsTested, err := getUserDiseaseInfo()
			if (err != nil) { return nil, err }

			//Outputs:
			//	-bool: My disease info exists
			//	-int: My probability of passing a disease variant
			//	-int: Number of variants tested
			//	-error
			getMyDiseaseInfo := func()(bool, int, int, error){
				if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
					return false, 0, 0, nil
				}

				diseaseInfoIsKnown, _, probabilityOfPassingADiseaseVariant, _, numberOfVariantsTested, _, _, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(myAnalysisObject, monogenicDiseaseName, myGenomeIdentifier)
				if (err != nil) { return false, 0, 0, err }
				if (diseaseInfoIsKnown == false){
					return false, 0, 0, nil
				}

				return true, probabilityOfPassingADiseaseVariant, numberOfVariantsTested, nil
			}

			myDiseaseInfoExists, myProbabilityOfPassingAVariant, myNumberOfVariantsTested, err := getMyDiseaseInfo()
			if (err != nil) { return nil, err }

			probabilityOffspringHasDiseaseIsKnown, probabilityOffspringHasDisease, probabilityOffspringHasVariantIsKnown, probabilityOffspringHasVariant, err := createCoupleGeneticAnalysis.GetOffspringMonogenicDiseaseProbabilities(diseaseIsDominantOrRecessive, userDiseaseInfoExists, userProbabilityOfPassingAVariant, myDiseaseInfoExists, myProbabilityOfPassingAVariant)
			if (err != nil) { return nil, err }

			getUserHasDiseaseString := func()string{
				if (userDiseaseInfoExists == false){
					return "Unknown"		
				}

				userHasDiseaseString := helpers.ConvertBoolToYesOrNoString(userHasDisease)

				return userHasDiseaseString
			}

			userHasDiseaseString := getUserHasDiseaseString()

			getUserProbabilityOfPassingAVariantString := func()string{
				if (userDiseaseInfoExists == false){
					return "Unknown"		
				}
				userProbabilityOfPassingAVariantString := helpers.ConvertIntToString(userProbabilityOfPassingAVariant)
				resultFormatted := userProbabilityOfPassingAVariantString + "%"
				return resultFormatted
			}

			userProbabilityOfPassingAVariantString := getUserProbabilityOfPassingAVariantString()

			getUserNumberOfVariantsTestedString := func()string{
				numberOfDiseaseVariantsString := helpers.ConvertIntToString(numberOfDiseaseVariants)
				if (userDiseaseInfoExists == false){
					result := "0/" + numberOfDiseaseVariantsString
					return result
				}
				userNumberOfVariantsTestedString := helpers.ConvertIntToString(userNumberOfVariantsTested)			

				resultFormatted := userNumberOfVariantsTestedString + "/" + numberOfDiseaseVariantsString
				return resultFormatted
			}

			userNumberOfVariantsTestedString := getUserNumberOfVariantsTestedString()

			getOffspringProbabilityOfHavingDiseaseString := func()string{

				if (probabilityOffspringHasDiseaseIsKnown == false){
					result := translate("Unknown")
					return result
				}
				offspringProbabilityOfHavingDiseaseString := helpers.ConvertIntToString(probabilityOffspringHasDisease)
				resultFormatted := offspringProbabilityOfHavingDiseaseString + "%"
				return resultFormatted
			}

			offspringProbabilityOfHavingDiseaseString := getOffspringProbabilityOfHavingDiseaseString()

			getOffspringProbabilityOfHavingAVariantFormatted := func()string{

				if (probabilityOffspringHasVariantIsKnown == false){
					result := translate("Unknown")
					return result
				}
				offspringProbabilityOfHavingAVariantString := helpers.ConvertIntToString(probabilityOffspringHasVariant)
				resultFormatted := offspringProbabilityOfHavingAVariantString + "%"

				return resultFormatted
			}

			offspringProbabilityOfHavingAVariantFormatted := getOffspringProbabilityOfHavingAVariantFormatted()

			totalNumberOfOffspringDiseaseVariantsString := helpers.ConvertIntToString(numberOfDiseaseVariants*2)

			offspringNumberOfVariantsTested := userNumberOfVariantsTested + myNumberOfVariantsTested

			offspringNumberOfVariantsTestedString := helpers.ConvertIntToString(offspringNumberOfVariantsTested)
			offspringNumberOfVariantsTestedFormatted := offspringNumberOfVariantsTestedString + "/" + totalNumberOfOffspringDiseaseVariantsString

			viewDiseaseInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
					setViewMonogenicDiseaseDetailsPage(window, monogenicDiseaseName, currentPage)
				})

			diseaseNameLabel := getBoldLabelCentered(monogenicDiseaseName)

			userHasDiseaseLabel := getBoldLabelCentered(userHasDiseaseString)
			userProbabilityOfPassingAVariantLabel := getBoldLabelCentered(userProbabilityOfPassingAVariantString)
			userNumberOfVariantsTestedLabel := getBoldLabelCentered(userNumberOfVariantsTestedString)

			offspringProbabilityOfHavingDiseaseLabel := getBoldLabelCentered(offspringProbabilityOfHavingDiseaseString)
			offspringProbabilityOfHavingAVariantLabel := getBoldLabelCentered(offspringProbabilityOfHavingAVariantFormatted)
			offspringNumberOfVariantsTestedLabel := getBoldLabelCentered(offspringNumberOfVariantsTestedFormatted)

			diseaseInfoButtonsColumn.Add(viewDiseaseInfoButton)
			diseaseNameColumn.Add(diseaseNameLabel)
			userHasDiseaseColumn.Add(userHasDiseaseLabel)
			userProbabilityOfPassingVariantColumn.Add(userProbabilityOfPassingAVariantLabel)
			userNumberOfVariantsTestedColumn.Add(userNumberOfVariantsTestedLabel)
			offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseLabel)
			offspringProbabilityOfHavingAVariantColumn.Add(offspringProbabilityOfHavingAVariantLabel)
			offspringNumberOfVariantsTestedColumn.Add(offspringNumberOfVariantsTestedLabel)

			diseaseInfoButtonsColumn.Add(widget.NewSeparator())
			diseaseNameColumn.Add(widget.NewSeparator())
			userHasDiseaseColumn.Add(widget.NewSeparator())
			userProbabilityOfPassingVariantColumn.Add(widget.NewSeparator())
			userNumberOfVariantsTestedColumn.Add(widget.NewSeparator())
			offspringProbabilityOfHavingDiseaseColumn.Add(widget.NewSeparator())
			offspringProbabilityOfHavingAVariantColumn.Add(widget.NewSeparator())
			offspringNumberOfVariantsTestedColumn.Add(widget.NewSeparator())
		}

		userHasDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonHasMonogenicDiseaseExplainerPage(window, currentPage)
			})
		probabilityOfPassingVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonProbabilityOfPassingVariantExplainerPage(window, currentPage)
			})
		numberOfVariantsTestedHelpButtonA := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setNumberOfTestedVariantsExplainerPage(window, currentPage)
			})
		numberOfVariantsTestedHelpButtonB := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setNumberOfTestedVariantsExplainerPage(window, currentPage)
			})
		probabilityOfHavingDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setOffspringProbabilityOfHavingMonogenicDiseaseExplainerPage(window, currentPage)
			})
		probabilityOfHavingAVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setOffspringProbabilityOfHavingVariantExplainerPage(window, currentPage)
			})

		userHasDiseaseColumn.Add(userHasDiseaseHelpButton)
		userProbabilityOfPassingVariantColumn.Add(probabilityOfPassingVariantHelpButton)
		userNumberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButtonA)
		offspringProbabilityOfHavingDiseaseColumn.Add(probabilityOfHavingDiseaseHelpButton)
		offspringProbabilityOfHavingAVariantColumn.Add(probabilityOfHavingAVariantHelpButton)
		offspringNumberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButtonB)

		if (userOrOffspring == "User"){

			diseasesInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseInfoButtonsColumn, diseaseNameColumn, userHasDiseaseColumn, userProbabilityOfPassingVariantColumn, userNumberOfVariantsTestedColumn, layout.NewSpacer())

			return diseasesInfoGrid, nil
		}

		diseasesInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseInfoButtonsColumn, diseaseNameColumn, offspringProbabilityOfHavingDiseaseColumn, offspringProbabilityOfHavingAVariantColumn, offspringNumberOfVariantsTestedColumn, layout.NewSpacer())

		return diseasesInfoGrid, nil
	}

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

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

	setPageContent(page, window)
}

func setViewMateProfilePage_PolygenicDiseases(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	if (userOrOffspring == "Offspring"){
		setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")
	}

	currentPage := func(){setViewMateProfilePage_PolygenicDiseases(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Polygenic Diseases"))

	description1 := getLabelCentered("Below is the polygenic disease risk analysis for this user.")
	description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
	description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.")

	handleSelectButton := func(newUserOrOffspring string){
			if (userOrOffspring == newUserOrOffspring){
				return
			}
			setViewMateProfilePage_PolygenicDiseases(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) 
		}

	userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
	userOrOffspringSelector.Selected = userOrOffspring	

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

	getDiseaseInfoGrid := func()(*fyne.Container, error){
	
		diseaseNameLabel := getItalicLabelCentered("Disease Name")

		userRiskScoreLabel := getItalicLabelCentered("User Risk Score")

		offspringRiskScoreLabel := getItalicLabelCentered("Offspring Risk Score")

		confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

		emptyLabel1 := widget.NewLabel("")

		emptyLabel2 := widget.NewLabel("")

		diseaseNameColumn := container.NewVBox(diseaseNameLabel, widget.NewSeparator())
		userRiskScoreColumn := container.NewVBox(userRiskScoreLabel, widget.NewSeparator())
		offspringRiskScoreColumn := container.NewVBox(offspringRiskScoreLabel, widget.NewSeparator())
		confidenceRangeColumn := container.NewVBox(confidenceRangeLabel, widget.NewSeparator())
		viewSampleOffspringsChartButtonsColumn := container.NewVBox(emptyLabel1, widget.NewSeparator())
		viewDiseaseInfoButtonsColumn := container.NewVBox(emptyLabel2, widget.NewSeparator())

		//Outputs:
		//	-map[int64]locusValue.LocusValue
		//	-error
		getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){

			if (userOrOffspring == "User"){
				// We don't need to retrieve our locus values map
				emptyMap := make(map[int64]locusValue.LocusValue)
				return emptyMap, nil
			}

			myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
			if (err != nil) { return nil, err }

			if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
				emptyMap := make(map[int64]locusValue.LocusValue)
				return emptyMap, nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
			if (err != nil) { return nil, err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.")
			}

			return myGenomeLocusValuesMap, nil
		}

		myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap()
		if (err != nil) { return nil, err }

		getConfidenceRangeLabel := func(analysisExists bool, predictionConfidenceRangesMap map[int]float64)(fyne.Widget, error){
			if (analysisExists == false){

				result := widget.NewLabel("Unknown")
				return result, nil
			}

			// This is a list of the percentage accuracies in the map
			// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
			//		accurate within that range
			confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)

			// We sort the list so the percentage is always the same upon refreshing the page
			slices.Sort(confidenceRangePercentagesList)

			closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
			if (err != nil) { return nil, err }

			closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
			if (exists == false){
				return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
			}

			closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)

			closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)

			confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "%)"

			confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)

			return confidenceRangeLabel, nil
		}

		diseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
		if (err != nil) { return nil, err }

		for _, diseaseObject := range diseaseObjectsList{

			diseaseName := diseaseObject.DiseaseName
			diseaseLociList := diseaseObject.LociList

			predictionModelExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName)
			if (predictionModelExists == false){
				// Prediction is not possible for this disease
				continue
			}

			diseaseNameLabel := getBoldLabelCentered(diseaseName)

			userDiseaseLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getAnyUserProfileAttributeFunction)
			if (err != nil) { return nil, err }

			if (userOrOffspring == "User"){

				neuralNetworkExists, analysisExists, userDiseaseRiskScore, predictionConfidenceRangesMap, _, _, err := createPersonGeneticAnalysis.GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject, userDiseaseLocusValuesMap, true)
				if (err != nil) { return nil, err }
				if (neuralNetworkExists == false){
					return nil, errors.New("GetPersonGenomePolygenicDiseaseAnalysis claims model doesn't exist when we already checked.")
				}

				getUserDiseaseRiskScoreLabel := func()fyne.Widget{

					if (analysisExists == false){

						result := widget.NewLabel(translate("Unknown"))

						return result
					}

					userRiskScoreString := helpers.ConvertIntToString(userDiseaseRiskScore)
					riskScoreFormatted := userRiskScoreString + "/10"

					riskScoreLabel := getBoldLabel(riskScoreFormatted)

					return riskScoreLabel
				}

				userDiseaseRiskScoreLabel := getUserDiseaseRiskScoreLabel()
				userDiseaseRiskScoreLabelCentered := getWidgetCentered(userDiseaseRiskScoreLabel)
				userRiskScoreColumn.Add(userDiseaseRiskScoreLabelCentered)

				confidenceRangeLabel, err := getConfidenceRangeLabel(analysisExists, predictionConfidenceRangesMap)
				if (err != nil) { return nil, err }

				confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

				confidenceRangeColumn.Add(confidenceRangeLabelCentered)

			} else if (userOrOffspring == "Offspring"){

				neuralNetworkExists, anyOffspringLociKnown, offspringDiseaseRiskScore, predictionConfidenceRangesMap, offspringSampleRiskScoresList, offspringQuantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringPolygenicDiseaseAnalysis(diseaseObject, myGenomeLocusValuesMap, userDiseaseLocusValuesMap)
				if (err != nil) { return nil, err }
				if (neuralNetworkExists == false){
					return nil, errors.New("GetOffspringPolygenicDiseaseAnalysis claims that neural network doesn't exist when we already checked.")
				}

				getOffspringDiseaseRiskScoreLabel := func()fyne.Widget{

					if (anyOffspringLociKnown == false){

						result := widget.NewLabel(translate("Unknown"))
		
						return result
					}

					offspringRiskScoreString := helpers.ConvertIntToString(offspringDiseaseRiskScore)
					riskScoreFormatted := offspringRiskScoreString + "/10"

					riskScoreLabel := getBoldLabel(riskScoreFormatted)

					return riskScoreLabel
				}

				offspringDiseaseRiskScoreLabel := getOffspringDiseaseRiskScoreLabel()
				offspringDiseaseRiskScoreLabelCentered := getWidgetCentered(offspringDiseaseRiskScoreLabel)

				offspringRiskScoreColumn.Add(offspringDiseaseRiskScoreLabelCentered)

				confidenceRangeLabel, err := getConfidenceRangeLabel(anyOffspringLociKnown, predictionConfidenceRangesMap)
				if (err != nil) { return nil, err }

				confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

				confidenceRangeColumn.Add(confidenceRangeLabelCentered)

				viewSampleOffspringsChartButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){

						setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window, diseaseName, offspringSampleRiskScoresList, offspringQuantityOfLociKnown, currentPage)
					})

				viewSampleOffspringsChartButtonsColumn.Add(viewSampleOffspringsChartButton)
			}

			viewDiseaseDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					//TODO
					showUnderConstructionDialog(window)
				})

			diseaseNameColumn.Add(diseaseNameLabel)

			viewDiseaseInfoButtonsColumn.Add(viewDiseaseDetailsButton)

			diseaseNameColumn.Add(widget.NewSeparator())
			userRiskScoreColumn.Add(widget.NewSeparator())
			offspringRiskScoreColumn.Add(widget.NewSeparator())
			confidenceRangeColumn.Add(widget.NewSeparator())
			viewSampleOffspringsChartButtonsColumn.Add(widget.NewSeparator())
			viewDiseaseInfoButtonsColumn.Add(widget.NewSeparator())
		}

		userRiskScoreHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPolygenicDiseaseRiskScoreExplainerPage(window, currentPage)
			})
		userRiskScoreColumn.Add(userRiskScoreHelpButton)

		offspringRiskScoreHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setOffspringPolygenicDiseaseRiskScoreExplainerPage(window, currentPage)
			})
		offspringRiskScoreColumn.Add(offspringRiskScoreHelpButton)

		confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				showUnderConstructionDialog(window)
				//TODO
			})		

		confidenceRangeColumn.Add(confidenceRangeHelpButton)

		if (userOrOffspring == "User"){
			diseaseInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, userRiskScoreColumn, confidenceRangeColumn, viewDiseaseInfoButtonsColumn, layout.NewSpacer())

			return diseaseInfoGrid, nil
		}

		diseaseInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, offspringRiskScoreColumn, confidenceRangeColumn, viewSampleOffspringsChartButtonsColumn, viewDiseaseInfoButtonsColumn, layout.NewSpacer())

		return diseaseInfoGrid, nil
	}

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

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

	setPageContent(page, window)
}


func setViewMateProfilePage_GeneticTraits(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	currentPage := func(){setViewMateProfilePage_GeneticTraits(window, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Genetic Traits"))

	description1 := getLabelCentered("Choose if you want to view Discrete traits or Numeric traits.")
	description2 := getLabelCentered("Discrete traits are traits which have discrete outcomes, such as Eye Color")
	description3 := getLabelCentered("Numeric traits are traits with numeric outcomes, such as Height.") 

	discreteTraitsButton := widget.NewButton("Discrete Traits", func(){
			setViewMateProfilePage_DiscreteGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
		})

	numericTraitsButton := widget.NewButton("Numeric Traits", func(){
			setViewMateProfilePage_NumericGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton))

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

	setPageContent(page, window)
}

func setViewMateProfilePage_DiscreteGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	if (userOrOffspring != "User" && userOrOffspring != "Offspring"){
		setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_DiscreteGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage)
		return
	}

	if (userOrOffspring == "Offspring"){
		setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")
	}

	currentPage := func(){setViewMateProfilePage_DiscreteGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Discrete Genetic Traits"))

	description1 := getLabelCentered("Below is the discrete genetic trait analysis for this user.")
	description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
	description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.")

	handleSelectButton := func(newUserOrOffspring string){
			if (userOrOffspring == newUserOrOffspring){
				return
			}
			setViewMateProfilePage_DiscreteGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) 
		}

	userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
	userOrOffspringSelector.Selected = userOrOffspring

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

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

		myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
		if (err != nil) { return nil, err }

		//Outputs:
		//	-map[int64]locusValue.LocusValue
		//	-error
		getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){

			if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
				emptyMap := make(map[int64]locusValue.LocusValue)
				return emptyMap, nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
			if (err != nil) { return nil, err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.")
			}

			return myGenomeLocusValuesMap, nil
		}

		myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap()
		if (err != nil) { return nil, err }


		emptyLabel1 := widget.NewLabel("")
		traitNameLabel := getItalicLabelCentered("Trait Name")

		emptyLabel2 := widget.NewLabel("")
		userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome")

		emptyLabel3 := widget.NewLabel("")
		offspringOutcomeProbabilitiesLabel := getItalicLabelCentered("Offspring Outcome Probabilities")

		quantityOfLabel1 := getItalicLabelCentered("Quantity Of")
		lociKnownLabel := getItalicLabelCentered("Loci Known")

		emptyLabel4 := widget.NewLabel("")
		emptyLabel5 := widget.NewLabel("")

		traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
		userPredictedOutcomeColumn := container.NewVBox(emptyLabel2, userPredictedOutcomeTitle, widget.NewSeparator())

		offspringOutcomeProbabilitiesColumn := container.NewVBox(emptyLabel3, offspringOutcomeProbabilitiesLabel, widget.NewSeparator())

		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator())
		viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())

		traitObjectsList, err := traits.GetTraitObjectsList()
		if (err != nil) { return nil, err }

		for _, traitObject := range traitObjectsList{

			traitName := traitObject.TraitName
			traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
			if (traitIsDiscreteOrNumeric != "Discrete"){
				continue
			}

			traitRulesList := traitObject.RulesList
			totalNumberOfTraitRules := len(traitRulesList)

			traitLociList := traitObject.LociList

			numberOfTraitLoci := len(traitLociList)

			if (totalNumberOfTraitRules == 0 && numberOfTraitLoci == 0){
				// We are not able to analyze these traits yet
				continue
			}

			traitNeuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)
			if (traitNeuralNetworkExists == false && totalNumberOfTraitRules == 0){
				// We are not able to analyze these traits yet
				continue
			}

			traitNameText := getBoldLabelCentered(translate(traitName))
			traitNameColumn.Add(traitNameText)

			viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					if (traitNeuralNetworkExists == true){
						//TODO
						showUnderConstructionDialog(window)
					} else {
						setViewMateProfilePage_DiscreteTraitRules(window, traitName, userOrOffspring, getAnyUserProfileAttributeFunction, currentPage)
					}
				})

			viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton)

			// We construct the user's trait locus values map

			userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList, getAnyUserProfileAttributeFunction)
			if (err != nil) { return nil, err }

			if (userOrOffspring == "User"){

				if (traitNeuralNetworkExists == true){

					traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, _, quantityOfLociKnown, _, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_NeuralNetwork(traitObject, userTraitLocusValuesMap, true)
					if (err != nil) { return nil, err }
					if (traitNeuralNetworkExists == false){
						return nil, errors.New("GetGenomeTraitAnalysis_NeuralNetwork claims neural network doesn't exist for trait, but we already checked.")
					}
					if (anyLocusValuesAreKnown == false){
						unknownLabel := getItalicLabelCentered(translate("Unknown"))
						userPredictedOutcomeColumn.Add(unknownLabel)
					} else {
						predictedOutcomeLabel := getBoldLabelCentered(predictedOutcome)
						userPredictedOutcomeColumn.Add(predictedOutcomeLabel)
					}

					totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci)

					quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)

					quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString

					quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

					quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)

				} else {

					// We use the rules-based analysis

					anyRulesExist, _, quantityOfLociKnown_Rules, _, predictedOutcomeIsKnown, predictedOutcome, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_Rules(traitObject, userTraitLocusValuesMap, true)
					if (err != nil) { return nil, err }
					if (anyRulesExist == false){
						return nil, errors.New("GetGenomeTraitAnalysis_Rules claims no rules exist when we already checked.")
					}

					if (predictedOutcomeIsKnown == false){
						unknownLabel := getItalicLabelCentered("Unknown")
						userPredictedOutcomeColumn.Add(unknownLabel)
					} else {
						predictedOutcomeLabel := getBoldLabelCentered(predictedOutcome)
						userPredictedOutcomeColumn.Add(predictedOutcomeLabel)
					}

					traitLociList_Rules := traitObject.LociList_Rules

					totalQuantityOfLoci := len(traitLociList_Rules)

					quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown_Rules)
					totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)

					quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

					quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

					quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
				}
			} else {

				// userOrOffspring == "Offspring"

				if (traitNeuralNetworkExists == true){

					neuralNetworkExists, anyLociKnown, outcomeProbabilitiesMap, _, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_NeuralNetwork(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap)
					if (err != nil) { return nil, err }
					if (neuralNetworkExists == false){
						return nil, errors.New("GetOffspringDiscreteTraitAnalysis_NeuralNetwork claiming that neural network doesn't exist when we already checked.")
					}

					totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci)

					quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)

					quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString

					quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

					quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)

					if (anyLociKnown == false){
						unknownLabel := getItalicLabelCentered("Unknown")

						offspringOutcomeProbabilitiesColumn.Add(unknownLabel)

					} else {

						outcomesList := helpers.GetListOfMapKeys(outcomeProbabilitiesMap)

						// We sort the outcomes in alphabetical order so they show up the same way each time
						slices.Sort(outcomesList)

						quantityOfAddedItems := 0

						for _, outcomeName := range outcomesList{

							outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
							if (exists == false){
								return nil, errors.New("GetListOfMapKeys returning element which doesn't exist in map.")
							}
							if (outcomeProbability == 0){
								continue
							}

							outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)
							outcomeProbabilityLabelText := outcomeName + ": " + outcomeProbabilityString + "%"

							outcomeProbabilityLabel := getBoldLabelCentered(outcomeProbabilityLabelText)

							offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilityLabel)

							quantityOfAddedItems += 1

							if (quantityOfAddedItems > 1){
								// We add whitespace for the other columns
								traitNameColumn.Add(widget.NewLabel(""))
								quantityOfLociKnownColumn.Add(widget.NewLabel(""))
								viewTraitDetailsButtonsColumn.Add(widget.NewLabel(""))
							}
						}
					}
				} else {

					// We use the rules-based analysis

					anyRulesExist, rulesAnalysisExists, _, offspringQuantityOfLociKnown, _, outcomeProbabilitiesMap, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_Rules(traitObject, myGenomeLocusValuesMap, userTraitLocusValuesMap)
					if (err != nil) { return nil, err }
					if (anyRulesExist == false){
						return nil, errors.New("GetOffspringDiscreteTraitAnalysis_Rules claiming that no rules exist when we already checked.")
					}

					lociList_Rules := traitObject.LociList_Rules

					totalQuantityOfLoci := len(lociList_Rules)

					quantityOfLociKnownString := helpers.ConvertIntToString(offspringQuantityOfLociKnown)
					totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)

					quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
					quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
					quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)

					if (rulesAnalysisExists == false){
						unknownLabel := getItalicLabelCentered(translate("Unknown"))

						offspringOutcomeProbabilitiesColumn.Add(unknownLabel)
					} else {

						outcomesList := helpers.GetListOfMapKeys(outcomeProbabilitiesMap)

						// We sort the outcomes in alphabetical order so they show up the same way each time
						slices.Sort(outcomesList)

						quantityOfAddedItems := 0

						for _, outcomeName := range outcomesList{

							outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
							if (exists == false){
								return nil, errors.New("GetListOfMapKeys returning element which doesn't exist in map.")
							}

							if (outcomeProbability == 0){
								continue
							}

							outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)
							outcomeProbabilityLabelText := outcomeName + ": " + outcomeProbabilityString + "%"

							outcomeProbabilityLabel := getBoldLabelCentered(outcomeProbabilityLabelText)

							offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilityLabel)

							quantityOfAddedItems += 1

							if (quantityOfAddedItems > 1){
								// We add whitespace for the other columns
								traitNameColumn.Add(widget.NewLabel(""))
								quantityOfLociKnownColumn.Add(widget.NewLabel(""))
								viewTraitDetailsButtonsColumn.Add(widget.NewLabel(""))
							}
						}
					}
				}
			}

			traitNameColumn.Add(widget.NewSeparator())
			userPredictedOutcomeColumn.Add(widget.NewSeparator())
			offspringOutcomeProbabilitiesColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			viewTraitDetailsButtonsColumn.Add(widget.NewSeparator())
		}

		if (userOrOffspring == "User"){

			predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){

					//TODO
					showUnderConstructionDialog(window)
				})

			userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)

		} else {
			// userOrOffspring == "Offspring"

			outcomeProbabilitiesHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){

					//TODO
					showUnderConstructionDialog(window)
				})

			offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilitiesHelpButton)
		}

		quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			})

		quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)

		traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn)

		if (userOrOffspring == "User"){
			
			traitsInfoGrid.Add(userPredictedOutcomeColumn)

		} else {

			// userOrOffspring == "Offspring"

			traitsInfoGrid.Add(offspringOutcomeProbabilitiesColumn)
		}

		traitsInfoGrid.Add(quantityOfLociKnownColumn)
		traitsInfoGrid.Add(viewTraitDetailsButtonsColumn)
		traitsInfoGrid.Add(layout.NewSpacer())

		return traitsInfoGrid, nil
	}

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

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

	setPageContent(page, window)
}

func setViewMateProfilePage_DiscreteTraitRules(window fyne.Window, traitName string, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	currentPage := func(){setViewMateProfilePage_DiscreteTraitRules(window, traitName, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered("View Profile - Physical")

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(userOrOffspring + " Trait Rules")

	traitNameLabel := widget.NewLabel("Trait Name:")

	traitNameText := getBoldLabel(traitName)

	viewTraitInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewTraitDetailsPage(window, traitName, currentPage)
		})

	traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, viewTraitInfoButton, layout.NewSpacer())

	//Outputs:
	//	-bool: Any trait locus value exists for this myself
	//	-map[int64]locusValue.LocusValue: My locus values map
	//	-error
	getMyGenomeLocusValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){

		myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
		if (err != nil) { return false, nil, err }

		if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
			// Without my genome person chosen, all offspring rule probabilities are unknown
			return false, nil, nil
		}

		_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
		if (err != nil){ return false, nil, err }

		myGenomeMap, exists := myGenomesMap[myGenomeIdentifier]
		if (exists == false){
			return false, nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which does not contain genome matching myGenomeIdentifier")
		}

		return true, myGenomeMap, nil
	}

	anyMyLocusValuesExist, myLocusValuesMap, err := getMyGenomeLocusValuesMap()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	traitObject, err := traits.GetTraitObject(traitName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	traitLociList_Rules := traitObject.LociList_Rules
	traitRulesList := traitObject.RulesList

	if (len(traitRulesList) == 0){
		setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_DiscreteTraitRules called with trait which has no rules."), previousPage)
		return
	}

	//Outputs:
	//	-bool: Any trait locus value exists for this user
	//	-map[int64]locusValue.LocusValue: User locus values map
	//	-error
	getUserTraitLocusValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){

		// We construct the user's trait locus values map

		userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList_Rules, getAnyUserProfileAttributeFunction)
		if (err != nil) { return false, nil, err }

		if (len(userTraitLocusValuesMap) == 0){
			return false, nil, nil
		}

		return true, userTraitLocusValuesMap, nil
	}

	anyUserTraitLocusValueExists, userTraitLocusValuesMap, err := getUserTraitLocusValuesMap()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	//Outputs:
	//	-bool: Any offspring probability of passing rule is known
	//	-map[[3]byte]int: Offspring probability of passing rules map
	//		Map Structure: Rule Identifier -> Probability offspring will pass rule (0-100%)
	//	-error
	getOffspringProbabilityOfPassingRulesMap := func()(bool, map[[3]byte]int, error){

		if (anyMyLocusValuesExist == false || anyUserTraitLocusValueExists == false){
			return false, nil, nil
		}

		anyRulesExist, anyOffspringRulesTested, _, _, offspringProbabilityOfPassingRulesMap, _, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_Rules(traitObject, myLocusValuesMap, userTraitLocusValuesMap)
		if (err != nil) { return false, nil, err }
		if (anyRulesExist == false){
			return false, nil, errors.New("GetOffspringDiscreteTraitAnalysis_Rules claiming no trait rules exist when we already checked.")
		}
		if (anyOffspringRulesTested == false){
			return false, nil, nil
		}

		return true, offspringProbabilityOfPassingRulesMap, nil
	}

	anyOffspringProbabilityOfPassingRuleIsKnown, offspringProbabilityOfPassingRulesMap, err := getOffspringProbabilityOfPassingRulesMap()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

		if (userOrOffspring == "Offspring"){
			if (anyOffspringProbabilityOfPassingRuleIsKnown == false){
				return 0, nil
			}

			numberOfRulesTested := len(offspringProbabilityOfPassingRulesMap)

			return numberOfRulesTested, nil
		}

		if (anyUserTraitLocusValueExists == false){
			return 0, nil
		}
	
		numberOfRulesTested := 0

		for _, ruleObject := range traitRulesList{

			ruleLociList := ruleObject.LociList

			ruleStatusIsKnown, _, err := createPersonGeneticAnalysis.GetGenomePassesDiscreteTraitRuleStatus(ruleLociList, userTraitLocusValuesMap, true)
			if (err != nil) { return 0, err }
			if (ruleStatusIsKnown == true){
				numberOfRulesTested += 1
			}
		}

		return numberOfRulesTested, nil	
	}

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

	rulesTestedLabel := widget.NewLabel("Rules Tested:")

	totalNumberOfTraitRules := len(traitRulesList)
	numberOfRulesTestedString := helpers.ConvertIntToString(numberOfRulesTested)
	totalNumberOfTraitRulesString := helpers.ConvertIntToString(totalNumberOfTraitRules)

	numberOfRulesTestedFormatted := numberOfRulesTestedString + "/" + totalNumberOfTraitRulesString
	numberOfRulesTestedLabel := getBoldLabel(numberOfRulesTestedFormatted)

	numberOfRulesTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			if (userOrOffspring == "User"){
				setDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage)
			} else {
				setOffspringDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage)
			}
		})

	numberOfRulesTestedRow := container.NewHBox(layout.NewSpacer(), rulesTestedLabel, numberOfRulesTestedLabel, numberOfRulesTestedHelpButton, layout.NewSpacer())

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

		//TODO: Sort results

		viewRuleInfoButtonsColumn := container.NewVBox()
		ruleIdentifierColumn := container.NewVBox()
		ruleEffectsColumn := container.NewVBox()
		userPassesRuleColumn := container.NewVBox()
		offspringProbabilityOfPassingRuleColumn := container.NewVBox()

		if (userOrOffspring == "Offspring"){

			// We need this because the header row is 2 rows tall when userOrOffspring == "Offspring"
			// Otherwise, it is 1 row tall

			emptyLabelA := widget.NewLabel("")
			emptyLabelB := widget.NewLabel("")
			emptyLabelC := widget.NewLabel("")
			emptyLabelD := widget.NewLabel("")

			viewRuleInfoButtonsColumn.Add(emptyLabelA)
			ruleIdentifierColumn.Add(emptyLabelB)
			ruleEffectsColumn.Add(emptyLabelC)
			userPassesRuleColumn.Add(emptyLabelD)
		}

		offspringProbabilityOfTitle := getItalicLabelCentered("Offspring Probability Of")
		offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfTitle)

		emptyLabelE := widget.NewLabel("")		
		ruleIdentifierTitle := getItalicLabelCentered("Rule Identifier")
		ruleEffectsTitle := getItalicLabelCentered("Rule Effects")
		userPassesRuleTitle := getItalicLabelCentered("User Passes Rule")
		passingRuleTitle := getItalicLabelCentered("Passing Rule")

		viewRuleInfoButtonsColumn.Add(emptyLabelE)
		ruleIdentifierColumn.Add(ruleIdentifierTitle)
		ruleEffectsColumn.Add(ruleEffectsTitle)
		userPassesRuleColumn.Add(userPassesRuleTitle)
		offspringProbabilityOfPassingRuleColumn.Add(passingRuleTitle)

		viewRuleInfoButtonsColumn.Add(widget.NewSeparator())
		ruleIdentifierColumn.Add(widget.NewSeparator())
		ruleEffectsColumn.Add(widget.NewSeparator())
		userPassesRuleColumn.Add(widget.NewSeparator())
		offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator())

		for _, ruleObject := range traitRulesList{

			ruleIdentifierHex := ruleObject.RuleIdentifier

			ruleIdentifier, err := encoding.DecodeHexStringTo3ByteArray(ruleIdentifierHex)
			if (err != nil) { return nil, err }

			ruleLociList := ruleObject.LociList

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

				userRuleStatusIsKnown, userPassesRule, err := createPersonGeneticAnalysis.GetGenomePassesDiscreteTraitRuleStatus(ruleLociList, userTraitLocusValuesMap, true)
				if (err != nil) { return "", err }

				if (userRuleStatusIsKnown == false){
					result := translate("Unknown")
					return result, nil
				}
				userPassesRuleString := helpers.ConvertBoolToYesOrNoString(userPassesRule)
				userPassesRuleTranslated := translate(userPassesRuleString)
				return userPassesRuleTranslated, nil
			}

			userPassesRuleString, err := getUserPassesRuleString()
			if (err != nil) { return nil, err }		

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

				if (anyOffspringProbabilityOfPassingRuleIsKnown == false){
					result := translate("Unknown")
					return result, nil
				}

				probabilityOfPassingRule, probabilityIsKnown := offspringProbabilityOfPassingRulesMap[ruleIdentifier]	
				if (probabilityIsKnown == false){
					result := translate("Unknown")
					return result, nil
				}

				ruleProbabilityString := helpers.ConvertIntToString(probabilityOfPassingRule)

				ruleProbabilityFormatted := ruleProbabilityString + "%"
				return ruleProbabilityFormatted, nil
			}

			offspringProbabilityOfPassingRuleString, err := getOffspringProbabilityOfPassingRuleString()
			if (err != nil) { return nil, err }

			// We add all of the columns except for the rule effects column
			// We do this because the rule effects column may be multiple rows tall

			viewRuleInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
					setViewDiscreteTraitRuleDetailsPage(window, traitName, ruleIdentifierHex, currentPage)
				})
			ruleIdentifierLabel := getBoldLabelCentered(ruleIdentifierHex)
			userPassesRuleLabel := getBoldLabelCentered(userPassesRuleString)
			offspringProbabilityOfPassingRuleLabel := getBoldLabelCentered(offspringProbabilityOfPassingRuleString)

			viewRuleInfoButtonsColumn.Add(viewRuleInfoButton)
			ruleIdentifierColumn.Add(ruleIdentifierLabel)
			userPassesRuleColumn.Add(userPassesRuleLabel)
			offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfPassingRuleLabel)

			traitOutcomesList := traitObject.OutcomesList
			ruleOutcomePointsMap := ruleObject.OutcomePointsMap

			// We have to sort the outcome names so they always show up in the same order

			outcomeNamesListSorted := helpers.CopyAndSortStringListToUnicodeOrder(traitOutcomesList)

			addedOutcomes := 0
			for _, outcomeName := range outcomeNamesListSorted{

				outcomeChange, exists := ruleOutcomePointsMap[outcomeName]
				if (exists == false){
					// This rule does not effect this outcome. Skip
					continue
				}

				getOutcomeEffectString := func()string{

					outcomeChangeString := helpers.ConvertIntToString(outcomeChange)					

					if (outcomeChange < 0){
						return outcomeChangeString
					}
					outcomeEffect := "+" + outcomeChangeString
					return outcomeEffect				
				}

				outcomeEffect := getOutcomeEffectString()

				outcomeRow := getBoldLabelCentered(outcomeName + ": " + outcomeEffect)
				ruleEffectsColumn.Add(outcomeRow)

				if (addedOutcomes > 0){
				
					emptyLabelA := widget.NewLabel("")
					emptyLabelB := widget.NewLabel("")
					emptyLabelC := widget.NewLabel("")
					emptyLabelD := widget.NewLabel("")

					viewRuleInfoButtonsColumn.Add(emptyLabelA)
					ruleIdentifierColumn.Add(emptyLabelB)
					userPassesRuleColumn.Add(emptyLabelC)
					offspringProbabilityOfPassingRuleColumn.Add(emptyLabelD)
				}
				addedOutcomes += 1
			}

			viewRuleInfoButtonsColumn.Add(widget.NewSeparator())
			ruleIdentifierColumn.Add(widget.NewSeparator())
			ruleEffectsColumn.Add(widget.NewSeparator())
			userPassesRuleColumn.Add(widget.NewSeparator())
			offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator())
		}

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

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

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

		ruleEffectsColumn.Add(ruleEffectsHelpButton)
		userPassesRuleColumn.Add(userPassesRuleHelpButton)
		offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfPassingRuleHelpButton)

		if (userOrOffspring == "User"){
			rulesGrid := container.NewHBox(layout.NewSpacer(), viewRuleInfoButtonsColumn, ruleIdentifierColumn, ruleEffectsColumn, userPassesRuleColumn, layout.NewSpacer())
			return rulesGrid, nil
		}
		rulesGrid := container.NewHBox(layout.NewSpacer(), viewRuleInfoButtonsColumn, ruleIdentifierColumn, ruleEffectsColumn, offspringProbabilityOfPassingRuleColumn, layout.NewSpacer())
		return rulesGrid, nil
	}

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), traitNameRow, numberOfRulesTestedRow, widget.NewSeparator(), rulesGrid)

	setPageContent(page, window)
}


func setViewMateProfilePage_NumericGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	if (userOrOffspring != "User" && userOrOffspring != "Offspring"){
		setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_NumericGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage)
		return
	}

	if (userOrOffspring == "Offspring"){
		setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")
	}

	currentPage := func(){setViewMateProfilePage_NumericGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}

	title := getPageTitleCentered(translate("View Profile - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Numeric Genetic Traits"))

	description1 := getLabelCentered("Below is the numeric genetic trait analysis for this user.")
	description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
	description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.")

	handleSelectButton := func(newUserOrOffspring string){
			if (userOrOffspring == newUserOrOffspring){
				return
			}
			setViewMateProfilePage_NumericGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) 
		}

	userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
	userOrOffspringSelector.Selected = userOrOffspring

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

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

		//Outputs:
		//	-map[int64]locusValue.LocusValue
		//	-error
		getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){

			if (userOrOffspring == "User"){
				// We don't need to retrieve our locus values map
				emptyMap := make(map[int64]locusValue.LocusValue)
				return emptyMap, nil
			}

			myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
			if (err != nil) { return nil, err }

			if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
				emptyMap := make(map[int64]locusValue.LocusValue)
				return emptyMap, nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
			if (err != nil) { return nil, err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.")
			}

			return myGenomeLocusValuesMap, nil
		}

		myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap()
		if (err != nil) { return nil, err }


		traitNameLabel := getItalicLabelCentered("Trait Name")

		userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome")

		offspringPredictedOutcomeTitle := getItalicLabelCentered("Offspring Predicted Outcome")

		confidenceRangeTitle := getItalicLabelCentered("Confidence Range")

		emptyLabel1 := widget.NewLabel("")

		emptyLabel2 := widget.NewLabel("")


		traitNameColumn := container.NewVBox(traitNameLabel, widget.NewSeparator())

		userPredictedOutcomeColumn := container.NewVBox(userPredictedOutcomeTitle, widget.NewSeparator())
		offspringPredictedOutcomeColumn := container.NewVBox(offspringPredictedOutcomeTitle, widget.NewSeparator())

		predictionConfidenceRangeColumn := container.NewVBox(confidenceRangeTitle, widget.NewSeparator())
		viewSampleOffspringsButtonsColumn := container.NewVBox(emptyLabel1, widget.NewSeparator())
		viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel2, widget.NewSeparator())

		getConfidenceRangeLabel := func(traitObject traits.Trait, analysisExists bool, predictionConfidenceRangesMap map[int]float64)(fyne.Widget, error){
			if (analysisExists == false){

				result := widget.NewLabel("Unknown")
				return result, nil
			}

			// This is a list of the percentage accuracies in the map
			// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
			//		accurate within that range
			confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)

			// We sort the list so the percentage is always the same upon refreshing the page
			slices.Sort(confidenceRangePercentagesList)

			closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
			if (err != nil) { return nil, err }

			closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
			if (exists == false){
				return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
			}

			outcomeFormatter := traitObject.NumericValueFormatter

			closestConfidenceDistanceString, err := outcomeFormatter(closestToEightyPercentageConfidenceDistance, false)
			if (err != nil) { return nil, err } 

			closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)

			confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "%)"

			confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)

			return confidenceRangeLabel, nil
		}

		traitObjectsList, err := traits.GetTraitObjectsList()
		if (err != nil) { return nil, err }

		for _, traitObject := range traitObjectsList{

			traitName := traitObject.TraitName

			traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
			if (traitIsDiscreteOrNumeric != "Numeric"){
				continue
			}

			traitLociList := traitObject.LociList

			numberOfTraitLoci := len(traitLociList)

			if (numberOfTraitLoci == 0){
				// We are not able to analyze these traits
				continue
			}

			traitNeuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)
			if (traitNeuralNetworkExists == false){
				// We are not able to analyze these traits yet
				continue
			}

			traitNameText := getBoldLabelCentered(translate(traitName))

			// We construct the user's trait locus values map

			userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList, getAnyUserProfileAttributeFunction)
			if (err != nil) { return nil, err }

			getPredictedOutcomeLabel := func(analysisExists bool, predictedOutcome float64)(fyne.Widget, error){

				if (analysisExists == false){
					unknownLabel := widget.NewLabel("Unknown")
					return unknownLabel, nil
				}

				outcomeFormatter := traitObject.NumericValueFormatter

				predictedOutcomeFormatted, err := outcomeFormatter(predictedOutcome, true)
				if (err != nil) { return nil, err }

				predictedOutcomeLabel := getBoldLabel(predictedOutcomeFormatted)

				return predictedOutcomeLabel, nil
			}

			if (userOrOffspring == "User"){

				traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, _, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, true)
				if (err != nil) { return nil, err }
				if (traitNeuralNetworkExists == false){
					return nil, errors.New("GetGenomeNumericTraitAnalysis claims neural network doesn't exist for trait, but we already checked.")
				}

				predictedOutcomeLabel, err := getPredictedOutcomeLabel(anyLocusValuesAreKnown, predictedOutcome)
				if (err != nil) { return nil, err }
				predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)

				userPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)

				confidenceRangeLabel, err := getConfidenceRangeLabel(traitObject, anyLocusValuesAreKnown, predictionAccuracyRangesMap)
				if (err != nil) { return nil, err }

				confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

				predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)

			} else {

				// userOrOffspring == "Offspring"

				neuralNetworkExists, anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, sampleOffspringOutcomesList, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap)
				if (err != nil) { return nil, err }
				if (neuralNetworkExists == false){
					return nil, errors.New("GetOffspringNumericTraitAnalysis claiming that neural network doesn't exist when we already checked.")
				}

				predictedOutcomeLabel, err := getPredictedOutcomeLabel(anyLociKnown, predictedOutcome)
				if (err != nil) { return nil, err }
				predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)

				offspringPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)

				confidenceRangeLabel, err := getConfidenceRangeLabel(traitObject, anyLociKnown, predictionAccuracyRangesMap)
				if (err != nil) { return nil, err }

				confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

				predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)

				viewSampleOffspringsButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
						setViewNumericTraitSampleOffspringOutcomesChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, currentPage)
					})

				viewSampleOffspringsButtonsColumn.Add(viewSampleOffspringsButton)
			}

			viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					//TODO
					showUnderConstructionDialog(window)
				})

			traitNameColumn.Add(traitNameText)

			viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton)

			traitNameColumn.Add(widget.NewSeparator())
			userPredictedOutcomeColumn.Add(widget.NewSeparator())
			offspringPredictedOutcomeColumn.Add(widget.NewSeparator())
			predictionConfidenceRangeColumn.Add(widget.NewSeparator())
			viewSampleOffspringsButtonsColumn.Add(widget.NewSeparator())
			viewTraitDetailsButtonsColumn.Add(widget.NewSeparator())
		}

		predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){

				//TODO
				showUnderConstructionDialog(window)
			})

		predictionConfidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			})

		predictionConfidenceRangeColumn.Add(predictionConfidenceRangeHelpButton)

		traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn)

		if (userOrOffspring == "User"){
			userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
			traitsInfoGrid.Add(userPredictedOutcomeColumn)
		} else {
			// userOrOffspring == "Offspring"
			offspringPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
			traitsInfoGrid.Add(offspringPredictedOutcomeColumn)
		}

		traitsInfoGrid.Add(predictionConfidenceRangeColumn)

		if (userOrOffspring == "Offspring"){
			traitsInfoGrid.Add(viewSampleOffspringsButtonsColumn)
		}

		traitsInfoGrid.Add(viewTraitDetailsButtonsColumn)
		traitsInfoGrid.Add(layout.NewSpacer())

		return traitsInfoGrid, nil
	}

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

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

	setPageContent(page, window)
}

func setViewMateProfilePage_Diet(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - Lifestyle"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Diet")

	description := getLabelCentered("Below are the user's food ratings.")
	
	foodNameTitle := getItalicLabelCentered("Food Name")

	ratingTitle := getItalicLabelCentered("Rating")

	foodNameColumn := container.NewVBox(foodNameTitle, widget.NewSeparator())

	foodRatingColumn := container.NewVBox(ratingTitle, widget.NewSeparator())

	foodsList := []string{"Fruit", "Vegetables", "Nuts", "Grains", "Dairy", "Seafood", "Beef", "Pork", "Poultry", "Eggs", "Beans"}

	for _, foodName := range foodsList{

		foodAttributeName := foodName + "Rating"

		ratingExists, _, foodRating, err := getAnyUserProfileAttributeFunction(foodAttributeName)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		foodNameLabel := getBoldLabelCentered(foodName)

		getRatingLabel := func()*fyne.Container{
			if (ratingExists == false){
				result := getBoldItalicLabelCentered(translate("No Response"))
				return result
			}

			result := getBoldLabelCentered(foodRating + "/10")
			return result
		}

		ratingLabel := getRatingLabel()

		foodNameColumn.Add(foodNameLabel)
		foodRatingColumn.Add(ratingLabel)

		foodNameColumn.Add(widget.NewSeparator())
		foodRatingColumn.Add(widget.NewSeparator())		
	}

	foodsGrid := container.NewHBox(layout.NewSpacer(), foodNameColumn, foodRatingColumn, layout.NewSpacer())

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

	setPageContent(page, window)
}

func setViewMateProfilePage_Language(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){

	title := getPageTitleCentered(translate("View Profile - Mental"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Language"))

	description := getLabelCentered("Below describes the language(s) the user can speak.")

	userLanguagesExist, _, userLanguageAttribute, err := getAnyUserProfileAttributeFunction("Language")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (userLanguagesExist == false){

		noLanguagesExistLabel := getBoldLabelCentered("This user's profile has no languages listed.")

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

		setPageContent(page, window)
		return
	}

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

		//TODO: Sort by fluency

		languageTitle := getItalicLabelCentered("Language")
		fluencyTitle := getItalicLabelCentered("Fluency")

		languageNameColumn := container.NewVBox(languageTitle, widget.NewSeparator())
		languageFluencyColumn := container.NewVBox(fluencyTitle, widget.NewSeparator())

		languageItemsList := strings.Split(userLanguageAttribute, "+&")

		worldLanguageObjectsMap, err := worldLanguages.GetWorldLanguageObjectsMap()		
		if (err != nil) { return nil, err }

		for _, languageItem := range languageItemsList{

			languageName, languageRating, delimiterFound := strings.Cut(languageItem, "$")
			if (delimiterFound == false){
				return nil, errors.New("setViewMateProfilePage_Language called with profile containing invalid language attribute item: " + languageItem)
			}

			getLanguageNameFormatted := func()string{

				// We only translate if language name is canonical

				_, languageIsCanonical := worldLanguageObjectsMap[languageName]
				if (languageIsCanonical == false){
					return languageName
				}
				languageNameTranslated := translate(languageName)
				return languageNameTranslated
			}

			languageNameFormatted := getLanguageNameFormatted()

			languageFluencyFormatted := languageRating + "/5"

			languageNameLabel := getBoldLabelCentered(languageNameFormatted)
			languageFluencyLabel := getBoldLabelCentered(languageFluencyFormatted)

			languageNameColumn.Add(languageNameLabel)
			languageFluencyColumn.Add(languageFluencyLabel)

			languageNameColumn.Add(widget.NewSeparator())
			languageFluencyColumn.Add(widget.NewSeparator())
		}

		languagesGrid := container.NewHBox(layout.NewSpacer(), languageNameColumn, languageFluencyColumn, layout.NewSpacer())

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

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

	setPageContent(page, window)
}


// We use this to view an ancestry composition for moderation/comparing profile changes
func setViewUser23andMeAncestryCompositionPage(window fyne.Window, inputAncestryCompositionAttribute string, previousPage func()){

	title := getPageTitleCentered("Viewing 23andMe Ancestry Composition")

	backButton := getBackButtonCentered(previousPage)

	attributeIsValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, inputAncestryCompositionAttribute)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (attributeIsValid == false){
		setErrorEncounteredPage(window, errors.New("setViewUser23andMeAncestryCompositionPage called with invalid inputAncestryCompositionAttribute"), previousPage)
		return
	}

	userCompositionDisplay, err := get23andMeAncestryCompositionDisplay(continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

	page := container.NewBorder(header, nil, nil, nil, userCompositionDisplay)

	setPageContent(page, window)
}

func get23andMeAncestryCompositionDisplay(inputContinentPercentagesMap map[string]float64, inputRegionPercentagesMap map[string]float64, inputSubregionPercentagesMap map[string]float64)(fyne.Widget, error){

	mapsAreValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.AddMissingParentsToAncestryCompositionMaps_23andMe(inputContinentPercentagesMap, inputRegionPercentagesMap, inputSubregionPercentagesMap)
	if (err != nil){ return nil, err }
	if (mapsAreValid == false){
		return nil, errors.New("get23andMeAncestryCompositionDisplay called with invalid ancestry location maps.")
	}

	// This map is used to create a fyne tree widget
	// Each item maps to a list of child items
	treeMap := make(map[string][]string)

	continentsList := make([]string, 0, len(continentPercentagesMap))

	// Map Structure: Continent Description -> Continent percentage
	continentDescriptionPercentagesMap := make(map[string]float64)

	for continentName, continentPercentage := range continentPercentagesMap{

		continentPercentageString := helpers.ConvertFloat64ToStringRounded(continentPercentage, 1)

		continentDescription := continentName + " - " + continentPercentageString + "%"	

		continentDescriptionPercentagesMap[continentDescription] = continentPercentage

		continentsList = append(continentsList, continentDescription)

		currentContinentRegionsList := make([]string, 0)

		allContinentRegionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(continentName)
		if (err != nil) {
			return nil, errors.New("continentPercentagesMap contains invalid continent.")
		}

		// Map Structure: Region Description -> Region percentage
		regionDescriptionPercentagesMap := make(map[string]float64)

		for regionName, regionPercentage := range regionPercentagesMap{

			regionIsRelevant := slices.Contains(allContinentRegionsList, regionName)
			if (regionIsRelevant == false){
				continue
			}

			regionPercentageString := helpers.ConvertFloat64ToStringRounded(regionPercentage, 1)

			regionDescription := regionName + " - " + regionPercentageString + "%"

			regionDescriptionPercentagesMap[regionDescription] = regionPercentage

			currentContinentRegionsList = append(currentContinentRegionsList, regionDescription)

			allRegionSubregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(continentName, regionName)
			if (err != nil){ return nil, err }

			currentRegionSubregionsList := make([]string, 0, len(subregionPercentagesMap))

			// Map Structure: Subregion description -> Subregion percentage
			subregionDescriptionPercentagesMap := make(map[string]float64)

			for subregionName, subregionPercentage := range subregionPercentagesMap{

				subregionIsRelevant := slices.Contains(allRegionSubregionsList, subregionName)
				if (subregionIsRelevant == false){
					continue
				}

				subregionPercentageString := helpers.ConvertFloat64ToStringRounded(subregionPercentage, 1)

				subregionDescription := subregionName + " - " + subregionPercentageString + "%"

				subregionDescriptionPercentagesMap[subregionDescription] = subregionPercentage

				currentRegionSubregionsList = append(currentRegionSubregionsList, subregionDescription)
			}

			// We sort region subregions list
			// We sort them from highest to lowest in percentage.

			compareSubregionsFunction := func(subregion1Description string, subregion2Description string)int{

				if (subregion1Description == subregion2Description){
					panic("compareSubregionsFunction called with identical subregion descriptions.")
				}

				subregion1Percentage, exists := subregionDescriptionPercentagesMap[subregion1Description]
				if (exists == false){
					panic("subregionPercentagesMap missing subregion during sort.")
				}

				subregion2Percentage, exists := subregionDescriptionPercentagesMap[subregion2Description]
				if (exists == false){
					panic("subregionPercentagesMap missing subregion during sort.")
				}
				if (subregion1Percentage == subregion2Percentage){
					// We sort subregions in unicode order
					if (subregion1Description < subregion2Description){
						return -1
					}
					return 1
				}
				if (subregion1Percentage > subregion2Percentage){
					return -1
				}

				return 1
			}

			slices.SortFunc(currentRegionSubregionsList, compareSubregionsFunction)

			treeMap[regionDescription] = currentRegionSubregionsList
		}

		// We sort continent regions list by highest to lowest percentage.

		compareRegionsFunction := func(region1Description string, region2Description string)int{

			if (region1Description == region2Description){
				panic("compareRegionsFunction called with identical regions.")
			}

			region1Percentage, exists := regionDescriptionPercentagesMap[region1Description]
			if (exists == false){
				panic("regionPercentagesMap missing subregion during sort.")
			}

			region2Percentage, exists := regionDescriptionPercentagesMap[region2Description]
			if (exists == false){
				panic("regionPercentagesMap missing subregion during sort.")
			}
			if (region1Percentage == region2Percentage){
				// We sort regions in unicode order
				if (region1Description < region2Description){
					return -1
				}
				return 1
			}

			if (region1Percentage > region2Percentage){
				return -1
			}

			return 1
		}

		slices.SortFunc(currentContinentRegionsList, compareRegionsFunction)

		treeMap[continentDescription] = currentContinentRegionsList
	}

	// We sort root list by highest to lowest proportions

	compareContinentsFunction := func(continent1Description string, continent2Description string)int{

		if (continent1Description == continent2Description){
			panic("compareContinentsFunction called with identical continents.")
		}

		continent1Percentage, exists := continentDescriptionPercentagesMap[continent1Description]
		if (exists == false){
			panic("Continent percentage not found when sorting root list.")
		}
		
		continent2Percentage, exists := continentDescriptionPercentagesMap[continent2Description]
		if (exists == false){
			panic("Continent percentage not found when sorting root list.")
		}
		if (continent1Percentage == continent2Percentage){
			// We sort continents in unicode order
			if (continent1Description < continent2Description){
				return -1
			}
			return 1
		}
		if (continent1Percentage > continent2Percentage){
			return -1
		}
		return 1
	}

	slices.SortFunc(continentsList, compareContinentsFunction)

	treeMap[""] = continentsList

	resultTree := widget.NewTreeWithStrings(treeMap)
	resultTree.OpenAllBranches()

	return resultTree, nil	
}




