package gui

// desiresGui_Physical.go implements pages to manage a user's Physical mate desires

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/allowedText"
import "seekia/internal/desires/mateDesires"
import "seekia/internal/desires/myLocalDesires"
import "seekia/internal/encoding"
import "seekia/internal/genetics/companyAnalysis"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/profiles/myLocalProfiles"

import "strings"
import "errors"
import "slices"


func setChooseDesiresCategoryPage_Physical(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresCategoryPage_Physical(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton :=  getBackButtonCentered(previousPage)

	sexButton := widget.NewButton(translate("Sex"), func(){
			setChooseDesiresPage_Sex(window, currentPage)
		})

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

	neanderthalVariantsButton := widget.NewButton("Neanderthal Variants", func(){
			setChooseDesiresPage_23andMe_NeanderthalVariants(window, currentPage)
		})

	maternalHaplogroupButton := widget.NewButton("Maternal Haplogroup", func(){
			setChooseDesiresPage_23andMe_Haplogroup(window, "Maternal", currentPage)
		})

	paternalHaplogroupButton := widget.NewButton("Paternal Haplogroup", func(){
			setChooseDesiresPage_23andMe_Haplogroup(window, "Paternal", currentPage)
		})
	
	monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){
			setChooseDesiresPage_MonogenicDiseases(window, currentPage)
		})

	polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){
			//TODO: Filter based on minimum variants tested
			// Users will be able to sort matches based on lowest overall disease risk
			showUnderConstructionDialog(window)
		})
	geneticTraitsButton := widget.NewButton("Genetic Traits", func(){
			//TODO: Filter based on number of variants tested
			// Users will be able to sort matches based on highest probability of blue eyes, brown eyes, straight hair, curly hair, etc..
			// Also, users should be able to filter based on outcome points for each trait outcome
			showUnderConstructionDialog(window)
		})

	heightButton := widget.NewButton(translate("Height"), func(){
			setChooseDesiresPage_Height(window, currentPage)
		})

	bodyFatButton := widget.NewButton(translate("Body Fat"), func(){
			setChooseDesiresPage_BodyFat(window, currentPage)
		})
	bodyMuscleButton := widget.NewButton(translate("Body Muscle"), func(){
			setChooseDesiresPage_BodyMuscle(window, currentPage)
		})

	eyeColorButton := widget.NewButton(translate("Eye Color"), func(){
			setChooseDesiresPage_EyeColor(window, currentPage)
		})

	hairColorButton := widget.NewButton(translate("Hair Color"), func(){
			setChooseDesiresPage_HairColor(window, currentPage)
		})

	hairTextureButton := widget.NewButton(translate("Hair Texture"), func(){
			setChooseDesiresPage_HairTexture(window, currentPage)
		})

	skinColorButton := widget.NewButton(translate("Skin Color"), func(){
			setChooseDesiresPage_SkinColor(window, currentPage)
		})

	infectionsButton := widget.NewButton(translate("Infections"), func(){
			setChooseDesiresPage_Infections(window, currentPage)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, sexButton, ancestryCompositionButton, neanderthalVariantsButton, maternalHaplogroupButton, paternalHaplogroupButton, monogenicDiseasesButton, polygenicDiseasesButton, geneticTraitsButton, heightButton, bodyFatButton, bodyMuscleButton, eyeColorButton, hairColorButton, hairTextureButton, skinColorButton, infectionsButton))

	buttonsGridPadded := container.NewPadded(buttonsGrid)

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

	setPageContent(page, window)
}

func setChooseDesiresPage_Sex(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_Sex(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(translate("Sex"))
	
	descriptionText := widget.NewLabel("Choose the sex(es) that you desire.")

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

	descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionText, sexHelpButton, layout.NewSpacer())

	optionTitlesList := []string{translate("Male"), translate("Female"), translate("Intersex Male"), translate("Intersex Female"), translate("Intersex")}

	optionNamesMap := make(map[string][]string)

	optionNamesMap[translate("Male")] = []string{"Male"}
	optionNamesMap[translate("Female")] = []string{"Female"}
	optionNamesMap[translate("Intersex Male")] = []string{"Intersex Male"}
	optionNamesMap[translate("Intersex Female")] = []string{"Intersex Female"}
	optionNamesMap[translate("Intersex")] = []string{"Intersex"}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "Sex", optionTitlesList, optionNamesMap, false, true, 2)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Sex", "Sex", true, "Donut", "Sex", currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton)

	setPageContent(page, window)
}

func setChooseDesiresPage_23andMe_AncestryComposition(window fyne.Window, userOrOffspring string, previousPage func()){

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

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Ancestry Composition")

	description1 := getLabelCentered("Choose your ancestry composition desires.")
	description2 := getLabelCentered("Each location is represented by a desired percentage range.")

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

		myDesireExists, restrictiveModeEnabled, err := myLocalDesires.GetDesire("23andMe_AncestryComposition_RestrictiveModeEnabled")
		if (err != nil) { return false, err }

		if (myDesireExists == true && restrictiveModeEnabled == "Yes"){
			return true, nil
		}

		return false, nil
	}

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

	restrictiveModeCheck := widget.NewCheck("Restrictive Mode", func(response bool){

			newValue := helpers.ConvertBoolToYesOrNoString(response)

			err := myLocalDesires.SetDesire("23andMe_AncestryComposition_RestrictiveModeEnabled", newValue)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}

			currentPage()
		})
	restrictiveModeCheck.Checked = restrictiveModeIsEnabled

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

	restrictiveModeRow := container.NewHBox(layout.NewSpacer(), restrictiveModeCheck, restrictiveModeHelpButton, layout.NewSpacer())

	chooseLocationDesiresButton := getWidgetCentered(widget.NewButtonWithIcon("Choose Location Desires", theme.NavigateNextIcon(), func(){
			setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, "User", currentPage)
		}))

	getDesireName := func()string{
		if (restrictiveModeIsEnabled == false){
			return "23andMe_AncestryComposition"
		}
		return "23andMe_AncestryComposition_Restrictive"
	}

	desireName := getDesireName()

	filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, desireName, true)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDesireTitle := func()string{

		if (restrictiveModeIsEnabled == false){
			return "23andMe Ancestry Composition"
		}
		return "23andMe Ancestry Composition (Restrictive)"
	}

	desireTitle := getDesireTitle()

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, desireTitle, desireName, false, "", "", currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), restrictiveModeRow, widget.NewSeparator(), chooseLocationDesiresButton, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton)

	setPageContent(page, window)
}

func setChooseDesiresPage_23andMe_ViewAncestryComposition(window fyne.Window, restrictiveModeIsEnabled bool, userOrOffspring string, previousPage func()){

	currentPage := func(){setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, userOrOffspring, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	getSubtitleText := func()string{
		if (restrictiveModeIsEnabled == false){
			return "23andMe - Ancestry Composition"
		}
		return "23andMe - Ancestry Composition (Restrictive)"
	}

	subtitleText := getSubtitleText()

	subtitle := getPageSubtitleCentered(subtitleText)

	description1 := getLabelCentered("You can toggle between viewing your user or offspring desires.")
	description2Label := widget.NewLabel("Your offspring desires are calculated using your profile ancestry composition.")

	offspringAncestryCompositionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setOffspringAncestryCompositionExplainerPage(window, currentPage)
		})
	description2Row := container.NewHBox(layout.NewSpacer(), description2Label, offspringAncestryCompositionHelpButton, layout.NewSpacer())

	userOrOffspringList := []string{"User", "Offspring"}
	userOrOffspringSelector := widget.NewSelect(userOrOffspringList, func(newUserOrOffspring string){
			setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, newUserOrOffspring, previousPage)
		})
	userOrOffspringSelector.Selected = userOrOffspring

	userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)

	getDesireName := func()string{
		if (restrictiveModeIsEnabled == false){
			return "23andMe_AncestryComposition"
		}
		return "23andMe_AncestryComposition_Restrictive"
	}

	desireName := getDesireName()

	modifyDesiresButton := getWidgetCentered(widget.NewButtonWithIcon("Modify Desires", theme.DocumentCreateIcon(), func(){
			setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, false, "", false, "", false, "", currentPage)
		}))

	myDesireExists, myDesiredAncestryComposition, err := myLocalDesires.GetDesire(desireName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myDesireExists == false){

		noLocationsAddedLabel := getBoldLabelCentered("No locations added.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), noLocationsAddedLabel, modifyDesiresButton)
	
		setPageContent(page, window)
		return
	}

	//Outputs:
	//	-bool: My ancestry composition exists 
	//	-map[string]float64: My continent percentages map
	//	-map[string]float64: My region percentages map
	//	-map[string]float64: My subregion percentages map
	//	-error
	getMyAncestryCompositionMaps := func()(bool, map[string]float64, map[string]float64, map[string]float64, error){

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

		attributeIsValid, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, myCurrentAncestryComposition)
		if (err != nil) { return false, nil, nil, nil, err }
		if (attributeIsValid == false){
			return false, nil, nil, nil, errors.New("MyLocalProfiles contains invalid 23andMe ancestry composition attribute: " + myCurrentAncestryComposition)
		}

		mapsAreValid, myFilledContinentPercentagesMap, myFilledRegionPercentagesMap, myFilledSubregionPercentagesMap, err := companyAnalysis.AddMissingParentsToAncestryCompositionMaps_23andMe(myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap)
		if (err != nil){ return false, nil, nil, nil, err }
		if (mapsAreValid == false){
			return false, nil, nil, nil, errors.New("ReadAncestryCompositionAttribute_23andMe not verifying maps.")
		}

		return true, myFilledContinentPercentagesMap, myFilledRegionPercentagesMap, myFilledSubregionPercentagesMap, nil
	}

	myAncestryCompositionExists, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := getMyAncestryCompositionMaps()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (userOrOffspring == "Offspring" && myAncestryCompositionExists == false){

		description3 := getBoldLabelCentered("Your ancestry composition does not exist.")
		description4 := getLabelCentered("To view your offspring desires, you must add your ancestry composition.")
		description5 := getLabelCentered("Add it on the Build Profile - Physical - Ancestry Composition page.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), modifyDesiresButton, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), description3, description4, description5)

		setPageContent(page, window)
		return
	}

	//Outputs:
	//	-map[string][]string: Location description -> List of sub-locations descriptions
	//	-error
	getDesiredLocationTreeMap := func()(map[string][]string, error){

		myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := mateDesires.ReadAncestryCompositionDesire_23andMe(myDesiredAncestryComposition)
		if (err != nil){
			return nil, errors.New("MyLocalDesires contains invalid ancestry composition desire: " + err.Error())
		}

		// This function will convert the range to an offspring range (if userOrOffspring == "Offspring")
		getLocationDescription := func(locationType string, locationName string, userMinimumDesiredBound float64, userMaximumDesiredBound float64)(string, error){

			if (locationType != "Continent" && locationType != "Region" && locationType != "Subregion"){
				return "", errors.New("getLocationDescription called with invalid locationType: " + locationType)
			}

			getMinimumAndMaximumBounds := func()(float64, float64){

				if (userOrOffspring == "User"){
					return userMinimumDesiredBound, userMaximumDesiredBound
				}

				// We calculate offspring range

				getMyLocationPercentage := func()float64{

					if (locationType == "Continent"){
						myPercentage, exists := myContinentPercentagesMap[locationName]	
						if (exists == false){
							return 0
						}
						return myPercentage		
					}
					if (locationType == "Region"){
						myPercentage, exists := myRegionPercentagesMap[locationName]	
						if (exists == false){
							return 0
						}
						return myPercentage
					}
					// locationType == "Subregion
					myPercentage, exists := mySubregionPercentagesMap[locationName]	
					if (exists == false){
						return 0
					}
					return myPercentage
				}

				myLocationPercentage := getMyLocationPercentage()

				offspringMinimumBound := (userMinimumDesiredBound + myLocationPercentage)/2
				offspringMaximumBound := (userMaximumDesiredBound + myLocationPercentage)/2

				return offspringMinimumBound, offspringMaximumBound
			}

			minimumDesiredBound, maximumDesiredBound := getMinimumAndMaximumBounds()

			minimumDesiredBoundString := helpers.ConvertFloat64ToStringRounded(minimumDesiredBound, 1)
			maximumDesiredBoundString := helpers.ConvertFloat64ToStringRounded(maximumDesiredBound, 1)		

			if (minimumDesiredBoundString == maximumDesiredBoundString){
				locationDescription := locationName + ": " + minimumDesiredBoundString + "%"
				return locationDescription, nil
			}

			locationDescription := locationName + ": " + minimumDesiredBoundString + "-" + maximumDesiredBoundString + "%"

			return locationDescription, nil
		}

		// We now build the tree map
		// This is used to display a fyne tree widget
		// Each item is mapped to a list of child items
		// The "" item represents the root of the tree

		treeMap := make(map[string][]string)

		// This list will store the desired continent descriptions
		desiredContinentsList := make([]string, 0)

		allContinentsList := companyAnalysis.GetAncestryContinentsList_23andMe()

		for _, continentName := range allContinentsList{

			continentRegionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(continentName)
			if (err != nil){
				return nil, errors.New("GetAncestryContinentRegionsList_23andMe missing continent regions: " + continentName)
			}

			if (len(continentRegionsList) == 0){
				// Continent has no sublocations
				// We see if we desire it
				minimumDesiredRange, exists := myContinentMinimumBoundsMap[continentName]
				if (exists == false){
					// We do not desire this continent
					continue
				}
				maximumDesiredRange, exists := myContinentMaximumBoundsMap[continentName]
				if (exists == false){
					return nil, errors.New("myContinentMinimumBoundsMap contains continent, myContinentMaximumBoundsMap does not.")				
				}

				continentDescription, err := getLocationDescription("Continent", continentName, minimumDesiredRange, maximumDesiredRange)
				if (err != nil) { return nil, err }

				desiredContinentsList = append(desiredContinentsList, continentDescription)
				continue
			}

			// These represent the minimum and maximum desired ranges for this continent
			// For example, if the user desires Japanese between 0-50%, and Korean between 50-100%, we say they desire East Asian from 0-100%

			// This list will store the desired region descriptions for this continent
			desiredContinentRegionsList := make([]string, 0)

			continentMinimumDesiredBound := float64(100)
			continentMaximumDesiredBound := float64(0)

			for _, regionName := range continentRegionsList{

				regionSubregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(continentName, regionName)
				if (err != nil){
					return nil, errors.New("GetAncestryRegionSubregionsList_23andMe missing region subregions: " + regionName)
				}

				if (len(regionSubregionsList) == 0){

					// This region has no sublocations
					// Check if we desire it

					regionMinimumDesiredBound, exists := myRegionMinimumBoundsMap[regionName]
					if (exists == false){
						// We do not desire this region
						continue
					}

					regionMaximumDesiredBound, exists := myRegionMaximumBoundsMap[regionName]
					if (exists == false){
						return nil, errors.New("myRegionMinimumBoundsMap contains region name, myRegionMaximumBoundsMap does not.")
					}

					if (regionMinimumDesiredBound < continentMinimumDesiredBound){
						continentMinimumDesiredBound = regionMinimumDesiredBound
					}

					if (regionMaximumDesiredBound > continentMaximumDesiredBound){
						continentMaximumDesiredBound = regionMaximumDesiredBound
					}

					regionDescription, err := getLocationDescription("Region", regionName, regionMinimumDesiredBound, regionMaximumDesiredBound)
					if (err != nil) { return nil, err }

					desiredContinentRegionsList = append(desiredContinentRegionsList, regionDescription)

					continue
				}

				// This list will store the descriptions for our desired subregions of this region
				desiredRegionSubregionsList := make([]string, 0)

				regionMinimumDesiredBound := float64(100)
				regionMaximumDesiredBound := float64(0)

				for _, subregionName := range regionSubregionsList{

					// All subregions have no sublocations
					// We see if we desire this subregion

					subregionMinimumDesiredBound, exists := mySubregionMinimumBoundsMap[subregionName]
					if (exists == false){
						// We do not desire this subregion
						continue
					}

					subregionMaximumDesiredBound, exists := mySubregionMaximumBoundsMap[subregionName]
					if (exists == false){
						return nil, errors.New("subregionMinimumBoundsMap contains subregion name, subregionMaximumBoundsMap does not.")
					}

					if (subregionMinimumDesiredBound < continentMinimumDesiredBound){
						continentMinimumDesiredBound = subregionMinimumDesiredBound
					}

					if (subregionMaximumDesiredBound > continentMaximumDesiredBound){
						continentMaximumDesiredBound = subregionMaximumDesiredBound
					}

					if (subregionMinimumDesiredBound < regionMinimumDesiredBound){
						regionMinimumDesiredBound = subregionMinimumDesiredBound
					}

					if (subregionMaximumDesiredBound > regionMaximumDesiredBound){
						regionMaximumDesiredBound = subregionMaximumDesiredBound
					}

					subregionDescription, err := getLocationDescription("Subregion", subregionName, subregionMinimumDesiredBound, subregionMaximumDesiredBound)
					if (err != nil) { return nil, err }

					desiredRegionSubregionsList = append(desiredRegionSubregionsList, subregionDescription)
				}

				if (len(desiredRegionSubregionsList) != 0){

					regionDescription, err := getLocationDescription("Region", regionName, regionMinimumDesiredBound, regionMaximumDesiredBound)
					if (err != nil) { return nil, err }

					desiredContinentRegionsList = append(desiredContinentRegionsList, regionDescription)
					treeMap[regionDescription] = desiredRegionSubregionsList
				}
			}

			if (len(desiredContinentRegionsList) != 0){

				continentDescription, err := getLocationDescription("Continent", continentName, continentMinimumDesiredBound, continentMaximumDesiredBound)
				if (err != nil) { return nil, err }

				desiredContinentsList = append(desiredContinentsList, continentDescription)
				treeMap[continentDescription] = desiredContinentRegionsList
			}
		}

		treeMap[""] = desiredContinentsList

		return treeMap, nil
	}

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

	locationDesireWidgetTree := widget.NewTreeWithStrings(desiredLocationTreeMap)
	locationDesireWidgetTree.OpenAllBranches()

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

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

	setPageContent(page, window)
}

func setChooseDesiresPage_23andMe_EditAncestryComposition(
		window fyne.Window,
		restrictiveModeIsEnabled bool,
		continentProvided bool,
		currentContinent string,
		regionProvided bool,
		currentRegion string,
		subregionProvided bool,
		currentSubregion string,
		previousPage func()){

	currentPage := func(){setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, continentProvided, currentContinent, regionProvided, currentRegion, subregionProvided, currentSubregion, previousPage)}

	title := getPageTitleCentered("My Mate Desires - Physical")

	backButton := getBackButtonCentered(previousPage)

	getSubtitleText := func()string{
		if (restrictiveModeIsEnabled == true){
			return "23andMe Ancestry Composition (Restrictive)"
		}

		return "23andMe Ancestry Composition"
	}

	subtitleText := getSubtitleText()

	subtitle := getPageSubtitleCentered(subtitleText)

	description := getLabelCentered("Choose the percentage range for each location you desire.")

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

		getRangeEditor := func(locationType string)(*fyne.Container, error){

			if (locationType != "Continent" && locationType != "Region" && locationType != "Subregion"){
				return nil, errors.New("getRangeEditor called with invalid locationType: " + locationType)
			}

			getDesireName := func()string{
				if (restrictiveModeIsEnabled == false){
					return "23andMe_AncestryComposition"
				}
				return "23andMe_AncestryComposition_Restrictive"
			}

			desireName := getDesireName()

			//Outputs:
			//	-map[string]float64: Continent minimum bounds map
			//	-map[string]float64: Continent maximum bounds map
			//	-map[string]float64: Region minimum bounds map
			//	-map[string]float64: Region maximum bounds map
			//	-map[string]float64: Subregion minimum bounds map
			//	-map[string]float64: Subregion maximum bounds map
			//	-error
			getMyDesiredAncestryCompositionMaps := func()(map[string]float64, map[string]float64, map[string]float64, map[string]float64, map[string]float64, map[string]float64, error){

				myDesireExists, myDesiredAncestryComposition, err := myLocalDesires.GetDesire(desireName)
				if (err != nil){ return nil, nil, nil, nil, nil, nil, err }
				if (myDesireExists == false){
					emptyMap1 := make(map[string]float64)
					emptyMap2 := make(map[string]float64)
					emptyMap3 := make(map[string]float64)
					emptyMap4 := make(map[string]float64)
					emptyMap5 := make(map[string]float64)
					emptyMap6 := make(map[string]float64)
					return emptyMap1, emptyMap2, emptyMap3, emptyMap4, emptyMap5, emptyMap6, nil
				}

				myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := mateDesires.ReadAncestryCompositionDesire_23andMe(myDesiredAncestryComposition)
				if (err != nil){
					return nil, nil, nil, nil, nil, nil, errors.New("MyLocalDesires contains invalid " + desireName + " desire: " + err.Error())
				}

				return myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, nil
			}

			myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := getMyDesiredAncestryCompositionMaps()
			if (err != nil){ return nil, err }

			// Outputs:
			//	-bool: Range bounds exist (any desire exists for current location)
			//	-float64: Minimum bound
			//	-float64: Maximum bound
			//	-error
			getMyDesiredRangeBounds := func()(bool, float64, float64, error){

				if (locationType == "Continent"){

					continentMinimumBound, exists := myContinentMinimumBoundsMap[currentContinent]
					if (exists == false){
						return false, 0, 0, nil
					}

					continentMaximumBound, exists := myContinentMaximumBoundsMap[currentContinent]
					if (exists == false){
						return false, 0, 0, errors.New("myContinentMinimumBoundsMap contains continent, maximumBoundsMap does not: " + currentContinent)
					}

					return true, continentMinimumBound, continentMaximumBound, nil
				}
				if (locationType == "Region"){

					regionMinimumBound, exists := myRegionMinimumBoundsMap[currentRegion]
					if (exists == false){
						return false, 0, 0, nil
					}

					regionMaximumBound, exists := myRegionMaximumBoundsMap[currentRegion]
					if (exists == false){
						return false, 0, 0, errors.New("myRegionMinimumBoundsMap contains region, maximumBoundsMap does not: " + currentRegion)
					}

					return true, regionMinimumBound, regionMaximumBound, nil
				}
				// locationType == "Subregion"

				subregionMinimumBound, exists := mySubregionMinimumBoundsMap[currentSubregion]
				if (exists == false){
					return false, 0, 0, nil
				}

				subregionMaximumBound, exists := mySubregionMaximumBoundsMap[currentSubregion]
				if (exists == false){
					return false, 0, 0, errors.New("mySubregionMinimumBoundsMap contains subregion, maximumBoundsMap does not: " + currentSubregion)
				}

				return true, subregionMinimumBound, subregionMaximumBound, nil
			}

			myDesireExists, myMinimumBound, myMaximumBound, err := getMyDesiredRangeBounds()			
			if (err != nil){ return nil, err }

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

				myDesireTitle := getItalicLabelCentered("My Desire:")

				getMyDesireText := func()string{

					if (myDesireExists == false){
						return "None"
					}
					myMinimumBoundString := helpers.ConvertFloat64ToStringRounded(myMinimumBound, 1)
					myMaximumBoundString := helpers.ConvertFloat64ToStringRounded(myMaximumBound, 1)

					if (myMinimumBoundString == myMaximumBoundString){
						result := myMinimumBoundString + "%"
						return result
					}

					result := myMinimumBoundString + "-" + myMaximumBoundString + "%"
					return result
				}

				myDesireText := getMyDesireText()

				myDesireLabel := getBoldLabelCentered(myDesireText)

				if (myDesireExists == false){

					displaySection := container.NewVBox(myDesireTitle, myDesireLabel)
					return displaySection, nil
				}

				// We check to see if we should display offspring range
				myAncestryCompositionExists, myCurrentAncestryComposition, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition")
				if (err != nil){ return nil, err }
				if (myAncestryCompositionExists == false){

					// We have not added our ancestry composition
					// Thus, we cannot determine the offspring location	composition

					displaySection := container.NewVBox(myDesireTitle, myDesireLabel)
					return displaySection, nil
				}

				// This returns the percentage of the location that we are, as described in our profile
				getMyLocationPercentage := func()(float64, error){

					attributeIsValid, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, myCurrentAncestryComposition)
					if (err != nil) { return 0, err }
					if (attributeIsValid == false){
						return 0, errors.New("MyLocalProfiles contains invalid 23andMe ancestry composition attribute: " + myCurrentAncestryComposition)
					}

					if (locationType == "Continent"){
						myPercentage, exists := myContinentPercentagesMap[currentContinent]
						if (exists == false){
							return 0, nil
						}
						return myPercentage, nil
					}
					if (locationType == "Region"){
						myPercentage, exists := myRegionPercentagesMap[currentRegion]
						if (exists == false){
							return 0, nil
						}
						return myPercentage, nil
					}
					// LocationType == "Subregion"
					myPercentage, exists := mySubregionPercentagesMap[currentSubregion]
					if (exists == false){
						return 0, nil
					}
					return myPercentage, nil
				}

				myLocationPercentage, err := getMyLocationPercentage()
				if (err != nil){ return nil, err }

				offspringMinimumBound := (myLocationPercentage + myMinimumBound)/2
				offspringMaximumBound := (myLocationPercentage + myMaximumBound)/2

				offspringMinimumBoundString := helpers.ConvertFloat64ToStringRounded(offspringMinimumBound, 1)
				offspringMaximumBoundString := helpers.ConvertFloat64ToStringRounded(offspringMaximumBound, 1)

				getOffspringDesiredRangeString := func()string{

					if (offspringMinimumBoundString == offspringMaximumBoundString){
						result := offspringMinimumBoundString + "%"
						return result
					}

					result := offspringMinimumBoundString + "-" + offspringMaximumBoundString + "%"
					return result
				}

				offspringDesiredRangeString := getOffspringDesiredRangeString()

				offspringRangeLabel := getItalicLabelCentered("(My Offspring: ~" + offspringDesiredRangeString + ")")

				displaySection := container.NewVBox(myDesireTitle, myDesireLabel, offspringRangeLabel)
				return displaySection, nil
			}

			myDesireDisplaySection, err := getMyDesireDisplaySection()
			if (err != nil) { return nil, err }

			minimumLabel := getBoldLabelCentered("Minimum:")
			minimumEntry := widget.NewEntry()
			if (myDesireExists == true){
				myMinimumBoundString := helpers.ConvertFloat64ToStringRounded(myMinimumBound, 1)
				minimumEntry.Text = myMinimumBoundString
			} else {
				minimumEntry.SetPlaceHolder("Enter Minimum...")
			}
			minimumEntryBoxed := getWidgetBoxed(minimumEntry)
			minimumEntryColumn := container.NewGridWithColumns(1, minimumLabel, minimumEntryBoxed)

			maximumLabel := getBoldLabelCentered("Maximum:")
			maximumEntry := widget.NewEntry()
			if (myDesireExists == true){
				myMaximumBoundString := helpers.ConvertFloat64ToStringRounded(myMaximumBound, 1)
				maximumEntry.Text = myMaximumBoundString
			} else {
				maximumEntry.SetPlaceHolder("Enter Maximum...")
			}
			maximumEntryBoxed := getWidgetBoxed(maximumEntry)
			maximumEntryColumn := container.NewGridWithColumns(1, maximumLabel, maximumEntryBoxed)

			minimumMaximumEntryColumnsRow := getContainerCentered(container.NewGridWithRows(1, minimumEntryColumn, maximumEntryColumn))

			submitButton := getWidgetCentered(widget.NewButtonWithIcon("Submit", theme.ConfirmIcon(), func(){

					newMinimum := minimumEntry.Text
					newMaximum := maximumEntry.Text

					newMinimumBoundFloat64, err := helpers.ConvertStringToFloat64(newMinimum)
					if (err != nil) {
						dialogTitle := translate("Invalid Minimum Bound")
						dialogMessageA := getLabelCentered(translate("Your minimum bound is invalid."))
						dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}
					newMaximumBoundFloat64, err := helpers.ConvertStringToFloat64(newMaximum)
					if (err != nil) {
						dialogTitle := translate("Invalid Maximum Bound")
						dialogMessageA := getLabelCentered(translate("Your maximum bound is invalid."))
						dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}
					if (newMinimumBoundFloat64 < 0 || newMinimumBoundFloat64 > 100){
						dialogTitle := translate("Invalid Minimum Bound")
						dialogMessageA := getLabelCentered(translate("Your minimum bound is invalid."))
						dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}
					if (newMaximumBoundFloat64 < 0 || newMaximumBoundFloat64 > 100){
						dialogTitle := translate("Invalid Maximum Bound")
						dialogMessageA := getLabelCentered(translate("Your maximum bound is invalid."))
						dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}		

					if (newMinimumBoundFloat64 > newMaximumBoundFloat64){
						dialogTitle := translate("Invalid Range")
						dialogMessageA := getLabelCentered(translate("Your range is invalid."))
						dialogMessageB := getLabelCentered(translate("The minimum must be less than the maximum."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}

					if (newMinimumBoundFloat64 == 0 && newMaximumBoundFloat64 == 100){

						dialogTitle := translate("Invalid Range")
						dialogMessageA := getLabelCentered(translate("Your range is invalid."))
						dialogMessageB := getLabelCentered(translate("You cannot set a desired range of 0-100%."))
						dialogMessageC := getLabelCentered(translate("All users will pass this range."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}

					if (locationType == "Continent"){

						myContinentMinimumBoundsMap[currentContinent] = newMinimumBoundFloat64
						myContinentMaximumBoundsMap[currentContinent] = newMaximumBoundFloat64

					} else if (locationType == "Region"){

						myRegionMinimumBoundsMap[currentRegion] = newMinimumBoundFloat64
						myRegionMaximumBoundsMap[currentRegion] = newMaximumBoundFloat64

					} else {

						// locationType == "Subregion"
						mySubregionMinimumBoundsMap[currentSubregion] = newMinimumBoundFloat64
						mySubregionMaximumBoundsMap[currentSubregion] = newMaximumBoundFloat64
					}

					newDesireValue, err := mateDesires.CreateAncestryCompositionDesire_23andMe(myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}

					err = myLocalDesires.SetDesire(desireName, newDesireValue)	
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					currentPage()
				}))

			noDesireButton := getWidgetCentered(widget.NewButtonWithIcon("No Desire", theme.CancelIcon(), func(){

					if (locationType == "Continent"){

						delete(myContinentMinimumBoundsMap, currentContinent)
						delete(myContinentMaximumBoundsMap, currentContinent)

					} else if (locationType == "Region"){

						delete(myRegionMinimumBoundsMap, currentRegion)
						delete(myRegionMaximumBoundsMap, currentRegion)
					} else {

						// locationType == "Subregion"

						delete(mySubregionMinimumBoundsMap, currentSubregion)
						delete(mySubregionMaximumBoundsMap, currentSubregion)
					}

					if (len(myContinentMinimumBoundsMap) == 0 && len(myContinentMaximumBoundsMap) == 0 && len(myRegionMinimumBoundsMap) == 0 && len(myRegionMaximumBoundsMap) == 0 && len(mySubregionMinimumBoundsMap) == 0 && len(mySubregionMaximumBoundsMap) == 0){

						err := myLocalDesires.DeleteDesire(desireName)
						if (err != nil){
							setErrorEncounteredPage(window, err, currentPage)
							return
						}
						currentPage()
						return
					}

					newDesireValue, err := mateDesires.CreateAncestryCompositionDesire_23andMe(myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}

					err = myLocalDesires.SetDesire(desireName, newDesireValue)	
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}

					currentPage()
				}))

			rangeEditor := container.NewVBox(myDesireDisplaySection, widget.NewSeparator(), minimumMaximumEntryColumnsRow, submitButton, noDesireButton)

			return rangeEditor, nil
		}

		continentLabel := getBoldLabel("Continent:")

		allContinentsList := companyAnalysis.GetAncestryContinentsList_23andMe()

		handleContinentSelectFunction := func(newContinent string){
				if (continentProvided == true && newContinent == currentContinent){
					return
				}

				setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, newContinent, false, "", false, "", previousPage)
			}

		continentSelector := widget.NewSelect(allContinentsList, handleContinentSelectFunction)
		if (continentProvided == true){
			continentSelector.Selected = currentContinent
		}

		continentRow := container.NewHBox(layout.NewSpacer(), continentLabel, continentSelector, layout.NewSpacer())

		if (continentProvided == false){

			pageContent := container.NewVBox(continentRow)
			return pageContent, nil
		}

		regionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(currentContinent)
		if (err != nil) { return nil, err }

		if (len(regionsList) == 0){

			rangeEditor, err := getRangeEditor("Continent")
			if (err != nil) { return nil, err }

			pageContent := container.NewVBox(continentRow, widget.NewSeparator(), rangeEditor)

			return pageContent, nil
		}

		regionLabel := getBoldLabel("Region:")

		handleRegionSelectFunction := func(newRegion string){

			if (regionProvided == true && currentRegion == newRegion){
				return
			}

			setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, currentContinent, true, newRegion, false, "", previousPage)
		}

		regionSelector := widget.NewSelect(regionsList, handleRegionSelectFunction)

		if (regionProvided == true){
			regionSelector.Selected = currentRegion
		}

		regionRow := container.NewHBox(layout.NewSpacer(), regionLabel, regionSelector, layout.NewSpacer())

		if (regionProvided == false){

			pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow)

			return pageContent, nil
		}

		subregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(currentContinent, currentRegion)
		if (err != nil) { return nil, err }		

		if (len(subregionsList) == 0){

			rangeEditor, err := getRangeEditor("Region")
			if (err != nil) { return nil, err }

			pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), rangeEditor)

			return pageContent, nil
		}

		subregionLabel := getBoldLabel("Subregion:")

		handleSubregionSelectFunction := func(newSubregion string){

			if (subregionProvided == true && currentSubregion == newSubregion){
				return
			}

			setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, currentContinent, true, currentRegion, true, newSubregion, previousPage)
		}

		subregionSelector := widget.NewSelect(subregionsList, handleSubregionSelectFunction)
		if (subregionProvided == true){
			subregionSelector.Selected = currentSubregion
		}

		subregionRow := container.NewHBox(layout.NewSpacer(), subregionLabel, subregionSelector, layout.NewSpacer())

		if (subregionProvided == false){

			pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), subregionRow)

			return pageContent, nil
		}

		rangeEditor, err := getRangeEditor("Subregion")
		if (err != nil) { return nil, err }

		pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), subregionRow, widget.NewSeparator(), rangeEditor)

		return pageContent, nil
	}

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

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

	setPageContent(page, window)
}


func setChooseDesiresPage_23andMe_NeanderthalVariants(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_23andMe_NeanderthalVariants(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Neanderthal Variants")

	description1 := getLabelCentered("Choose your neanderthal variant desires.")
	description2 := getLabelCentered("Enter the neanderthal variant count you desire in your matches.")
	description3 := getLabelCentered("The data is provided by 23andMe.")
	description4 := getLabelCentered("More companies will be added soon.")

	myVariantCountExists, myCurrentNeanderthalVariants, err := myLocalProfiles.GetProfileData("Mate", "23andMe_NeanderthalVariants")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getOffspringVariantsCount := func(userVariantsCount float64)(float64, error){

		if (myVariantCountExists == false){
			return 0, errors.New("Trying to get offspring neanderthal variants count when my count does not exist.")
		}
		myVariantsCountFloat64, err := helpers.ConvertStringToFloat64(myCurrentNeanderthalVariants)
		if (err != nil) {
			return 0, errors.New("MyLocalProfile neanderthal variants count is invalid: " + myCurrentNeanderthalVariants)
		}

		offspringVariantsCount := (userVariantsCount + myVariantsCountFloat64)/2

		return offspringVariantsCount, nil
	}

	desireEditor, err := getDesireEditor_Numeric(window, currentPage, "23andMe_NeanderthalVariants", 0, 7462, "variants", 0, false, nil, nil, myVariantCountExists, "My Offspring", "~", getOffspringVariantsCount)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "23andMe Neanderthal Variants", "23andMe_NeanderthalVariants", true, "Bar", "23andMe_NeanderthalVariants", currentPage)
		}))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_23andMe_Haplogroup(window fyne.Window, maternalOrPaternal string, previousPage func()){

	if (maternalOrPaternal != "Maternal" && maternalOrPaternal != "Paternal"){
		setErrorEncounteredPage(window, errors.New("setChooseDesiresPage_23andMe_Haplogroup called with invalid maternalOrPaternal: " + maternalOrPaternal), previousPage)
		return
	}

	currentPage := func(){setChooseDesiresPage_23andMe_Haplogroup(window, maternalOrPaternal, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered(maternalOrPaternal + " Haplogroup")

	maternalOrPaternalLowercase := strings.ToLower(maternalOrPaternal)

	description1 := getLabelCentered("Choose your " + maternalOrPaternalLowercase + " haplogroup desires.")
	description2 := getLabelCentered("Add each " + maternalOrPaternalLowercase + " haplogroup you desire.")
	description3 := getLabelCentered("You can choose from the known haplogroups, or enter ones that are not listed.")
	description4 := getLabelCentered("The haplogroups are provided by 23andMe. More companies will be added soon.")

	desireName := "23andMe_" + maternalOrPaternal + "Haplogroup"

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

		getCurrentDesiredChoicesList := func()([]string, error){

			currentChoicesListExists, currentChoicesList, err := myLocalDesires.GetDesire(desireName)
			if (err != nil) { return nil, err }
			if (currentChoicesListExists == false){

				emptyList := make([]string, 0)
				return emptyList, nil
			}
			//currentChoicesList is a "+" separated list of choices
			// Each choice option is encoded in base64
			currentDesiredChoicesList := strings.Split(currentChoicesList, "+")

			return currentDesiredChoicesList, nil
		}

		currentDesiredChoicesList, err := getCurrentDesiredChoicesList()
		if (err != nil) { return nil, err }

		getKnownHaplogroupsList := func()[]string{

			if (maternalOrPaternal == "Maternal"){
				maternalHaplogroupsList := companyAnalysis.GetKnownMaternalHaplogroupsList_23andMe()
				return maternalHaplogroupsList
			}

			paternalHaplogroupsList := companyAnalysis.GetKnownPaternalHaplogroupsList_23andMe()
			return paternalHaplogroupsList
		}

		knownHaplogroupsList := getKnownHaplogroupsList()

		haplogroupEntry := widget.NewSelectEntry(knownHaplogroupsList)
		haplogroupEntry.SetPlaceHolder("Enter haplogroup...")

		addHaplogroupButton := getWidgetCentered(widget.NewButtonWithIcon("Add Haplogroup", theme.ContentAddIcon(), func(){

				newHaplogroupName := haplogroupEntry.Text

				if (newHaplogroupName == ""){
					return		
				}
				isAllowed := allowedText.VerifyStringIsAllowed(newHaplogroupName)
				if (isAllowed == false){
					dialogTitle := translate("Invalid Haplogroup Name")
					dialogMessageA := getLabelCentered(translate("Your haplogroup contains an invalid character."))
					dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8."))
					dialogMessageC := getLabelCentered(translate("Remove this character and resubmit."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}

				containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(newHaplogroupName)
				if (containsTabsOrNewlines == true){
					dialogTitle := translate("Invalid Haplogroup Name")
					dialogMessageA := getLabelCentered(translate("Your haplogroup contains a tab or a newline."))
					dialogMessageB := getLabelCentered(translate("Remove the tab or newline and resubmit."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}
				if (len(newHaplogroupName) > 25){

					currentBytesLengthString := helpers.ConvertIntToString(len(newHaplogroupName))

					dialogTitle := translate("Invalid Haplogroup Name")
					dialogMessageA := getLabelCentered(translate("Your haplogroup name is too long."))
					dialogMessageB := getLabelCentered(translate("It cannot exceed 25 bytes."))
					dialogMessageC := getLabelCentered(translate("Current Length:") + " " + currentBytesLengthString)
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}

				haplogroupNameBase64 := encoding.EncodeBytesToBase64String([]byte(newHaplogroupName))

				newDesireList := helpers.AddItemToStringListAndAvoidDuplicate(currentDesiredChoicesList, haplogroupNameBase64)
				newDesireAttributeValue := strings.Join(newDesireList, "+")

				err := myLocalDesires.SetDesire(desireName, newDesireAttributeValue)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}

				currentPage()
			}))

		addHaplogroupRow := getContainerCentered(container.NewGridWithRows(1, haplogroupEntry, addHaplogroupButton))

		otherSelectCheck := widget.NewCheck("Allow Other", func(selection bool){

				//Outputs:
				//	-[]string: New desire attribute list (list of base64 option names)
				getNewDesireAttributeList := func()[]string{

					if (selection == false){
						if (len(currentDesiredChoicesList) == 0){
							// This should not happen, because "Other" had to have been selected for it to be deselected
							emptyList := make([]string, 0)
							return emptyList
						}

						newAttributeList, _ := helpers.DeleteAllMatchingItemsFromList(currentDesiredChoicesList, "Other")

						return newAttributeList
					}
					//selection == true

					newAttributeList := helpers.AddItemToStringListAndAvoidDuplicate(currentDesiredChoicesList, "Other")

					return newAttributeList
				}

				newDesireAttributeList := getNewDesireAttributeList()
				if (len(newDesireAttributeList) == 0){

					err := myLocalDesires.DeleteDesire(desireName)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					currentPage()
					return
				}
				newDesireAttribute := strings.Join(newDesireAttributeList, "+")	

				err := myLocalDesires.SetDesire(desireName, newDesireAttribute)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
				currentPage()
			})

		otherIsSelected := slices.Contains(currentDesiredChoicesList, "Other")
		if (otherIsSelected == true){
			otherSelectCheck.Checked = true
		}

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

		otherSelectCheckRow := container.NewHBox(layout.NewSpacer(), otherSelectCheck, allowOtherHelpButton, layout.NewSpacer())

		checkIfAnyHaplogroupsAreSelected := func()bool{

			if (len(currentDesiredChoicesList) == 0){
				return false
			}
			if (len(currentDesiredChoicesList) == 1){
				onlySelection := currentDesiredChoicesList[0]
				if (onlySelection == "Other"){
					return false
				}
			}
			return true
		}

		anyHaplogroupsAreSelected := checkIfAnyHaplogroupsAreSelected()
		if (anyHaplogroupsAreSelected == false){

			noHaplogroupsExistLabel := getBoldLabelCentered("No Haplogroups Chosen.")

			selectedHaplogroupsSection := container.NewVBox(addHaplogroupRow, widget.NewSeparator(), noHaplogroupsExistLabel, widget.NewSeparator(), otherSelectCheckRow)

			return selectedHaplogroupsSection, nil
		}

		myDesiredHaplogroupsLabel := getItalicLabelCentered("My Desired Haplogroups:")

		haplogroupNameColumn := container.NewVBox(widget.NewSeparator())

		deleteButtonsColumn := container.NewVBox(widget.NewSeparator())

		for _, haplogroupNameBase64 := range currentDesiredChoicesList{

			if (haplogroupNameBase64 == "Other"){
				continue			
			}

			haplogroupName, err := encoding.DecodeBase64StringToUnicodeString(haplogroupNameBase64)
			if (err != nil){
				return nil, errors.New("My current profile " + maternalOrPaternal + " haplogroup desire is malformed: Contains invalid haplogroup: " + haplogroupNameBase64)
			}

			haplogroupNameLabel := getBoldLabelCentered(haplogroupName)

			deleteHaplogroupButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func(){

					newDesiredHaplogroupsList, _ := helpers.DeleteAllMatchingItemsFromList(currentDesiredChoicesList, haplogroupNameBase64)

					if (len(newDesiredHaplogroupsList) == 0){

						err := myLocalDesires.DeleteDesire(desireName)
						if (err != nil){
							setErrorEncounteredPage(window, err, currentPage)
							return
						}
						currentPage()
						return
					}

					newDesireValueString := strings.Join(newDesiredHaplogroupsList, "+")

					err := myLocalDesires.SetDesire(desireName, newDesireValueString)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					currentPage()
				})

			haplogroupNameColumn.Add(haplogroupNameLabel)
			deleteButtonsColumn.Add(deleteHaplogroupButton)

			haplogroupNameColumn.Add(widget.NewSeparator())
			deleteButtonsColumn.Add(widget.NewSeparator())
		}

		haplogroupsGrid := container.NewHBox(layout.NewSpacer(), haplogroupNameColumn, deleteButtonsColumn, layout.NewSpacer())

		selectedHaplogroupsSection := container.NewVBox(addHaplogroupRow, widget.NewSeparator(), myDesiredHaplogroupsLabel, haplogroupsGrid, widget.NewSeparator(), otherSelectCheckRow)

		return selectedHaplogroupsSection, nil
	}

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

	filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, desireName, true)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "23andMe " + maternalOrPaternal + " Haplogroup", desireName, true, "Bar", desireName, currentPage)
		}))
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), selectedHaplogroupsSection, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton)

	setPageContent(page, window)
}

func setChooseDesiresPage_MonogenicDiseases(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_MonogenicDiseases(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("My Offspring Monogenic Disease Desires")

	description1 := getLabelCentered("Choose your offspring monogenic disease probability desires.")
	description2 := getLabelCentered("You must link your genome person on the Build Profile - Genetic Analysis page.")
	description3 := getLabelCentered("This desire will filter users based upon the probability that your offspring will have any monogenic disease.")
	description4 := widget.NewLabel("If you use genetic testing, you can ensure your offspring will not have any monogenic diseases.")

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

	description4Row := container.NewHBox(layout.NewSpacer(), description4, geneticTestingHelpButton, layout.NewSpacer())

	description5 := getBoldLabel("Choose the monogenic disease probability for your offspring that you desire.")
	desireHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setOffspringMonogenicDiseaseProbabilityDesireExplainerPage(window, currentPage)
		})

	description5Row := container.NewHBox(layout.NewSpacer(), description5, desireHelpButton, layout.NewSpacer())

	description6 := getLabelCentered("Be aware that if you select 0%, all users with dominant monogenic diseases will be filtered.")
	description7 := getLabelCentered("You should not select 0% if you have a dominant monogenic disease.")

	getCurrentDesireStatus := func()(string, error){
	
		currentValueExists, currentValue, err := myLocalDesires.GetDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum")
		if (err != nil){ return "", err }
		if (currentValueExists == false || currentValue == "100"){

			result := translate("0-100% (No Preference)")
			return result, nil
		}

		if (currentValue == "99"){
			return "0-99%", nil
		}
		if (currentValue != "0"){
			return "", errors.New("MyLocalDesires malformed: Invalid OffspringProbabilityOfAnyMonogenicDisease_Maximum: " + currentValue)
		}

		return "0%", nil
	}

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

	myDesireLabel := widget.NewLabel("My Desire:")

	currentStatusText := getBoldLabel(currentDesireStatus)

	currentStatusRow := container.NewHBox(layout.NewSpacer(), myDesireLabel, currentStatusText, layout.NewSpacer())

	desireOptionsList := []string{"0%", "0-99%", "0-100%"}

	desireSelector := widget.NewSelect(desireOptionsList, func(newSelection string){

			if (newSelection == "0-100%"){
				err = myLocalDesires.DeleteDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum")
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
				currentPage()
				return
			}

			getNewMaximumBound := func()string{
				if (newSelection == "0%"){
					return "0"
				}
				// newSelection == "0-99%"
				return "99"
			}

			newMaximumBound := getNewMaximumBound()

			err = myLocalDesires.SetDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum", newMaximumBound)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			currentPage()
		})

	if (currentDesireStatus == translate("0-100% (No Preference)")){
		desireSelector.Selected = "0-100%"
	} else {
		desireSelector.Selected = currentDesireStatus
	}

	desireSelectorCentered := getWidgetCentered(desireSelector)

	filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, "OffspringProbabilityOfAnyMonogenicDisease", false)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Offspring Has Any Monogenic Disease Probability", "OffspringProbabilityOfAnyMonogenicDisease", true, "Donut", "OffspringProbabilityOfAnyMonogenicDisease", currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4Row, widget.NewSeparator(), description5Row, description6, description7, widget.NewSeparator(), currentStatusRow, desireSelectorCentered, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton)

	setPageContent(page, window)
}


func setChooseDesiresPage_Height(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_Height(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Height")

	description := getLabelCentered("Choose your desired height.")

	metricOrImperialSwitchButton, err := getMetricImperialSwitchButton(window, currentPage)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}

	currentUnitsLabel := getItalicLabel("Current Units:")

	currentUnitsRow := container.NewHBox(layout.NewSpacer(), currentUnitsLabel, metricOrImperialSwitchButton, layout.NewSpacer())

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

		exists, metricOrImperial, err := globalSettings.GetSetting("MetricOrImperial")
		if (err != nil) { return "", err }
		if (exists == false){
			return "Metric", nil
		}
		if (metricOrImperial != "Metric" && metricOrImperial != "Imperial"){
			return "", errors.New("Malformed globalSettings: Invalid metricOrImperial: " + metricOrImperial)
		}

		return metricOrImperial, nil
	}

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

	getBoundColumn := func(minimumOrMaximum string)(*fyne.Container, error){

		if (minimumOrMaximum != "Minimum" && minimumOrMaximum != "Maximum"){
			return nil, errors.New("getBoundColumn called with invalid minimumOrMaximum: " + minimumOrMaximum)
		}

		desireBoundName := "Height_" + minimumOrMaximum

		boundLabel := getBoldLabelCentered(minimumOrMaximum)

		myBoundExists, myCurrentBound, err := myLocalDesires.GetDesire(desireBoundName)
		if (err != nil) { return nil, err }

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

			if (myBoundExists == false){
				noneLabel := getBoldItalicLabelCentered(translate("None"))
				return noneLabel, nil
			}

			myCurrentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound)
			if (err != nil) {
				return nil, errors.New("My current height " + minimumOrMaximum + " desire is invalid: " + myCurrentBound)
			}

			if (myMetricOrImperial == "Metric"){

				myCurrentBoundRounded := helpers.ConvertFloat64ToStringRounded(myCurrentBoundFloat64, 2)

				myBoundLabel := getBoldLabelCentered(myCurrentBoundRounded + " " + translate("centimeters"))
				return myBoundLabel, nil
			}

			feetInchesString, err := helpers.ConvertCentimetersToFeetInchesTranslatedString(myCurrentBoundFloat64)
			if (err != nil) { return nil, err }

			myBoundLabel := getBoldLabelCentered(feetInchesString)
			return myBoundLabel, nil
		}

		myBoundLabel, err := getMyBoundLabel()
		if (err != nil){ return nil, err }

		minimumOrMaximumLowercase := strings.ToLower(minimumOrMaximum)

		//Outputs:
		//	-fyne.Container: Bound entry (either metric or imperial)
		//	-fyne.Widget: Submit button
		//	-error
		getBoundEntryAndSubmitButton := func()(*fyne.Container, fyne.Widget, error){

			if (myMetricOrImperial == "Metric"){

				boundEntry := widget.NewEntry()

				boundEntryBoxed := getWidgetBoxed(boundEntry)

				if (myBoundExists == true){
					currentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound)
					if (err != nil) { return nil, nil, err }
					if (currentBoundFloat64 < 30 || currentBoundFloat64 > 400) {
						return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound)
					}

					myCurrentBoundRounded := helpers.ConvertFloat64ToStringRounded(currentBoundFloat64, 2)

					boundEntry.SetText(myCurrentBoundRounded)
				} else {
					boundEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "..."))
				}

				saveBoundButton := widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){

					newHeightBound := boundEntry.Text

					if (newHeightBound == ""){

						err := myLocalDesires.DeleteDesire(desireBoundName)
						if (err != nil){
							setErrorEncounteredPage(window, err, currentPage)
							return
						}
						currentPage()
						return
					}

					//TODO: If min is greater than max, delete min

					newHeightBoundFloat64, err := helpers.ConvertStringToFloat64(newHeightBound)
					if (err != nil){

						title := translate("Invalid " + minimumOrMaximum + " Height")
						dialogMessage := getLabelCentered(translate("Your height bound must be a number."))
						dialogContent := container.NewVBox(dialogMessage)
						dialog.ShowCustom(title, translate("Close"), dialogContent, window)
						return
					}
					if (newHeightBoundFloat64 < 30 || newHeightBoundFloat64 > 400){

						title := translate("Invalid " + minimumOrMaximum + " Height")
						dialogMessage := getLabelCentered(translate("Your height desire must be a number between 30 and 400."))
						dialogContent := container.NewVBox(dialogMessage)
						dialog.ShowCustom(title, translate("Close"), dialogContent, window)
						return
					}

					err = myLocalDesires.SetDesire(desireBoundName, newHeightBound)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}

					currentPage()
				})

				return boundEntryBoxed, saveBoundButton, nil
			}

			feetEntry := widget.NewEntry()
			inchesEntry := widget.NewEntry()

			feetLabel := getBoldLabel(translate("feet"))
			inchesLabel := getBoldLabel(translate("inches"))

			entryRow := container.NewGridWithRows(1, feetEntry, feetLabel, inchesEntry, inchesLabel)

			if (myBoundExists == true){
				currentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound)
				if (err != nil) { return nil, nil, err }
				if (currentBoundFloat64 < 30 || currentBoundFloat64 > 400) {
					return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound)
				}

				feetInt, inchesFloat, err := helpers.ConvertCentimetersToFeetInches(currentBoundFloat64)
				if (err != nil) {
					return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound)
				}

				feetString := helpers.ConvertIntToString(feetInt)
				inchesString := helpers.ConvertFloat64ToStringRounded(inchesFloat, 1)

				feetEntry.SetText(feetString)
				inchesEntry.SetText(inchesString)
			} else {
				feetEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "..."))
				inchesEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "..."))
			}

			saveBoundButton := widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){

				newHeightFeetBound := feetEntry.Text
				newHeightInchesBound := inchesEntry.Text

				if (newHeightFeetBound == "" && newHeightInchesBound == ""){

					err := myLocalDesires.DeleteDesire(desireBoundName)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					currentPage()
					return
				}

				newFeetBoundInt, err := helpers.ConvertStringToInt(newHeightFeetBound)
				if (err != nil){
					title := translate("Invalid " + minimumOrMaximum + " Feet")
					dialogMessage := getLabelCentered(translate("Your feet bound must be a number."))
					dialogContent := container.NewVBox(dialogMessage)
					dialog.ShowCustom(title, translate("Close"), dialogContent, window)
					return
				}

				newInchesBoundFloat64, err := helpers.ConvertStringToFloat64(newHeightInchesBound)
				if (err != nil){
					title := translate("Invalid " + minimumOrMaximum + " Inches")
					dialogMessage := getLabelCentered(translate("Your inches bound must be a number."))
					dialogContent := container.NewVBox(dialogMessage)
					dialog.ShowCustom(title, translate("Close"), dialogContent, window)
					return
				}

				newCentimetersBound, err := helpers.ConvertFeetInchesToCentimeters(newFeetBoundInt, newInchesBoundFloat64)
				if (err != nil){

					title := translate("Invalid " + minimumOrMaximum + " Height")
					dialogMessage := getLabelCentered(translate("Your height bound must be a positive number."))
					dialogContent := container.NewVBox(dialogMessage)
					dialog.ShowCustom(title, translate("Close"), dialogContent, window)
					return
				}

				if (newCentimetersBound < 30 || newCentimetersBound > 400){

					title := translate("Invalid " + minimumOrMaximum + " Height")
					dialogMessage := getLabelCentered(translate("Your height bound must be between 30 and 400 centimeters."))
					dialogContent := container.NewVBox(dialogMessage)
					dialog.ShowCustom(title, translate("Close"), dialogContent, window)
					return
				}

				newCentimetersBoundString := helpers.ConvertFloat64ToString(newCentimetersBound)

				err = myLocalDesires.SetDesire(desireBoundName, newCentimetersBoundString)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}

				currentPage()
			})

			return entryRow, saveBoundButton, nil
		}

		boundEntryRow, saveBoundButton, err := getBoundEntryAndSubmitButton()
		if (err != nil){ return nil, err }

		deleteBoundButton := widget.NewButtonWithIcon(translate("No Preference"), theme.CancelIcon(), func(){

				if (myBoundExists == false){
					return
				}

				err := myLocalDesires.DeleteDesire(desireBoundName)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
				currentPage()
			})

		boundColumn := getContainerCentered(container.NewVBox(boundLabel, widget.NewSeparator(), myBoundLabel, widget.NewSeparator(), boundEntryRow, saveBoundButton, deleteBoundButton))
		return boundColumn, nil
	}

	minimumColumn, err := getBoundColumn("Minimum")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	maximumColumn, err := getBoundColumn("Maximum")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	boundColumns := getContainerCentered(container.NewGridWithRows(1, minimumColumn, maximumColumn))

	filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, "Height", true)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Height", "Height", true, "Bar", "Height", currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), currentUnitsRow, widget.NewSeparator(), boundColumns, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton)

	setPageContent(page, window)
}

func setChooseDesiresPage_BodyFat(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_BodyFat(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Body Fat")

	description1 := getLabelCentered("Choose your body fat desires.")
	description2 := getLabelCentered("Select each rating which you desire.")
	description3 := getLabelCentered("1/4 = Least body fat, 4/4 = Most body fat.")

	optionTitlesList := []string{"1/4", "2/4", "3/4", "4/4"}

	optionNamesMap := map[string][]string{
		"1/4": []string{"1"},
		"2/4": []string{"2"},
		"3/4": []string{"3"},
		"4/4": []string{"4"},
	}	

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "BodyFat", optionTitlesList, optionNamesMap, false, true, 1)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Body Fat", "BodyFat", true, "Donut", "BodyFat", currentPage)
		}))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_BodyMuscle(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_BodyMuscle(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Body Muscle")

	description1 := getLabelCentered("Choose your body muscle desires.")
	description2 := getLabelCentered("Select each rating which you desire.")
	description3 := getLabelCentered("1/4 = Least body muscle, 4/4 = Most body muscle.")

	optionTitlesList := []string{"1/4", "2/4", "3/4", "4/4"}

	optionNamesMap := map[string][]string{
		"1/4": []string{"1"},
		"2/4": []string{"2"},
		"3/4": []string{"3"},
		"4/4": []string{"4"},
	}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "BodyMuscle", optionTitlesList, optionNamesMap, false, true, 1)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Body Muscle", "BodyMuscle", true, "Donut", "BodyMuscle", currentPage)
		}))

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

	setPageContent(page, window)
}


func setChooseDesiresPage_EyeColor(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_EyeColor(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Eye Color")

	description1 := getLabelCentered("Choose your eye color desires.")
	description2 := getLabelCentered("Select each eye color that you desire.")

	optionTitlesList := []string{"Blue", "Green", "Brown", "Amber", "Blue+Green", "Blue+Brown", "Blue+Amber", "Amber+Brown", "Green+Brown", "Green+Amber", "Green+Amber+Brown", "Blue+Amber+Brown", "Blue+Green+Brown", "Blue+Green+Amber", "Blue+Green+Amber+Brown"}

	optionNamesMap := make(map[string][]string)
	
	optionNamesMap["Blue"] = []string{"Blue"}
	optionNamesMap["Green"] = []string{"Green"}
	optionNamesMap["Brown"] = []string{"Brown"}
	optionNamesMap["Amber"] = []string{"Amber"}
	optionNamesMap["Blue+Green"] = []string{"Blue+Green", "Green+Blue"}
	optionNamesMap["Blue+Brown"] = []string{"Blue+Brown", "Brown+Blue"}
	optionNamesMap["Blue+Amber"] = []string{"Blue+Amber", "Amber+Blue"}
	optionNamesMap["Amber+Brown"] = []string{"Amber+Brown", "Brown+Amber"}
	optionNamesMap["Green+Brown"] = []string{"Green+Brown", "Brown+Green"}
	optionNamesMap["Green+Amber"] = []string{"Green+Amber", "Amber+Green"}

	optionNamesMap["Green+Amber+Brown"] = []string{"Green+Amber+Brown", "Green+Brown+Amber", "Amber+Green+Brown", "Amber+Brown+Green", "Brown+Amber+Green", "Brown+Green+Amber"}

	optionNamesMap["Blue+Amber+Brown"] = []string{"Blue+Amber+Brown", "Blue+Brown+Amber", "Amber+Blue+Brown", "Amber+Brown+Blue", "Brown+Amber+Blue", "Brown+Blue+Amber"}

	optionNamesMap["Blue+Green+Brown"] = []string{"Blue+Green+Brown", "Blue+Brown+Green", "Green+Blue+Brown", "Green+Brown+Blue", "Brown+Green+Blue", "Brown+Blue+Green"}

	optionNamesMap["Blue+Green+Amber"] = []string{"Blue+Green+Amber", "Blue+Amber+Green", "Green+Blue+Amber", "Green+Amber+Blue", "Amber+Green+Blue", "Amber+Blue+Green"}

	optionNamesMap["Blue+Green+Amber+Brown"] = []string{"Blue+Green+Brown+Amber", "Blue+Green+Amber+Brown", "Blue+Brown+Green+Amber", "Blue+Brown+Amber+Green", "Blue+Amber+Green+Brown", "Blue+Amber+Brown+Green", "Green+Blue+Brown+Amber", "Green+Blue+Amber+Brown", "Green+Brown+Blue+Amber", "Green+Brown+Amber+Blue", "Green+Amber+Blue+Brown", "Green+Amber+Brown+Blue", "Brown+Blue+Green+Amber", "Brown+Blue+Amber+Green", "Brown+Green+Blue+Amber", "Brown+Green+Amber+Blue", "Brown+Amber+Blue+Green", "Brown+Amber+Green+Blue", "Amber+Blue+Green+Brown", "Amber+Blue+Brown+Green", "Amber+Green+Blue+Brown", "Amber+Green+Brown+Blue", "Amber+Brown+Blue+Green", "Amber+Brown+Green+Blue"}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "EyeColor", optionTitlesList, optionNamesMap, false, true, 2)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Eye Color", "EyeColor", true, "Donut", "EyeColor", currentPage)
		}))

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

	setPageContent(page, window)
}


func setChooseDesiresPage_HairColor(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_HairColor(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Hair Color")

	description1 := getLabelCentered("Choose your natural hair color desires.")
	description2 := getLabelCentered("Select each natural hair color that you desire.")

	optionTitlesList := []string{"Black", "Brown", "Blonde", "Orange", "Black+Brown", "Black+Blonde", "Black+Orange", "Brown+Blonde", "Brown+Orange", "Blonde+Orange"}

	//TODO: Add color guide page, so users can see the colors
	// We already have this on the Build Profile page for hair/eye color

	optionNamesMap := make(map[string][]string)
	optionNamesMap["Black"] = []string{"Black"}
	optionNamesMap["Brown"] = []string{"Brown"}
	optionNamesMap["Blonde"] = []string{"Blonde"}
	optionNamesMap["Orange"] = []string{"Orange"}
	optionNamesMap["Black+Brown"] = []string{"Black+Brown", "Brown+Black"}
	optionNamesMap["Black+Blonde"] = []string{"Black+Blonde", "Blonde+Black"}
	optionNamesMap["Black+Orange"] = []string{"Black+Orange", "Orange+Black"}
	optionNamesMap["Brown+Blonde"] = []string{"Brown+Blonde", "Blonde+Brown"}
	optionNamesMap["Brown+Orange"] = []string{"Brown+Orange", "Orange+Brown"}
	optionNamesMap["Blonde+Orange"] = []string{"Blonde+Orange", "Orange+Blonde"}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "HairColor", optionTitlesList, optionNamesMap, false, true, 2)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Hair Color", "HairColor", true, "Donut", "HairColor", currentPage)
		}))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_HairTexture(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_HairTexture(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Hair Texture")

	description1 := getLabelCentered("Choose your hair texture desires.")
	description2 := getLabelCentered("Select each hair texture that you desire.")

	// These descriptions are taken from 23andMe.

	option1Translated := translate("1 - Straight Hair")
	option2Translated := translate("2 - Slightly Wavy Hair")
	option3Translated := translate("3 - Wavy Hair")
	option4Translated := translate("4 - Big Curls")
	option5Translated := translate("5 - Small Curls")
	option6Translated := translate("6 - Very Tight Curls")

	optionTitlesList := []string{option1Translated, option2Translated, option3Translated, option4Translated, option5Translated, option6Translated}

	optionNamesMap := map[string][]string{
		option1Translated: []string{"1"},
		option2Translated: []string{"2"},
		option3Translated: []string{"3"},
		option4Translated: []string{"4"},
		option5Translated: []string{"5"},
		option6Translated: []string{"6"},
	}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "HairTexture", optionTitlesList, optionNamesMap, false, true, 1)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Hair Texture", "HairTexture", true, "Donut", "HairTexture", currentPage)
		}))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_SkinColor(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_SkinColor(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Skin Color")

	description1 := getLabelCentered("Choose your skin color desires.")
	description2 := getLabelCentered("Select each skin color that you desire.")
	description3 := getLabelCentered("Skin color can change under different conditions.")
	description4 := getLabelCentered("You may want to select multiple options to account for this.")

	getSkinColorExampleColumn := func(colorIdentifier string, colorCode string)(*fyne.Container, error){

		colorSquare, err := getColorSquareAsFyneImage(colorCode)
		if (err != nil){ return nil, err }

		colorSquare.FillMode = canvas.ImageFillStretch

		colorIdentifierLabel := getBoldLabelCentered(colorIdentifier)

		colorColumn := container.NewGridWithColumns(1, colorSquare, colorIdentifierLabel)

		return colorColumn, nil
	}

	skinColorColumn_1, err := getSkinColorExampleColumn("1", "f4e3da")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorColumn_2, err := getSkinColorExampleColumn("2", "f5d6b9")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorColumn_3, err := getSkinColorExampleColumn("3", "dabe91")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorColumn_4, err := getSkinColorExampleColumn("4", "ba9175")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorColumn_5, err := getSkinColorExampleColumn("5", "916244")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorColumn_6, err := getSkinColorExampleColumn("6", "744d2d")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	skinColorExamplesRow := container.NewHBox(layout.NewSpacer(), skinColorColumn_1, skinColorColumn_2, skinColorColumn_3, skinColorColumn_4, skinColorColumn_5, skinColorColumn_6, layout.NewSpacer())

	optionTitlesList := []string{"1", "2", "3", "4", "5", "6"}

	optionNamesMap := map[string][]string{
		"1": []string{"1"},
		"2": []string{"2"},
		"3": []string{"3"},
		"4": []string{"4"},
		"5": []string{"5"},
		"6": []string{"6"},
	}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "SkinColor", optionTitlesList, optionNamesMap, false, true, 6)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Skin Color", "SkinColor", true, "Donut", "SkinColor", currentPage)
		}))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_Infections(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_Infections(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Infections")

	description1 := getLabelCentered("Choose your infection desires.")

	hivButton := widget.NewButton("HIV", func(){
			setChooseDesiresPage_HIV(window, currentPage)
		})

	genitalHerpesButton := widget.NewButton("Genital Herpes", func(){
			setChooseDesiresPage_GenitalHerpes(window, currentPage)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, hivButton, genitalHerpesButton))

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

	setPageContent(page, window)
}

func setChooseDesiresPage_HIV(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_HIV(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("HIV")

	description1 := getLabelCentered("Choose your HIV desires.")
	description2 := getLabelCentered("Select each option which you desire.")
	description3 := getLabelCentered("Positive = Has disease, Negative = Does not have disease.")

	optionTitlesList := []string{"HIV Positive", "HIV Negative"}

	optionNamesMap := map[string][]string{
		"HIV Positive": []string{"Yes"},
		"HIV Negative": []string{"No"},
	}

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "HasHIV", optionTitlesList, optionNamesMap, false, true, 1)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Has HIV", "HasHIV", true, "Donut", "HasHIV", currentPage)
		}))

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

	setPageContent(page, window)
}


func setChooseDesiresPage_GenitalHerpes(window fyne.Window, previousPage func()){

	currentPage := func(){setChooseDesiresPage_GenitalHerpes(window, previousPage)}

	title := getPageTitleCentered(translate("My Mate Desires - Physical"))

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Genital Herpes")

	description1 := getLabelCentered("Choose your Genital Herpes desires.")
	description2 := getLabelCentered("Select each option which you desire.")
	description3 := getLabelCentered("Positive = Has disease, Negative = Does not have disease.")

	optionTitlesList := []string{"Genital Herpes Positive", "Genital Herpes Negative"}

	optionNamesMap := map[string][]string{
		"Genital Herpes Positive": []string{"Yes"},
		"Genital Herpes Negative": []string{"No"},
	}	

	desireEditor, err := getDesireEditor_Choice(window, currentPage, "HasGenitalHerpes", optionTitlesList, optionNamesMap, false, true, 1)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			setViewMyMateDesireStatisticsPage(window, "Has Genital Herpes", "HasGenitalHerpes", true, "Donut", "HasGenitalHerpes", currentPage)
		}))

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

	setPageContent(page, window)
}


