
package gui

// viewAnalysisGui_Person.go implements pages to view a person genetic analysis

//TODO: Use person's sex to show opposite sex diseases and traits in different section
// For example, Ovarian Cancer for men, and Testicular Cancer for women
// We still want these risks/traits to be viewable, just put into a different part of the gui

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/container"
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/geneticAnalysis"
import "seekia/internal/genetics/myGenomes"
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/helpers"

import "slices"
import "errors"


func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, numberOfGenomesAnalyzed int, previousPage func()){

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

	currentPage := func(){setViewPersonGeneticAnalysisPage(window, personIdentifier, analysisObject, numberOfGenomesAnalyzed, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis")

	backButton := getBackButtonCentered(previousPage)

	warningLabel1 := getBoldLabelCentered("WARNING: Results are not accurate!")
	warningLabel2 := getBoldLabelCentered("The analysis algorithms have known issues that are being worked on.")

	personFound, personName, _, _, err := myPeople.GetPersonInfo(personIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (personFound == false){
		setErrorEncounteredPage(window, errors.New("setViewPersonGeneticAnalysisPage called with unknown personIdentifier: " + personIdentifier), previousPage)
		return
	}

	personNameLabel := widget.NewLabel("Person Name:")
	personNameText := getBoldLabel(personName)
	personNameRow := container.NewHBox(layout.NewSpacer(), personNameLabel, personNameText, layout.NewSpacer())

	numberOfGenomesAnalyzedString := helpers.ConvertIntToString(numberOfGenomesAnalyzed)

	numberOfAnalyzedGenomesLabel := widget.NewLabel("Number of Analyzed Genomes:")
	numberOfAnalyzedGenomesText := getBoldLabel(numberOfGenomesAnalyzedString)
	numberOfAnalyzedGenomesRow := container.NewHBox(layout.NewSpacer(), numberOfAnalyzedGenomesLabel, numberOfAnalyzedGenomesText, layout.NewSpacer())

	generalButton := widget.NewButton("General", func(){
			//TODO: Inbred rating (parent relatedness), ancestry
			showUnderConstructionDialog(window)
		})
	monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){
			setViewPersonGeneticAnalysisMonogenicDiseasesPage(window, analysisObject, currentPage)
		})
	polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){
			setViewPersonGeneticAnalysisPolygenicDiseasesPage(window, personIdentifier, analysisObject, currentPage)
		})
	discreteTraitsButton := widget.NewButton("Discrete Traits", func(){
			setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, currentPage)
		})
	numericTraitsButton := widget.NewButton("Numeric Traits", func(){
			setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, currentPage)
		})

	categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), warningLabel1, warningLabel2, widget.NewSeparator(), personNameRow, numberOfAnalyzedGenomesRow, widget.NewSeparator(), categoryButtonsGrid)

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisMonogenicDiseasesPage(window fyne.Window, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisMonogenicDiseasesPage(window, analysisObject, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - Monogenic Diseases")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the monogenic diseases found in the person's genome.")

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
		if (err != nil){ return nil, err }

		// Outputs:
		//	-[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
		//	-error
		getMainGenomeIdentifier := func()([16]byte, error){

			if (multipleGenomesExist == true){
				return onlyExcludeConflictsGenomeIdentifier, nil
			}
			// Only 1 genome exists

			genomeIdentifier := allRawGenomeIdentifiersList[0]

			return genomeIdentifier, nil
		}

		mainGenomeIdentifier, err := getMainGenomeIdentifier()
		if (err != nil){ return nil, err }
		
		diseaseNameLabel := getItalicLabelCentered("Disease Name")
		emptyLabelA := widget.NewLabel("")

		personLabel := getItalicLabelCentered("Person")
		hasDiseaseLabel := getItalicLabelCentered("Has Disease")

		probabilityOfLabel := getItalicLabelCentered("Probability of")
		passingVariantLabel := getItalicLabelCentered("Passing Variant")

		emptyLabelB := widget.NewLabel("")
		conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")

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

		diseaseNameColumn := container.NewVBox(emptyLabelA, diseaseNameLabel, widget.NewSeparator())
		personHasDiseaseColumn := container.NewVBox(personLabel, hasDiseaseLabel, widget.NewSeparator())
		probabilityOfPassingVariantColumn := container.NewVBox(probabilityOfLabel, passingVariantLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(emptyLabelB, conflictExistsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabelC, emptyLabelD, widget.NewSeparator())

		monogenicDiseaseNamesList, err := monogenicDiseases.GetMonogenicDiseaseNamesList()
		if (err != nil) { return nil, err }		

		for _, diseaseName := range monogenicDiseaseNamesList{

			diseaseInfoIsKnown, personHasDisease, _, probabilityOfPassingAVariantFormatted, _, _, _, conflictExistsBool, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(analysisObject, diseaseName, mainGenomeIdentifier)
			if (err != nil) { return nil, err }

			getPersonHasDiseaseText := func()string{
				if (diseaseInfoIsKnown == false){
					result := translate("Unknown")
					return result
				}

				personHasDiseaseString := helpers.ConvertBoolToYesOrNoString(personHasDisease)

				return personHasDiseaseString
			}

			personHasDiseaseText := getPersonHasDiseaseText()

			getProbabilityOfPassingAVariantText := func()string{
				if (diseaseInfoIsKnown == false){
					result := translate("Unknown")
					return result
				}

				return probabilityOfPassingAVariantFormatted
			}

			probabilityOfPassingAVariantText := getProbabilityOfPassingAVariantText()

			diseaseNameText := getBoldLabelCentered(diseaseName)
			diseaseNameColumn.Add(diseaseNameText)

			personHasDiseaseLabel := getBoldLabelCentered(personHasDiseaseText)
			personHasDiseaseColumn.Add(personHasDiseaseLabel)

			probabilityOfPassingVariantLabel := getBoldLabelCentered(probabilityOfPassingAVariantText)
			probabilityOfPassingVariantColumn.Add(probabilityOfPassingVariantLabel)

			conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExistsBool)
			conflictExistsLabel := getBoldLabelCentered(conflictExistsString)
			conflictExistsColumn.Add(conflictExistsLabel)

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisMonogenicDiseaseDetailsPage(window, analysisObject, diseaseName, currentPage)
				}))
			viewButtonsColumn.Add(viewDetailsButton)
	
			diseaseNameColumn.Add(widget.NewSeparator())
			personHasDiseaseColumn.Add(widget.NewSeparator())
			probabilityOfPassingVariantColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())
		}

		personHasDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonHasMonogenicDiseaseExplainerPage(window, currentPage)
			})
		personHasDiseaseColumn.Add(personHasDiseaseHelpButton)

		probabilityOfPassingVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonProbabilityOfPassingVariantExplainerPage(window, currentPage)
			})
		probabilityOfPassingVariantColumn.Add(probabilityOfPassingVariantHelpButton)

		diseasesContainer := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, personHasDiseaseColumn, probabilityOfPassingVariantColumn)

		if (multipleGenomesExist == true){

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

			conflictExistsColumn.Add(conflictExistsHelpButton)
			diseasesContainer.Add(conflictExistsColumn)
		}

		diseasesContainer.Add(viewButtonsColumn)
		diseasesContainer.Add(layout.NewSpacer())

		return diseasesContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisMonogenicDiseaseDetailsPage(window fyne.Window, analysisObject geneticAnalysis.PersonAnalysis, diseaseName string, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisMonogenicDiseaseDetailsPage(window, analysisObject, diseaseName, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - " + diseaseName)

	backButton := getBackButtonCentered(previousPage)

	allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (multipleGenomesExist == false){
			description := getLabelCentered("Below is the disease information for this person's genome.")

			return description
		}

		description1 := getLabelCentered("Below is the disease information for this person's genomes.")
		description2 := getLabelCentered("The first two genomes are created by combining multiple genomes.")
	
		descriptionsSection := container.NewVBox(description1, description2)
	
		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

	diseaseNameLabel := widget.NewLabel("Disease:")
	diseaseNameText := getBoldLabel(diseaseName)
	diseaseNameInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewMonogenicDiseaseDetailsPage(window, diseaseName, currentPage)
		})
	diseaseNameRow := container.NewHBox(layout.NewSpacer(), diseaseNameLabel, diseaseNameText, diseaseNameInfoButton, layout.NewSpacer())		

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

		diseaseVariantsMap, err := monogenicDiseases.GetMonogenicDiseaseVariantsMap(diseaseName)
		if (err != nil){ return nil, err }

		totalNumberOfVariants := len(diseaseVariantsMap)
		totalNumberOfVariantsString := helpers.ConvertIntToString(totalNumberOfVariants)

		emptyLabelA := widget.NewLabel("")
		genomeNameLabel := getItalicLabelCentered("Genome Name")

		personLabel := getItalicLabelCentered("Person")
		hasDiseaseLabel := getItalicLabelCentered("Has Disease")

		probabilityOfLabel := getItalicLabelCentered("Probability of")
		passingVariantLabel := getItalicLabelCentered("Passing Variant")

		numberOfLabel1 := getItalicLabelCentered("Number of")
		variantsTestedLabel := getItalicLabelCentered("Variants Tested")

		numberOfLabel2 := getItalicLabelCentered("Number of")
		phasedLociLabel := getItalicLabelCentered("Phased Loci")

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

		genomeNameColumn := container.NewVBox(emptyLabelA, genomeNameLabel, widget.NewSeparator())
		personHasDiseaseColumn := container.NewVBox(personLabel, hasDiseaseLabel, widget.NewSeparator())
		probabilityOfPassingAVariantColumn := container.NewVBox(probabilityOfLabel, passingVariantLabel, widget.NewSeparator())
		numberOfVariantsTestedColumn := container.NewVBox(numberOfLabel1, variantsTestedLabel, widget.NewSeparator())
		numberOfPhasedLociColumn := container.NewVBox(numberOfLabel2, phasedLociLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabelB, emptyLabelC, widget.NewSeparator())
		
		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			diseaseInfoIsKnown, personHasDisease, _, probabilityOfPassingAVariantFormatted, numberOfVariantsTested, numberOfLociTested, numberOfPhasedLoci, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(analysisObject, diseaseName, genomeIdentifier)
			if (err != nil) { return err }

			getPersonHasDiseaseText := func()string{
				if (diseaseInfoIsKnown == false){
					result := translate("Unknown")

					return result
				}

				personHasDiseaseString := helpers.ConvertBoolToYesOrNoString(personHasDisease)

				return personHasDiseaseString
			}

			personHasDiseaseText := getPersonHasDiseaseText()

			getProbabilityOfPassingAVariantText := func()string{

				if (diseaseInfoIsKnown == false){
					result := translate("Unknown")

					return result
				}

				return probabilityOfPassingAVariantFormatted
			}

			probabilityOfPassingAVariantText := getProbabilityOfPassingAVariantText()

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})

				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())

				return genomeNameCell
			}

			genomeNameCell := getGenomeNameCell()
			genomeNameColumn.Add(genomeNameCell)

			personHasDiseaseLabel := getBoldLabelCentered(personHasDiseaseText)
			personHasDiseaseColumn.Add(personHasDiseaseLabel)

			probabilityOfPassingAVariantLabel := getBoldLabelCentered(probabilityOfPassingAVariantText)
			probabilityOfPassingAVariantColumn.Add(probabilityOfPassingAVariantLabel)

			numberOfVariantsTestedString := helpers.ConvertIntToString(numberOfVariantsTested)
			numberOfVariantsTestedFormatted := numberOfVariantsTestedString + "/" + totalNumberOfVariantsString
			numberOfVariantsTestedLabel := getBoldLabelCentered(numberOfVariantsTestedFormatted)
			numberOfVariantsTestedColumn.Add(numberOfVariantsTestedLabel)

			numberOfLociTestedString := helpers.ConvertIntToString(numberOfLociTested)
			numberOfPhasedLociString := helpers.ConvertIntToString(numberOfPhasedLoci)
			numberOfPhasedLociFormatted := numberOfPhasedLociString + "/" + numberOfLociTestedString
			numberOfPhasedLociLabel := getBoldLabelCentered(numberOfPhasedLociFormatted)
			numberOfPhasedLociColumn.Add(numberOfPhasedLociLabel)

			viewButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGenomeMonogenicDiseaseVariantsPage(window, analysisObject, genomeIdentifier, genomeName, diseaseName, currentPage)
				})
			viewButtonsColumn.Add(viewButton)

			genomeNameColumn.Add(widget.NewSeparator())
			personHasDiseaseColumn.Add(widget.NewSeparator())
			probabilityOfPassingAVariantColumn.Add(widget.NewSeparator())
			numberOfVariantsTestedColumn.Add(widget.NewSeparator())
			numberOfPhasedLociColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

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

				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date that the genome was exported

				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

		personHasDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonHasMonogenicDiseaseExplainerPage(window, currentPage)
			})
		personHasDiseaseColumn.Add(personHasDiseaseHelpButton)

		probabilityOfPassingAVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPersonProbabilityOfPassingVariantExplainerPage(window, currentPage)
			})
		probabilityOfPassingAVariantColumn.Add(probabilityOfPassingAVariantHelpButton)

		numberOfVariantsTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setNumberOfTestedVariantsExplainerPage(window, currentPage)
			})
		numberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButton)

		numberOfPhasedLociHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setNumberOfPhasedLociExplainerPage(window, currentPage)
			})
		numberOfPhasedLociColumn.Add(numberOfPhasedLociHelpButton)

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, personHasDiseaseColumn, probabilityOfPassingAVariantColumn, numberOfVariantsTestedColumn, numberOfPhasedLociColumn, viewButtonsColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


// This page is used to view the person's variants for a particular genome
func setViewPersonGenomeMonogenicDiseaseVariantsPage(window fyne.Window, geneticAnalysisObject geneticAnalysis.PersonAnalysis, genomeIdentifier [16]byte, genomeName string, diseaseName string, previousPage func()){

	setLoadingScreen(window, "Loading Disease Variants", "Loading disease variants...")

	currentPage := func(){setViewPersonGenomeMonogenicDiseaseVariantsPage(window, geneticAnalysisObject, genomeIdentifier, genomeName, diseaseName, previousPage)}

	title := getPageTitleCentered("View Monogenic Disease Variants - " + diseaseName)

	backButton := getBackButtonCentered(previousPage)

	description1 := widget.NewLabel("Below are the monogenic disease variant results for this genome.")
	variantsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setMonogenicDiseaseVariantsExplainerPage(window, currentPage)
		})
	description1Row := container.NewHBox(layout.NewSpacer(), description1, variantsHelpButton, layout.NewSpacer())

	getGenomeNameRow := func()*fyne.Container{

		genomeLabel := widget.NewLabel("Genome:")
		genomeNameLabel := getBoldLabel(genomeName)

		if (genomeName == "Only Exclude Conflicts" || genomeName == "Only Include Shared"){
			genomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setCombinedGenomesExplainerPage(window, currentPage)
				})
			genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, genomeHelpButton, layout.NewSpacer())
			return genomeNameRow		
		}
		genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, layout.NewSpacer())
		return genomeNameRow
	}

	genomeNameRow := getGenomeNameRow()

	_, _, _, _, numberOfVariantsTested, numberOfLociTested, numberOfPhasedLoci, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(geneticAnalysisObject, diseaseName, genomeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	diseaseVariantsMap, err := monogenicDiseases.GetMonogenicDiseaseVariantsMap(diseaseName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	totalNumberOfVariants := len(diseaseVariantsMap)
	totalNumberOfVariantsString := helpers.ConvertIntToString(totalNumberOfVariants)

	numberOfVariantsTestedString := helpers.ConvertIntToString(numberOfVariantsTested)

	variantsTestedLabel := widget.NewLabel("Variants Tested:")
	variantsTestedText := getBoldLabel(numberOfVariantsTestedString + "/" + totalNumberOfVariantsString)
	variantsTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setNumberOfTestedVariantsExplainerPage(window, currentPage)
		})

	variantsTestedRow := container.NewHBox(layout.NewSpacer(), variantsTestedLabel, variantsTestedText, variantsTestedHelpButton, layout.NewSpacer())

	numberOfLociTestedString := helpers.ConvertIntToString(numberOfLociTested)
	numberOfPhasedLociString := helpers.ConvertIntToString(numberOfPhasedLoci)

	phasedLociLabel := widget.NewLabel("Phased Loci:")
	phasedLociText := getBoldLabel(numberOfPhasedLociString + "/" + numberOfLociTestedString)
	phasedLociHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setNumberOfPhasedLociExplainerPage(window, currentPage)
		})
	phasedLociRow := container.NewHBox(layout.NewSpacer(), phasedLociLabel, phasedLociText, phasedLociHelpButton, layout.NewSpacer())

	//TODO: Add navigation buttons and pages

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

		variantNameLabel := getItalicLabelCentered("Variant Name")
		genomeHasMutationLabel := getItalicLabelCentered("Genome Has Mutation")
		numberOfMutationsLabel := getItalicLabelCentered("Number of Mutations")
		emptyLabel := widget.NewLabel("")

		variantNameColumn := container.NewVBox(variantNameLabel, widget.NewSeparator())
		genomeHasMutationColumn := container.NewVBox(genomeHasMutationLabel, widget.NewSeparator())
		numberOfMutationsColumn := container.NewVBox(numberOfMutationsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator())

		addVariantRow := func(variantIdentifierHex string)error{

			variantObject, exists := diseaseVariantsMap[variantIdentifierHex]
			if (exists == false) {
				return errors.New("Cannot add variantRow: diseaseVariantsMap missing variant.")
			}

			variantName := variantObject.VariantNames[0]

			variantIdentifier, err := encoding.DecodeHexStringTo3ByteArray(variantIdentifierHex)
			if (err != nil) { return err }

			numberOfMutationsIsKnown, genomeNumberOfMutations, err := readGeneticAnalysis.GetPersonMonogenicDiseaseVariantInfoFromGeneticAnalysis(geneticAnalysisObject, diseaseName, variantIdentifier, genomeIdentifier)
			if (err != nil) { return err }

			variantNameLabel := getBoldLabelCentered(variantName)
			variantNameColumn.Add(variantNameLabel)

			getGenomeHasMutationString := func()string{
				if (numberOfMutationsIsKnown == false){
					result := translate("Unknown")
					return result
				}
				if (genomeNumberOfMutations == 0){
					result := translate("No")
					return result
				}

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

			genomeHasMutationString := getGenomeHasMutationString()

			genomeHasMutationLabel := getBoldLabelCentered(genomeHasMutationString)
			genomeHasMutationColumn.Add(genomeHasMutationLabel)

			getNumberOfVariantMutationsString := func()string{

				if (numberOfMutationsIsKnown == false){
					result := translate("Unknown")
					return result	
				}
				variantNumberOfMutationsString := helpers.ConvertIntToString(genomeNumberOfMutations)
				return variantNumberOfMutationsString
			}

			numberOfVariantMutationsString := getNumberOfVariantMutationsString()

			genomeNumberOfMutationsLabel := getBoldLabelCentered(numberOfVariantMutationsString)
			numberOfMutationsColumn.Add(genomeNumberOfMutationsLabel)

			viewMutationButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window, geneticAnalysisObject, diseaseName, variantIdentifier, currentPage)
				})
			viewButtonsColumn.Add(viewMutationButton)

			variantNameColumn.Add(widget.NewSeparator())
			genomeHasMutationColumn.Add(widget.NewSeparator())
			numberOfMutationsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		variantsList_2Mutations := make([]string, 0)
		variantsList_1Mutation := make([]string, 0)
		variantsList_0Mutations := make([]string, 0)
		variantsList_Unknown := make([]string, 0)		

		for variantIdentifierHex, _ := range diseaseVariantsMap{

			variantIdentifier, err := encoding.DecodeHexStringTo3ByteArray(variantIdentifierHex)
			if (err != nil) { return nil, err }

			numberOfMutationsIsKnown, genomeNumberOfMutations, err := readGeneticAnalysis.GetPersonMonogenicDiseaseVariantInfoFromGeneticAnalysis(geneticAnalysisObject, diseaseName, variantIdentifier, genomeIdentifier)
			if (err != nil) { return nil, err }
			if (numberOfMutationsIsKnown == false){
				variantsList_Unknown = append(variantsList_Unknown, variantIdentifierHex)
				continue
			}
			if (genomeNumberOfMutations == 0){

				variantsList_0Mutations = append(variantsList_0Mutations, variantIdentifierHex)

			} else if (genomeNumberOfMutations == 1) {

				variantsList_1Mutation = append(variantsList_1Mutation, variantIdentifierHex)

			} else if (genomeNumberOfMutations == 2){

				variantsList_2Mutations = append(variantsList_2Mutations, variantIdentifierHex)

			} else {
				return nil, errors.New("GetPersonMonogenicDiseaseVariantInfoFromGeneticAnalysis returning invalid genome number of mutations")
			}
		}

		// Items within each group are sorted so they will display the same way whenever user refreshes page

		helpers.SortStringListToUnicodeOrder(variantsList_2Mutations)
		helpers.SortStringListToUnicodeOrder(variantsList_1Mutation)
		helpers.SortStringListToUnicodeOrder(variantsList_0Mutations)
		helpers.SortStringListToUnicodeOrder(variantsList_Unknown)

		for _, variantIdentifier := range variantsList_2Mutations{

			err = addVariantRow(variantIdentifier)	
			if (err != nil) { return nil, err }
		}
		for _, variantIdentifier := range variantsList_1Mutation{

			err = addVariantRow(variantIdentifier)	
			if (err != nil) { return nil, err }
		}
		for _, variantIdentifier := range variantsList_0Mutations{

			err = addVariantRow(variantIdentifier)
			if (err != nil) { return nil, err }
		}
		for _, variantIdentifier := range variantsList_Unknown{

			err = addVariantRow(variantIdentifier)	
			if (err != nil) { return nil, err }
		}

		genomeHasMutationHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setGenomeHasMonogenicDiseaseVariantMutationExplainerPage(window, currentPage)
			})
		genomeHasMutationColumn.Add(genomeHasMutationHelpButton)

		genomeNumberOfMutationsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setMonogenicDiseaseVariantsExplainerPage(window, currentPage)
			})
		numberOfMutationsColumn.Add(genomeNumberOfMutationsHelpButton)
		

		variantsGrid := container.NewHBox(layout.NewSpacer(), variantNameColumn, genomeHasMutationColumn, numberOfMutationsColumn, viewButtonsColumn, layout.NewSpacer())

		return variantsGrid, nil
	}

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1Row, widget.NewSeparator(), genomeNameRow, widget.NewSeparator(), variantsTestedRow, widget.NewSeparator(), phasedLociRow, widget.NewSeparator(), variantsGrid)

	setPageContent(page, window)
}


// This page will show the details of a specific variant from a person's genetic analysis
// It will show the variant details for all of the person's genomes
func setViewPersonGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window fyne.Window, geneticAnalysisObject geneticAnalysis.PersonAnalysis, diseaseName string, variantIdentifier [3]byte, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window, geneticAnalysisObject, diseaseName, variantIdentifier, previousPage)}

	title := getPageTitleCentered("Monogenic Disease Variant Details - " + diseaseName)

	backButton := getBackButtonCentered(previousPage)

	variantIdentifierHex := encoding.EncodeBytesToHexString(variantIdentifier[:])

	variantObject, err := monogenicDiseases.GetMonogenicDiseaseVariantObject(diseaseName, variantIdentifierHex)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	variantName := variantObject.VariantNames[0]

	description := getLabelCentered("Below is the variant status for the person's genomes.")

	variantNameLabel := widget.NewLabel("Variant Name:")
	variantNameText := getBoldLabel(variantName)
	variantNameHelpButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewMonogenicDiseaseVariantDetailsPage(window, diseaseName, variantIdentifierHex, currentPage)
		})
	variantNameRow := container.NewHBox(layout.NewSpacer(), variantNameLabel, variantNameText, variantNameHelpButton, layout.NewSpacer())

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(geneticAnalysisObject)
		if (err != nil) { return nil, err }

		genomeNameLabel := getItalicLabelCentered("Genome Name")
		genomeHasMutationLabel := getItalicLabelCentered("Genome Has Mutation")
		numberOfMutationsLabel := getItalicLabelCentered("Number of Mutations")

		genomeNameColumn := container.NewVBox(genomeNameLabel, widget.NewSeparator())
		genomeHasMutationColumn := container.NewVBox(genomeHasMutationLabel, widget.NewSeparator())
		numberOfMutationsColumn := container.NewVBox(numberOfMutationsLabel, widget.NewSeparator())

		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			genomeMutationsKnown, genomeNumberOfMutations, err := readGeneticAnalysis.GetPersonMonogenicDiseaseVariantInfoFromGeneticAnalysis(geneticAnalysisObject, diseaseName, variantIdentifier, genomeIdentifier)
			if (err != nil) { return err }

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})

				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())

				return genomeNameCell			
			}

			genomeNameCell := getGenomeNameCell()
			genomeNameColumn.Add(genomeNameCell)

			getGenomeHasMutationText := func()string{
				if (genomeMutationsKnown == false){
					result := translate("Unknown")
					return result
				}

				if (genomeNumberOfMutations == 0){
					result := translate("No")
					return result
				}

				result := translate("Yes")

				return result
			}

			genomeHasMutationText := getGenomeHasMutationText()

			getGenomeNumberOfMutationsText := func()string{
				if (genomeMutationsKnown == false){
					result := translate("Unknown")
					return result
				}

				genomeNumberOfMutationsString := helpers.ConvertIntToString(genomeNumberOfMutations)

				return genomeNumberOfMutationsString
			}

			genomeNumberOfMutationsText := getGenomeNumberOfMutationsText()

			genomeHasMutationLabel := getBoldLabelCentered(genomeHasMutationText)
			genomeHasMutationColumn.Add(genomeHasMutationLabel)

			genomeNumberOfMutationsLabel := getBoldLabelCentered(genomeNumberOfMutationsText)
			numberOfMutationsColumn.Add(genomeNumberOfMutationsLabel)

			genomeNameColumn.Add(widget.NewSeparator())
			genomeHasMutationColumn.Add(widget.NewSeparator())
			numberOfMutationsColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

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

				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date that the genome was exported
				
				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

		genomeHasMutationHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setGenomeHasMonogenicDiseaseVariantMutationExplainerPage(window, currentPage)
			})
		genomeHasMutationColumn.Add(genomeHasMutationHelpButton)

		genomeNumberOfMutationsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setMonogenicDiseaseVariantsExplainerPage(window, currentPage)
			})
		numberOfMutationsColumn.Add(genomeNumberOfMutationsHelpButton)

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, genomeHasMutationColumn, numberOfMutationsColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisPolygenicDiseasesPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisPolygenicDiseasesPage(window, personIdentifier, analysisObject, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - Polygenic Diseases")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the polygenic disease risks for this person's genome.")

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
		if (err != nil){ return nil, err }

		// Outputs:
		//	-[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
		//	-error
		getMainGenomeIdentifier := func()([16]byte, error){

			if (multipleGenomesExist == true){
				return onlyExcludeConflictsGenomeIdentifier, nil
			}
			// Only 1 genome exists

			genomeIdentifier := allRawGenomeIdentifiersList[0]

			return genomeIdentifier, nil
		}

		mainGenomeIdentifier, err := getMainGenomeIdentifier()
		if (err != nil){ return nil, err }

		diseaseNameLabel := getItalicLabelCentered("Disease Name")

		riskScoreLabel := getItalicLabelCentered("Risk Score")

		confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

		conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")

		emptyLabel5 := widget.NewLabel("")

		diseaseNameColumn := container.NewVBox(diseaseNameLabel, widget.NewSeparator())
		riskScoreColumn := container.NewVBox(riskScoreLabel, widget.NewSeparator())	
		confidenceRangeColumn := container.NewVBox(confidenceRangeLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(conflictExistsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel5, widget.NewSeparator())

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

		for _, diseaseObject := range polygenicDiseasesList{

			diseaseName := diseaseObject.DiseaseName

			neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName)
			if (neuralNetworkExists == false){
				// We can't analyze this trait
				continue
			}

			diseaseNameLabel := getBoldLabelCentered(diseaseName)

			analysisExists, personRiskScore, predictionConfidenceRangesMap, _, _, conflictExists, err := readGeneticAnalysis.GetPersonPolygenicDiseaseInfoFromGeneticAnalysis(analysisObject, diseaseName, mainGenomeIdentifier)	
			if (err != nil) { return nil, err }

			getPersonRiskScoreLabel := func()fyne.Widget{

				if (analysisExists == false){
					result := widget.NewLabel(translate("Unknown"))
	
					return result
				}

				riskScoreString := helpers.ConvertIntToString(personRiskScore)
				riskScoreFormatted := riskScoreString + "/10"

				riskScoreLabel := getBoldLabel(riskScoreFormatted)

				return riskScoreLabel
			}

			personRiskScoreLabel := getPersonRiskScoreLabel()
			personRiskScoreLabelCentered := getWidgetCentered(personRiskScoreLabel)

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

				if (analysisExists == false){
					unknownLabel := widget.NewLabel("Unknown")
					return unknownLabel, 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
			}

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

			conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
			conflictExistsLabel := getBoldLabelCentered(conflictExistsString)

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisPolygenicDiseaseDetailsPage(window, personIdentifier, analysisObject, diseaseName, currentPage)
				}))

			diseaseNameColumn.Add(diseaseNameLabel)
			riskScoreColumn.Add(personRiskScoreLabelCentered)
			confidenceRangeColumn.Add(confidenceRangeLabel)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewButtonsColumn.Add(viewDetailsButton)
	
			diseaseNameColumn.Add(widget.NewSeparator())
			riskScoreColumn.Add(widget.NewSeparator())
			confidenceRangeColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())
		}	

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

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

		diseasesContainer := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, riskScoreColumn, confidenceRangeColumn)

		if (multipleGenomesExist == true){

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

			conflictExistsColumn.Add(conflictExistsHelpButton)
			diseasesContainer.Add(conflictExistsColumn)
		}

		diseasesContainer.Add(viewButtonsColumn)
		diseasesContainer.Add(layout.NewSpacer())

		return diseasesContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisPolygenicDiseaseDetailsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, diseaseName string, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisPolygenicDiseaseDetailsPage(window, personIdentifier, analysisObject, diseaseName, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - " + diseaseName)

	backButton := getBackButtonCentered(previousPage)

	allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (multipleGenomesExist == false){
			description := getLabelCentered("Below is the disease information for this person's genome.")

			return description
		}

		description1 := getLabelCentered("Below is the disease information for this person's genomes.")
		description2 := getLabelCentered("The first two genomes are created by combining multiple genomes.")

		descriptionsSection := container.NewVBox(description1, description2)

		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

	diseaseNameLabel := widget.NewLabel("Disease:")
	diseaseNameText := getBoldLabel(diseaseName)
	diseaseInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewPolygenicDiseaseDetailsPage(window, diseaseName, currentPage)
		})
	diseaseNameRow := container.NewHBox(layout.NewSpacer(), diseaseNameLabel, diseaseNameText, diseaseInfoButton, layout.NewSpacer())		

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

		diseaseObject, err := polygenicDiseases.GetPolygenicDiseaseObject(diseaseName)
		if (err != nil){ return nil, err }

		diseaseLociList := diseaseObject.LociList

		totalQuantityOfLoci := len(diseaseLociList)
		totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)

		emptyLabel1 := widget.NewLabel("")
		genomeNameLabel := getItalicLabelCentered("Genome Name")

		emptyLabel2 := widget.NewLabel("")
		riskScoreLabel := getItalicLabelCentered("Risk Score")

		emptyLabel3 := widget.NewLabel("")
		predictionConfidenceLabel := getItalicLabelCentered("Confidence Range")

		quantityOfLabel := getItalicLabelCentered("Quantity of")
		lociKnownLabel := getItalicLabelCentered("Loci Known")

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

		emptyLabel6 := widget.NewLabel("")
		emptyLabel7 := widget.NewLabel("")

		genomeNameColumn := container.NewVBox(emptyLabel1, genomeNameLabel, widget.NewSeparator())
		riskScoreColumn := container.NewVBox(emptyLabel2, riskScoreLabel, widget.NewSeparator())
		predictionConfidenceColumn := container.NewVBox(emptyLabel3, predictionConfidenceLabel, widget.NewSeparator())
		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
		viewLifetimeRiskButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())
		viewDetailsButtonsColumn := container.NewVBox(emptyLabel6, emptyLabel7, widget.NewSeparator())

		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})

				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())
				
				return genomeNameCell	
			}

			genomeNameCell := getGenomeNameCell()

			analysisExists, predictedRiskScore, predictionConfidenceRangesMap, quantityOfLociKnown, _, _, err := readGeneticAnalysis.GetPersonPolygenicDiseaseInfoFromGeneticAnalysis(analysisObject, diseaseName, genomeIdentifier)
			if (err != nil) { return err }

			getRiskScoreLabel := func()fyne.Widget{

				if (analysisExists == false){

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

					return result
				}

				riskScoreString := helpers.ConvertIntToString(predictedRiskScore)
				riskScoreFormatted := riskScoreString + "/10"

				riskScoreLabel := getBoldLabel(riskScoreFormatted)

				return riskScoreLabel
			}

			genomeRiskScoreLabel := getRiskScoreLabel()
			genomeRiskScoreLabelCentered := getWidgetCentered(genomeRiskScoreLabel)

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

				if (analysisExists == false){
					unknownLabel := widget.NewLabel("Unknown")
					return unknownLabel, 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
			}

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

			confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

			genomeQuantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
			quantityOfLociKnownLabel := getBoldLabelCentered(genomeQuantityOfLociKnownString + "/" + totalQuantityOfLociString)

			viewLifetimeRiskButton := widget.NewButtonWithIcon("", theme.HistoryIcon(), func(){

					personFound, _, _, personSex, err := myPeople.GetPersonInfo(personIdentifier)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					if (personFound == false){
						setErrorEncounteredPage(window, errors.New("setViewPersonGeneticAnalysisPolygenicDiseaseDetailsPage called with unknown personIdentifier"), currentPage)
						return
					}

					getSexToDisplay := func()string{
						if (personSex == "Male" || personSex == "Female"){
							return personSex						
						}
						return "Male"
					}
					sexToDisplay := getSexToDisplay()

					setViewPersonPolygenicDiseaseLifetimeProbabilitiesPage(window, diseaseName, genomeName, sexToDisplay, currentPage)
				})

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

			genomeNameColumn.Add(genomeNameCell)
			riskScoreColumn.Add(genomeRiskScoreLabelCentered)
			predictionConfidenceColumn.Add(confidenceRangeLabelCentered)
			quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
			viewLifetimeRiskButtonsColumn.Add(viewLifetimeRiskButton)
			viewDetailsButtonsColumn.Add(viewDetailsButton)

			genomeNameColumn.Add(widget.NewSeparator())
			riskScoreColumn.Add(widget.NewSeparator())
			predictionConfidenceColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			viewLifetimeRiskButtonsColumn.Add(widget.NewSeparator())
			viewDetailsButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

			getGenomeName := func()(string, error){
				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date the genome was exported

				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

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

		predictionConfidenceHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				showUnderConstructionDialog(window)
			})
		predictionConfidenceColumn.Add(predictionConfidenceHelpButton)

		quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPolygenicDiseaseNumberOfLociTestedExplainerPage(window, currentPage)
			})
		quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, riskScoreColumn, predictionConfidenceColumn, quantityOfLociKnownColumn, viewLifetimeRiskButtonsColumn, viewDetailsButtonsColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonPolygenicDiseaseLifetimeProbabilitiesPage(window fyne.Window, diseaseName string, genomeName string, maleOrFemale string, previousPage func()){

	currentPage := func(){setViewPersonPolygenicDiseaseLifetimeProbabilitiesPage(window, diseaseName, genomeName, maleOrFemale, previousPage)}

	title := getPageTitleCentered("Viewing Disease Lifetime Probabilities")

	backButton := getBackButtonCentered(previousPage)

	description1 := getLabelCentered("Below are the lifetime probabilities for this disease.")
	description2 := getLabelCentered("The average risk column describes the probability for the average person.")

	//TODO: Once we understand how to calculate it, we will add adjusted risk column with estimated probability

	getGenomeNameRow := func()*fyne.Container{

		genomeLabel := widget.NewLabel("Genome:")
		genomeNameLabel := getBoldLabel(genomeName)

		if (genomeName == "Only Exclude Conflicts" || genomeName == "Only Include Shared"){
			genomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setCombinedGenomesExplainerPage(window, currentPage)
				})
			genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, genomeHelpButton, layout.NewSpacer())
			return genomeNameRow
		}
		genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, layout.NewSpacer())
		return genomeNameRow
	}

	genomeNameRow := getGenomeNameRow()

	getTabContainer := func(tabMaleOrFemale string)(*container.TabItem, error){

		ageLabel := getItalicLabelCentered("Age")

		averageRiskLabel := getItalicLabelCentered("Average Risk")

		ageColumn := container.NewVBox(ageLabel, widget.NewSeparator())
		averageRiskColumn := container.NewVBox(averageRiskLabel, widget.NewSeparator())

		diseaseObject, err := polygenicDiseases.GetPolygenicDiseaseObject(diseaseName)
		if (err != nil) { return nil, err }

		getAverageRiskProbabilitiesFunction := diseaseObject.GetAverageRiskProbabilitiesFunction

		rowInitialValuesSet := false
		rowInitialAge := 0
		rowInitialAgeRisk := float64(0)

		rowSummedRisks := float64(0)		

		for age := 0; age <= 110; age++{

			ageRisk, err := getAverageRiskProbabilitiesFunction(tabMaleOrFemale, age)
			if (err != nil) { return nil, err }

			if (rowInitialValuesSet == false){
				rowInitialValuesSet = true
				rowInitialAge = age
				rowInitialAgeRisk = ageRisk
				rowSummedRisks = ageRisk
				continue
			}

			rowSummedRisks += ageRisk

			nextAge := age+1
			if (nextAge % 10 != 0){
				continue
			}
			if (age < 109 && rowInitialAgeRisk == ageRisk){
				continue
			}
			// We make a new row
			
			averageRisk := rowSummedRisks / float64(age - rowInitialAge + 1) 

			if (rowInitialAge >= age){
				return nil, errors.New("rowInitialAge is >= age.")
			}

			startAgeString := helpers.ConvertIntToString(rowInitialAge)
			endAgeString := helpers.ConvertIntToString(age)

			averageRiskPercentage := averageRisk * 100

			averageRiskString := helpers.ConvertFloat64ToStringRounded(averageRiskPercentage, 2)

			ageRangeLabel := getBoldLabel(startAgeString + " - " + endAgeString)
			averageRiskLabel := getBoldLabelCentered(averageRiskString + "%")

			ageColumn.Add(ageRangeLabel)
			averageRiskColumn.Add(averageRiskLabel)

			ageColumn.Add(widget.NewSeparator())
			averageRiskColumn.Add(widget.NewSeparator())

			rowInitialValuesSet = false
		}

		averageRiskHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setPolygenicDiseaseAverageLifetimeRiskExplainerPage(window, currentPage)
			})
		averageRiskColumn.Add(averageRiskHelpButton)

		ageProbabilitiesGrid := container.NewHBox(layout.NewSpacer(), ageColumn, averageRiskColumn, layout.NewSpacer()) 

		ageProbabilitiesTabItem := container.NewTabItem(tabMaleOrFemale, ageProbabilitiesGrid)

		return ageProbabilitiesTabItem, nil
	}

	maleTab, err := getTabContainer("Male")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	femaleTab, err := getTabContainer("Female")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	ageProbabilityTabs := container.NewAppTabs(maleTab, femaleTab)
	
	if (maleOrFemale == "Male"){
		ageProbabilityTabs.Select(maleTab)
	} else {
		ageProbabilityTabs.Select(femaleTab)
	}

	ageProbabilityTabsCentered := getContainerCentered(getAppTabsBoxed(ageProbabilityTabs))

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisDiscreteTraitsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - Discrete Traits")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the discrete traits for this person's genome.")

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
		if (err != nil){ return nil, err }

		// Outputs:
		//	-[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
		//	-error
		getMainGenomeIdentifier := func()([16]byte, error){

			if (multipleGenomesExist == true){
				return onlyExcludeConflictsGenomeIdentifier, nil
			}
			// Only 1 genome exists

			genomeIdentifier := allRawGenomeIdentifiersList[0]

			return genomeIdentifier, nil
		}

		mainGenomeIdentifier, err := getMainGenomeIdentifier()
		if (err != nil){ return nil, err }

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

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

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

		emptyLabel3 := widget.NewLabel("")
		conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")

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

		traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
		predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(emptyLabel3, conflictExistsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())

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

		for _, traitObject := range traitObjectsList{

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

			traitName := traitObject.TraitName
			traitLociList := traitObject.LociList
			traitRulesList := traitObject.RulesList

			if (len(traitLociList) == 0 && len(traitRulesList) == 0){
				// This trait does not have any rules or loci to analyze
				// We cannot analyze it yet
				continue
			}

			traitNameText := getBoldLabelCentered(traitName)

			neuralNetworkExists, neuralNetworkAnalysisExists, neuralNetworkPredictedOutcome, _, quantityOfLociKnown_NeuralNetwork, _, anyRulesExist, rulesAnalysisExists, _, rulesPredictedOutcomeExists, rulesPredictedOutcome, _, quantityOfLociKnown_Rules, conflictExists, err := readGeneticAnalysis.GetPersonDiscreteTraitInfoFromGeneticAnalysis(analysisObject, traitName, mainGenomeIdentifier)	
			if (err != nil) { return nil, err }
			if (neuralNetworkExists == false && anyRulesExist == false){
				// We can't analyze this trait
				continue
			}

			getPredictionLabel := func()fyne.Widget{

				if (neuralNetworkExists == true){

					if (neuralNetworkAnalysisExists == false){
						result := getItalicLabel("Unknown")
						return result
					}

					result := getBoldLabel(neuralNetworkPredictedOutcome)

					return result
				}
				// anyRulesExist must be true

				if (rulesAnalysisExists == false || rulesPredictedOutcomeExists == false){
					result := getItalicLabel("Unknown")
					return result
				}

				result := getBoldLabel(rulesPredictedOutcome)

				return result
			}

			predictionLabel := getPredictionLabel()

			predictionLabelCentered := getWidgetCentered(predictionLabel)

			getQuantityOfLociKnown := func()int{

				if (neuralNetworkExists == true){

					return quantityOfLociKnown_NeuralNetwork
				}
				return quantityOfLociKnown_Rules
			}

			quantityOfLociKnown := getQuantityOfLociKnown()

			getTotalQuantityOfLoci := func()int{

				if (neuralNetworkExists == true){

					totalQuantityOfLoci := len(traitLociList)

					return totalQuantityOfLoci
				}

				traitLociList_Rules := traitObject.LociList_Rules

				totalQuantityOfLoci := len(traitLociList_Rules)

				return totalQuantityOfLoci
			}

			totalQuantityOfLoci := getTotalQuantityOfLoci()

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

			quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

			quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

			conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
			conflictExistsLabel := getBoldLabelCentered(conflictExistsString)

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisDiscreteTraitDetailsPage(window, personIdentifier, analysisObject, traitName, currentPage)
				}))

			traitNameColumn.Add(traitNameText)
			predictedOutcomeColumn.Add(predictionLabelCentered)
			quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewButtonsColumn.Add(viewDetailsButton)

			traitNameColumn.Add(widget.NewSeparator())
			predictedOutcomeColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())
		}	

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

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

		traitsContainer := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, quantityOfLociKnownColumn)

		if (multipleGenomesExist == true){

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

			conflictExistsColumn.Add(conflictExistsHelpButton)
			traitsContainer.Add(conflictExistsColumn)
		}

		traitsContainer.Add(viewButtonsColumn)
		traitsContainer.Add(layout.NewSpacer())

		return traitsContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, traitName string, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisDiscreteTraitDetailsPage(window, personIdentifier, analysisObject, traitName, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - " + traitName)

	backButton := getBackButtonCentered(previousPage)

	allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (multipleGenomesExist == false){
			description := getLabelCentered("Below is the trait information for this person's genome.")

			return description
		}

		description1 := getLabelCentered("Below is the trait information for this person's genomes.")
		description2 := getLabelCentered("The first two genomes are created by combining multiple genomes.")
	
		descriptionsSection := container.NewVBox(description1, description2)
	
		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

	traitNameLabel := widget.NewLabel("Trait:")
	traitNameText := getBoldLabel(traitName)
	traitInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewTraitDetailsPage(window, traitName, currentPage)
		})
	traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitInfoButton, layout.NewSpacer())

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

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

		emptyLabel1 := widget.NewLabel("")
		genomeNameLabel := getItalicLabelCentered("Genome Name")

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

		emptyLabel3 := widget.NewLabel("")
		predictionConfidenceLabel := getItalicLabelCentered("Prediction Confidence")

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

		quantityOfLabel2 := getItalicLabelCentered("Quantity Of")
		rulesTestedLabel := getItalicLabelCentered("Rules Tested")

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

		genomeNameColumn := container.NewVBox(emptyLabel1, genomeNameLabel, widget.NewSeparator())
		predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
		neuralNetworkPredictionConfidenceColumn := container.NewVBox(emptyLabel3, predictionConfidenceLabel, widget.NewSeparator())
		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator())
		numberOfRulesTestedColumn := container.NewVBox(quantityOfLabel2, rulesTestedLabel, widget.NewSeparator())
		viewDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())

		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})

				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())

				return genomeNameCell
			}

			genomeNameCell := getGenomeNameCell()

			neuralNetworkExists, neuralNetworkAnalysisExists, neuralNetworkPredictedOutcome, neuralNetworkPredictionConfidence, quantityOfLociKnown_NeuralNetwork, _, anyRulesExist, rulesAnalysisExists, _, rulesPredictedOutcomeExists, rulesPredictedOutcome, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetPersonDiscreteTraitInfoFromGeneticAnalysis(analysisObject, traitName, genomeIdentifier)
			if (err != nil) { return err }

			if (neuralNetworkExists == false && anyRulesExist == false){
				// This trait is not analyzable
				return errors.New("setViewPersonGeneticAnalysisTraitDetailsPage called with trait which cannot be analyzed via neural networks and traits.")
			}

			getPredictedOutcomeLabel := func()fyne.Widget{

				if (neuralNetworkExists == true){
					if (neuralNetworkAnalysisExists == false){

						predictedOutcomeLabel := getItalicLabel("Unknown")

						return predictedOutcomeLabel
					}

					predictedOutcomeLabel := getBoldLabel(neuralNetworkPredictedOutcome)

					return predictedOutcomeLabel
				}

				// anyRulesExist must be true

				if (rulesAnalysisExists == false || rulesPredictedOutcomeExists == false){

					predictedOutcomeLabel := getItalicLabel("Unknown")
					return predictedOutcomeLabel
				}

				predictedOutcomeLabel := getBoldLabel(rulesPredictedOutcome)

				return predictedOutcomeLabel
			}

			predictedOutcomeLabel := getPredictedOutcomeLabel()

			predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)

			if (neuralNetworkExists == true){

				if (neuralNetworkAnalysisExists == false){

					predictedOutcomeConfidenceLabel := getItalicLabelCentered("Unknown")

					neuralNetworkPredictionConfidenceColumn.Add(predictedOutcomeConfidenceLabel)
				} else {

					neuralNetworkPredictionConfidenceString := helpers.ConvertIntToString(neuralNetworkPredictionConfidence)
					predictedOutcomeConfidenceLabel := getBoldLabelCentered(neuralNetworkPredictionConfidenceString + "%")

					neuralNetworkPredictionConfidenceColumn.Add(predictedOutcomeConfidenceLabel)
				}

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

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

				quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

				quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

				quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)

			} else {

				traitRulesMap, err := traits.GetTraitRulesMap(traitName)
				if (err != nil){ return err }

				totalNumberOfRules := len(traitRulesMap)
				totalNumberOfRulesString := helpers.ConvertIntToString(totalNumberOfRules)	

				quantityOfRulesTestedString := helpers.ConvertIntToString(quantityOfRulesTested)	

				quantityOfRulesTestedLabel := getBoldLabelCentered(quantityOfRulesTestedString + "/" + totalNumberOfRulesString)

				numberOfRulesTestedColumn.Add(quantityOfRulesTestedLabel)
			}

			viewDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					if (neuralNetworkExists == true){
						//TODO
						showUnderConstructionDialog(window)
					} else {
						setViewPersonGenomeDiscreteTraitRulesPage(window, analysisObject, traitName, genomeIdentifier, genomeName, currentPage)
					}
				})

			genomeNameColumn.Add(genomeNameCell)
			predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)

			viewDetailsButtonsColumn.Add(viewDetailsButton)
			genomeNameColumn.Add(widget.NewSeparator())
			predictedOutcomeColumn.Add(widget.NewSeparator())
			neuralNetworkPredictionConfidenceColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			numberOfRulesTestedColumn.Add(widget.NewSeparator())
			viewDetailsButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

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

				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date that the genome was exported
				
				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

		neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)

		if (neuralNetworkExists == true){

			neuralNetworksHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setDiscreteTraitNeuralNetworkPredictionExplainerPage(window, currentPage)
				})
			predictedOutcomeColumn.Add(neuralNetworksHelpButton)
		} else {

			rulesHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setDiscreteTraitRulesPredictionExplainerPage(window, currentPage)
				})
			predictedOutcomeColumn.Add(rulesHelpButton)
		}

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

		numberOfRulesTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage)
			})
		numberOfRulesTestedColumn.Add(numberOfRulesTestedHelpButton)

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

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, predictedOutcomeColumn)

		if (neuralNetworkExists == true){
			genomesContainer.Add(neuralNetworkPredictionConfidenceColumn)
			genomesContainer.Add(quantityOfLociKnownColumn)
		} else {
			genomesContainer.Add(numberOfRulesTestedColumn)
		}

		genomesContainer.Add(viewDetailsButtonsColumn)
		genomesContainer.Add(layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


// Ths function provides a page to view the discrete trait rules for a particular genome from a genetic analysis
func setViewPersonGenomeDiscreteTraitRulesPage(window fyne.Window, geneticAnalysisObject geneticAnalysis.PersonAnalysis, traitName string, genomeIdentifier [16]byte, genomeName string, previousPage func()){

	setLoadingScreen(window, "Loading Trait Rules", "Loading trait rules...")

	currentPage := func(){setViewPersonGenomeDiscreteTraitRulesPage(window, geneticAnalysisObject, traitName, genomeIdentifier, genomeName, previousPage)}

	title := getPageTitleCentered("View Trait Rules - " + traitName)

	backButton := getBackButtonCentered(previousPage)

	description1 := widget.NewLabel("Below are the trait rule results for this genome.")
	rulesHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setDiscreteTraitRulesExplainerPage(window, currentPage)
		})
	description1Row := container.NewHBox(layout.NewSpacer(), description1, rulesHelpButton, layout.NewSpacer())

	getGenomeNameRow := func()*fyne.Container{

		genomeLabel := widget.NewLabel("Genome:")
		genomeNameLabel := getBoldLabel(genomeName)

		if (genomeName == "Only Exclude Conflicts" || genomeName == "Only Include Shared"){
			genomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
					setCombinedGenomesExplainerPage(window, currentPage)
				})
			genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, genomeHelpButton, layout.NewSpacer())
			return genomeNameRow		
		}
		genomeNameRow := container.NewHBox(layout.NewSpacer(), genomeLabel, genomeNameLabel, layout.NewSpacer())
		return genomeNameRow
	}

	genomeNameRow := getGenomeNameRow()

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

	numberOfRulesTested := 0

	rulesList_RulePassed := make([]string, 0)
	rulesList_RuleNotPassed := make([]string, 0)
	rulesList_Unknown := make([]string, 0)

	for ruleIdentifierHex, _ := range traitRulesMap{

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

		ruleStatusIsKnown, genomePassesRule, err := readGeneticAnalysis.GetPersonDiscreteTraitRuleInfoFromGeneticAnalysis(geneticAnalysisObject, traitName, ruleIdentifier, genomeIdentifier)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		if (ruleStatusIsKnown == false){
			rulesList_Unknown = append(rulesList_Unknown, ruleIdentifierHex)
			continue
		}
		numberOfRulesTested += 1			

		if (genomePassesRule == true){
			rulesList_RulePassed = append(rulesList_RulePassed, ruleIdentifierHex)
		} else {
			rulesList_RuleNotPassed = append(rulesList_RuleNotPassed, ruleIdentifierHex)
		}
	}

	numberOfRulesTestedString := helpers.ConvertIntToString(numberOfRulesTested)		

	totalNumberOfRules := len(traitRulesMap)
	totalNumberOfRulesString := helpers.ConvertIntToString(totalNumberOfRules)	

	rulesTestedLabel := widget.NewLabel("Rules Tested:")
	rulesTestedText := getBoldLabel(numberOfRulesTestedString + "/" + totalNumberOfRulesString)
	rulesTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage)
		})

	rulesTestedRow := container.NewHBox(layout.NewSpacer(), rulesTestedLabel, rulesTestedText, rulesTestedHelpButton, layout.NewSpacer())

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

		ruleIdentifierLabel := getItalicLabelCentered("Rule Identifier")
		ruleEffectsLabel := getItalicLabelCentered("Rule Effects")
		genomePassesRuleLabel := getItalicLabelCentered("Genome Passes Rule")
		emptyLabel := widget.NewLabel("")

		ruleIdentifierColumn := container.NewVBox(ruleIdentifierLabel, widget.NewSeparator())
		ruleEffectsColumn := container.NewVBox(ruleEffectsLabel, widget.NewSeparator())
		genomePassesRuleColumn := container.NewVBox(genomePassesRuleLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator())

		addRuleRow := func(ruleIdentifierHex string)error{

			ruleIdentifierLabel := getBoldLabelCentered(ruleIdentifierHex)

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

			ruleStatusIsKnown, genomePassesRule, err := readGeneticAnalysis.GetPersonDiscreteTraitRuleInfoFromGeneticAnalysis(geneticAnalysisObject, traitName, ruleIdentifier, genomeIdentifier)
			if (err != nil) { return err }

			getGenomePassesRuleText := func()string{
				if (ruleStatusIsKnown == false){

					result := translate("Unknown")
					return result
				}
				genomePassesRuleString := helpers.ConvertBoolToYesOrNoString(genomePassesRule)
				genomePassesRuleTranslated := translate(genomePassesRuleString)

				return genomePassesRuleTranslated
			}

			genomePassesRuleText := getGenomePassesRuleText()
			genomePassesRuleLabel := getBoldLabelCentered(genomePassesRuleText)

			// 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
			
			viewRuleButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisDiscreteTraitRuleDetailsPage(window, geneticAnalysisObject, traitName, ruleIdentifier, currentPage)
				})

			ruleIdentifierColumn.Add(ruleIdentifierLabel)
			genomePassesRuleColumn.Add(genomePassesRuleLabel)
			viewButtonsColumn.Add(viewRuleButton)

			traitRuleObject, exists := traitRulesMap[ruleIdentifierHex]
			if (exists == false){
				return errors.New("Trait rule not found after being found already.")
			}

			ruleOutcomePointsMap := traitRuleObject.OutcomePointsMap

			outcomeNamesList := helpers.GetListOfMapKeys(ruleOutcomePointsMap)

			// We have to sort the outcome names so they always show up in the same order
			helpers.SortStringListToUnicodeOrder(outcomeNamesList)

			for index, outcomeName := range outcomeNamesList{

				outcomeEffectInt, exists := ruleOutcomePointsMap[outcomeName]
				if (exists == false){
					return errors.New("OutcomeName not found in ruleOutcomePointsMap after being found already: " + outcomeName)
				}

				getOutcomeEffectString := func()string{
		
					outcomeEffectString := helpers.ConvertIntToString(outcomeEffectInt)					
	
					if (outcomeEffectInt < 0){
						return outcomeEffectString
					}
					outcomeEffect := "+" + outcomeEffectString
					return outcomeEffect				
				}

				outcomeEffect := getOutcomeEffectString()

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

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

					ruleIdentifierColumn.Add(emptyLabelA)
					genomePassesRuleColumn.Add(emptyLabelB)
					viewButtonsColumn.Add(emptyLabelC)
				}
			}

			ruleIdentifierColumn.Add(widget.NewSeparator())
			ruleEffectsColumn.Add(widget.NewSeparator())
			genomePassesRuleColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		// Items within each group are sorted so they will display the same way whenever user refreshes page

		helpers.SortStringListToUnicodeOrder(rulesList_RulePassed)
		helpers.SortStringListToUnicodeOrder(rulesList_RuleNotPassed)
		helpers.SortStringListToUnicodeOrder(rulesList_Unknown)

		for _, ruleIdentifier := range rulesList_RulePassed{

			err = addRuleRow(ruleIdentifier)
			if (err != nil) { return nil, err }
		}
		for _, ruleIdentifier := range rulesList_RuleNotPassed{

			err = addRuleRow(ruleIdentifier)
			if (err != nil) { return nil, err }
		}
		for _, ruleIdentifier := range rulesList_Unknown{

			err = addRuleRow(ruleIdentifier)
			if (err != nil) { return nil, err }
		}

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

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

		genomePassesRuleColumn.Add(genomePassesRuleHelpButton)

		traitRulesGrid := container.NewHBox(layout.NewSpacer(), ruleIdentifierColumn, ruleEffectsColumn, genomePassesRuleColumn, viewButtonsColumn, layout.NewSpacer())

		return traitRulesGrid, nil
	}

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1Row, widget.NewSeparator(), genomeNameRow, widget.NewSeparator(), rulesTestedRow, widget.NewSeparator(), traitRulesGrid)

	setPageContent(page, window)
}


// This function provides a page to view the details of a specific trait rule from a person genetic analysis
// The page will show the rule details for all of the person's genomes
func setViewPersonGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window, geneticAnalysisObject geneticAnalysis.PersonAnalysis, traitName string, ruleIdentifier [3]byte, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisDiscreteTraitRuleDetailsPage(window, geneticAnalysisObject, traitName, ruleIdentifier, previousPage)}

	title := getPageTitleCentered("Trait Rule Details - " + traitName)

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is the rule result for the person's genomes.")

	ruleIdentifierHex := encoding.EncodeBytesToHexString(ruleIdentifier[:])

	ruleIdentifierLabel := widget.NewLabel("Rule Identifier:")
	ruleIdentifierText := getBoldLabel(ruleIdentifierHex)
	ruleInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewDiscreteTraitRuleDetailsPage(window, traitName, ruleIdentifierHex, currentPage)
		})
	ruleIdentifierRow := container.NewHBox(layout.NewSpacer(), ruleIdentifierLabel, ruleIdentifierText, ruleInfoButton, layout.NewSpacer())

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(geneticAnalysisObject)
		if (err != nil) { return nil, err }

		genomeNameLabel := getItalicLabelCentered("Genome Name")
		genomePassesRuleLabel := getItalicLabelCentered("Genome Passes Rule")

		genomeNameColumn := container.NewVBox(genomeNameLabel, widget.NewSeparator())
		genomePassesRuleColumn := container.NewVBox(genomePassesRuleLabel, widget.NewSeparator())
		
		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			genomeRuleStatusKnown, genomePassesRule, err := readGeneticAnalysis.GetPersonDiscreteTraitRuleInfoFromGeneticAnalysis(geneticAnalysisObject, traitName, ruleIdentifier, genomeIdentifier)
			if (err != nil) { return err }

			getGenomePassesRuleText := func()string{

				if (genomeRuleStatusKnown == false){
					result := translate("Unknown")

					return result
				}

				genomePassesRuleString := helpers.ConvertBoolToYesOrNoString(genomePassesRule)

				genomePassesRuleTranslated := translate(genomePassesRuleString)
				return genomePassesRuleTranslated
			}
		
			genomePassesRuleText := getGenomePassesRuleText()

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})
	
				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())
				
				return genomeNameCell			
			}

			genomeNameCell := getGenomeNameCell()
			genomeNameColumn.Add(genomeNameCell)

			genomePassesRuleLabel := getBoldLabelCentered(genomePassesRuleText)
			genomePassesRuleColumn.Add(genomePassesRuleLabel)

			genomeNameColumn.Add(widget.NewSeparator())
			genomePassesRuleColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

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

				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date that the genome was exported

				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

		genomePassesRuleHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setGenomePassesDiscreteTraitRuleExplainerPage(window, currentPage)
			})
		genomePassesRuleColumn.Add(genomePassesRuleHelpButton)

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, genomePassesRuleColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


func setViewPersonGeneticAnalysisNumericTraitsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - Numeric Traits")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the numeric traits for this person's genome.")

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

		allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
		if (err != nil){ return nil, err }

		// Outputs:
		//	-[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
		//	-error
		getMainGenomeIdentifier := func()([16]byte, error){

			if (multipleGenomesExist == true){
				return onlyExcludeConflictsGenomeIdentifier, nil
			}
			// Only 1 genome exists

			genomeIdentifier := allRawGenomeIdentifiersList[0]

			return genomeIdentifier, nil
		}

		mainGenomeIdentifier, err := getMainGenomeIdentifier()
		if (err != nil){ return nil, err }

		traitNameLabel := getItalicLabelCentered("Trait Name")

		predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")

		confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

		conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")

		emptyLabel5 := widget.NewLabel("")

		traitNameColumn := container.NewVBox(traitNameLabel, widget.NewSeparator())
		predictedOutcomeColumn := container.NewVBox(predictedOutcomeLabel, widget.NewSeparator())
		confidenceRangeColumn := container.NewVBox(confidenceRangeLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(conflictExistsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel5, widget.NewSeparator())

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

		for _, traitObject := range traitObjectsList{

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

			traitName := traitObject.TraitName
			traitLociList := traitObject.LociList

			if (len(traitLociList) == 0){
				// This trait does not have any loci to analyze
				// We cannot analyze it yet
				continue
			}

			traitNameText := getBoldLabelCentered(traitName)

			neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)
			if (neuralNetworkExists == false){
				// This trait has no neural network
				// We cannot analyze it
				continue
			}

			analysisExists, predictedOutcome, confidenceRangesMap, _, _, conflictExists, err := readGeneticAnalysis.GetPersonNumericTraitInfoFromGeneticAnalysis(analysisObject, traitName, mainGenomeIdentifier)	
			if (err != nil) { return nil, err }

			outcomeFormatter := traitObject.NumericValueFormatter

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

				if (analysisExists == false){
					result := getItalicLabel("Unknown")
					return result, nil
				}

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

				result := getBoldLabel(predictedOutcomeString)

				return result, nil
			}

			predictionLabel, err := getPredictionLabel()
			if (err != nil) { return nil, err } 

			predictionLabelCentered := getWidgetCentered(predictionLabel)

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

				if (analysisExists == false){
					unknownLabel := widget.NewLabel("Unknown")
					return unknownLabel, 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(confidenceRangesMap)

				// 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 := confidenceRangesMap[closestToEightyPercentage]
				if (exists == false){
					return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
				}

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

				closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)

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

				confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)

				return confidenceRangeLabel, nil
			}

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

			confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

			conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
			conflictExistsLabel := getBoldLabelCentered(conflictExistsString)

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewPersonGeneticAnalysisNumericTraitDetailsPage(window, personIdentifier, analysisObject, traitName, currentPage)
				}))

			traitNameColumn.Add(traitNameText)
			predictedOutcomeColumn.Add(predictionLabelCentered)
			confidenceRangeColumn.Add(confidenceRangeLabelCentered)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewButtonsColumn.Add(viewDetailsButton)

			traitNameColumn.Add(widget.NewSeparator())
			predictedOutcomeColumn.Add(widget.NewSeparator())
			confidenceRangeColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())
		}	

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

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

		traitsContainer := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, confidenceRangeColumn)

		if (multipleGenomesExist == true){

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

			conflictExistsColumn.Add(conflictExistsHelpButton)
			traitsContainer.Add(conflictExistsColumn)
		}

		traitsContainer.Add(viewButtonsColumn)
		traitsContainer.Add(layout.NewSpacer())

		return traitsContainer, nil
	}

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

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

	setPageContent(page, window)
}



func setViewPersonGeneticAnalysisNumericTraitDetailsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, traitName string, previousPage func()){

	currentPage := func(){setViewPersonGeneticAnalysisNumericTraitDetailsPage(window, personIdentifier, analysisObject, traitName, previousPage)}

	title := getPageTitleCentered("Viewing Genetic Analysis - " + traitName)

	backButton := getBackButtonCentered(previousPage)

	allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (multipleGenomesExist == false){
			description := getLabelCentered("Below is the trait information for this person's genome.")

			return description
		}

		description1 := getLabelCentered("Below is the trait information for this person's genomes.")
		description2 := getLabelCentered("The first two genomes are created by combining multiple genomes.")

		descriptionsSection := container.NewVBox(description1, description2)

		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

	traitNameLabel := widget.NewLabel("Trait:")
	traitNameText := getBoldLabel(traitName)
	traitInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewTraitDetailsPage(window, traitName, currentPage)
		})
	traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitInfoButton, layout.NewSpacer())		

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

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

		traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
		if (traitIsDiscreteOrNumeric != "Numeric"){
			return nil, errors.New("setViewPersonGeneticAnalysisNumericTraitDetailsPage called with non-numeric trait: " + traitName)
		}

		traitLociList := traitObject.LociList

		totalQuantityOfLoci := len(traitLociList)
		totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)

		emptyLabel1 := widget.NewLabel("")
		genomeNameLabel := getItalicLabelCentered("Genome Name")

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

		emptyLabel3 := widget.NewLabel("")
		predictionConfidenceLabel := getItalicLabelCentered("Confidence Range")

		quantityOfLabel := getItalicLabelCentered("Quantity of")
		lociKnownLabel := getItalicLabelCentered("Loci Known")

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

		genomeNameColumn := container.NewVBox(emptyLabel1, genomeNameLabel, widget.NewSeparator())
		predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
		predictionConfidenceColumn := container.NewVBox(emptyLabel3, predictionConfidenceLabel, widget.NewSeparator())
		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
		viewDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())

		addGenomeRow := func(genomeName string, genomeIdentifier [16]byte, isACombinedGenome bool)error{

			getGenomeNameCell := func()*fyne.Container{
				if (isACombinedGenome == false){

					genomeNameLabel := getBoldLabelCentered(genomeName)
					return genomeNameLabel
				}
				viewHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
						setCombinedGenomesExplainerPage(window, currentPage)
					})

				genomeNameLabel := getBoldLabel(genomeName)
				genomeNameCell := container.NewHBox(layout.NewSpacer(), viewHelpButton, genomeNameLabel, layout.NewSpacer())

				return genomeNameCell
			}

			genomeNameCell := getGenomeNameCell()

			analysisExists, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, _, err := readGeneticAnalysis.GetPersonNumericTraitInfoFromGeneticAnalysis(analysisObject, traitName, genomeIdentifier)
			if (err != nil) { return err }

			outcomeFormatter := traitObject.NumericValueFormatter

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

				if (analysisExists == false){

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

					return result, nil
				}

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

				outcomeLabel := getBoldLabel(outcomeFormatted)

				return outcomeLabel, nil
			}

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

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

				if (analysisExists == false){
					unknownLabel := widget.NewLabel("Unknown")
					return unknownLabel, 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, err := outcomeFormatter(closestToEightyPercentageConfidenceDistance, false)
				if (err != nil) { return nil, err }

				closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)

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

				confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)

				return confidenceRangeLabel, nil
			}

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

			confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)

			genomeQuantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
			quantityOfLociKnownLabel := getBoldLabelCentered(genomeQuantityOfLociKnownString + "/" + totalQuantityOfLociString)

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

			genomeNameColumn.Add(genomeNameCell)
			predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
			predictionConfidenceColumn.Add(confidenceRangeLabelCentered)
			quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
			viewDetailsButtonsColumn.Add(viewDetailsButton)

			genomeNameColumn.Add(widget.NewSeparator())
			predictedOutcomeColumn.Add(widget.NewSeparator())
			predictionConfidenceColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			viewDetailsButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		if (multipleGenomesExist == true){

			err := addGenomeRow("Only Exclude Conflicts", onlyExcludeConflictsGenomeIdentifier, true)
			if (err != nil){ return nil, err }

			err = addGenomeRow("Only Include Shared", onlyIncludeSharedGenomeIdentifier, true)
			if (err != nil){ return nil, err }
		}

		for _, genomeIdentifier := range allRawGenomeIdentifiersList{

			getGenomeName := func()(string, error){
				genomeFound, _, timeGenomeWasExported, _, _, _, companyName, _, _, err := myGenomes.GetMyRawGenomeMetadata(genomeIdentifier)
				if (err != nil) { return "", err }
				if (genomeFound == false){
					return "", errors.New("MyGenomeInfo for genome from analysisObject not found.")
				}

				if (multipleGenomesExist == false){
					return companyName, nil
				}

				// We show the date the genome was exported

				exportTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(timeGenomeWasExported, false)
				if (err != nil){ return "", err }

				genomeName := companyName + " (Exported " + exportTimeAgo + ")"

				return genomeName, nil
			}

			genomeName, err := getGenomeName()
			if (err != nil) { return nil, err }

			err = addGenomeRow(genomeName, genomeIdentifier, false)
			if (err != nil){ return nil, err }
		}

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

		predictionConfidenceHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				showUnderConstructionDialog(window)
			})
		predictionConfidenceColumn.Add(predictionConfidenceHelpButton)

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

		genomesContainer := container.NewHBox(layout.NewSpacer(), genomeNameColumn, predictedOutcomeColumn, predictionConfidenceColumn, quantityOfLociKnownColumn, viewDetailsButtonsColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)
}


