
package gui

// viewAnalysisGui_Couple.go implements pages to view a couple genetic analysis

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 "fyne.io/fyne/v2/canvas"

import "seekia/internal/appMemory"
import "seekia/internal/createCharts"
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 "seekia/internal/statisticsDatum"

import "slices"
import "image"
import "errors"


func setViewCoupleGeneticAnalysisPage(window fyne.Window, person1Identifier string, person2Identifier string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, numberOfPerson1GenomesAnalyzed int, numberOfPerson2GenomesAnalyzed int, previousPage func()){

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

	currentPage := func(){setViewCoupleGeneticAnalysisPage(window, person1Identifier, person2Identifier, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, numberOfPerson1GenomesAnalyzed, numberOfPerson2GenomesAnalyzed, 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.")

	person1Found, person1Name, _, _, err := myPeople.GetPersonInfo(person1Identifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (person1Found == false){
		setErrorEncounteredPage(window, errors.New("Couple person 1 not found."), previousPage)
		return
	}

	person2Found, person2Name, _, _, err := myPeople.GetPersonInfo(person2Identifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (person2Found == false){
		setErrorEncounteredPage(window, errors.New("Couple person 2 not found."), previousPage)
		return
	}

	coupleNameLabel := widget.NewLabel("Couple Name:")
	coupleNameText := getBoldLabel(person1Name + " + " + person2Name)
	coupleNameRow := container.NewHBox(layout.NewSpacer(), coupleNameLabel, coupleNameText, layout.NewSpacer())

	numberOfAnalyzedGenomesLabel := getLabelCentered("Number of Analyzed Genomes:")

	person1NameLabel := widget.NewLabel(person1Name + ":")
	person1NumberOfGenomesAnalyzedString := helpers.ConvertIntToString(numberOfPerson1GenomesAnalyzed)
	person1NumberOfGenomesAnalyzedLabel := getBoldLabel(person1NumberOfGenomesAnalyzedString)
	person1NumberOfAnalyzedGenomesRow := container.NewHBox(layout.NewSpacer(), person1NameLabel, person1NumberOfGenomesAnalyzedLabel, layout.NewSpacer())

	person2NameLabel := widget.NewLabel(person2Name + ":")
	person2NumberOfGenomesAnalyzedString := helpers.ConvertIntToString(numberOfPerson2GenomesAnalyzed)
	person2NumberOfGenomesAnalyzedLabel := getBoldLabel(person2NumberOfGenomesAnalyzedString)
	person2NumberOfAnalyzedGenomesRow := container.NewHBox(layout.NewSpacer(), person2NameLabel, person2NumberOfGenomesAnalyzedLabel, layout.NewSpacer())

	generalButton := widget.NewButton("General", func(){
			//TODO: Offspring inbred rating (kinship of parents), ancestry
			showUnderConstructionDialog(window)
		})
	monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){
			setViewCoupleGeneticAnalysisMonogenicDiseasesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
		})
	polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){
			setViewCoupleGeneticAnalysisPolygenicDiseasesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
		})
	discreteTraitsButton := widget.NewButton("Discrete Traits", func(){
			setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
		})
	numericTraitsButton := widget.NewButton("Numeric Traits", func(){
			setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
		})

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), warningLabel1, warningLabel2, widget.NewSeparator(), coupleNameRow, widget.NewSeparator(), numberOfAnalyzedGenomesLabel, person1NumberOfAnalyzedGenomesRow, person2NumberOfAnalyzedGenomesRow, widget.NewSeparator(), categoryButtonsGrid)

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisMonogenicDiseasesPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisMonogenicDiseasesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the monogenic disease probabilities for the couple's offspring.")

	//TODO: Sort so highest risk diseases are at the top. Everything else should be in normal order

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

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

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

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

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

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

		diseaseNameColumn := container.NewVBox(emptyLabelA, diseaseNameLabel, widget.NewSeparator())
		offspringProbabilityOfHavingDiseaseColumn := container.NewVBox(offspringProbabilityOfLabel, havingDiseaseLabel, 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{

			pair1GenomeIdentifierSlice := slices.Concat(pair1Person1GenomeIdentifier[:], pair1Person2GenomeIdentifier[:])

			pair1GenomeIdentifier := [32]byte(pair1GenomeIdentifierSlice)

			offspringHasDiseaseProbabilityIsKnown, _, genomePairOffspringProbabilityOfHavingDisease, _, _, _, conflictExists, err := readGeneticAnalysis.GetOffspringMonogenicDiseaseInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, pair1GenomeIdentifier)
			if (err != nil) { return nil, err }

			getOffspringProbabilityOfHavingDiseaseText := func()string{
				if (offspringHasDiseaseProbabilityIsKnown == false){
					result := translate("Unknown")
					return result
				}

				return genomePairOffspringProbabilityOfHavingDisease
			}

			offspringProbabilityOfHavingDiseaseText := getOffspringProbabilityOfHavingDiseaseText()

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

			offspringProbabilityOfHavingDiseaseLabel := getBoldLabelCentered(offspringProbabilityOfHavingDiseaseText)
			offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseLabel)

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

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewCoupleGeneticAnalysisMonogenicDiseaseDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, currentPage)
				}))
			viewButtonsColumn.Add(viewDetailsButton)
		
			diseaseNameColumn.Add(widget.NewSeparator())
			offspringProbabilityOfHavingDiseaseColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())	
		}

		offspringProbabilityOfHavingDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
				setOffspringProbabilityOfHavingMonogenicDiseaseExplainerPage(window, currentPage)
			})
		offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseHelpButton)

		diseasesGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, offspringProbabilityOfHavingDiseaseColumn)

		if (secondGenomePairExists == true){

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

			diseasesGrid.Add(conflictExistsColumn)
		}

		diseasesGrid.Add(viewButtonsColumn)
		diseasesGrid.Add(layout.NewSpacer())

		return diseasesGrid, nil
	}

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

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

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisMonogenicDiseaseDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisMonogenicDiseaseDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)
	
	pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (secondGenomePairExists == false){
			description := getLabelCentered("Below are the disease probabilities for the couple's offspring.")

			return description
		}

		description1 := getLabelCentered("Below are the disease probabilities for the couple's offspring.")
		description2 := getLabelCentered("Each genome pair combines different genomes from each person.")
	
		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())		

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

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

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

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

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

	viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
	pairNameColumn := container.NewVBox(emptyLabel3, emptyLabel4, widget.NewSeparator())
	offspringProbabilityOfHavingDiseaseColumn := container.NewVBox(offspringProbabilityOfLabel1, havingDiseaseLabel, widget.NewSeparator())
	offspringProbabilityOfHavingVariantColumn := container.NewVBox(offspringProbabilityOfLabel2, havingVariantLabel, widget.NewSeparator())
	viewOffspringVariantButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())

	addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{

		genomePairIdentifierSlice := slices.Concat(person1GenomeIdentifier[:], person2GenomeIdentifier[:])

		genomePairIdentifier := [32]byte(genomePairIdentifierSlice)

		offspringProbabilityOfHavingDiseaseIsKnown, _, genomePairOffspringProbabilityOfHavingDisease, offspringProbabilityOfHavingAVariantIsKnown, _, genomePairOffspringProbabilityOfHavingAVariant, _, err := readGeneticAnalysis.GetOffspringMonogenicDiseaseInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, genomePairIdentifier)
		if (err != nil) { return err }

		getOffspringProbabilityOfHavingDiseaseText := func()string{
			if (offspringProbabilityOfHavingDiseaseIsKnown == false){
				result := translate("Unknown")
				return result
			}

			return genomePairOffspringProbabilityOfHavingDisease
		}

		offspringProbabilityOfHavingDiseaseText := getOffspringProbabilityOfHavingDiseaseText()

		getOffspringProbabilityOfHavingAVariantText := func()string{

			if (offspringProbabilityOfHavingAVariantIsKnown == false){
				result := translate("Unknown")
				return result
			}
			return genomePairOffspringProbabilityOfHavingAVariant
		}

		offspringProbabilityOfHavingAVariantText := getOffspringProbabilityOfHavingAVariantText()

		genomePairNameLabel := getBoldLabelCentered(genomePairName)

		offspringProbabilityOfHavingDiseaseLabel := getBoldLabelCentered(offspringProbabilityOfHavingDiseaseText)

		offspringProbabilityOfHavingAVariantLabel := getBoldLabelCentered(offspringProbabilityOfHavingAVariantText)

		viewGenomePairButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
				setViewCoupleGeneticAnalysisMonogenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, currentPage)
			})

		viewOffspringVariantsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewCoupleMonogenicDiseaseVariantsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, currentPage)
			})

		viewGenomePairButtonsColumn.Add(viewGenomePairButton)
		pairNameColumn.Add(genomePairNameLabel)
		offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseLabel)
		offspringProbabilityOfHavingVariantColumn.Add(offspringProbabilityOfHavingAVariantLabel)
		viewOffspringVariantButtonsColumn.Add(viewOffspringVariantsButton)

		viewGenomePairButtonsColumn.Add(widget.NewSeparator())
		pairNameColumn.Add(widget.NewSeparator())
		offspringProbabilityOfHavingDiseaseColumn.Add(widget.NewSeparator())
		offspringProbabilityOfHavingVariantColumn.Add(widget.NewSeparator())		
		viewOffspringVariantButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (secondGenomePairExists == true){
		err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
		if (err != nil) {	
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

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

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

	offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseHelpButton)
	offspringProbabilityOfHavingVariantColumn.Add(offspringProbabilityOfHavingVariantHelpButton)

	genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, offspringProbabilityOfHavingDiseaseColumn, offspringProbabilityOfHavingVariantColumn, viewOffspringVariantButtonsColumn, layout.NewSpacer())

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

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisMonogenicDiseaseGenomePairDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, genomePairIdentifier [32]byte, genomePairName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisMonogenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, previousPage)}

	title := getPageTitleCentered("Viewing Couple Genome Pair Info")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is the disease information for both genomes in the genome pair.")

	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())	

	genomePairLabel := widget.NewLabel("Genome Pair:")
	genomePairNameLabel := getBoldLabel(genomePairName)
	genomePairHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setCoupleGenomePairExplainerPage(window, currentPage)
		})
	genomePairRow := container.NewHBox(layout.NewSpacer(), genomePairLabel, genomePairNameLabel, genomePairHelpButton, layout.NewSpacer())

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

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

	emptyLabelA := widget.NewLabel("")
	personNameLabel := getItalicLabelCentered("Person Name")

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

	probabilityOfLabel := getItalicLabelCentered("Probability Of")
	passingAVariantLabel := getItalicLabelCentered("Passing A Variant")

	numberOfLabel := getItalicLabelCentered("Number Of")
	variantsTestedLabel := getItalicLabelCentered("Variants Tested")

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

	personNameColumn := container.NewVBox(emptyLabelA, personNameLabel, widget.NewSeparator())
	genomeNameColumn := container.NewVBox(emptyLabelB, genomeNameLabel, widget.NewSeparator())
	probabilityOfPassingAVariantColumn := container.NewVBox(probabilityOfLabel, passingAVariantLabel, widget.NewSeparator())
	numberOfVariantsTestedColumn := container.NewVBox(numberOfLabel, variantsTestedLabel, widget.NewSeparator())
	viewGenomeButtonsColumn := container.NewVBox(emptyLabelC, emptyLabelD, widget.NewSeparator())

	addGenomeRow := func(isPerson1 bool, personName string, inputGenomeIdentifier [16]byte)error{

		personAnalysisGenomeIdentifier, _, genomeIsCombined, combinedType, err := readGeneticAnalysis.GetMatchingPersonAnalysisGenomeIdentifierFromCoupleAnalysis(isPerson1, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, inputGenomeIdentifier)
		if (err != nil) { return err }

		personNameTrimmed, _, err := helpers.TrimAndFlattenString(personName, 10)
		if (err != nil) { return err }
		
		personNameLabel := getBoldLabelCentered(personNameTrimmed)

		getPersonAnalysisObject := func()geneticAnalysis.PersonAnalysis{
			if (isPerson1 == true){
				return person1AnalysisObject
			}
			return person2AnalysisObject
		}

		personAnalysisObject := getPersonAnalysisObject()

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

			if (genomeIsCombined == false){

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

				return companyName, nil
			}

			return combinedType, nil
		}

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

		genomeNameLabel := getBoldLabelCentered(genomeName)

		probabilitiesKnown, _, _, probabilityOfPassingAVariantFormatted, personNumberOfVariantsTested, _, _, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(personAnalysisObject, diseaseName, personAnalysisGenomeIdentifier)
		if (err != nil) { return err }

		getProbabilityOfPassingAVariantText := func()string{

			if (probabilitiesKnown == false){

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

			return probabilityOfPassingAVariantFormatted
		}
		genomeProbabilityOfPassingAVariantText := getProbabilityOfPassingAVariantText()

		genomeProbabilityOfPassingAVariantLabel := getBoldLabelCentered(genomeProbabilityOfPassingAVariantText)

		personNumberOfVariantsTestedString := helpers.ConvertIntToString(personNumberOfVariantsTested)
		genomeNumberOfVariantsTestedLabel := getBoldLabelCentered(personNumberOfVariantsTestedString + "/" + totalNumberOfVariantsString)

		viewGenomeButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewPersonGenomeMonogenicDiseaseVariantsPage(window, personAnalysisObject, personAnalysisGenomeIdentifier, genomeName, diseaseName, currentPage)
			})

		personNameColumn.Add(personNameLabel)
		genomeNameColumn.Add(genomeNameLabel)
		probabilityOfPassingAVariantColumn.Add(genomeProbabilityOfPassingAVariantLabel)
		numberOfVariantsTestedColumn.Add(genomeNumberOfVariantsTestedLabel)
		viewGenomeButtonsColumn.Add(viewGenomeButton)

		personNameColumn.Add(widget.NewSeparator())
		genomeNameColumn.Add(widget.NewSeparator())
		probabilityOfPassingAVariantColumn.Add(widget.NewSeparator())
		numberOfVariantsTestedColumn.Add(widget.NewSeparator())
		viewGenomeButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	person1GenomeIdentifier, person2GenomeIdentifier := helpers.Split32ByteArrayInHalf(genomePairIdentifier)

	err = addGenomeRow(true, person1Name, person1GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	err = addGenomeRow(false, person2Name, person2GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

	probabilityOfPassingAVariantColumn.Add(probabilityOfPassingAVariantHelpButton)
	numberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButton)

	genomesGrid := container.NewHBox(layout.NewSpacer(), personNameColumn, genomeNameColumn, probabilityOfPassingAVariantColumn, numberOfVariantsTestedColumn, viewGenomeButtonsColumn, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, diseaseNameRow, genomePairRow, widget.NewSeparator(), genomesGrid)	

	setPageContent(page, window)
}


// This function provides a page to view the offspring disease variant probabilities for a particular genome pair from a couple genetic analysis
func setViewCoupleMonogenicDiseaseVariantsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, genomePairIdentifier [32]byte, genomePairName string, previousPage func()){

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

	currentPage := func(){setViewCoupleMonogenicDiseaseVariantsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

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

	genomePairLabel := widget.NewLabel("Genome Pair:")
	genomePairNameLabel := getBoldLabel(genomePairName)
	viewGenomePairInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewCoupleGeneticAnalysisMonogenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, currentPage)
		})

	genomePairRow := container.NewHBox(layout.NewSpacer(), genomePairLabel, genomePairNameLabel, viewGenomePairInfoButton, layout.NewSpacer())

	//Outputs:
	//	-int: Number of variants tested
	//	-int: Number of loci tested
	//	-int: Number of phased loci
	getVariantAndLociInfo := func()(int, int, int, error){

		person1GenomeIdentifier, person2GenomeIdentifier := helpers.Split32ByteArrayInHalf(genomePairIdentifier)

		person1AnalysisGenomeIdentifier, _, _, _, err := readGeneticAnalysis.GetMatchingPersonAnalysisGenomeIdentifierFromCoupleAnalysis(true, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, person1GenomeIdentifier)
		if (err != nil) { return 0, 0, 0, err }

		_, _, _, _, person1NumberOfVariantsTested, person1NumberOfLociTested, person1NumberOfPhasedLoci, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(person1AnalysisObject, diseaseName, person1AnalysisGenomeIdentifier)
		if (err != nil) { return 0, 0, 0, err }

		person2AnalysisGenomeIdentifier, _, _, _, err := readGeneticAnalysis.GetMatchingPersonAnalysisGenomeIdentifierFromCoupleAnalysis(false, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, person2GenomeIdentifier)
		if (err != nil) { return 0, 0, 0, err }
		
		_, _, _, _, person2NumberOfVariantsTested, person2NumberOfLociTested, person2NumberOfPhasedLoci, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(person2AnalysisObject, diseaseName, person2AnalysisGenomeIdentifier)
		if (err != nil) { return 0, 0, 0, err }

		numberOfVariantsTested := person1NumberOfVariantsTested + person2NumberOfVariantsTested
		numberOfLociTested := person1NumberOfLociTested + person2NumberOfLociTested
		numberOfPhasedLoci := person1NumberOfPhasedLoci + person2NumberOfPhasedLoci

		return numberOfVariantsTested, numberOfLociTested, numberOfPhasedLoci, nil
	}

	numberOfVariantsTested, numberOfLociTested, numberOfPhasedLoci, err := getVariantAndLociInfo()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

	totalNumberOfVariants := len(diseaseVariantsMap) * 2
	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 with pages

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

		emptyLabelA := widget.NewLabel("")
		variantNameLabel := getItalicLabelCentered("Variant Name")

		probabilityOfLabelA := getItalicLabelCentered("Probability Of")
		zeroMutationsLabel := getItalicLabelCentered("0 Mutations")

		probabilityOfLabelB := getItalicLabelCentered("Probability Of")
		oneMutationLabel := getItalicLabelCentered("1 Mutation")

		probabilityOfLabelC := getItalicLabelCentered("Probability Of")
		twoMutationsLabel := getItalicLabelCentered("2 Mutations")

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

		variantNameColumn := container.NewVBox(emptyLabelA, variantNameLabel, widget.NewSeparator())
		probabilityOf0MutationsColumn := container.NewVBox(probabilityOfLabelA, zeroMutationsLabel, widget.NewSeparator())
		probabilityOf1MutationColumn := container.NewVBox(probabilityOfLabelB, oneMutationLabel, widget.NewSeparator())
		probabilityOf2MutationsColumn := container.NewVBox(probabilityOfLabelC, twoMutationsLabel, widget.NewSeparator())
		variantInfoButtonsColumn := container.NewVBox(emptyLabelB, emptyLabelC, widget.NewSeparator())

		addVariantRow := func(variantIdentifierHex string)error{
			
			variantObject, exists := diseaseVariantsMap[variantIdentifierHex]
			if (exists == false){
				return errors.New("Cannot add variant row: Variant missing from diseaseVariantsMap")
			}

			variantIdentifier, err := encoding.DecodeHexStringTo3ByteArray(variantIdentifierHex)
			if (err != nil){
				return errors.New("addVariantRow called with invalid variantIdentifier: " + variantIdentifierHex)
			}

			variantName := variantObject.VariantNames[0]

			offspringProbabilitiesKnown, probabilityOf0MutationsLowerBound, probabilityOf0MutationsUpperBound, probabilityOf0MutationsFormatted, probabilityOf1MutationLowerBound, probabilityOf1MutationUpperBound, probabilityOf1MutationFormatted, probabilityOf2MutationsLowerBound, probabilityOf2MutationsUpperBound, probabilityOf2MutationsFormatted, err := readGeneticAnalysis.GetOffspringMonogenicDiseaseVariantInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, variantIdentifier, genomePairIdentifier)
			if (err != nil) { return err }

			getProbabilityOf0MutationsText := func()string{
				if (offspringProbabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf0MutationsLowerBound == 0 && probabilityOf0MutationsUpperBound == 100){
					result := translate("Unknown")
					return result
				}

				return probabilityOf0MutationsFormatted
			}
	
			probabilityOf0MutationsText := getProbabilityOf0MutationsText()

			getProbabilityOf1MutationText := func()string{
				if (offspringProbabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf1MutationLowerBound == 0 && probabilityOf1MutationUpperBound == 100){
					result := translate("Unknown")
					return result
				}

				return probabilityOf1MutationFormatted
			}

			probabilityOf1MutationText := getProbabilityOf1MutationText()

			getProbabilityOf2MutationsText := func()string{
				if (offspringProbabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf2MutationsLowerBound == 0 && probabilityOf2MutationsUpperBound == 100){
					result := translate("Unknown")
					return result
				}

				return probabilityOf2MutationsFormatted
			}

			probabilityOf2MutationsText := getProbabilityOf2MutationsText()

			variantNameLabel := getBoldLabelCentered(variantName)
			
			probabilityOf0MutationsLabel := getBoldLabelCentered(probabilityOf0MutationsText)
			probabilityOf1MutationLabel := getBoldLabelCentered(probabilityOf1MutationText)
			probabilityOf2MutationsLabel := getBoldLabelCentered(probabilityOf2MutationsText)

			viewVariantDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewCoupleGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, variantIdentifier, currentPage)
				})

			variantNameColumn.Add(variantNameLabel)
			probabilityOf0MutationsColumn.Add(probabilityOf0MutationsLabel)
			probabilityOf1MutationColumn.Add(probabilityOf1MutationLabel)
			probabilityOf2MutationsColumn.Add(probabilityOf2MutationsLabel)			
			variantInfoButtonsColumn.Add(viewVariantDetailsButton)

			variantNameColumn.Add(widget.NewSeparator())
			probabilityOf0MutationsColumn.Add(widget.NewSeparator())
			probabilityOf1MutationColumn.Add(widget.NewSeparator())
			probabilityOf2MutationsColumn.Add(widget.NewSeparator())
			variantInfoButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		variantsWithNonZeroRiskList := make([]string, 0)
		variantsWithZeroRiskFullyKnownList := make([]string, 0)
		variantsWithZeroRiskPartiallyKnownList := make([]string, 0)
		variantsWithUnknownRiskList := make([]string, 0)

		for variantIdentifierHex, _ := range diseaseVariantsMap{

			variantIdentifier, err := encoding.DecodeHexStringTo3ByteArray(variantIdentifierHex)
			if (err != nil){
				return nil, errors.New("diseaseVariantsMap contains invalid variantIdentifier: " + variantIdentifierHex)
			}

			probabilitesKnown, probabilityOf0MutationsLowerBound, probabilityOf0MutationsUpperBound, _, probabilityOf1MutationLowerBound, probabilityOf1MutationUpperBound, _, probabilityOf2MutationsLowerBound, probabilityOf2MutationsUpperBound, _, err := readGeneticAnalysis.GetOffspringMonogenicDiseaseVariantInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, variantIdentifier, genomePairIdentifier)
			if (err != nil) { return nil, err }

			if (probabilitesKnown == false){
				variantsWithUnknownRiskList = append(variantsWithUnknownRiskList, variantIdentifierHex)
				continue
			}

			if (probabilityOf1MutationLowerBound != 0 || probabilityOf2MutationsLowerBound != 0){
				variantsWithNonZeroRiskList = append(variantsWithNonZeroRiskList, variantIdentifierHex)
				continue		
			}

			// Risk is either 0 or partially unknown
			if (probabilityOf0MutationsLowerBound == 100 && probabilityOf0MutationsUpperBound == 100 && probabilityOf1MutationLowerBound == 0 && probabilityOf1MutationUpperBound == 0 && probabilityOf2MutationsLowerBound == 0 && probabilityOf2MutationsUpperBound == 0){
				variantsWithZeroRiskFullyKnownList = append(variantsWithZeroRiskFullyKnownList, variantIdentifierHex)
				continue
			}

			variantsWithZeroRiskPartiallyKnownList = append(variantsWithZeroRiskPartiallyKnownList, variantIdentifierHex)
		}

		// We sort each list so the variants show up in the same order each time

		helpers.SortStringListToUnicodeOrder(variantsWithNonZeroRiskList)
		helpers.SortStringListToUnicodeOrder(variantsWithZeroRiskFullyKnownList)
		helpers.SortStringListToUnicodeOrder(variantsWithZeroRiskPartiallyKnownList)
		helpers.SortStringListToUnicodeOrder(variantsWithUnknownRiskList)

		for _, variantIdentifier := range variantsWithNonZeroRiskList{

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

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

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

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

		variantsGrid := container.NewHBox(layout.NewSpacer(), variantNameColumn, probabilityOf0MutationsColumn, probabilityOf1MutationColumn, probabilityOf2MutationsColumn, variantInfoButtonsColumn, 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(), genomePairRow, widget.NewSeparator(), variantsTestedRow, widget.NewSeparator(), phasedLociRow, widget.NewSeparator(), variantsGrid)

	setPageContent(page, window)
}


// This function provides a page to view the details of a specific variant from a genetic analysis
// It will show the variant details for all of the couple's genome pairs
func setViewCoupleGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, variantIdentifier [3]byte, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisMonogenicDiseaseVariantDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, variantIdentifier, previousPage)}

	title := getPageTitleCentered("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 disease variant analysis for the couple.")

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

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

		emptyLabelA := widget.NewLabel("")
		emptyLabelB := widget.NewLabel("")
		
		emptyLabelC := widget.NewLabel("")
		genomePairLabel := getItalicLabelCentered("Genome Pair")

		probabilityOfLabelA := getItalicLabelCentered("Probability Of")
		zeroMutationsLabel := getItalicLabelCentered("0 Mutations")

		probabilityOfLabelB := getItalicLabelCentered("Probability Of")
		oneMutationLabel := getItalicLabelCentered("1 Mutation")

		probabilityOfLabelC := getItalicLabelCentered("Probability Of")
		twoMutationsLabel := getItalicLabelCentered("2 Mutations")

		viewGenomePairInfoButtonsColumn := container.NewVBox(emptyLabelA, emptyLabelB, widget.NewSeparator())
		genomePairNameColumn := container.NewVBox(emptyLabelC, genomePairLabel, widget.NewSeparator())
		probabilityOf0MutationsColumn := container.NewVBox(probabilityOfLabelA, zeroMutationsLabel, widget.NewSeparator())
		probabilityOf1MutationColumn := container.NewVBox(probabilityOfLabelB, oneMutationLabel, widget.NewSeparator())
		probablityOf2MutationsColumn := container.NewVBox(probabilityOfLabelC, twoMutationsLabel, widget.NewSeparator())
		
		addGenomePairRow := func(genomePairName string, genomePairIdentifier [32]byte)error{

		 	probabilitiesKnown, probabilityOf0MutationsLowerBound, probabilityOf0MutationsUpperBound, probabilityOf0MutationsFormatted, probabilityOf1MutationLowerBound, probabilityOf1MutationUpperBound, probabilityOf1MutationFormatted, probabilityOf2MutationsLowerBound, probabilityOf2MutationsUpperBound, probabilityOf2MutationsFormatted, err := readGeneticAnalysis.GetOffspringMonogenicDiseaseVariantInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, variantIdentifier, genomePairIdentifier)
			if (err != nil) { return err }

			getProbabilityOf0MutationsText := func()string{
				if (probabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf0MutationsLowerBound == 0 && probabilityOf0MutationsUpperBound == 100){
					result := translate("Unknown")
					return result
				}
	
				return probabilityOf0MutationsFormatted
			}
	
			probabilityOf0MutationsText := getProbabilityOf0MutationsText()

			getProbabilityOf1MutationText := func()string{
				if (probabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf1MutationLowerBound == 0 && probabilityOf1MutationUpperBound == 100){
					result := translate("Unknown")
					return result
				}
	
				return probabilityOf1MutationFormatted
			}
	
			probabilityOf1MutationText := getProbabilityOf1MutationText()

			getProbabilityOf2MutationsText := func()string{
				if (probabilitiesKnown == false){

					result := translate("Unknown")
					return result
				}
				if (probabilityOf2MutationsLowerBound == 0 && probabilityOf2MutationsUpperBound == 100){
					result := translate("Unknown")
					return result
				}
	
				return probabilityOf2MutationsFormatted
			}
	
			probabilityOf2MutationsText := getProbabilityOf2MutationsText()

			viewGenomePairInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
					setViewCoupleGeneticAnalysisMonogenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, currentPage)
				})

			genomePairNameLabel := getBoldLabelCentered(genomePairName)
	
			probabilityOf0MutationsLabel := getBoldLabelCentered(probabilityOf0MutationsText)
			probabilityOf1MutationLabel := getBoldLabelCentered(probabilityOf1MutationText)
			probabilityOf2MutationsLabel := getBoldLabelCentered(probabilityOf2MutationsText)

			viewGenomePairInfoButtonsColumn.Add(viewGenomePairInfoButton)
			genomePairNameColumn.Add(genomePairNameLabel)
			probabilityOf0MutationsColumn.Add(probabilityOf0MutationsLabel)
			probabilityOf1MutationColumn.Add(probabilityOf1MutationLabel)
			probablityOf2MutationsColumn.Add(probabilityOf2MutationsLabel)

			viewGenomePairInfoButtonsColumn.Add(widget.NewSeparator())
			genomePairNameColumn.Add(widget.NewSeparator())
			probabilityOf0MutationsColumn.Add(widget.NewSeparator())
			probabilityOf1MutationColumn.Add(widget.NewSeparator())
			probablityOf2MutationsColumn.Add(widget.NewSeparator())
		
			return nil
		}

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

		genomePair1Identifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)

		err = addGenomePairRow("Pair 1", genomePair1Identifier)
		if (err != nil) { return nil, err }

		if (secondGenomePairExists == true){

			genomePair2Identifier := helpers.JoinTwo16ByteArrays(pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)

			err := addGenomePairRow("Pair 2", genomePair2Identifier)
			if (err != nil) { return nil, err }
		}
		
		genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairInfoButtonsColumn, genomePairNameColumn, probabilityOf0MutationsColumn, probabilityOf1MutationColumn, probablityOf2MutationsColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)	
}


func setViewCoupleGeneticAnalysisPolygenicDiseasesPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisPolygenicDiseasesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the polygenic disease risk probabilities for the couple's offspring.")

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

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

		mainGenomePairIdentifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)

		diseaseNameLabel := getItalicLabelCentered("Disease Name")

		offspringRiskScoreLabel := getItalicLabelCentered("Offspring Risk Score")

		confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

		conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")

		emptyLabel4 := widget.NewLabel("")

		diseaseNameColumn := container.NewVBox(diseaseNameLabel, widget.NewSeparator())
		offspringRiskScoreColumn := container.NewVBox(offspringRiskScoreLabel, widget.NewSeparator())
		confidenceRangeColumn := container.NewVBox(confidenceRangeLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(conflictExistsLabel, widget.NewSeparator())
		viewButtonsColumn := container.NewVBox(emptyLabel4, widget.NewSeparator())

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

		for _, diseaseObject := range diseaseObjectsList{

			diseaseName := diseaseObject.DiseaseName

			neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName)
			if (neuralNetworkExists == false){
				// We cannot analyze this disease
				continue
			}

			diseaseNameText := getBoldLabelCentered(diseaseName)

			analysisExists, offspringRiskScore, predictionConfidenceRangesMap, _, _, _, conflictExists, err := readGeneticAnalysis.GetOffspringPolygenicDiseaseInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, mainGenomePairIdentifier)
			if (err != nil) { return nil, err }

			getRiskScoreLabel := func()fyne.Widget{

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

					return result
				}

				offspringRiskScoreString := helpers.ConvertIntToString(offspringRiskScore)
				offspringRiskScoreFormatted := offspringRiskScoreString + "/10"

				riskScoreLabel := getBoldLabel(offspringRiskScoreFormatted)

				return riskScoreLabel
			}

			riskScoreLabel := getRiskScoreLabel()

			riskScoreLabelCentered := getWidgetCentered(riskScoreLabel)

			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(){
					setViewCoupleGeneticAnalysisPolygenicDiseaseDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, currentPage)
				}))

			diseaseNameColumn.Add(diseaseNameText)
			offspringRiskScoreColumn.Add(riskScoreLabelCentered)
			confidenceRangeColumn.Add(confidenceRangeLabel)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewButtonsColumn.Add(viewDetailsButton)

			diseaseNameColumn.Add(widget.NewSeparator())
			offspringRiskScoreColumn.Add(widget.NewSeparator())
			confidenceRangeColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewButtonsColumn.Add(widget.NewSeparator())
		}

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

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

		diseasesGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, offspringRiskScoreColumn, confidenceRangeColumn)

		if (secondGenomePairExists == true){

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

			diseasesGrid.Add(conflictExistsColumn)
		}

		diseasesGrid.Add(viewButtonsColumn)
		diseasesGrid.Add(layout.NewSpacer())

		return diseasesGrid, nil
	}

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

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

	setPageContent(page, window)
}

func setViewCoupleGeneticAnalysisPolygenicDiseaseDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisPolygenicDiseaseDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, previousPage)}

	neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName)
	if (neuralNetworkExists == false){
		// We cannot analyze this disease
		setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisPolygenicDiseaseDetailsPage called non-analyzable trait: " + diseaseName), previousPage)
		return
	}

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

	diseaseLociList := diseaseObject.LociList

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

	backButton := getBackButtonCentered(previousPage)

	pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (secondGenomePairExists == false){
			description := getLabelCentered("Below is the disease analysis for the couple's offspring.")

			return description
		}

		description1 := getLabelCentered("Below is the disease analysis for the couple's offspring.")
		description2 := getLabelCentered("Each genome pair combines different genomes from each person.")
	
		descriptionsSection := container.NewVBox(description1, description2)
	
		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

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

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

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

	offspringLabel := getItalicLabelCentered("Offspring")
	riskScoreLabel := getItalicLabelCentered("Risk Score")

	emptyLabel5 := widget.NewLabel("")
	confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

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

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

	emptyLabel8 := widget.NewLabel("")
	emptyLabel9 := widget.NewLabel("")

	emptyLabel10 := widget.NewLabel("")
	emptyLabel11 := widget.NewLabel("")

	viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
	pairNameColumn := container.NewVBox(emptyLabel3, emptyLabel4, widget.NewSeparator())
	offspringRiskScoreColumn := container.NewVBox(offspringLabel, riskScoreLabel, widget.NewSeparator())
	confidenceRangeColumn := container.NewVBox(emptyLabel5, confidenceRangeLabel, widget.NewSeparator())
	quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
	viewSampleOffspringsChartButtonsColumn := container.NewVBox(emptyLabel6, emptyLabel7, widget.NewSeparator())
	viewLifetimeRiskButtonsColumn := container.NewVBox(emptyLabel8, emptyLabel9, widget.NewSeparator())
	viewDetailsButtonsColumn := container.NewVBox(emptyLabel10, emptyLabel11, widget.NewSeparator())

	addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{

		genomePairNameLabel := getBoldLabelCentered(genomePairName)

		genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)

		analysisExists, offspringRiskScore, predictionConfidenceRangesMap, quantityOfLociKnown, _, sampleOffspringRiskScoresList, _, err := readGeneticAnalysis.GetOffspringPolygenicDiseaseInfoFromGeneticAnalysis(coupleAnalysisObject, diseaseName, genomePairIdentifier)
		if (err != nil) { return err }

		getRiskScoreLabel := func()fyne.Widget{

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

				return result
			}

			offspringRiskScoreString := helpers.ConvertIntToString(offspringRiskScore)
			offspringRiskScoreFormatted := offspringRiskScoreString + "/10"

			offspringRiskScoreLabel := getBoldLabel(offspringRiskScoreFormatted)

			return offspringRiskScoreLabel
		}

		riskScoreLabel := getRiskScoreLabel()

		riskScoreLabelCentered := getWidgetCentered(riskScoreLabel)

		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 }

		totalQuantityOfLoci := len(diseaseLociList)

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

		quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

		quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

		viewGenomePairButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
				setViewCoupleGeneticAnalysisPolygenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, currentPage)
			})

		viewSampleOffspringsChartButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
				setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window, diseaseName, sampleOffspringRiskScoresList, quantityOfLociKnown, currentPage)
			})

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

				getPageMaleOrFemale := func()string{

					//TODO: Get user sex from myLocalProfiles

					diseaseEffectedSex := diseaseObject.EffectedSex
					if (diseaseEffectedSex == "Both"){
						return "Male"
					}

					return diseaseEffectedSex
				}

				pageMaleOrFemale := getPageMaleOrFemale()

				setViewPersonPolygenicDiseaseLifetimeProbabilitiesPage(window, diseaseName, genomePairName, pageMaleOrFemale, currentPage)
			})

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

		viewGenomePairButtonsColumn.Add(viewGenomePairButton)
		pairNameColumn.Add(genomePairNameLabel)
		offspringRiskScoreColumn.Add(riskScoreLabelCentered)
		confidenceRangeColumn.Add(confidenceRangeLabel)
		quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
		viewSampleOffspringsChartButtonsColumn.Add(viewSampleOffspringsChartButton)
		viewLifetimeRiskButtonsColumn.Add(viewOffspringLifetimeRiskButton)
		viewDetailsButtonsColumn.Add(viewOffspringDetailsButton)

		viewGenomePairButtonsColumn.Add(widget.NewSeparator())
		pairNameColumn.Add(widget.NewSeparator())
		offspringRiskScoreColumn.Add(widget.NewSeparator())
		confidenceRangeColumn.Add(widget.NewSeparator())
		quantityOfLociKnownColumn.Add(widget.NewSeparator())
		viewSampleOffspringsChartButtonsColumn.Add(widget.NewSeparator())
		viewLifetimeRiskButtonsColumn.Add(widget.NewSeparator())
		viewDetailsButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (secondGenomePairExists == true){
		err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
		if (err != nil) {	
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

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

	offspringRiskScoreColumn.Add(offspringRiskScoreHelpButton)

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

	confidenceRangeColumn.Add(confidenceRangeHelpButton)

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

			setOffspringPolygenicDiseaseNumberOfLociTestedExplainerPage(window, currentPage)
		})

	quantityOfLociKnownColumn.Add(offspringQuantityOfLociKnownHelpButton)

	genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, offspringRiskScoreColumn, confidenceRangeColumn, quantityOfLociKnownColumn, viewSampleOffspringsChartButtonsColumn, viewLifetimeRiskButtonsColumn, viewDetailsButtonsColumn, layout.NewSpacer())

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

	setPageContent(page, window)
}

func setViewCoupleGeneticAnalysisPolygenicDiseaseGenomePairDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, diseaseName string, genomePairIdentifier [32]byte, genomePairName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisPolygenicDiseaseGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, diseaseName, genomePairIdentifier, genomePairName, previousPage)}

	title := getPageTitleCentered("Viewing Couple Genome Pair Info")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is the disease information for both genomes in the genome pair.")

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

	diseaseLociList := diseaseObject.LociList

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

	genomePairLabel := widget.NewLabel("Genome Pair:")
	genomePairNameLabel := getBoldLabel(genomePairName)
	genomePairHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setCoupleGenomePairExplainerPage(window, currentPage)
		})
	genomePairRow := container.NewHBox(layout.NewSpacer(), genomePairLabel, genomePairNameLabel, genomePairHelpButton, layout.NewSpacer())

	emptyLabelA := widget.NewLabel("")
	personNameLabel := getItalicLabelCentered("Person Name")

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

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

	quantityOfLabel := getItalicLabelCentered("Quantity Of")
	lociTestedLabel := getItalicLabelCentered("Loci Tested")

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

	personNameColumn := container.NewVBox(emptyLabelA, personNameLabel, widget.NewSeparator())
	genomeNameColumn := container.NewVBox(emptyLabelB, genomeNameLabel, widget.NewSeparator())
	riskScoreColumn := container.NewVBox(emptyLabelC, riskScoreLabel, widget.NewSeparator())
	quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociTestedLabel, widget.NewSeparator())
	viewGenomeButtonsColumn := container.NewVBox(emptyLabelD, emptyLabelE, widget.NewSeparator())

	addGenomeRow := func(isPerson1 bool, personName string, inputGenomeIdentifier [16]byte)error{

		personAnalysisGenomeIdentifier, _, genomeIsCombined, combinedType, err := readGeneticAnalysis.GetMatchingPersonAnalysisGenomeIdentifierFromCoupleAnalysis(isPerson1, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, inputGenomeIdentifier)
		if (err != nil) { return err }

		personNameTrimmed, _, err := helpers.TrimAndFlattenString(personName, 10)
		if (err != nil) { return err }

		personNameLabel := getBoldLabelCentered(personNameTrimmed)

		getPersonAnalysisObject := func()geneticAnalysis.PersonAnalysis{
			if (isPerson1 == true){
				return person1AnalysisObject
			}
			return person2AnalysisObject
		}

		personAnalysisObject := getPersonAnalysisObject()

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

			if (genomeIsCombined == false){

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

				return companyName, nil
			}

			return combinedType, nil
		}

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

		genomeNameLabel := getBoldLabelCentered(genomeName)

		analysisExists, personRiskScore, _, quantityOfLociKnown, _, _, err := readGeneticAnalysis.GetPersonPolygenicDiseaseInfoFromGeneticAnalysis(personAnalysisObject, diseaseName, personAnalysisGenomeIdentifier)
		if (err != nil) { return err }

		getPersonRiskScoreLabel := func()fyne.Widget{

			if (analysisExists == false){

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

			personRiskScoreString := helpers.ConvertIntToString(personRiskScore)
			personRiskScoreFormatted := personRiskScoreString + "/10"
			personRiskScoreLabel := getBoldLabel(personRiskScoreFormatted)

			return personRiskScoreLabel
		}

		personRiskScoreLabel := getPersonRiskScoreLabel()

		genomeRiskScoreLabelCentered := getWidgetCentered(personRiskScoreLabel)

		totalQuantityOfLoci := len(diseaseLociList)

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

		quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

		quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

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

		personNameColumn.Add(personNameLabel)
		genomeNameColumn.Add(genomeNameLabel)
		riskScoreColumn.Add(genomeRiskScoreLabelCentered)
		quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
		viewGenomeButtonsColumn.Add(viewGenomeButton)

		personNameColumn.Add(widget.NewSeparator())
		genomeNameColumn.Add(widget.NewSeparator())
		riskScoreColumn.Add(widget.NewSeparator())
		quantityOfLociKnownColumn.Add(widget.NewSeparator())
		viewGenomeButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	person1GenomeIdentifier, person2GenomeIdentifier := helpers.Split32ByteArrayInHalf(genomePairIdentifier)

	err = addGenomeRow(true, person1Name, person1GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	err = addGenomeRow(false, person2Name, person2GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

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

	riskScoreColumn.Add(riskScoreHelpButton)

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

	genomesGrid := container.NewHBox(layout.NewSpacer(), personNameColumn, genomeNameColumn, riskScoreColumn, quantityOfLociKnownColumn, viewGenomeButtonsColumn, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), diseaseNameRow, widget.NewSeparator(), genomePairRow, widget.NewSeparator(), genomesGrid)	

	setPageContent(page, window)
}

// This is a page that shows the user 100 sample offspring polygenic disease risk scores on a bar chart
// This helps users to visualize the standard deviation of their offspring's disease risk with this user
func setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window fyne.Window, diseaseName string, sampleOffspringRiskScoresList []int, numberOfLociTested int, previousPage func()){

	currentPage := func(){setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window, diseaseName, sampleOffspringRiskScoresList, numberOfLociTested, previousPage)}

	title := getPageTitleCentered("Viewing Sample Offspring Risk Scores Chart")

	backButton := getBackButtonCentered(previousPage)

	description := widget.NewLabel("Below is a chart of 100 sample offspring risk scores for this disease.")
	descriptionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			//TODO
			showUnderConstructionDialog(window)
		})
	descriptionRow := container.NewHBox(layout.NewSpacer(), description, descriptionHelpButton, layout.NewSpacer())

	diseaseNameTitle := widget.NewLabel("Disease Name:")
	diseaseNameLabel := getBoldLabel(diseaseName)
	diseaseNameRow := container.NewHBox(layout.NewSpacer(), diseaseNameTitle, diseaseNameLabel, layout.NewSpacer())

	if (len(sampleOffspringRiskScoresList) == 0){
		description2 := getBoldLabelCentered("There is no offspring information available for this disease.")
		description3 := getBoldLabelCentered("This is because there were no disease loci for which both prospective parents had information.")

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

		setPageContent(page, window)
		return
	}

	diseaseLocusObject, err := polygenicDiseases.GetPolygenicDiseaseObject(diseaseName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	diseaseLociList := diseaseLocusObject.LociList

	totalNumberOfLoci := len(diseaseLociList)
	totalNumberOfLociString := helpers.ConvertIntToString(totalNumberOfLoci)

	numberOfLociTestedTitle := widget.NewLabel("Number Of Loci Tested:")
	numberOfLociTestedString := helpers.ConvertIntToString(numberOfLociTested)
	numberOfLociTestedLabel := getBoldLabel(numberOfLociTestedString + "/" + totalNumberOfLociString)

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

	numberOfLociTestedRow := container.NewHBox(layout.NewSpacer(), numberOfLociTestedTitle, numberOfLociTestedLabel, lociTestedHelpButton, layout.NewSpacer())

	getOffspringSampleRiskScoresChartImage := func()(image.Image, error){

		offspringStatisticsDatumsList := make([]statisticsDatum.DiscreteStatisticsDatum, 0)

		for riskScore:=0; riskScore <= 10; riskScore += 1{

			offspringCount := helpers.CountMatchingElementsInSlice(sampleOffspringRiskScoresList, riskScore)

			riskScoreString := helpers.ConvertIntToString(riskScore)
			offspringCountString := helpers.ConvertIntToString(offspringCount)

			newStatisticsDatum := statisticsDatum.DiscreteStatisticsDatum{

				Label: riskScoreString + "/10",
				LabelFormatted: riskScoreString + "/10",
				Value: float64(offspringCount),
				ValueFormatted: offspringCountString,
			}

			offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)
		}

		chartTitle := diseaseName + ": 100 Prospective Offspring Risk Scores"

		formatYAxisValuesFunction := func(inputOffspringCount float64)(string, error){

			offspringCountString := helpers.ConvertIntToString(int(inputOffspringCount))

			return offspringCountString, nil
		}

		offspringsChart, err := createCharts.CreateBarChart(chartTitle, offspringStatisticsDatumsList, formatYAxisValuesFunction, true, " Offspring")
		if (err != nil) { return nil, err }

		return offspringsChart, nil
	}

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

	viewChartFullscreenButton := getWidgetCentered(widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
			setViewFullpageImagePage(window, offspringRiskScoresChartImage, currentPage)
		}))

	pageHeader := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), diseaseNameRow, widget.NewSeparator(), numberOfLociTestedRow, widget.NewSeparator())

	chartFyneImage := canvas.NewImageFromImage(offspringRiskScoresChartImage)
	chartFyneImage.FillMode = canvas.ImageFillContain

	page := container.NewBorder(pageHeader, viewChartFullscreenButton, nil, nil, chartFyneImage)

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisDiscreteTraitsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the average discrete trait outcomes for the couple's offspring.")

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

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

		mainGenomePairIdentifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)

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

		emptyLabel2 := widget.NewLabel("")
		predictedProbabilitiesLabel := getItalicLabelCentered("Predicted Probabilities")

		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())
		predictedProbabilitiesColumn := container.NewVBox(emptyLabel2, predictedProbabilitiesLabel, widget.NewSeparator())
		quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
		conflictExistsColumn := container.NewVBox(emptyLabel3, conflictExistsLabel, widget.NewSeparator())
		viewDetailsButtonsColumn := 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
			}

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

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

			traitName := traitObject.TraitName

			neuralNetworkExists, neuralNetworkAnalysisExists, offspringOutcomeProbabilitiesMap_NeuralNetwork, _, quantityOfLociKnown_NeuralNetwork, _, anyRulesExist, rulesAnalysisExists, offspringOutcomeProbabilitiesMap_Rules, _, _, quantityOfLociKnown_Rules, conflictExists, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, mainGenomePairIdentifier)
			if (err != nil) { return nil, err }
			if (neuralNetworkExists == false && anyRulesExist == false){
				// We cannot analyze this trait
				continue
			}

			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)

			// We add each row except for the outcome rows
			// The outcome grid cell can be multiple rows tall

			traitNameLabel := getBoldLabelCentered(traitName)

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

			viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, currentPage)
				}))

			traitNameColumn.Add(traitNameLabel)
			quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewDetailsButtonsColumn.Add(viewDetailsButton)

			// Outputs:
			//	-bool: Outcome probabilities exist
			//	-map[string]int: Outcome Name -> Probability of outcome (0-100)
			getOutcomeProbabilitiesMap := func()(bool, map[string]int){

				if (neuralNetworkExists == true){

					if (neuralNetworkAnalysisExists == false){
						return false, nil
					}

					return true, offspringOutcomeProbabilitiesMap_NeuralNetwork
				}

				//anyRulesExist must be true
				if (rulesAnalysisExists == false){
					return false, nil
				}
				return true, offspringOutcomeProbabilitiesMap_Rules
			}

			outcomeProbabilitiesExist, outcomeProbabilitiesMap := getOutcomeProbabilitiesMap()
			if (outcomeProbabilitiesExist == false){
				unknownLabel := getItalicLabelCentered("Unknown")
				predictedProbabilitiesColumn.Add(unknownLabel)
			} else {

				outcomeNamesList := traitObject.OutcomesList

				outcomeNamesListSorted := helpers.CopyAndSortStringListToUnicodeOrder(outcomeNamesList)

				addedItems := 0

				for _, outcomeName := range outcomeNamesListSorted{

					outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
					if (exists == false){
						continue
					}

					if (outcomeProbability == 0){
						continue
					}

					outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)

					outcomeRowLabel := getBoldLabel(outcomeName + ": " + outcomeProbabilityString + "%")

					predictedProbabilitiesColumn.Add(outcomeRowLabel)

					addedItems += 1

					if (addedItems != 1){
						// We have to add whitespace to the other columns
						traitNameColumn.Add(widget.NewLabel(""))
						quantityOfLociKnownColumn.Add(widget.NewLabel(""))
						conflictExistsColumn.Add(widget.NewLabel(""))
						viewDetailsButtonsColumn.Add(widget.NewLabel(""))
					}
				}
			}

			traitNameColumn.Add(widget.NewSeparator())
			predictedProbabilitiesColumn.Add(widget.NewSeparator())
			quantityOfLociKnownColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewDetailsButtonsColumn.Add(widget.NewSeparator())
		}

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

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

		traitsGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedProbabilitiesColumn, quantityOfLociKnownColumn)

		if (secondGenomePairExists == true){

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

			traitsGrid.Add(conflictExistsColumn)
		}

		traitsGrid.Add(viewDetailsButtonsColumn)
		traitsGrid.Add(layout.NewSpacer())

		return traitsGrid, nil
	}

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

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

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (secondGenomePairExists == false){
			description := getLabelCentered("Below is the trait analysis for the couple's offspring.")

			return description
		}

		description1 := getLabelCentered("Below is the trait analysis for the couple's offspring.")
		description2 := getLabelCentered("Each genome pair combines different genomes from each person.")
	
		descriptionsSection := container.NewVBox(description1, description2)
	
		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

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

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

	traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
	if (traitIsDiscreteOrNumeric != "Discrete"){
		setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with non-discrete trait: " + traitName), previousPage)
		return
	}

	neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)

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

	emptyLabel3 := widget.NewLabel("")
	genomePairLabel := getItalicLabelCentered("Genome Pair")

	emptyLabel4 := widget.NewLabel("")
	predictedProbabilitiesLabel := getItalicLabelCentered("Predicted Probabilities")

	predictionLabel := getItalicLabelCentered("Prediction")
	confidenceLabel := getItalicLabelCentered("Confidence")

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

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

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

	viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
	pairNameColumn := container.NewVBox(emptyLabel3, genomePairLabel, widget.NewSeparator())
	predictedProbabilitiesColumn := container.NewVBox(emptyLabel4, predictedProbabilitiesLabel, widget.NewSeparator())
	neuralNetworkPredictionConfidenceColumn := container.NewVBox(predictionLabel, confidenceLabel, widget.NewSeparator())
	quantityOfRulesTestedColumn := container.NewVBox(quantityOfLabel1, rulesTestedLabel, widget.NewSeparator())
	quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel2, lociKnownLabel, widget.NewSeparator())
	viewDetailsButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())

	addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{

		genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)

		genomePairNameLabel := getBoldLabelCentered(genomePairName)

		viewGenomePairButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
				setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, currentPage)
			})

		viewAnalysisDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				if (neuralNetworkExists == true){
					//TODO
					showUnderConstructionDialog(window)
				} else {
					setViewCoupleDiscreteTraitRulesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, currentPage)
				}
			})

		viewGenomePairButtonsColumn.Add(viewGenomePairButton)
		pairNameColumn.Add(genomePairNameLabel)
		viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)

		neuralNetworkExists, neuralNetworkAnalysisExists, offspringOutcomeProbabilitiesMap_NeuralNetwork, neuralNetworkPredictionConfidence, quantityOfLociKnown_NeuralNetwork, _, anyRulesExist, rulesAnalysisExists, offspringOutcomeProbabilitiesMap_Rules, _, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
		if (err != nil) { return err }
		if (neuralNetworkExists == false && anyRulesExist == false){
			return errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with trait that is not analyzable.")
		}

		// First we add analysis details to their respective column

		if (neuralNetworkExists == true){

			if (neuralNetworkAnalysisExists == false){

				neuralNetworkPredictionConfidenceColumn.Add(getLabelCentered("-"))
			} else {

				predictionConfidenceString := helpers.ConvertIntToString(neuralNetworkPredictionConfidence)

				predictionConfidenceLabel := getBoldLabelCentered(predictionConfidenceString + "%")

				neuralNetworkPredictionConfidenceColumn.Add(predictionConfidenceLabel)
			}

			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 {
			if (anyRulesExist == false){
				return errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with analysis which is missing ")
			}

			traitRulesList := traitObject.RulesList

			totalNumberOfRules := len(traitRulesList)

			totalNumberOfRulesString := helpers.ConvertIntToString(totalNumberOfRules)

			quantityOfRulesTestedString := helpers.ConvertIntToString(quantityOfRulesTested)

			quantityOfRulesTestedFormatted := quantityOfRulesTestedString + "/" + totalNumberOfRulesString

			quantityOfRulesTestedLabel := getBoldLabelCentered(quantityOfRulesTestedFormatted)

			quantityOfRulesTestedColumn.Add(quantityOfRulesTestedLabel)
		}

		// Now we add the outcome probabilities cell

		// Outputs:
		//	-bool: Outcome probabilities exist
		//	-map[string]int: Outcome Name -> Probability of outcome (0-100)
		getOutcomeProbabilitiesMap := func()(bool, map[string]int){

			if (neuralNetworkExists == true){

				if (neuralNetworkAnalysisExists == false){
					return false, nil
				}

				return true, offspringOutcomeProbabilitiesMap_NeuralNetwork
			}

			//anyRulesExist must be true
			if (rulesAnalysisExists == false){
				return false, nil
			}
			return true, offspringOutcomeProbabilitiesMap_Rules
		}

		outcomeProbabilitiesExist, outcomeProbabilitiesMap := getOutcomeProbabilitiesMap()
		if (outcomeProbabilitiesExist == false){
			unknownLabel := getItalicLabelCentered("Unknown")
			predictedProbabilitiesColumn.Add(unknownLabel)
		} else {

			outcomeNamesList := traitObject.OutcomesList

			outcomeNamesListSorted := helpers.CopyAndSortStringListToUnicodeOrder(outcomeNamesList)

			addedItems := 0

			for _, outcomeName := range outcomeNamesListSorted{

				outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
				if (exists == false){
					continue
				}

				if (outcomeProbability == 0){
					continue
				}

				outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)

				outcomeRowLabel := getBoldLabel(outcomeName + ": " + outcomeProbabilityString + "%")

				predictedProbabilitiesColumn.Add(outcomeRowLabel)

				addedItems += 1

				if (addedItems > 1){
					// We have to add whitespace to the other columns
					viewGenomePairButtonsColumn.Add(widget.NewLabel(""))
					pairNameColumn.Add(widget.NewLabel(""))
					neuralNetworkPredictionConfidenceColumn.Add(widget.NewLabel(""))
					quantityOfRulesTestedColumn.Add(widget.NewLabel(""))
					quantityOfLociKnownColumn.Add(widget.NewLabel(""))
					viewDetailsButtonsColumn.Add(widget.NewLabel(""))
				}
			}
		}

		viewGenomePairButtonsColumn.Add(widget.NewSeparator())
		pairNameColumn.Add(widget.NewSeparator())
		predictedProbabilitiesColumn.Add(widget.NewSeparator())
		neuralNetworkPredictionConfidenceColumn.Add(widget.NewSeparator())
		quantityOfRulesTestedColumn.Add(widget.NewSeparator())
		quantityOfLociKnownColumn.Add(widget.NewSeparator())
		viewDetailsButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (secondGenomePairExists == true){
		err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
		if (err != nil) {	
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

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

	predictedProbabilitiesColumn.Add(predictedProbabilitiesHelpButton)

	if (neuralNetworkExists == true){

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

		neuralNetworkPredictionConfidenceColumn.Add(neuralNetworkPredictionConfidenceHelpButton)

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

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

		quantityOfRulesTestedColumn.Add(quantityOfRulesTestedHelpButton)
	}

	genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, predictedProbabilitiesColumn)

	if (neuralNetworkExists == true){

		genomesContainer.Add(neuralNetworkPredictionConfidenceColumn)
		genomesContainer.Add(quantityOfLociKnownColumn)
	} else {
		genomesContainer.Add(quantityOfRulesTestedColumn)
	}

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

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

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, genomePairIdentifier [32]byte, genomePairName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, previousPage)}

	title := getPageTitleCentered("Viewing Couple Genome Pair Info")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is the trait information for both genomes in the genome pair.")

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

	genomePairLabel := widget.NewLabel("Genome Pair:")
	genomePairNameLabel := getBoldLabel(genomePairName)
	genomePairHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setCoupleGenomePairExplainerPage(window, currentPage)
		})
	genomePairRow := container.NewHBox(layout.NewSpacer(), genomePairLabel, genomePairNameLabel, genomePairHelpButton, layout.NewSpacer())

	emptyLabel1 := widget.NewLabel("")
	personNameLabel := getItalicLabelCentered("Person Name")

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

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

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

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

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

	personNameColumn := container.NewVBox(emptyLabel1, personNameLabel, widget.NewSeparator())
	genomeNameColumn := container.NewVBox(emptyLabel2, genomeNameLabel, widget.NewSeparator())
	predictedOutcomeColumn := container.NewVBox(emptyLabel3, predictedOutcomeLabel, widget.NewSeparator())
	quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator())
	quantityOfRulesTestedColumn := container.NewVBox(quantityOfLabel2, rulesTestedLabel, widget.NewSeparator())
	viewGenomeButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())

	addGenomeRow := func(isPerson1 bool, personName string, inputGenomeIdentifier [16]byte)error{

		personAnalysisGenomeIdentifier, _, genomeIsCombined, combinedType, err := readGeneticAnalysis.GetMatchingPersonAnalysisGenomeIdentifierFromCoupleAnalysis(isPerson1, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, inputGenomeIdentifier)
		if (err != nil) { return err }

		personNameTrimmed, _, err := helpers.TrimAndFlattenString(personName, 10)
		if (err != nil) { return err }

		personNameLabel := getBoldLabelCentered(personNameTrimmed)

		getPersonAnalysisObject := func()geneticAnalysis.PersonAnalysis{
			if (isPerson1 == true){
				return person1AnalysisObject
			}
			return person2AnalysisObject
		}

		personAnalysisObject := getPersonAnalysisObject()

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

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

				return companyName, nil
			}

			return combinedType, nil
		}

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

		genomeNameLabel := getBoldLabelCentered(genomeName)

		neuralNetworkExists, neuralNetworkAnalysisExists, neuralNetworkPredictedOutcome, _, neuralNetworkQuantityOfLociTested, _, _, rulesAnalysisExists, _, rulesPredictedOutcomeExists, rulesPredictedOutcome, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetPersonDiscreteTraitInfoFromGeneticAnalysis(personAnalysisObject, traitName, personAnalysisGenomeIdentifier)
		if (err != nil) { return err }


		personNameColumn.Add(personNameLabel)
		genomeNameColumn.Add(genomeNameLabel)

		//Outputs:
		//	-bool: Outcome is known
		//	-string: Prediction outcome
		getPredictedOutcome := func()(bool, string){
			if (neuralNetworkExists == true){

				if (neuralNetworkAnalysisExists == false){
					return false, ""
				}

				return true, neuralNetworkPredictedOutcome
			}

			// Analysis must be rule-based
			if (rulesAnalysisExists == false || rulesPredictedOutcomeExists == false){
				return false, ""
			}

			return true, rulesPredictedOutcome
		}

		predictedOutcomeExists, predictedOutcome := getPredictedOutcome()
		if (predictedOutcomeExists == false){
			unknownLabel := getItalicLabelCentered(translate("Unknown"))
			predictedOutcomeColumn.Add(unknownLabel)
		} else {
			predictedOutcomeLabel := getBoldLabelCentered(predictedOutcome)
			predictedOutcomeColumn.Add(predictedOutcomeLabel)
		}

		// Now we add outcome details

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

		traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
		if (traitIsDiscreteOrNumeric != "Discrete"){
			return errors.New("setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage called with non-discrete trait: " + traitName)
		}

		if (neuralNetworkExists == true){

			traitLociList := traitObject.LociList

			totalQuantityOfLoci := len(traitLociList)

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

			quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

			quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

			quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)

		} else {

			// Analysis is rule-based

			traitRulesList := traitObject.RulesList

			totalQuantityOfRules := len(traitRulesList)

			quantityOfRulesTestedString := helpers.ConvertIntToString(quantityOfRulesTested)
			totalQuantityOfRulesString := helpers.ConvertIntToString(totalQuantityOfRules)

			quantityOfRulesTestedFormatted := quantityOfRulesTestedString + "/" + totalQuantityOfRulesString

			quantityOfRulesTestedLabel := getBoldLabelCentered(quantityOfRulesTestedFormatted)

			quantityOfRulesTestedColumn.Add(quantityOfRulesTestedLabel)
		}

		viewGenomeButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){

				if (neuralNetworkExists == true){
					//TODO
					showUnderConstructionDialog(window)
				} else {
					setViewPersonGenomeDiscreteTraitRulesPage(window, personAnalysisObject, traitName, personAnalysisGenomeIdentifier, genomeName, currentPage)
				}
			})

		viewGenomeButtonsColumn.Add(viewGenomeButton)

		personNameColumn.Add(widget.NewSeparator())
		genomeNameColumn.Add(widget.NewSeparator())
		predictedOutcomeColumn.Add(widget.NewSeparator())
		quantityOfLociKnownColumn.Add(widget.NewSeparator())
		quantityOfRulesTestedColumn.Add(widget.NewSeparator())
		viewGenomeButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	person1GenomeIdentifier, person2GenomeIdentifier := helpers.Split32ByteArrayInHalf(genomePairIdentifier)

	err := addGenomeRow(true, person1Name, person1GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	err = addGenomeRow(false, person2Name, person2GenomeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)

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

			if (neuralNetworkExists == true){
				setDiscreteTraitNeuralNetworkPredictionExplainerPage(window, currentPage)
			} else {
				setOffspringDiscreteTraitNeuralNetworkPredictionExplainerPage(window, currentPage)
			}
		})

	predictedOutcomeColumn.Add(predictedOutcomeHelpButton)

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

	quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)

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

	quantityOfRulesTestedColumn.Add(numberOfRulesTestedHelpButton)

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

	if (neuralNetworkExists == true){
	
		genomesGrid.Add(quantityOfLociKnownColumn)
	} else {

		genomesGrid.Add(quantityOfRulesTestedColumn)
	}

	genomesGrid.Add(viewGenomeButtonsColumn)
	genomesGrid.Add(layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), genomePairRow, widget.NewSeparator(), genomesGrid)	

	setPageContent(page, window)
}


// This function provides a page to view the couple offspring rule probabilities for a particular genome pair
func setViewCoupleDiscreteTraitRulesPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, genomePairIdentifier [32]byte, genomePairName string, previousPage func()){

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

	currentPage := func(){setViewCoupleDiscreteTraitRulesPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description1 := widget.NewLabel("Below are the trait rule probabilities for offspring from this genome pair.")
	traitRulesHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setOffspringDiscreteTraitRulesExplainerPage(window, currentPage)
		})
	description1Row := container.NewHBox(layout.NewSpacer(), description1, traitRulesHelpButton, layout.NewSpacer())

	genomePairLabel := widget.NewLabel("Genome Pair:")
	genomePairNameLabel := getBoldLabel(genomePairName)
	viewGenomePairInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
			setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, currentPage)
		})

	genomePairRow := container.NewHBox(layout.NewSpacer(), genomePairLabel, genomePairNameLabel, viewGenomePairInfoButton, layout.NewSpacer())

	neuralNetworkExists, _, _, _, _, _, anyRulesExist, rulesAnalysisExists, _, _, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (neuralNetworkExists == true){
		setErrorEncounteredPage(window, errors.New("setViewCoupleTraitRulesPage called when neural network analysis for trait exists."), previousPage)
		return
	}
	if (anyRulesExist == false){
		setErrorEncounteredPage(window, errors.New("GetOffspringTraitInfoFromGeneticAnalysis claiming that no analysis method exists for triat: " + traitName), previousPage)
		return
	}

	quantityOfRulesTestedString := helpers.ConvertIntToString(quantityOfRulesTested)

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

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

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

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

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

		emptyLabelA := widget.NewLabel("")
		ruleIdentifierLabel := getItalicLabelCentered("Rule Identifier")

		emptyLabelB := widget.NewLabel("")
		ruleEffectsLabel := getItalicLabelCentered("Rule Effects")

		offspringProbabilityOfLabel := getItalicLabelCentered("Offspring Probability Of")
		passingRuleLabel := getItalicLabelCentered("Passing Rule")
		
		emptyLabelC := widget.NewLabel("")
		emptyLabelD := widget.NewLabel("")

		ruleIdentifierColumn := container.NewVBox(emptyLabelA, ruleIdentifierLabel, widget.NewSeparator())
		ruleEffectsColumn := container.NewVBox(emptyLabelB, ruleEffectsLabel, widget.NewSeparator())
		offspringProbabilityOfPassingRuleColumn := container.NewVBox(offspringProbabilityOfLabel, passingRuleLabel, widget.NewSeparator())
		ruleInfoButtonsColumn := container.NewVBox(emptyLabelC, emptyLabelD, widget.NewSeparator())

		addRuleRow := func(ruleIdentifierHex string)error{

			ruleIdentifierLabel := getBoldLabelCentered(ruleIdentifierHex)

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

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

				if (rulesAnalysisExists == false){
					// No rules were tested
					result := translate("Unknown")
					return result, nil
				}

				offspringRuleProbabilityKnown, _, offspringProbabilityOfPassingRuleFormatted, err := readGeneticAnalysis.GetOffspringDiscreteTraitRuleInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, ruleIdentifier, genomePairIdentifier)
				if (err != nil) { return "", err }
				if (offspringRuleProbabilityKnown == false){

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

				return offspringProbabilityOfPassingRuleFormatted, nil
			}

			probabilityOfPassingRuleText, err := getProbabilityOfPassingRuleText()
			if (err != nil) { return err }
			probabilityOfPassingRuleTextLabel := getBoldLabelCentered(probabilityOfPassingRuleText)

			viewRuleDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewCoupleGeneticAnalysisDiscreteTraitRuleDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, ruleIdentifier, currentPage)
				})

			// 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

			ruleIdentifierColumn.Add(ruleIdentifierLabel)
			offspringProbabilityOfPassingRuleColumn.Add(probabilityOfPassingRuleTextLabel)
			ruleInfoButtonsColumn.Add(viewRuleDetailsButton)

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

			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{

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

				getOutcomeEffectString := func()string{

					outcomeChangeString := helpers.ConvertIntToString(outcomeChange)					

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

				outcomeEffect := getOutcomeEffectString()

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

				if (index > 0){

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

					ruleIdentifierColumn.Add(emptyLabelA)
					offspringProbabilityOfPassingRuleColumn.Add(emptyLabelB)
					ruleInfoButtonsColumn.Add(emptyLabelC)
				}
			}

			ruleIdentifierColumn.Add(widget.NewSeparator())
			ruleEffectsColumn.Add(widget.NewSeparator())
			offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator())			
			ruleInfoButtonsColumn.Add(widget.NewSeparator())

			return nil
		}

		rulesWithKnownProbabilityList := make([]string, 0)
		rulesWithUnknownProbabilityList := make([]string, 0)

		for ruleIdentifierHex, _ := range traitRulesMap{

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

			offspringRuleProbabilityKnown, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitRuleInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, ruleIdentifier, genomePairIdentifier)
			if (err != nil) { return nil, err }
			if (offspringRuleProbabilityKnown == true){
				rulesWithKnownProbabilityList = append(rulesWithKnownProbabilityList, ruleIdentifierHex)
			} else {
				rulesWithUnknownProbabilityList = append(rulesWithUnknownProbabilityList, ruleIdentifierHex)
			}
		}

		// Now we sort rules so they show up in the same order each time

		helpers.SortStringListToUnicodeOrder(rulesWithKnownProbabilityList)
		helpers.SortStringListToUnicodeOrder(rulesWithUnknownProbabilityList)

		for _, ruleIdentifier := range rulesWithKnownProbabilityList{

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

		for _, ruleIdentifier := range rulesWithUnknownProbabilityList{

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

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

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

		rulesGrid := container.NewHBox(layout.NewSpacer(), ruleIdentifierColumn, ruleEffectsColumn, offspringProbabilityOfPassingRuleColumn, ruleInfoButtonsColumn, layout.NewSpacer())

		return rulesGrid, nil
	}

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1Row, widget.NewSeparator(), genomePairRow, widget.NewSeparator(), rulesTestedRow, widget.NewSeparator(), rulesGrid)

	setPageContent(page, window)
}


// This function implements a page to view the details of a specific rule from a genetic analysis
// It will show the rule details for all of the couple's genome pairs
func setViewCoupleGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, ruleIdentifier [3]byte, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisDiscreteTraitRuleDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, ruleIdentifier, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is the trait rule analysis for the couple.")

	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())

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

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

		emptyLabelC := widget.NewLabel("")
		genomePairLabel := getItalicLabelCentered("Genome Pair")

		offspringProbabilityOfLabel := getItalicLabelCentered("Offspring Probability Of")
		passingRuleLabel := getItalicLabelCentered("Passing Rule")

		viewGenomePairInfoButtonsColumn := container.NewVBox(emptyLabelA, emptyLabelB, widget.NewSeparator())
		genomePairNameColumn := container.NewVBox(emptyLabelC, genomePairLabel, widget.NewSeparator())
		offspringProbabilityOfPassingRuleColumn := container.NewVBox(offspringProbabilityOfLabel, passingRuleLabel, widget.NewSeparator())
		
		addGenomePairRow := func(genomePairName string, genomePairIdentifier [32]byte)error{

			offspringRuleProbabilityKnown, _, offspringProbabilityOfPassingRuleFormatted, err := readGeneticAnalysis.GetOffspringDiscreteTraitRuleInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, ruleIdentifier, genomePairIdentifier)
			if (err != nil) { return err }

			getOffspringProbabilityOfPassingRuleText := func()string{

				if (offspringRuleProbabilityKnown == false){
					result := translate("Unknown")
					return result	
				}

				return offspringProbabilityOfPassingRuleFormatted
			}

			offspringProbabilityOfPassingRuleText := getOffspringProbabilityOfPassingRuleText()

			viewGenomePairInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
					setViewCoupleGeneticAnalysisDiscreteTraitGenomePairDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, genomePairIdentifier, genomePairName, currentPage)
				})

			genomePairNameLabel := getBoldLabelCentered(genomePairName)

			offspringProbabilityOfPassingRuleLabel := getBoldLabelCentered(offspringProbabilityOfPassingRuleText)

			viewGenomePairInfoButtonsColumn.Add(viewGenomePairInfoButton)
			genomePairNameColumn.Add(genomePairNameLabel)
			offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfPassingRuleLabel)

			viewGenomePairInfoButtonsColumn.Add(widget.NewSeparator())
			genomePairNameColumn.Add(widget.NewSeparator())
			offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator())
		
			return nil
		}

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

		genomePair1Identifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)

		err = addGenomePairRow("Pair 1", genomePair1Identifier)
		if (err != nil) { return nil, err }

		if (secondGenomePairExists == true){

			genomePair2Identifier := helpers.JoinTwo16ByteArrays(pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)

			err := addGenomePairRow("Pair 2", genomePair2Identifier)
			if (err != nil) { return nil, err }
		}

		genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairInfoButtonsColumn, genomePairNameColumn, offspringProbabilityOfPassingRuleColumn, layout.NewSpacer())

		return genomesContainer, nil
	}

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

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

	setPageContent(page, window)	
}


func setViewCoupleGeneticAnalysisNumericTraitsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Below is an analysis of the average numeric trait outcomes for the couple's offspring.")

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

		pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
		if (err != nil){ return nil, err }

		mainGenomePairIdentifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)

		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())
		viewDetailsButtonsColumn := 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
			}

			traitLociList := traitObject.LociList

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

			traitName := traitObject.TraitName

			neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)
			if (neuralNetworkExists == false){
				// We cannot analyze this trait
				continue
			}

			traitNameLabel := getBoldLabelCentered(traitName)

			analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, _, _, _, conflictExists, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, mainGenomePairIdentifier)
			if (err != nil) { return nil, err }

			outcomeFormatter := traitObject.NumericValueFormatter

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

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

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

				outcomeLabel := getBoldLabel(predictedOutcomeFormatted)

				return outcomeLabel, nil
			}

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

			predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)

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

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

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

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

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

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

				closestConfidenceDistanceString, 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(){
					setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, currentPage)
				}))

			traitNameColumn.Add(traitNameLabel)
			predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
			confidenceRangeColumn.Add(confidenceRangeLabelCentered)
			conflictExistsColumn.Add(conflictExistsLabel)
			viewDetailsButtonsColumn.Add(viewDetailsButton)

			traitNameColumn.Add(widget.NewSeparator())
			predictedOutcomeColumn.Add(widget.NewSeparator())
			confidenceRangeColumn.Add(widget.NewSeparator())
			conflictExistsColumn.Add(widget.NewSeparator())
			viewDetailsButtonsColumn.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)

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

		if (secondGenomePairExists == true){

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

			traitsGrid.Add(conflictExistsColumn)
		}

		traitsGrid.Add(viewDetailsButtonsColumn)
		traitsGrid.Add(layout.NewSpacer())

		return traitsGrid, nil
	}

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

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

	setPageContent(page, window)
}


func setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, previousPage func()){

	currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, previousPage)}

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

	backButton := getBackButtonCentered(previousPage)

	pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getDescriptionSection := func()*fyne.Container{

		if (secondGenomePairExists == false){
			description := getLabelCentered("Below is the trait analysis for the couple's offspring.")

			return description
		}

		description1 := getLabelCentered("Below is the trait analysis for the couple's offspring.")
		description2 := getLabelCentered("Each genome pair combines different genomes from each person.")

		descriptionsSection := container.NewVBox(description1, description2)

		return descriptionsSection
	}

	descriptionSection := getDescriptionSection()

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

	neuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName)
	if (neuralNetworkExists == false){
		// We cannot analyze this trait
		setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisNumericTraitDetailsPage called non-analyzable trait: " + traitName), previousPage)
		return
	}

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

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

	traitLociList := traitObject.LociList

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

	emptyLabel3 := widget.NewLabel("")
	genomePairLabel := getItalicLabelCentered("Genome Pair")

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

	emptyLabel5 := widget.NewLabel("")
	confidenceRangeLabel := getItalicLabelCentered("Confidence Range")

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

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

	emptyLabel8 := widget.NewLabel("")
	emptyLabel9 := widget.NewLabel("")

	viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
	pairNameColumn := container.NewVBox(emptyLabel3, genomePairLabel, widget.NewSeparator())
	predictedOutcomeColumn := container.NewVBox(emptyLabel4, predictedOutcomeLabel, widget.NewSeparator())
	predictionConfidenceRangeColumn := container.NewVBox(emptyLabel5, confidenceRangeLabel, widget.NewSeparator())
	quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
	viewSampleOffspringButtonsColumn := container.NewVBox(emptyLabel6, emptyLabel7, widget.NewSeparator())
	viewDetailsButtonsColumn := container.NewVBox(emptyLabel8, emptyLabel9, widget.NewSeparator())

	addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{

		genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)

		genomePairNameLabel := getBoldLabelCentered(genomePairName)

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

		analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, sampleOffspringOutcomesList, _, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
		if (err != nil) { return err }

		outcomeFormatter := traitObject.NumericValueFormatter

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

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

			offspringAverageOutcomeFormatted, err := outcomeFormatter(offspringAverageOutcome, true)
			if (err != nil) { return nil, err }

			predictedOutcomeLabel := getBoldLabel(offspringAverageOutcomeFormatted)

			return predictedOutcomeLabel, nil
		}

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

		predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)

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

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

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

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

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

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

			closestConfidenceDistanceString, 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)

		totalQuantityOfLoci := len(traitLociList)

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

		quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString

		quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)

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

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

		viewGenomePairButtonsColumn.Add(viewGenomePairButton)
		pairNameColumn.Add(genomePairNameLabel)
		predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
		predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)
		quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
		viewSampleOffspringButtonsColumn.Add(viewSampleOffspringsButton)
		viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)

		viewGenomePairButtonsColumn.Add(widget.NewSeparator())
		pairNameColumn.Add(widget.NewSeparator())
		predictedOutcomeColumn.Add(widget.NewSeparator())
		predictionConfidenceRangeColumn.Add(widget.NewSeparator())
		quantityOfLociKnownColumn.Add(widget.NewSeparator())
		viewSampleOffspringButtonsColumn.Add(widget.NewSeparator())
		viewDetailsButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (secondGenomePairExists == true){
		err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
		if (err != nil) {	
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

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

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

	predictionConfidenceRangeColumn.Add(confidenceRangeHelpButton)

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

	genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, predictedOutcomeColumn, predictionConfidenceRangeColumn, quantityOfLociKnownColumn, viewSampleOffspringButtonsColumn, viewDetailsButtonsColumn, layout.NewSpacer())

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

	setPageContent(page, window)
}


// This is a page that shows the user 100 sample offspring trait outcomes on a bar chart
// This helps users to visualize the standard deviation of their offspring's trait outcomes with this user
func setViewNumericTraitSampleOffspringOutcomesChart(window fyne.Window, traitName string, sampleOffspringOutcomesList []float64, quantityOfLociKnown int, previousPage func()){

	currentPage := func(){setViewNumericTraitSampleOffspringOutcomesChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, previousPage)}

	title := getPageTitleCentered("Viewing Sample Offspring Outcomes Chart")

	backButton := getBackButtonCentered(previousPage)

	description := widget.NewLabel("Below is a chart of 100 sample offspring trait outcomes for this disease.")
	descriptionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			//TODO
			showUnderConstructionDialog(window)
		})
	descriptionRow := container.NewHBox(layout.NewSpacer(), description, descriptionHelpButton, layout.NewSpacer())

	traitNameTitle := widget.NewLabel("Trait Name:")
	traitNameLabel := getBoldLabel(traitName)
	traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameTitle, traitNameLabel, layout.NewSpacer())

	if (len(sampleOffspringOutcomesList) == 0){
		description2 := getBoldLabelCentered("There is no offspring information available for this trait.")
		description3 := getBoldLabelCentered("This is because there were no trait loci for which both prospective parents had information.")

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

		setPageContent(page, window)
		return
	}

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

	traitLociList := traitObject.LociList

	totalNumberOfLoci := len(traitLociList)
	totalNumberOfLociString := helpers.ConvertIntToString(totalNumberOfLoci)

	numberOfLociKnownTitle := widget.NewLabel("Quantity Of Loci Known:")
	numberOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
	numberOfLociKnownLabel := getBoldLabel(numberOfLociKnownString + "/" + totalNumberOfLociString)

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

	quantityOfLociKnownRow := container.NewHBox(layout.NewSpacer(), numberOfLociKnownTitle, numberOfLociKnownLabel, lociKnownHelpButton, layout.NewSpacer())

	getOffspringSampleOutcomesChartImage := func()(image.Image, error){

		if (len(sampleOffspringOutcomesList) != 100){
			return nil, errors.New("setViewNumericTraitSampleOffspringOutcomesChart called with offspringOutcomesList that is not 100 items in length.")
		}

		// We sort the list in ascending order
		slices.Sort(sampleOffspringOutcomesList)

		outcomeFormatter := traitObject.NumericValueFormatter

		getOffspringStatisticsDatumsList := func()([]statisticsDatum.DiscreteStatisticsDatum, error){

			offspringStatisticsDatumsList := make([]statisticsDatum.DiscreteStatisticsDatum, 0)

			smallestItem := sampleOffspringOutcomesList[0]
			largestItem := sampleOffspringOutcomesList[99]

			if (smallestItem == largestItem){

				// We can't split the values into groups
				// Every offspring has the same value

				onlyValue := helpers.ConvertFloat64ToString(smallestItem)

				onlyValueFormatted, err := outcomeFormatter(smallestItem, true)
				if (err != nil) { return nil, err }

				newStatisticsDatum := statisticsDatum.DiscreteStatisticsDatum{
					Label: onlyValue,
					LabelFormatted: onlyValueFormatted,
					Value: float64(100),
					ValueFormatted: "100",
				}

				offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)

				return offspringStatisticsDatumsList, nil
			}

			// We split all outcomes into ten groups

			sizeOfEachGroup := (largestItem-smallestItem)/10

			index := float64(smallestItem)

			for {

				getGroupUpperBound := func()float64{

					if (len(offspringStatisticsDatumsList) == 9){
						// We are adding the last datum
						return largestItem
					}

					groupUpperBound := index + sizeOfEachGroup

					return groupUpperBound
				}

				groupUpperBound := getGroupUpperBound()

				offspringInGroupCount := 0

				for _, outcomeValue := range sampleOffspringOutcomesList{

					if (outcomeValue >= index && outcomeValue < groupUpperBound){
						offspringInGroupCount += 1
					}
				}

				groupLowerBoundString, err := outcomeFormatter(index, true)
				if (err != nil) { return nil, err }
				groupUpperBoundString, err := outcomeFormatter(groupUpperBound, true)
				if (err != nil) { return nil, err }

				groupDescription := groupLowerBoundString + "-" + groupUpperBoundString

				offspringCountString := helpers.ConvertIntToString(offspringInGroupCount)

				newStatisticsDatum := statisticsDatum.DiscreteStatisticsDatum{
					Label: groupDescription,
					LabelFormatted: groupDescription,
					Value: float64(offspringInGroupCount),
					ValueFormatted: offspringCountString,
				}

				offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)

				if (len(offspringStatisticsDatumsList) == 10){
					break
				}

				index += sizeOfEachGroup
			}

			return offspringStatisticsDatumsList, nil
		}

		offspringStatisticsDatumsList, err := getOffspringStatisticsDatumsList()
		if (err != nil) { return nil, err }

		chartTitle := traitName + ": 100 Prospective Offspring Values"

		formatYAxisValuesFunction := func(inputOffspringCount float64)(string, error){

			result := helpers.ConvertIntToString(int(inputOffspringCount))

			return result, nil
		}

		offspringsChart, err := createCharts.CreateBarChart(chartTitle, offspringStatisticsDatumsList, formatYAxisValuesFunction, true, " Offspring")
		if (err != nil) { return nil, err }

		return offspringsChart, nil
	}

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

	viewChartFullscreenButton := getWidgetCentered(widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
			setViewFullpageImagePage(window, offspringOutcomesChartImage, currentPage)
		}))

	pageHeader := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), quantityOfLociKnownRow, widget.NewSeparator())

	chartFyneImage := canvas.NewImageFromImage(offspringOutcomesChartImage)
	chartFyneImage.FillMode = canvas.ImageFillContain

	page := container.NewBorder(pageHeader, viewChartFullscreenButton, nil, nil, chartFyneImage)

	setPageContent(page, window)
}

