package gui

// createIdentityGui.go implements pages to choose/create new identity hashes

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

import "seekia/internal/appMemory"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/encoding"
import "seekia/internal/myIdentity"
import "seekia/internal/seedPhrase"
import "seekia/internal/mySeedPhrases"
import "seekia/internal/wordLists"

import "sync"
import "strings"
import "math"
import "time"
import "errors"


func setChooseNewIdentityHashPage(window fyne.Window, myIdentityType string, previousPage func(), onceCompletePage func()){

	currentPage := func(){ setChooseNewIdentityHashPage(window, myIdentityType, previousPage, onceCompletePage) }	

	title := getPageTitleCentered(translate("Choose " + myIdentityType + " Identity Hash"))

	backButton := getBackButtonCentered(previousPage)

	myIdentityExists, _, err := myIdentity.GetMyIdentityHash(myIdentityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == true){
		// This should not occur. This page should only be called if no identity exists.
		description1 := getLabelCentered("Your " + myIdentityType + " identity already exists.")
		description2 := getLabelCentered("Delete your identity before creating a new one.")
		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)

		setPageContent(page, window)
		return
	}

	description1 := getLabelCentered("Choose your " + myIdentityType + " profile identity hash.")
	description2 := getLabelCentered("Your identity hash cannot be changed once selected.")

	createCustomDescription := getLabelCentered("Create a custom identity hash:")
	
	submitPageFunction := func(newSeedPhrase string, prevPage func()){
		setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, prevPage, onceCompletePage)
	}

	createCustomIdentityHashButton := getWidgetCentered(widget.NewButtonWithIcon("Create Custom", theme.SearchReplaceIcon(), func(){
			setCreateCustomIdentityHashPage(window, myIdentityType, false, 0, 0, currentPage, submitPageFunction)
		}))
	
	selectRandomIdentityHashLabel := getLabelCentered("Select a random identity hash:")

	identityHashLabelColumn := container.NewVBox(widget.NewSeparator())
	chooseIdentityHashColumn := container.NewVBox(widget.NewSeparator())

	//TODO: Fix to retrieve language from settings
	myLanguage := "English"

	for n := 0; n < 3; n++ {

		newSeedPhrase, newSeedPhraseHash, err := seedPhrase.GetNewRandomSeedPhrase(myLanguage)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		newIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(newSeedPhraseHash, myIdentityType)
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		newIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(newIdentityHash)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		identityHashText := getBoldLabelCentered(newIdentityHashString)

		selectButton := widget.NewButtonWithIcon("Select", theme.ConfirmIcon(), func(){ 
				setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, currentPage, onceCompletePage) 
			})

		identityHashLabelColumn.Add(identityHashText)
		chooseIdentityHashColumn.Add(selectButton)

		identityHashLabelColumn.Add(widget.NewSeparator())
		chooseIdentityHashColumn.Add(widget.NewSeparator())
	}

	randomIdentityHashesGrid := container.NewHBox(layout.NewSpacer(), identityHashLabelColumn, chooseIdentityHashColumn, layout.NewSpacer())

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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), createCustomDescription, createCustomIdentityHashButton, widget.NewSeparator(), selectRandomIdentityHashLabel, randomIdentityHashesGrid, refreshOptionsButton)

	setPageContent(page, window)
}

func setConfirmMyNewSeedPhrasePage(window fyne.Window, myIdentityType string, newSeedPhrase string, previousPage func(), nextPage func()){

	currentPage := func(){setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, previousPage, nextPage)}	

	title := getPageTitleCentered("Confirm New Identity Hash")

	backButton := getBackButtonCentered(previousPage)
	
	description := getLabelCentered("Confirm new identity hash for " + myIdentityType + " profile?")

	isValid := seedPhrase.VerifySeedPhrase(newSeedPhrase)
	if (isValid == false){
		setErrorEncounteredPage(window, errors.New("setConfirmMyNewSeedPhrasePage called with invalid seed phrase."), previousPage) 
		return
	}

	newSeedPhraseHash, err := seedPhrase.ConvertSeedPhraseToSeedPhraseHash(newSeedPhrase)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	newIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(newSeedPhraseHash, myIdentityType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	newIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(newIdentityHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	identityHashLabel := getContainerCentered(getWidgetBoxed(getBoldLabel(newIdentityHashString)))

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){

		exists, _, err := myIdentity.GetMyIdentityHash(myIdentityType)
		if (err != nil) {
			setErrorEncounteredPage(window, err, currentPage) 
			return
		}
		if (exists == true){
			// This should not occur, as this page should only be shown if existing identity hash is not present.
			setErrorEncounteredPage(window, errors.New("Trying to set seed phrase when existing identity is present."), previousPage)
			return
		}

		err = mySeedPhrases.SetMySeedPhrase(myIdentityType, newSeedPhrase)
		if (err != nil) {
			setErrorEncounteredPage(window, err, currentPage) 
			return
		}

		setEnterMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, nextPage)
	}))

	seedPhraseDescription := getLabelCentered("Write down your seed phrase to backup your identity.")

	seedPhraseLabel := widget.NewMultiLineEntry()
	seedPhraseLabel.Wrapping = 3
	seedPhraseLabel.SetText(newSeedPhrase)
	seedPhraseLabel.OnChanged = func(_ string){
			seedPhraseLabel.SetText(newSeedPhrase)
		}
	seedPhraseLabelBoxed := getWidgetBoxed(seedPhraseLabel)
	widener := widget.NewLabel("                                                                               ")
	seedPhraseLabelWidened := getContainerCentered(container.NewGridWithColumns(1, seedPhraseLabelBoxed, widener))
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, identityHashLabel, confirmButton, widget.NewSeparator(), seedPhraseDescription, seedPhraseLabelWidened)
	
	setPageContent(page, window)
}


func setEnterMyNewSeedPhrasePage(window fyne.Window, myIdentityType string, newSeedPhrase string, nextPage func()){

	title := getPageTitleCentered("Enter Seed Phrase")
	
	description1 := getLabelCentered("Enter your new " + myIdentityType + " seed phrase to confirm your wrote it down correctly.")
	description2 := getLabelCentered("You can skip this step.")

	descriptionsContainer := container.NewVBox(layout.NewSpacer(), description1, description2, layout.NewSpacer())

	seedPhraseLabel := widget.NewMultiLineEntry()
	seedPhraseLabel.Wrapping = 3
	seedPhraseLabel.SetText(newSeedPhrase)
	seedPhraseLabel.OnChanged = func(_ string){
			seedPhraseLabel.SetText(newSeedPhrase)
		}
	seedPhraseLabelBoxed := getWidgetBoxed(seedPhraseLabel)

	seedPhraseLabelWithDescriptions := getContainerCentered(container.NewGridWithColumns(1, descriptionsContainer, seedPhraseLabelBoxed))

	isCorrectStatusBinding := binding.NewString()
	isCorrectStatusBinding.Set("Incorrect")

	isCorrectStatusLabel := widget.NewLabelWithData(isCorrectStatusBinding)
	isCorrectStatusLabel.TextStyle = getFyneTextStyle_Bold()

	isCorrectStatusIcon := widget.NewIcon(theme.CancelIcon())
	statusLabel := widget.NewLabel("Status:")

	isCorrectRow := container.NewHBox(layout.NewSpacer(), statusLabel, isCorrectStatusLabel, isCorrectStatusIcon, layout.NewSpacer())

	skipOrExitButton := widget.NewButtonWithIcon("Skip", theme.MediaFastForwardIcon(), nextPage)

	seedPhraseEntryOnChangedFunction := func(newText string){
		if (newText != newSeedPhrase){
			isCorrectStatusBinding.Set("Incorrect")
			isCorrectStatusIcon.SetResource(theme.CancelIcon())
			skipOrExitButton.SetText("Skip")
			skipOrExitButton.SetIcon(theme.MediaFastForwardIcon())
			return
		}
		isCorrectStatusBinding.Set("Correct")
		isCorrectStatusIcon.SetResource(theme.ConfirmIcon())
		skipOrExitButton.SetText("Exit")
		skipOrExitButton.SetIcon(theme.ConfirmIcon())
	}

	seedPhraseEntry := widget.NewMultiLineEntry()
	seedPhraseEntry.Wrapping = 3
	seedPhraseEntry.OnChanged = seedPhraseEntryOnChangedFunction
	seedPhraseEntryBoxed := getWidgetBoxed(seedPhraseEntry)

	skipOrExitButtonCentered := getWidgetCentered(skipOrExitButton)	

	whitespace := "                                                     "
	emptyLabel := widget.NewLabel("")
	widener := widget.NewLabel(whitespace)
	skipOrExitButtonWithWhitespace := container.NewVBox(skipOrExitButtonCentered, emptyLabel, widener)

	seedPhraseEntryWithButton := getContainerCentered(container.NewGridWithColumns(1, seedPhraseEntryBoxed, skipOrExitButtonWithWhitespace))

	page := container.NewVBox(title, widget.NewSeparator(), seedPhraseLabelWithDescriptions, widget.NewSeparator(), isCorrectRow, seedPhraseEntryWithButton)
	
	setPageContent(page, window)
}

//Inputs:
//	-fyne.Window
//	-string: Identity Type ("Mate"/"Host"/"Moderator")
//	-bool: Benchmark is done (is false if we need to perform the benchmark
//	-int64: Hashes per second (is 0 if benchmark hasn't been performed)
//	-int: Optimal number of goroutines 
//	-previousPage()
//	func(string, func()): submitPage function = func(newSeedPhrase string, previousPage func())
func setCreateCustomIdentityHashPage(window fyne.Window, identityType string, benchmarkIsDone bool, computerHashesPerSecond int64, optimalNumberOfGoroutines int, previousPage func(), submitPage func(string, func()) ){

	if (benchmarkIsDone == false){
		setLoadingScreen(window, "Create Custom Identity Hash", "Performing benchmark, this will take 4 seconds...")

		// Output:
		//	-int64: Hashes per second
		//	-int: Optimal number of goroutines
		//	-error
		performBenchmark := func()(int64, int, error){

			//TODO: Fix to retrieve language from settings
			currentLanguage := "English"
			currentLanguageWordList, err := wordLists.GetWordListFromLanguage(currentLanguage)
			if (err != nil) { return 0, 0, err }

			optimalNumberOfGoroutines := 0
			optimalNumberOfGoroutinesHashesPerSecond := int64(0)

			// We will try 1-8 goroutines to see which is the fastest

			for numberOfGoroutines:=1; numberOfGoroutines<=8; numberOfGoroutines++{

				// This counter will store the number of hashes we can compute in 1 second
				var counterMutex sync.Mutex
				counter := int64(0)

				// This bool keeps track of if the identity hash benchmark is happening
				var generateHashesStatusBoolMutex sync.RWMutex
				generateHashesStatusBool := false

				var errorEncounteredMutex sync.Mutex
				var errorEncountered error

				setErrorEncounteredFunction := func(inputError error){

					errorEncounteredMutex.Lock()
					errorEncountered = inputError
					errorEncounteredMutex.Unlock()

					generateHashesStatusBoolMutex.Lock()
					generateHashesStatusBool = false
					generateHashesStatusBoolMutex.Unlock()
				}

				var identityHashGenerationWaitgroup sync.WaitGroup

				generateIdentityHashesFunction := func(){

					subCounter := int64(0)

					for{

						_, newSeedPhraseHash, err := seedPhrase.GetNewSeedPhraseFromWordList(currentLanguageWordList)
						if (err != nil) {
							setErrorEncounteredFunction(err)
							break
						}

						currentIdentityHashPrefix, err := identity.GetIdentityHash16CharacterPrefixFromSeedPhraseHash(newSeedPhraseHash)
						if (err != nil) {
							setErrorEncounteredFunction(err)
							break
						}

						// We have this check because we want to simulate how long it would take to check for a prefix
						// The majority of the time is spent performing the hashing and ed25519 operations
						strings.HasPrefix(currentIdentityHashPrefix, "seekia")

						subCounter += 1

						generateHashesStatusBoolMutex.RLock()
						generatingStatus := generateHashesStatusBool
						generateHashesStatusBoolMutex.RUnlock()

						if (generatingStatus == false){
							counterMutex.Lock()
							counter += subCounter
							counterMutex.Unlock()
							break
						}
					}

					identityHashGenerationWaitgroup.Done()
				}

				generateHashesStatusBool = true

				identityHashGenerationWaitgroup.Add(numberOfGoroutines)

				for i:=0; i < numberOfGoroutines; i++{

					go generateIdentityHashesFunction()
				}

				// We run all goroutines for 1/2 of a second
				time.Sleep(time.Second/2)

				generateHashesStatusBoolMutex.Lock()
				generateHashesStatusBool = false
				generateHashesStatusBoolMutex.Unlock()

				identityHashGenerationWaitgroup.Wait()

				if (errorEncountered != nil){
					return 0, 0, errorEncountered
				}

				currentGoroutinesNumberOfHashesPerSecond := counter*2

				if (currentGoroutinesNumberOfHashesPerSecond > optimalNumberOfGoroutinesHashesPerSecond){
					optimalNumberOfGoroutines = numberOfGoroutines
					optimalNumberOfGoroutinesHashesPerSecond = currentGoroutinesNumberOfHashesPerSecond
				}
			}

			return optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, nil
		}

		optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, err := performBenchmark()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		setCreateCustomIdentityHashPage(window, identityType, true, optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, previousPage, submitPage) 
		return
	}

	currentPage := func(){
		setCreateCustomIdentityHashPage(window, identityType, true, computerHashesPerSecond, optimalNumberOfGoroutines, previousPage, submitPage)
	}

	title := getPageTitleCentered("Create Custom Identity Hash")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Generate an identity hash with a custom prefix.")
	description2 := getLabelCentered("The longer the prefix, the more difficult it will be to generate.")

	enterDesiredPrefixText := getBoldLabelCentered("         Enter desired prefix:         ")

	customPrefixEntry := widget.NewEntry()
	customPrefixEntry.SetPlaceHolder(translate("Enter desired prefix"))

	estimatedTimeLabelBinding := binding.NewString()
	estimatedTimeUnitsBinding := binding.NewString()
	estimatedTimeLabel := widget.NewLabelWithData(estimatedTimeLabelBinding)
	estimatedTimeLabel.TextStyle = getFyneTextStyle_Bold()
	estimatedTimeLabelCentered := getWidgetCentered(estimatedTimeLabel)
	estimatedTimeUnitsText := getWidgetCentered(widget.NewLabelWithData(estimatedTimeUnitsBinding))

	customPrefixEntryOnChangedFunction := func(newPrefix string){

		if (newPrefix == ""){
			err := estimatedTimeLabelBinding.Set("")
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			err = estimatedTimeUnitsBinding.Set("")	
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			return
		}		

		prefixLength := len(newPrefix)
		if (prefixLength >= 13){
			err := estimatedTimeLabelBinding.Set("Prefix is too long.")
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			err = estimatedTimeUnitsBinding.Set("")	
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			return
		}

		isBase32, invalidCharacter := encoding.VerifyStringContainsOnlyBase32Charset(newPrefix)
		if (isBase32 == false){
			err := estimatedTimeLabelBinding.Set("Invalid character detected: " + invalidCharacter)
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			err = estimatedTimeUnitsBinding.Set(translate("Allowed characters") + ": abcdefghijklmnopqrstuvwxyz234567")	
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage) 
				return
			}
			return
		}

		numberOfCharacters := len(newPrefix)
		numberOfBits := float64(numberOfCharacters * 5)
		numberOfRequiredHashes := math.Pow(2, numberOfBits)

		estimatedTimeToGenerateInSeconds := numberOfRequiredHashes / float64(computerHashesPerSecond)

		estimatedTimeUnitsTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(int64(estimatedTimeToGenerateInSeconds), false)
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage) 
			return
		}

		err = estimatedTimeLabelBinding.Set("Estimated time to generate:")
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage) 
			return
		}
		err = estimatedTimeUnitsBinding.Set(estimatedTimeUnitsTranslated)	
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
	}

	customPrefixEntry.OnChanged = customPrefixEntryOnChangedFunction

	startGeneratingHashesButton := getWidgetCentered(widget.NewButtonWithIcon("Start Generating", theme.ConfirmIcon(), func(){
		prefix := customPrefixEntry.Text
		if (prefix == "") {
			dialogTitle := translate("No Prefix Provided")
			dialogMessage := widget.NewLabel("You must enter a prefix to generate.")
			dialogContent := container.NewVBox(dialogMessage)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		}

		prefixLength := len(prefix)
		if (prefixLength >= 13){
			dialogTitle := translate("Prefix Is Too Long")
			dialogMessage := widget.NewLabel("Prefix is too long.")
			dialogContent := container.NewVBox(dialogMessage)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		}

		isValid, offendingCharacter := encoding.VerifyStringContainsOnlyBase32Charset(prefix)
		if (isValid == false){
			dialogTitle := translate("Prefix Not Allowed")
			dialogMessage := widget.NewLabel("Prefix contains unallowed character: " + offendingCharacter)
			dialogContent := container.NewVBox(dialogMessage)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		}

		estimatedTimeUnitsTranslated, err := estimatedTimeUnitsBinding.Get()
		if (err != nil) {
			setErrorEncounteredPage(window, err, previousPage) 
			return
		}

		emptyList := make([]string, 0)
		setRunCustomIdentityHashGenerationPage(window, identityType, prefix, estimatedTimeUnitsTranslated, optimalNumberOfGoroutines, computerHashesPerSecond, emptyList, currentPage, submitPage)
	}))

	customPrefixEntrySection := getContainerCentered(container.NewGridWithColumns(1, enterDesiredPrefixText, customPrefixEntry, startGeneratingHashesButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, description2, widget.NewSeparator(), customPrefixEntrySection, widget.NewSeparator(), estimatedTimeLabelCentered, estimatedTimeUnitsText)

	setPageContent(page, window)
}

//TODO: Update the estimated time every ~10 seconds

func setRunCustomIdentityHashGenerationPage(window fyne.Window, identityType string, desiredPrefix string, estimatedTimeRequired string, numberOfGoroutines int, initialHashesPerSecond int64, seedPhrasesFoundList []string, previousPage func(), submitPage func(string, func()) ){

	currentPage := func(){setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, initialHashesPerSecond, seedPhrasesFoundList, previousPage, submitPage)}

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

	title := getPageTitleCentered("Generating Custom Identity Hash")

	// We use this bool to keep track of the status of the identity hash generation goroutines
	var generateHashesStatusBoolMutex sync.RWMutex
	generateHashesStatusBool := false

	setGenerateHashesStatusBool := func(newStatus bool){

		generateHashesStatusBoolMutex.Lock()
		generateHashesStatusBool = newStatus
		generateHashesStatusBoolMutex.Unlock()
	}

	getGenerateHashesStatusBool := func()bool{
		generateHashesStatusBoolMutex.RLock()
		currentStatus := generateHashesStatusBool
		generateHashesStatusBoolMutex.RUnlock()

		return currentStatus
	}

	// This waitgroup is used to manage the identity hash generation goroutines
	var generateHashesWaitgroup sync.WaitGroup

	numberOfFoundSeedPhrases := len(seedPhrasesFoundList)

	backButtonFunction := func(){

		// We stop generating hashes
		setGenerateHashesStatusBool(false)
		generateHashesWaitgroup.Wait()

		if (numberOfFoundSeedPhrases == 0){
			previousPage()
			return
		}

		confirmDialogCallbackFunction := func(response bool){
			if (response == true){

				previousPage()
				return
			}
			if (numberOfFoundSeedPhrases < 30){
				// Not enough hashes found yet
				// We restart hash generation
				currentPage()
			}
		}

		dialogTitle := translate("Go Back?")
		dialogMessageA := getBoldLabelCentered("Confirm to go back?")
		dialogMessageB := getLabelCentered("You will lose all of your generated identity hashes.")
		dialogMessageC := getLabelCentered("Write down each identity hash's seed phrase to retain them.")
		dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
		dialog.ShowCustomConfirm(dialogTitle, translate("Go Back"), translate("Cancel"), dialogContent, confirmDialogCallbackFunction, window)
	}

	backButton := getBackButtonCentered(backButtonFunction)

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

		if (numberOfFoundSeedPhrases == 0){
			emptyContainer := container.NewVBox()
			return emptyContainer, nil
		}

		identityHashesColumn := container.NewVBox(widget.NewSeparator())
		selectButtonsColumn := container.NewVBox(widget.NewSeparator())

		for _, currentSeedPhrase := range seedPhrasesFoundList{

			currentSeedPhraseHash, err := seedPhrase.ConvertSeedPhraseToSeedPhraseHash(currentSeedPhrase)
			if (err != nil){ return nil, err }

			currentIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(currentSeedPhraseHash, identityType)
			if (err != nil){ return nil, err }

			currentIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(currentIdentityHash)
			if (err != nil){ return nil, err }

			currentHashLabel := getBoldLabelCentered(currentIdentityHashString)

			currentHashSelectButton := widget.NewButtonWithIcon("Select", theme.ConfirmIcon(), func(){

					setGenerateHashesStatusBool(false)
					generateHashesWaitgroup.Wait()

					submitPage(currentSeedPhrase, currentPage)
				})

			identityHashesColumn.Add(currentHashLabel)
			selectButtonsColumn.Add(currentHashSelectButton)

			identityHashesColumn.Add(widget.NewSeparator())
			selectButtonsColumn.Add(widget.NewSeparator())
		}

		foundHashesGrid := container.NewHBox(layout.NewSpacer(), identityHashesColumn, selectButtonsColumn, layout.NewSpacer())

		return foundHashesGrid, nil
	}

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

	if (numberOfFoundSeedPhrases >= 30){

		// We are done generating identity hashes

		doneDescription1 := getLabelCentered("Hash generation is complete.")
		doneDescription2 := getBoldLabelCentered("Found 30 identity hashes.")

		retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), func(){
				emptyList := make([]string, 0)
				setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, initialHashesPerSecond, emptyList, previousPage, submitPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), doneDescription1, doneDescription2, widget.NewSeparator(), foundHashesGrid, retryButton)

		setPageContent(page, window)
		return
	}

	getGenerationStatusString := func()string{
		if (numberOfFoundSeedPhrases == 0){
			return "Generating identity hash."
		}
		if (numberOfFoundSeedPhrases == 1){
			return "Found 1 identity hash."
		}

		numberOfFoundSeedPhrasesString := helpers.ConvertIntToString(numberOfFoundSeedPhrases)

		return "Found " + numberOfFoundSeedPhrasesString + " identity hashes."
	}

	generationStatusString := getGenerationStatusString()

	generationStatusWithAnimationBinding := binding.NewString()
	generationStatusLabel := widget.NewLabelWithData(generationStatusWithAnimationBinding)
	generationStatusLabel.TextStyle = getFyneTextStyle_Bold()
	generationStatusLabelCentered := getWidgetCentered(generationStatusLabel)

	timeElapsedStringBinding := binding.NewString()
	timeElapsedLabel := getWidgetCentered(widget.NewLabelWithData(timeElapsedStringBinding))

	estimatedTimeRequiredLabelBinding := binding.NewString()

	numberOfHashesPerSecondBinding := binding.NewString()
	numberOfHashesPerSecondLabel := getWidgetCentered(widget.NewLabelWithData(numberOfHashesPerSecondBinding))

	initializeBindingsFunction := func()error{

		err = estimatedTimeRequiredLabelBinding.Set("Estimated time required: " + estimatedTimeRequired)
		if (err != nil) { return err }

		err = timeElapsedStringBinding.Set("Time elapsed: 0 Seconds")
		if (err != nil) { return err }

		hashesPerSecondString, err := helpers.ConvertFloat64ToRoundedStringWithTranslatedUnits(float64(initialHashesPerSecond))
		if (err != nil) { return err }

		err = numberOfHashesPerSecondBinding.Set("Hashes per second: " + hashesPerSecondString)
		if (err != nil) { return err }	

		err = generationStatusWithAnimationBinding.Set(generationStatusString + "... ")
		if (err != nil) { return err }

		return nil
	}

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

	estimatedTimeRequiredLabel := widget.NewLabelWithData(estimatedTimeRequiredLabelBinding)

	estimatedTimeHelpDialogButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			dialogTitle := translate("Estimated Time To Generate")
			dialogMessageA := getLabelCentered("To generate a custom identity hash, many random identity hashes are created.")
			dialogMessageB := getLabelCentered("Finding your desired prefix requires luck.")
			dialogMessageC := getLabelCentered("The time to generate may vary significantly from the estimated time.")
			dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		})

	estimatedTimeRequiredRow := container.NewHBox(layout.NewSpacer(), estimatedTimeRequiredLabel, estimatedTimeHelpDialogButton, layout.NewSpacer())

	//TODO: Retrieve language from user settings
	currentLanguage := "English"
	currentLanguageWordList, err := wordLists.GetWordListFromLanguage(currentLanguage)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage) 
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), timeElapsedLabel, widget.NewSeparator(), estimatedTimeRequiredRow, widget.NewSeparator(), numberOfHashesPerSecondLabel, widget.NewSeparator(), generationStatusLabelCentered, foundHashesGrid)

	setPageContent(page, window)

	// Now we start hash generation

	startHashGenerationFunction := func(){

		// This variable stores the current number of hashes per second that are being generated
		var currentHashesPerSecondMutex sync.RWMutex
		currentHashesPerSecond := initialHashesPerSecond

		// We use this mutex when adding new seed phrases to the list
		var seedPhrasesFoundListMutex sync.Mutex

		// We use this error to keep track of any errors
		var encounteredErrorMutex sync.RWMutex
		var encounteredError error

		setErrorEncountered := func(newError error){

			encounteredErrorMutex.Lock()
			encounteredError = newError
			encounteredErrorMutex.Unlock()
		}

		generateIdentityHashesFunction := func(){

			// We use this counter to count how many hashes we have generated
			// We add this periodically to the hashesPerSecond counter
			// We do this so we can avoid having to Rlock a mutex for each increment
			subcounter := int64(0)

			for{
				generatingStatus := getGenerateHashesStatusBool()
				if (generatingStatus == false){
					generateHashesWaitgroup.Done()
					return
				}

				newSeedPhrase, newSeedPhraseHash, err := seedPhrase.GetNewSeedPhraseFromWordList(currentLanguageWordList)
				if (err != nil) {
					setErrorEncountered(err)
					break
				}

				newIdentityHashPrefix, err := identity.GetIdentityHash16CharacterPrefixFromSeedPhraseHash(newSeedPhraseHash)
				if (err != nil) {
					setErrorEncountered(err)
					break
				}

				subcounter += 1

				if (subcounter >= 2000){
					currentHashesPerSecondMutex.Lock()
					currentHashesPerSecond += subcounter
					currentHashesPerSecondMutex.Unlock()
					subcounter = 0
				}

				hasPrefix := strings.HasPrefix(newIdentityHashPrefix, desiredPrefix)
				if (hasPrefix == false){
					continue
				}

				// We found a valid identity hash

				seedPhrasesFoundListMutex.Lock()

				if (len(seedPhrasesFoundList) >= 30){
					// We have found enough identity hashes
					seedPhrasesFoundListMutex.Unlock()
					generateHashesWaitgroup.Done()
					return
				}

				seedPhrasesFoundList = append(seedPhrasesFoundList, newSeedPhrase)
				seedPhrasesFoundListMutex.Unlock()

				// We keep searching
			}

			// This should only be reached if an error is encountered
			generateHashesWaitgroup.Done()
		}

		setGenerateHashesStatusBool(true)

		generateHashesWaitgroup.Add(numberOfGoroutines)

		for i:=0; i < numberOfGoroutines; i++{
			go generateIdentityHashesFunction()
		}

		numberOfSecondsElapsed := 0

		// This variable holds the status of the trailing dots after the generation progress text
		progressAnimationString := "... "

		for{

			currentHashesPerSecondMutex.Lock()
			// We reset the counter
			currentHashesPerSecond = 0
			currentHashesPerSecondMutex.Unlock()

			time.Sleep(time.Second)

			generatingStatus := getGenerateHashesStatusBool()
			if (generatingStatus == false){
				return
			}

			encounteredErrorMutex.RLock()
			currentEncounteredError := encounteredError
			encounteredErrorMutex.RUnlock()

			if (currentEncounteredError != nil){
				// One of the goroutines experienced an error
				break
			}

			numberOfSecondsElapsed += 1

			timeElapsedUnitsString, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(int64(numberOfSecondsElapsed), true)
			if (err != nil) {
				setErrorEncountered(err)
				break
			}

			err = timeElapsedStringBinding.Set("Time elapsed: " + timeElapsedUnitsString)
			if (err != nil) {
				setErrorEncountered(err)
				break
			}

			if (progressAnimationString == ".   "){
				progressAnimationString = "..  "
			} else if (progressAnimationString == "..  " ){
				progressAnimationString = "... "
			} else if (progressAnimationString == "... " ){
				progressAnimationString = "...."
			} else if (progressAnimationString == "...." ){
				progressAnimationString = ".   "
			}

			err = generationStatusWithAnimationBinding.Set(generationStatusString + progressAnimationString)
			if (err != nil) {
				setErrorEncountered(err)
				break
			}

			exists, currentPage := appMemory.GetMemoryEntry("CurrentViewedPage")
			if (exists == false || currentPage != "RunCustomIdentityHashGeneration"){
				setGenerateHashesStatusBool(false)
				return
			}

			seedPhrasesFoundListMutex.Lock()
			newSeedPhrasesListLength := len(seedPhrasesFoundList)
			seedPhrasesFoundListMutex.Unlock()

			if (newSeedPhrasesListLength > numberOfFoundSeedPhrases){
				// At least 1 new identity hash has been found.
				// We will refresh the page.
				setGenerateHashesStatusBool(false)

				// We wait for all loops to exit
				generateHashesWaitgroup.Wait()

				fyne.Do(func(){setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, currentHashesPerSecond, seedPhrasesFoundList, previousPage, submitPage)})
				return
			}

			currentHashesPerSecondMutex.Lock()
			currentHashesPerSecondCopy := currentHashesPerSecond
			currentHashesPerSecondMutex.Unlock()

			currentHashesPerSecondString, err := helpers.ConvertFloat64ToRoundedStringWithTranslatedUnits(float64(currentHashesPerSecondCopy))
			if (err != nil) {
				setErrorEncountered(err)
				break
			}

			err = numberOfHashesPerSecondBinding.Set("Hashes per second: " + currentHashesPerSecondString)
			if (err != nil) {
				setErrorEncountered(err)
				break
			}
		}

		// This should only be reached if an error is encountered
		setGenerateHashesStatusBool(false)

		// We wait for goroutines to exit
		generateHashesWaitgroup.Wait()

		fyne.Do(func(){setErrorEncounteredPage(window, errors.New("Something went wrong during hash generation: " + encounteredError.Error()), previousPage)})
	}

	go startHashGenerationFunction()
}





