package gui

// statisticsGui.go implements pages to view statistics about the Seekia network

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

import "seekia/internal/appMemory"
import "seekia/internal/badgerDatabase"
import "seekia/internal/createCharts"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/userStatistics"
import "seekia/internal/statisticsDatum"

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

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

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

	title := getPageTitleCentered("Network Statistics")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered(translate("View statistics about the Seekia network."))

	mateStatisticsButton := widget.NewButton("Mate Statistics", func(){
			setNetworkUserStatisticsPage(window, "Mate", currentPage)
		})

	hostStatisticsButton := widget.NewButton("Host Statistics", func(){
			setNetworkUserStatisticsPage(window, "Host", currentPage)
		})

	moderatorStatisticsButton := widget.NewButton("Moderator Statistics", func(){
			setNetworkUserStatisticsPage(window, "Moderator", currentPage)
		})

	messageStatisticsButton := widget.NewButton("Message Statistics", func(){
			//TODO
			showUnderConstructionDialog(window)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, mateStatisticsButton, hostStatisticsButton, moderatorStatisticsButton, messageStatisticsButton))

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

	setPageContent(page, window)
}


func setNetworkUserStatisticsPage(window fyne.Window, identityType string, previousPage func()){

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

	setLoadingScreen(window, identityType + " Statistics", "Loading " + identityType + " statistics page...")

	currentPage := func(){setNetworkUserStatisticsPage(window, identityType, previousPage)}

	title := getPageTitleCentered(identityType + " Statistics")

	backButton := getBackButtonCentered(previousPage)

	description1 := getLabelCentered("Below are statistics about " + identityType + " profiles and users.")

	description2 := widget.NewLabel("Be aware that these statistics may not represent the entire Seekia network.")

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

	description2Row := container.NewHBox(layout.NewSpacer(), description2, description2HelpButton, layout.NewSpacer())

	profileHashesList, err := badgerDatabase.GetAllProfileHashes(identityType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfUserProfiles := len(profileHashesList)

	profileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes(identityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfUserIdentities := len(profileIdentityHashesList)

	numberOfUserProfilesString := helpers.ConvertIntToString(numberOfUserProfiles)
	numberOfUserProfilesLabel := widget.NewLabel("Number of " + identityType + " Profiles:")
	numberOfUserProfilesText := getBoldLabel(numberOfUserProfilesString)
	numberOfUserProfilesRow := container.NewHBox(layout.NewSpacer(), numberOfUserProfilesLabel, numberOfUserProfilesText, layout.NewSpacer())

	numberOfUserIdentitiesString := helpers.ConvertIntToString(numberOfUserIdentities)
	numberOfUserIdentitiesLabel := widget.NewLabel("Number of " + identityType + " Identities:")
	numberOfUserIdentitiesText := getBoldLabel(numberOfUserIdentitiesString)
	numberOfUserIdentitiesRow := container.NewHBox(layout.NewSpacer(), numberOfUserIdentitiesLabel, numberOfUserIdentitiesText, layout.NewSpacer())

	//TODO: add % of users who are disabled.
	//TODO: Add more statistics

	getIdentityTypeDefaultAttribute := func()string{

		if (identityType == "Mate"){
			return "Age"
		}
		if (identityType == "Host"){
			return "SeekiaVersion"
		}
		// identityType == "Moderator"
		return "IdentityScore"
	}

	identityTypeDefaultAttribute := getIdentityTypeDefaultAttribute()

	attributeStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Attribute Statistics", theme.VisibilityIcon(), func(){
			setViewUserAttributeStatisticsPage_BarChart(window, identityType, identityTypeDefaultAttribute, "Number Of Users", " users", true, false, false, nil, false, nil, nil, currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), numberOfUserProfilesRow, numberOfUserIdentitiesRow, attributeStatisticsButton)

	setPageContent(page, window)
}

func setViewUserAttributeStatisticsPage_BarChart(
		window fyne.Window,
		identityType string,
		xAxisAttributeName string,
		yAxisAttribute string,
		yAxisUnits string,
		showYAxisPercentage bool,
		statisticsReady bool,
		anyUsersExist bool,
		statisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		groupingPerformed bool,
		groupedStatisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		chartImage image.Image,
		previousPage func()){

	currentPage := func(){setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, statisticsReady, anyUsersExist, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, chartImage, previousPage)}

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

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

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

	title := getPageTitleCentered("User Attribute Statistics - Bar Chart")

	backButton := getBackButtonCentered(previousPage)

	identityTypeLabel := getBoldLabelCentered("Identity Type")

	handleIdentityTypeSelectFunction := func(newIdentityType string){

		if (newIdentityType == identityType){
			return
		}
		if (newIdentityType == "Mate"){

			setViewUserAttributeStatisticsPage_BarChart(window, "Mate", "Age", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
			return
		}
		if (newIdentityType == "Host"){
			setViewUserAttributeStatisticsPage_BarChart(window, "Host", "SeekiaVersion", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
			return
		}
		// newIdentityType == "Moderator"
		setViewUserAttributeStatisticsPage_BarChart(window, "Moderator", "IdentityScore", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
	}

	identityTypesList := []string{"Mate", "Host", "Moderator"}

	identityTypeSelector := widget.NewSelect(identityTypesList, handleIdentityTypeSelectFunction)
	identityTypeSelector.Selected = identityType

	identityTypeColumn := container.NewVBox(identityTypeLabel, identityTypeSelector)

	chartTypeLabel := getBoldLabelCentered("Chart Type")

	chartTypesList := []string{"Bar Chart", "Line Chart", "Donut Chart"}
	handleChartTypeSelectFunction := func(newChartType string){
		if (newChartType == "Bar Chart"){
			return
		}
		if (newChartType == "Line Chart"){
			setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAxisAttributeName, "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
			return
		}
		// newChartType == "Donut Chart"
		setViewUserAttributeStatisticsPage_DonutChart(window, identityType, xAxisAttributeName, false, false, nil, false, nil, nil, previousPage)
	}

	chartTypeSelector := widget.NewSelect(chartTypesList, handleChartTypeSelectFunction)
	chartTypeSelector.Selected = "Bar Chart"

	chartTypeColumn := container.NewVBox(chartTypeLabel, chartTypeSelector)

	xAxisAttributeTitle, xAxisIsNumerical, formatXAxisValuesFunction, xAxisUnits, unknownXAxisValuesTextTranslated, err := attributeDisplay.GetProfileAttributeDisplayInfo(xAxisAttributeName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	xAxisLabel := getBoldLabelCentered("X-Axis")
	changeXAxisAttributeButton := widget.NewButton(xAxisAttributeTitle, func(){

			currentPageWithNewAttributeFunction := func(newXAxisAttributeName string){
					setViewUserAttributeStatisticsPage_BarChart(window, identityType, newXAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage)
				}

			setViewUserAttributeStatistics_ChooseXAxisAttributePage(window, identityType, false, currentPageWithNewAttributeFunction, currentPage)
		})

	xAxisColumn := container.NewVBox(xAxisLabel, changeXAxisAttributeButton)

	yAxisLabel := getBoldLabelCentered("Y-Axis")
	changeYAxisAttributeButton := widget.NewButton(translate(yAxisAttribute), func(){

			currentPageWithNewAttributeFunction := func(newYAxisAttribute string, newYAxisUnits string, showYAxisPercentage bool){
					setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, newYAxisAttribute, newYAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage)
				}

			setViewUserAttributeStatistics_ChooseYAxisAttributePage(window, identityType, currentPageWithNewAttributeFunction, currentPage)
		})

	yAxisColumn := container.NewVBox(yAxisLabel, changeYAxisAttributeButton)

	chartSettingsRow := container.NewHBox(layout.NewSpacer(), widget.NewSeparator(), identityTypeColumn, widget.NewSeparator(), chartTypeColumn, widget.NewSeparator(), xAxisColumn, widget.NewSeparator(), yAxisColumn, widget.NewSeparator(), layout.NewSpacer())

	if (statisticsReady == true){

		if (anyUsersExist == false){

			description1 := getBoldLabelCentered("No results exist.")
			description2 := getLabelCentered("No statistics are available to show.")
			description3 := getLabelCentered("You may have to wait for profiles to be downloaded or add more information in your profile.")

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

			setPageContent(page, window)
			return
		}

		viewDataButton := widget.NewButtonWithIcon("View Data", theme.ListIcon(), func(){
				setViewUserStatisticsDataPage(window, xAxisAttributeTitle, yAxisAttribute, showYAxisPercentage, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, true, xAxisUnits, yAxisUnits, currentPage)
			})

		viewFullscreenButton := widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
				setViewFullpageImagePage(window, chartImage, currentPage)
			})

		warningButton := widget.NewButtonWithIcon("Warning", theme.WarningIcon(), func(){
				setNetworkStatisticsInaccuracyWarningPage(window, currentPage)
			})

		chartCompleteButtonsRow := getContainerCentered(container.NewGridWithRows(1, viewDataButton, viewFullscreenButton, warningButton))

		header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), chartCompleteButtonsRow, widget.NewSeparator())

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

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

		setPageContent(page, window)
		return
	}

	loadingTextBinding := binding.NewString()
	loadingTextLabel := widget.NewLabelWithData(loadingTextBinding)
	loadingTextLabel.TextStyle = getFyneTextStyle_Bold()
	loadingTextLabelCentered := container.NewCenter(loadingTextLabel)

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

	createChartAndRefreshPageFunction := func(){

		var functionCompleteBoolMutex sync.RWMutex
		functionCompleteBool := false

		updateLoadingBindingFunction := func(){
	
			secondsElapsed := 0

			for{

				if (secondsElapsed%3 == 0){
					loadingTextBinding.Set("Loading Statistics.")
				} else if (secondsElapsed%3 == 1){
					loadingTextBinding.Set("Loading Statistics..")
				} else {
					loadingTextBinding.Set("Loading Statistics...")
				}
				pageHasChanged := checkIfPageHasChangedFunction()
				if (pageHasChanged == true){
					return
				}

				functionCompleteBoolMutex.RLock()
				functionIsComplete := functionCompleteBool
				functionCompleteBoolMutex.RUnlock()
				if (functionIsComplete == true){
					return
				}

				secondsElapsed += 1
				time.Sleep(time.Second)
			}
		}

		go updateLoadingBindingFunction()

		totalAnalyzedUsers, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, formatYAxisValuesFunction, err := userStatistics.GetUserStatisticsDatumsLists_BarChart(identityType, appNetworkType, xAxisAttributeName, xAxisIsNumerical, formatXAxisValuesFunction, unknownXAxisValuesTextTranslated, yAxisAttribute)
		if (err != nil) {

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		if (len(statisticsDatumsList) == 0){

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == false){
				fyne.Do(func(){setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, false, nil, false, nil, nil, previousPage)})
			}
			return
		}

		getChartStatisticsDatumsList := func()[]statisticsDatum.DiscreteStatisticsDatum{
			if (groupingPerformed == false){
				return statisticsDatumsList
			}
			return groupedStatisticsDatumsList
		}
		
		chartStatisticsDatumsList := getChartStatisticsDatumsList()

		getChartTitle := func()string{

			totalAnalyzedUsersString := helpers.ConvertIntToString(totalAnalyzedUsers)

			chartTitle := identityType + " Statistics: " + xAxisAttributeTitle

			if (xAxisUnits != ""){

				xAxisUnitsTrimmed := strings.TrimSpace(xAxisUnits)

				chartTitle += " (" + xAxisUnitsTrimmed + ")"
			}

			chartTitle += " by " + yAxisAttribute

			if (yAxisUnits != "" && yAxisUnits != translate(" users")){

				yAxisUnitsTrimmed := strings.TrimSpace(yAxisUnits)

				chartTitle += " (" + yAxisUnitsTrimmed + ")"
			}

			chartTitle += " - " + totalAnalyzedUsersString + " Users"

			return chartTitle
		}

		chartTitle := getChartTitle()

		newChartImage, err := createCharts.CreateBarChart(chartTitle, chartStatisticsDatumsList, formatYAxisValuesFunction, true, yAxisUnits)
		if (err != nil) {

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		functionCompleteBoolMutex.Lock()
		functionCompleteBool = true
		functionCompleteBoolMutex.Unlock()

		pageHasChanged := checkIfPageHasChangedFunction()
		if (pageHasChanged == false){
			fyne.Do(func(){setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, newChartImage, previousPage)})
		}
	}

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

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

	setPageContent(page, window)

	go createChartAndRefreshPageFunction()
}



func setViewUserAttributeStatisticsPage_LineChart(
		window fyne.Window,
		identityType string,
		xAxisAttributeName string,
		yAxisAttribute string,
		yAxisUnits string,
		showYAxisPercentage bool,
		statisticsReady bool,
		anyUsersExist bool,
		discreteStatisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		groupingPerformed bool,
		groupedStatisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		chartImage image.Image,
		previousPage func()){

	currentPage := func(){setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, statisticsReady, anyUsersExist, discreteStatisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, chartImage, previousPage)}

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

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

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

	title := getPageTitleCentered("User Attribute Statistics - Line Chart")

	backButton := getBackButtonCentered(previousPage)

	identityTypeLabel := getBoldLabelCentered("Identity Type")

	handleIdentityTypeSelectFunction := func(newIdentityType string){

		if (newIdentityType == identityType){
			return
		}
		if (newIdentityType == "Mate"){

			setViewUserAttributeStatisticsPage_LineChart(window, "Mate", "Age", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
			return
		}
		if (newIdentityType == "Host"){
			setViewUserAttributeStatisticsPage_LineChart(window, "Host", "SeekiaVersion", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
			return
		}
		// newIdentityType == "Moderator"
		setViewUserAttributeStatisticsPage_LineChart(window, "Moderator", "IdentityScore", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
	}

	identityTypesList := []string{"Mate", "Host", "Moderator"}

	identityTypeSelector := widget.NewSelect(identityTypesList, handleIdentityTypeSelectFunction)
	identityTypeSelector.Selected = identityType

	identityTypeColumn := container.NewVBox(identityTypeLabel, identityTypeSelector)

	chartTypeLabel := getBoldLabelCentered("Chart Type")

	chartTypesList := []string{"Bar Chart", "Line Chart", "Donut Chart"}
	handleChartTypeSelectFunction := func(newChartType string){
		if (newChartType == "Line Chart"){
			return
		}
		if (newChartType == "Donut Chart"){
			setViewUserAttributeStatisticsPage_DonutChart(window, identityType, xAxisAttributeName, false, false, nil, false, nil, nil, previousPage)
			return
		}

		// newChartType == "Bar Chart"
		setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage)
	}

	chartTypeSelector := widget.NewSelect(chartTypesList, handleChartTypeSelectFunction)
	chartTypeSelector.Selected = "Line Chart"

	chartTypeColumn := container.NewVBox(chartTypeLabel, chartTypeSelector)

	xAxisAttributeTitle, xAxisIsNumerical, formatXAxisValuesFunction, xAxisUnits, unknownXAxisValuesTextTranslated, err := attributeDisplay.GetProfileAttributeDisplayInfo(xAxisAttributeName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (xAxisIsNumerical == false){
		setErrorEncounteredPage(window, errors.New("setViewUserAttributeStatisticsPage_LineChart called with non-numerical xAxis attribute: " + xAxisAttributeName), previousPage)
		return
	}

	xAxisLabel := getBoldLabelCentered("X-Axis")
	changeXAxisAttributeButton := widget.NewButton(xAxisAttributeTitle, func(){

			currentPageWithNewAttributeFunction := func(newXAxisAttributeName string){
					setViewUserAttributeStatisticsPage_LineChart(window, identityType, newXAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage)
				}

			setViewUserAttributeStatistics_ChooseXAxisAttributePage(window, identityType, true, currentPageWithNewAttributeFunction, currentPage)
		})

	xAxisColumn := container.NewVBox(xAxisLabel, changeXAxisAttributeButton)

	yAxisLabel := getBoldLabelCentered("Y-Axis")
	changeYAxisAttributeButton := widget.NewButton(translate(yAxisAttribute), func(){

			currentPageWithNewAttributeFunction := func(newYAxisAttribute string, newYAxisUnits string, showYAxisPercentage bool){
					setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAxisAttributeName, newYAxisAttribute, newYAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage)
				}

			setViewUserAttributeStatistics_ChooseYAxisAttributePage(window, identityType, currentPageWithNewAttributeFunction, currentPage)
		})

	yAxisColumn := container.NewVBox(yAxisLabel, changeYAxisAttributeButton)

	chartSettingsRow := container.NewHBox(layout.NewSpacer(), widget.NewSeparator(), identityTypeColumn, widget.NewSeparator(), chartTypeColumn, widget.NewSeparator(), xAxisColumn, widget.NewSeparator(), yAxisColumn, widget.NewSeparator(), layout.NewSpacer())

	if (statisticsReady == true){

		if (anyUsersExist == false){

			description1 := getBoldLabelCentered("No results exist.")
			description2 := getLabelCentered("No statistics are available to show.")
			description3 := getLabelCentered("You may have to wait for profiles to be downloaded or add more information in your profile.")

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

			setPageContent(page, window)
			return
		}

		viewDataButton := widget.NewButtonWithIcon("View Data", theme.ListIcon(), func(){
				setViewUserStatisticsDataPage(window, xAxisAttributeTitle, yAxisAttribute, showYAxisPercentage, discreteStatisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, true, xAxisUnits, yAxisUnits, currentPage)
			})

		viewFullscreenButton := widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
				setViewFullpageImagePage(window, chartImage, currentPage)
			})

		warningButton := widget.NewButtonWithIcon("Warning", theme.WarningIcon(), func(){
				setNetworkStatisticsInaccuracyWarningPage(window, currentPage)
			})

		chartCompleteButtonsRow := getContainerCentered(container.NewGridWithRows(1, viewDataButton, viewFullscreenButton, warningButton))

		header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), chartCompleteButtonsRow, widget.NewSeparator())

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

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

		setPageContent(page, window)
		return
	}

	loadingTextBinding := binding.NewString()
	loadingTextLabel := widget.NewLabelWithData(loadingTextBinding)
	loadingTextLabel.TextStyle = getFyneTextStyle_Bold()
	loadingTextLabelCentered := container.NewCenter(loadingTextLabel)

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

	createChartAndRefreshPageFunction := func(){

		var functionCompleteBoolMutex sync.RWMutex
		functionCompleteBool := false

		updateLoadingBindingFunction := func(){
	
			secondsElapsed := 0

			for{

				if (secondsElapsed%3 == 0){
					loadingTextBinding.Set("Loading Statistics.")
				} else if (secondsElapsed%3 == 1){
					loadingTextBinding.Set("Loading Statistics..")
				} else {
					loadingTextBinding.Set("Loading Statistics...")
				}
				pageHasChanged := checkIfPageHasChangedFunction()
				if (pageHasChanged == true){
					return
				}

				functionCompleteBoolMutex.RLock()
				functionIsComplete := functionCompleteBool
				functionCompleteBoolMutex.RUnlock()
				if (functionIsComplete == true){
					return
				}

				secondsElapsed += 1
				time.Sleep(time.Second)
			}
		}

		go updateLoadingBindingFunction()

		totalAnalyzedUsers, chartStatisticsDatumsList, newDiscreteStatisticsDatumsList, groupingPerformed, newGroupedStatisticsDatumsList, formatYAxisValuesFunction, err := userStatistics.GetUserStatisticsDatumsLists_LineChart(identityType, appNetworkType, xAxisAttributeName, formatXAxisValuesFunction, unknownXAxisValuesTextTranslated, yAxisAttribute)
		if (err != nil) {

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		if (len(chartStatisticsDatumsList) < 2){

			// We need at least 2 points to create a line chart

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == false){
				fyne.Do(func(){setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, false, nil, false, nil, nil, previousPage)})
			}

			return
		}

		getChartTitle := func()string{

			totalAnalyzedUsersString := helpers.ConvertIntToString(totalAnalyzedUsers)

			chartTitle := identityType + " Statistics: " + xAxisAttributeTitle

			if (xAxisUnits != ""){

				xAxisUnitsTrimmed := strings.TrimSpace(xAxisUnits)

				chartTitle += " (" + xAxisUnitsTrimmed + ")"
			}

			chartTitle += " by " + yAxisAttribute

			if (yAxisUnits != "" && yAxisUnits != translate(" users")){

				yAxisUnitsTrimmed := strings.TrimSpace(yAxisUnits)

				chartTitle += " (" + yAxisUnitsTrimmed + ")"
			}

			chartTitle += " - " + totalAnalyzedUsersString + " Users"

			return chartTitle
		}

		chartTitle := getChartTitle()

		newChartLineObject := createCharts.ChartLineData{
			Name: "",
			LineDatumsList: chartStatisticsDatumsList,
		}

		newChartLineDatasList := []createCharts.ChartLineData{newChartLineObject}

		formatXAxisValuesFunctionFloat64 := func(inputValue float64)(string, error){

			inputValueString := helpers.ConvertFloat64ToString(inputValue)

			result, err := formatXAxisValuesFunction(inputValueString)
			if (err != nil){ return "", err }

			return result, nil
		}

		newChartImage, err := createCharts.CreateLineChart(chartTitle, newChartLineDatasList, xAxisAttributeTitle, formatXAxisValuesFunctionFloat64, yAxisAttribute, formatYAxisValuesFunction, true, yAxisUnits)
		if (err != nil) {

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		functionCompleteBoolMutex.Lock()
		functionCompleteBool = true
		functionCompleteBoolMutex.Unlock()

		pageHasChanged := checkIfPageHasChangedFunction()
		if (pageHasChanged == false){
			fyne.Do(func(){setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, true, newDiscreteStatisticsDatumsList, groupingPerformed, newGroupedStatisticsDatumsList, newChartImage, previousPage)})
		}
	}

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

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

	setPageContent(page, window)

	go createChartAndRefreshPageFunction()
}


func setViewUserAttributeStatisticsPage_DonutChart(
		window fyne.Window,
		identityType string,
		attributeName string,
		statisticsReady bool,
		anyUsersExist bool,
		statisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		groupingPerformed bool,
		groupedStatisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		chartImage image.Image,
		previousPage func()){

	currentPage := func(){setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, statisticsReady, anyUsersExist, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, chartImage, previousPage)}

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

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

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

	title := getPageTitleCentered("User Attribute Statistics - Donut Chart")

	backButton := getBackButtonCentered(previousPage)

	identityTypeLabel := getBoldLabelCentered("Identity Type")

	handleIdentityTypeSelectFunction := func(newIdentityType string){

		if (newIdentityType == identityType){
			return
		}
		if (newIdentityType == "Mate"){

			setViewUserAttributeStatisticsPage_DonutChart(window, "Mate", "Age", false, false, nil, false, nil, nil, previousPage)
			return
		}
		if (newIdentityType == "Host"){
			setViewUserAttributeStatisticsPage_DonutChart(window, "Host", "SeekiaVersion", false, false, nil, false, nil, nil, previousPage)
			return
		}
		// newIdentityType == "Moderator"
		setViewUserAttributeStatisticsPage_DonutChart(window, "Moderator", "IdentityScore", false, false, nil, false, nil, nil, previousPage)
	}

	identityTypesList := []string{"Mate", "Host", "Moderator"}

	identityTypeSelector := widget.NewSelect(identityTypesList, handleIdentityTypeSelectFunction)
	identityTypeSelector.Selected = identityType

	identityTypeColumn := container.NewVBox(identityTypeLabel, identityTypeSelector)

	attributeTitle, attributeIsNumerical, formatAttributeValuesFunction, attributeUnits, unknownValuesTextTranslated, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	chartTypeLabel := getBoldLabelCentered("Chart Type:")

	chartTypesList := []string{"Bar Chart", "Line Chart", "Donut Chart"}
	handleSelectFunction := func(newType string){
			if (newType == "Donut Chart"){
				return
			}
			if (newType == "Bar Chart"){
				setViewUserAttributeStatisticsPage_BarChart(window, identityType, attributeName, "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
				return
			}

			// newType == "Line Chart"
			getXAttributeForLineChart := func()string{
				if (attributeIsNumerical == true){
					return attributeName
				}
				// We default to age.
				return "Age"
			}

			xAttributeForLineChart := getXAttributeForLineChart()

			setViewUserAttributeStatisticsPage_LineChart(window, identityType, xAttributeForLineChart, "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage)
		}

	chartTypeSelector := widget.NewSelect(chartTypesList, handleSelectFunction)
	chartTypeSelector.Selected = "Donut Chart"

	chartTypeColumn := container.NewVBox(chartTypeLabel, chartTypeSelector)

	attributeLabel := getBoldLabelCentered("Attribute:")
	changeAttributeButton := widget.NewButton(attributeTitle, func(){
			currentPageWithNewAttributeFunction := func(newAttributeName string){
					setViewUserAttributeStatisticsPage_DonutChart(window, identityType, newAttributeName, false, false, nil, false, nil, nil, previousPage)
				}
			setViewUserAttributeStatistics_ChooseXAxisAttributePage(window, identityType, false, currentPageWithNewAttributeFunction, currentPage)
		})
	attributeColumn := container.NewVBox(attributeLabel, changeAttributeButton)

	chartSettingsRow := container.NewHBox(layout.NewSpacer(), widget.NewSeparator(), identityTypeColumn, widget.NewSeparator(), chartTypeColumn, widget.NewSeparator(), attributeColumn, widget.NewSeparator(), layout.NewSpacer())

	if (statisticsReady == true){

		if (anyUsersExist == false){

			description1 := getBoldLabelCentered("No results exist.")
			description2 := getLabelCentered("No statistics are available to show.")
			description3 := getLabelCentered("You may have to wait for profiles to be downloaded or add more information in your profile.")

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

			setPageContent(page, window)
			return
		}

		viewDataButton := widget.NewButtonWithIcon("View Data", theme.ListIcon(), func(){

				setViewUserStatisticsDataPage(window, attributeTitle, "Number Of Users", true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, true, attributeUnits, "Users", currentPage)
			})

		viewFullscreenButton := widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
				setViewFullpageImagePage(window, chartImage, currentPage)
			})

		warningButton := widget.NewButtonWithIcon("Warning", theme.WarningIcon(), func(){
				setNetworkStatisticsInaccuracyWarningPage(window, currentPage)
			})

		chartCompleteButtonsRow := getContainerCentered(container.NewGridWithRows(1, viewDataButton, viewFullscreenButton, warningButton))

		header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), chartCompleteButtonsRow, widget.NewSeparator())

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

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

	loadingTextBinding := binding.NewString()
	loadingTextLabel := widget.NewLabelWithData(loadingTextBinding)
	loadingTextLabel.TextStyle = getFyneTextStyle_Bold()
	loadingTextLabelCentered := container.NewCenter(loadingTextLabel)

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

	createChartAndRefreshPageFunction := func(){

		var functionCompleteBoolMutex sync.RWMutex
		functionCompleteBool := false

		updateLoadingBindingFunction := func(){
	
			secondsElapsed := 0
	
			for{

				if (secondsElapsed%3 == 0){
					loadingTextBinding.Set("Loading Statistics.")
				} else if (secondsElapsed%3 == 1){
					loadingTextBinding.Set("Loading Statistics..")
				} else {
					loadingTextBinding.Set("Loading Statistics...")
				}
				pageHasChanged := checkIfPageHasChangedFunction()
				if (pageHasChanged == true){
					return
				}

				functionCompleteBoolMutex.RLock()
				functionIsComplete := functionCompleteBool
				functionCompleteBoolMutex.RUnlock()
				if (functionIsComplete == true){
					return
				}

				secondsElapsed += 1
				time.Sleep(time.Second)
			}
		}

		go updateLoadingBindingFunction()

		totalAnalyzedUsers, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, err := userStatistics.GetUserStatisticsDatumsLists_DonutChart(identityType, appNetworkType, attributeName, attributeIsNumerical, formatAttributeValuesFunction, unknownValuesTextTranslated)
		if (err != nil) {

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		if (len(statisticsDatumsList) == 0){

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == false){
				fyne.Do(func(){setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, true, false, nil, false, nil, nil, previousPage)})
			}
			return
		}

		getChartStatisticsDatumsList := func()[]statisticsDatum.DiscreteStatisticsDatum{
			if (groupingPerformed == false){
				return statisticsDatumsList
			}
			return groupedStatisticsDatumsList
		}

		chartStatisticsDatumsList := getChartStatisticsDatumsList()

		getChartTitle := func()string{

			totalAnalyzedUsersString := helpers.ConvertIntToString(totalAnalyzedUsers)

			chartTitle := identityType + " Statistics: " + attributeTitle

			if (attributeUnits != ""){

				attributeUnitsTrimmed := strings.TrimPrefix(attributeUnits, " ")

				chartTitle += " (" + attributeUnitsTrimmed + ")"
			}

			chartTitle += " - " + totalAnalyzedUsersString + " Users"

			return chartTitle
		}

		chartTitle := getChartTitle()

		newChartImage, err := createCharts.CreateDonutChart(chartTitle, chartStatisticsDatumsList)
		if (err != nil){

			functionCompleteBoolMutex.Lock()
			functionCompleteBool = true
			functionCompleteBoolMutex.Unlock()

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

		functionCompleteBoolMutex.Lock()
		functionCompleteBool = true
		functionCompleteBoolMutex.Unlock()

		pageHasChanged := checkIfPageHasChangedFunction()
		if (pageHasChanged == false){
			fyne.Do(func(){setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, true, true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, newChartImage, previousPage)})
		}
	}

	header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator())
	page := container.NewBorder(header, nil, nil, nil, loadingTextLabelCentered)

	setPageContent(page, window)

	go createChartAndRefreshPageFunction()
}


//Inputs:
//	-fyne.Window
//	-string: Profile Type
//	-bool: Only show numeric attributes
//	-func(attributeName string)
//	-func(): Previous page
func setViewUserAttributeStatistics_ChooseXAxisAttributePage(window fyne.Window, profileType string, onlyShowNumericAttributes bool, viewStatisticsPageWithNewAttribute func(string), previousPage func()){

	title := getPageTitleCentered(translate("Choose Attribute"))

	backButton := getBackButtonCentered(previousPage)

	attributesToShowList := make([]string, 0)

	addAttributeToListFunction := func(attributeName string, attributeIsNumeric bool){

		if (onlyShowNumericAttributes == true && attributeIsNumeric == false){
			return
		}

		attributesToShowList = append(attributesToShowList, attributeName)
	}

	if (profileType == "Mate"){

		addAttributeToListFunction("Age", true)
		addAttributeToListFunction("Height", true)
		addAttributeToListFunction("Sex", false)
		addAttributeToListFunction("Sexuality", false)
		addAttributeToListFunction("Distance", true)
		addAttributeToListFunction("WealthInGold", true)
	}

	addAttributeToListFunction("ProfileLanguage", false)

	if (profileType == "Mate" || profileType == "Moderator"){
		addAttributeToListFunction("HasMessagedMe", false)
		addAttributeToListFunction("IHaveMessaged", false)
	}

	if (profileType == "Mate"){
		addAttributeToListFunction("HasRejectedMe", false)
		addAttributeToListFunction("IsLiked", false)
		addAttributeToListFunction("IsIgnored", false)
	}

	addAttributeToListFunction("IsMyContact", false)

	if (profileType == "Mate"){
		addAttributeToListFunction("PrimaryLocationCountry", false)
		addAttributeToListFunction("BodyFat", false)
		addAttributeToListFunction("BodyMuscle", false)
		addAttributeToListFunction("HairColor", false)
		addAttributeToListFunction("HairTexture", false)
		addAttributeToListFunction("EyeColor", false)
		addAttributeToListFunction("SkinColor", false)
		addAttributeToListFunction("DeepAppearanceSimilarity", true)
		addAttributeToListFunction("EyeColorSimilarity", true)
		addAttributeToListFunction("EyeColorGenesSimilarity", true)
		addAttributeToListFunction("HairColorSimilarity", true)
		addAttributeToListFunction("HairColorGenesSimilarity", true)
		addAttributeToListFunction("SkinColorSimilarity", true)
		addAttributeToListFunction("SkinColorGenesSimilarity", true)
		addAttributeToListFunction("HairTextureSimilarity", true)
		addAttributeToListFunction("HairTextureGenesSimilarity", true)
		addAttributeToListFunction("FacialStructureGenesSimilarity", true)
		addAttributeToListFunction("23andMe_AncestralSimilarity", true)
		addAttributeToListFunction("23andMe_MaternalHaplogroupSimilarity", true)
		addAttributeToListFunction("23andMe_PaternalHaplogroupSimilarity", true)
		addAttributeToListFunction("HasHIV", false)
		addAttributeToListFunction("HasGenitalHerpes", false)
		addAttributeToListFunction("GenderIdentity", false)
		addAttributeToListFunction("23andMe_MaternalHaplogroup", false)
		addAttributeToListFunction("23andMe_PaternalHaplogroup", false)
		addAttributeToListFunction("23andMe_NeanderthalVariants", true)
		addAttributeToListFunction("FruitRating", true)
		addAttributeToListFunction("VegetablesRating", true)
		addAttributeToListFunction("NutsRating", true)
		addAttributeToListFunction("GrainsRating", true)
		addAttributeToListFunction("DairyRating", true)
		addAttributeToListFunction("SeafoodRating", true)
		addAttributeToListFunction("BeefRating", true)
		addAttributeToListFunction("PorkRating", true)
		addAttributeToListFunction("PoultryRating", true)
		addAttributeToListFunction("EggsRating", true)
		addAttributeToListFunction("BeansRating", true)
		addAttributeToListFunction("Fame", true)
		addAttributeToListFunction("AlcoholFrequency", true)
		addAttributeToListFunction("TobaccoFrequency", true)
		addAttributeToListFunction("CannabisFrequency", true)
		addAttributeToListFunction("PetsRating", true)
		addAttributeToListFunction("DogsRating", true)
		addAttributeToListFunction("CatsRating", true)
		addAttributeToListFunction("OffspringProbabilityOfAnyMonogenicDisease", true)
		addAttributeToListFunction("TotalPolygenicDiseaseRiskScore", true)
		addAttributeToListFunction("OffspringTotalPolygenicDiseaseRiskScore", true)
	}

	buttonsGrid := container.NewGridWithColumns(1)

	for _, attributeName := range attributesToShowList{

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

		attributeButton := widget.NewButton(attributeTitle, func(){
				viewStatisticsPageWithNewAttribute(attributeName)
			})

		buttonsGrid.Add(attributeButton)
	}

	buttonsGridCentered := getContainerCentered(buttonsGrid)

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

	setPageContent(page, window)
}

//Inputs:
//	-fyne.Window
//	-string: Profile type
//	-func(attributeName string, yAxisUnits string, showYAxisPercentage bool)
//	-func(): Previous page
func setViewUserAttributeStatistics_ChooseYAxisAttributePage(window fyne.Window, profileType string, viewStatisticsPageWithNewAttribute func(string, string, bool), previousPage func()){

	title := getPageTitleCentered("Choose Y-Axis Attribute")

	backButton := getBackButtonCentered(previousPage)

	numberOfUsersButton := widget.NewButton("Number Of Users", func(){
			viewStatisticsPageWithNewAttribute("Number Of Users", translate(" Users"), true)
		})

	buttonsGrid := container.NewGridWithColumns(1, numberOfUsersButton)

	if (profileType == "Mate"){

		averageHeightButton := widget.NewButton("Average Height", func(){
				viewStatisticsPageWithNewAttribute("Average Height", translate(" Centimeters"), false)
			})
		buttonsGrid.Add(averageHeightButton)

		averageAgeButton := widget.NewButton("Average Age", func(){
				viewStatisticsPageWithNewAttribute("Average Age", translate(" Years Old"), false)
			})
		buttonsGrid.Add(averageAgeButton)


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

			exists, appCurrencyCode, err := globalSettings.GetSetting("Currency")
			if (err != nil) { return "", err }
			if (exists == false){
				return "USD", nil
			}
			return appCurrencyCode, nil
		}
		appCurrencyCode, err := getAppCurrency()
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		averageWealthButton := widget.NewButton("Average Wealth", func(){

				wealthUnits := " " + appCurrencyCode

				viewStatisticsPageWithNewAttribute("Average Wealth", wealthUnits, false)
			})
		buttonsGrid.Add(averageWealthButton)

		averageNeanderthalVariantsButton := widget.NewButton("Average 23andMe Neanderthal Variants", func(){
				viewStatisticsPageWithNewAttribute("Average 23andMe Neanderthal Variants", translate(" Variants"), false)
			})
		buttonsGrid.Add(averageNeanderthalVariantsButton)

		averageBodyFatButton := widget.NewButton("Average Body Fat", func(){
				viewStatisticsPageWithNewAttribute("Average Body Fat", "/4", false)
			})
		buttonsGrid.Add(averageBodyFatButton)

		averageBodyMuscleButton := widget.NewButton("Average Body Muscle", func(){
				viewStatisticsPageWithNewAttribute("Average Body Muscle", "/4", false)
			})
		buttonsGrid.Add(averageBodyMuscleButton)

		oneToTenValueAttributesList := []string{
			"Fruit Rating",
			"Vegetables Rating",
			"Nuts Rating",
			"Grains Rating",
			"Dairy Rating",
			"Seafood Rating",
			"Beef Rating",
			"Pork Rating",
			"Poultry Rating",
			"Eggs Rating",
			"Beans Rating",
			"Fame",
			"Alcohol Frequency",
			"Tobacco Frequency",
			"Cannabis Frequency",
			"Pets Rating",
			"Dogs Rating",
			"Cats Rating",
		}

		for _, attributeTitle := range oneToTenValueAttributesList{

			averageAttributeTitle := "Average " + attributeTitle	

			averageAttributeButton := widget.NewButton(averageAttributeTitle, func(){
					viewStatisticsPageWithNewAttribute(averageAttributeTitle, "/10", false)
				})
			buttonsGrid.Add(averageAttributeButton)
		}
	}

	buttonsGridCentered := getContainerCentered(buttonsGrid)

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

	setPageContent(page, window)
}


func setViewUserStatisticsDataPage(
		window fyne.Window,
		attributeTitle string,
		rightColumnName string,
		showPercentageColumn bool,
		statisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		groupingPerformed bool,
		groupedStatisticsDatumsList []statisticsDatum.DiscreteStatisticsDatum,
		showGroupedStatistics bool,
		attributeColumnUnits string,
		rightColumnUnits string,
		previousPage func()){

	setLoadingScreen(window, "User Statistics Data", "Loading statistics data...")

	title := getPageTitleCentered("User Statistics Data")

	backButton := getBackButtonCentered(previousPage)

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

	if (groupingPerformed == true){

		if (showGroupedStatistics == false){
			showDataGroupedButton := getWidgetCentered(widget.NewButton("Show Data Grouped", func(){
					setViewUserStatisticsDataPage(window, attributeTitle, rightColumnName, showPercentageColumn, statisticsDatumsList, true, groupedStatisticsDatumsList, true, attributeColumnUnits, rightColumnUnits, previousPage)
				}))

			header.Add(showDataGroupedButton)
		} else {
			showDataRawButton := getWidgetCentered(widget.NewButton("Show Data Raw", func(){
					setViewUserStatisticsDataPage(window, attributeTitle, rightColumnName, showPercentageColumn, statisticsDatumsList, true, groupedStatisticsDatumsList, false, attributeColumnUnits, rightColumnUnits, previousPage)
				}))

			header.Add(showDataRawButton)
		}
		header.Add(widget.NewSeparator())
	}

	getStatisticsDatumsListToShow := func()[]statisticsDatum.DiscreteStatisticsDatum{
		if (groupingPerformed == true && showGroupedStatistics == true){
			return groupedStatisticsDatumsList
		}
		return statisticsDatumsList
	}

	statisticsDatumsToShowList := getStatisticsDatumsListToShow()

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

		allValuesSummed := float64(0)

		if (showPercentageColumn == true){

			for _, datum := range statisticsDatumsToShowList{

				datumValue := datum.Value

				allValuesSummed += datumValue
			}
		}

		attributeColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList))
		rightColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList))
		percentageColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList))

		for _, datum := range statisticsDatumsToShowList{

			// Each datum is represents a row in the data grid.

			datumLabelFormatted := datum.LabelFormatted

			datumValueFormatted := datum.ValueFormatted

			attributeColumnValuesList = append(attributeColumnValuesList, datumLabelFormatted)
			rightColumnValuesList = append(rightColumnValuesList, datumValueFormatted)

			if (showPercentageColumn == true){

				datumValue := datum.Value

				getValuePercentage := func()float64{
					if (allValuesSummed == 0){
						return 0
					}
					valuePercentage := (datumValue/allValuesSummed)*100
					return valuePercentage
				}

				valuePercentage := getValuePercentage()
				valuePercentageString := helpers.ConvertFloat64ToStringRounded(valuePercentage, 2)
				valuePercentageFormatted := valuePercentageString + "%"

				percentageColumnValuesList = append(percentageColumnValuesList, valuePercentageFormatted)
			}
		}

		getAttributeColumnTitle := func()string{

			if (attributeColumnUnits != ""){

				attributeColumnUnitsTrimmed := strings.TrimSpace(attributeColumnUnits)

				attributeColumnTitle := attributeTitle + " (" + attributeColumnUnitsTrimmed + ")"

				return attributeColumnTitle
			}

			return attributeTitle
		}

		attributeColumnTitle := getAttributeColumnTitle()

		getRightColumnTitle := func()string{

			if (rightColumnUnits != ""){

				rightColumnUnitsTrimmed := strings.TrimSpace(rightColumnUnits)

				rightColumnTitle := rightColumnName + " (" + rightColumnUnitsTrimmed + ")"

				return rightColumnTitle
			}

			return rightColumnName
		}

		rightColumnTitle := getRightColumnTitle()

		if (showGroupedStatistics == false){

			// Raw statistics must be rendered using a widget list
			// Otherwise, rendering thousands of rows is too difficult for fyne

			headerText := attributeColumnTitle + " - " + rightColumnTitle

			if (showPercentageColumn == true){
				headerText += " - Percentage"
			}

			headerLabel := getBoldLabelCentered(headerText)

			rowValuesList := make([]string, 0, len(attributeColumnValuesList))

			for index, attributeColumnValue := range attributeColumnValuesList{

				rightColumnValue := rightColumnValuesList[index]

				if (showPercentageColumn == false){

					rowValue := attributeColumnValue + " - " + rightColumnValue
					rowValuesList = append(rowValuesList, rowValue)
				} else {

					percentageColumnValue := percentageColumnValuesList[index]

					rowValue := attributeColumnValue + " - " + rightColumnValue + " - " + percentageColumnValue
					rowValuesList = append(rowValuesList, rowValue)
				}
			}

			onClickedFunction := func(_ int){}

			widgetList, err := getFyneWidgetListFromStringList(rowValuesList, onClickedFunction)
			if (err != nil) { return nil, err }

			statisticsDataGrid := container.NewBorder(headerLabel, nil, nil, nil, widgetList)

			return statisticsDataGrid, nil
		}

		attributeColumnHeader := getItalicLabelCentered(attributeColumnTitle)
		rightColumnHeader := getItalicLabelCentered(rightColumnTitle)
		percentageHeader := getItalicLabelCentered("Percentage")

		attributeColumn := container.NewVBox(attributeColumnHeader, widget.NewSeparator())
		rightColumn := container.NewVBox(rightColumnHeader, widget.NewSeparator())
		percentageColumn := container.NewVBox(percentageHeader, widget.NewSeparator())

		for index, attributeColumnValue := range attributeColumnValuesList{

			rightColumnValue := rightColumnValuesList[index]

			attributeColumnLabel := getBoldLabelCentered(attributeColumnValue)
			rightColumnLabel := getBoldLabelCentered(rightColumnValue)

			attributeColumn.Add(attributeColumnLabel)
			rightColumn.Add(rightColumnLabel)

			if (showPercentageColumn == true){

				percentageColumnValue := percentageColumnValuesList[index]

				valuePercentageLabel := getBoldLabelCentered(percentageColumnValue)
				percentageColumn.Add(valuePercentageLabel)
			}
		}

		if (showPercentageColumn == true){
			statisticsDataGrid := container.NewHBox(layout.NewSpacer(), attributeColumn, rightColumn, percentageColumn, layout.NewSpacer())
			return statisticsDataGrid, nil
		}

		statisticsDataGrid := container.NewHBox(layout.NewSpacer(), attributeColumn, rightColumn, layout.NewSpacer())
		return statisticsDataGrid, nil
	}

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

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

	setPageContent(page, window)
}


