
package gui

// downloadGui.go implements pages to monitor manual downloads
// These are downloads whose status and progress the user is able to monitor

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

import "seekia/internal/helpers"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/manualDownloads"
import "seekia/internal/appMemory"

import "errors"
import "time"


func setDownloadMissingUserProfilePage(window fyne.Window, profileAuthorIdentityHash [16]byte, getViewableOnly bool, stopDownloadOnPageExit bool, previousPage func(), nextPage func(), exitPage func()){

	pageTitleText := "Download User Profile"
	description1Text := "The user's profile is missing."

	//Outputs:
	//	-bool: Any hosts found
	//	-[][23]byte: Download identifiers list
	//	-error
	startNewDownloadFunction := func()(bool, [][23]byte, error){

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

		anyHostsFound, processIdentifier, err := manualDownloads.StartNewestUserProfileDownload(profileAuthorIdentityHash, appNetworkType, getViewableOnly, 1, 10)
		if (err != nil) { return false, nil, err }
		if (anyHostsFound == false){
			emptyList := make([][23]byte, 0)
			return false, emptyList, nil
		}

		processIdentifiersList := [][23]byte{processIdentifier}

		return true, processIdentifiersList, nil
	}

	anyHostsFound, processIdentifiersList, err := startNewDownloadFunction()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	noHostsFound := !anyHostsFound

	setMonitorManualDownloadsPage(window, pageTitleText, "Profile", description1Text, noHostsFound, processIdentifiersList, startNewDownloadFunction, stopDownloadOnPageExit, 1, previousPage, nextPage, exitPage) 
}

//Inputs:
//	-fyne.Window:
//	-string
//	-string
//	-string
//	-bool: True if no hosts are found
//	-[][23]byte: List of process identifiers to monitor
//	-func()(bool, [][23]byte, error): Function to retry all downloads
//		-bool: Any hosts found
//		-[][23]byte: New process identifiers of new download
//		-error
//	-bool: Stop download on page exit
//	-int: expectedSuccessfulDownloadsPerProcess
//	-func(): The page to visit if back button is pressed.
//	-func(): The page to visit after a successful download
//	-func()
func setMonitorManualDownloadsPage(window fyne.Window,
								pageTitleText string,
								downloadType string,
								description1Text string,
								noHostsFound bool,
								processIdentifiersList [][23]byte,
								startNewDownloadFunction func()(bool, [][23]byte, error),
								stopDownloadOnPageExit bool,
								expectedSuccessfulDownloadsPerProcess int,
								previousPage func(), 
								afterCompletionPage func(),
								exitPage func()){

	currentPage := func(){setMonitorManualDownloadsPage(window, pageTitleText, downloadType, description1Text, noHostsFound, processIdentifiersList, startNewDownloadFunction, stopDownloadOnPageExit, expectedSuccessfulDownloadsPerProcess, previousPage, afterCompletionPage, exitPage)}

	title := getPageTitleCentered(pageTitleText)

	previousPageWithCancel := func(){
		if (stopDownloadOnPageExit == true && noHostsFound == false){
			for _, processIdentifier := range processIdentifiersList{
				manualDownloads.EndProcess(processIdentifier)
			}
		}
		appMemory.DeleteMemoryEntry("CurrentViewedPage")
		previousPage()
	}

	if (downloadType != "Profile" && downloadType != "Message"){
		//TODO: Replace downloadType with custom descriptions for missing hosts, download in progress, and content may not exist
		setErrorEncounteredPage(window, errors.New("setMonitorProfileDownloadPage called with invalid downloadType: " + downloadType), previousPageWithCancel)
		return
	}

	backButton := getBackButtonCentered(previousPageWithCancel)

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

	appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)

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

	description1 := getLabelCentered(description1Text)

	retryFunction := func(){
		newDownloadAnyHostsFound, newProcessIdentifiersList, err := startNewDownloadFunction()
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPageWithCancel)
			return
		}

		newDownloadNoHostsFound := !newDownloadAnyHostsFound

		setMonitorManualDownloadsPage(window, pageTitleText, downloadType, description1Text, newDownloadNoHostsFound, newProcessIdentifiersList, startNewDownloadFunction, stopDownloadOnPageExit, expectedSuccessfulDownloadsPerProcess, previousPage, afterCompletionPage, exitPage)
	}

	if (noHostsFound == true){

		description2 := getLabelCentered(downloadType + " download failed because no available hosts were found.")
		description3 := getLabelCentered("Please wait for Seekia to find more hosts.")
		description4 := getLabelCentered("This should take less than 1 minute.")

		retryingInSecondsBinding := binding.NewString()

		startRetryCountdownFunction := func(){

			secondsRemaining := 30
			for {

				secondsRemainingString := helpers.ConvertIntToString(secondsRemaining)

				if (secondsRemaining != 1){
					retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " seconds...")
				} else {
					retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " second...")
				}

				time.Sleep(time.Second)
				secondsRemaining -= 1

				if (secondsRemaining <= 0){

					pageHasChanged := checkIfPageHasChangedFunction()
					if (pageHasChanged == true){
						return
					}
					fyne.Do(retryFunction)
					return
				}
			}
		}

		retryingInLabel := widget.NewLabelWithData(retryingInSecondsBinding)
		retryingInLabel.TextStyle = getFyneTextStyle_Bold()
		retryingInLabelCentered := getWidgetCentered(retryingInLabel)

		retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))	
		exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), exitPage))

		manageConnectionDescription := getLabelCentered("Check if your internet connection is working below.")

		manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
				setManageNetworkConnectionPage(window, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), retryingInLabelCentered, widget.NewSeparator(), retryButton, exitButton, widget.NewSeparator(), manageConnectionDescription, manageConnectionButton)

		setPageContent(page, window)

		go startRetryCountdownFunction()

		return
	}

	downloadProgressStatusBinding := binding.NewString()
	downloadNumHostsContactedBinding := binding.NewString()
	downloadProgressDetailsBinding := binding.NewString()

	updateBindingsFunction := func(){

		startTime := time.Now().Unix()

		setDownloadProgressStatus := func(processComplete bool, newStatus string){

			getProgressEllipsis := func()string{
				if (processComplete == true){
					return ""			
				}

				currentTime := time.Now().Unix()
				secondsElapsed := currentTime - startTime
				if (secondsElapsed % 3 == 0){
					return "."
				}
				if (secondsElapsed % 3 == 1){
					return ".."
				}
				return "..."
			}
			progressEllipsis := getProgressEllipsis()
			downloadProgressStatusBinding.Set(newStatus + progressEllipsis)
		}

		// Map Structure: Process identifier -> Progress details
		processLatestProgressDetailsMap := make(map[[23]byte]string)
		// Map Structure: Process identifier -> Progress details last update time
		processLatestUpdatedTimesMap := make(map[[23]byte]int64)

		for {

			// We use the below function to combine the stats from all processes, if multiple processes exist.
			//Outputs:
			//	-bool: Download is complete
			//	-bool: Download ran out of hosts
			//	-int: Number of successful downloads
			//	-int: Number of hosts missing content
			//	-string: Download progress details
			//	-error
			getDownloadInfo := func()(bool, bool, int, int, string, error){

				allProcessesAreComplete := false

				// This will sum all the successful downloads for all processes
				numberOfSuccessfulDownloads := 0

				// This will sum all of the number of hosts missing content for all processes
				numberOfHostsMissingContent := 0

				for _, processIdentifier := range processIdentifiersList{

					processFound, processIsComplete, processEncounteredError, processError, processNumberOfSuccessfulDownloads, processNumberOfHostsMissingContent, processProgressDetails := manualDownloads.GetProcessInfo(processIdentifier)
					if (processFound == false){
						// This should not happen
						return false, false, 0, 0, "", errors.New("Download process not found.")
					}
					if (processIsComplete == true && processEncounteredError == true){
						return true, false, 0, 0, "", processError
					}
					if (processIsComplete == true && processNumberOfSuccessfulDownloads == 0){
						// Process failed and ran out of hosts
						return true, true, 0, 0, "", nil
					}

					numberOfSuccessfulDownloads += processNumberOfSuccessfulDownloads
					numberOfHostsMissingContent += processNumberOfHostsMissingContent

					if (processIsComplete == false){
						allProcessesAreComplete = false
					}

					latestDetails, exists := processLatestProgressDetailsMap[processIdentifier]
					if (exists == true && latestDetails == processProgressDetails){
						// This process has not had a new details update
						// We can skip it
						continue
					}

					// This process has new details

					processLatestProgressDetailsMap[processIdentifier] = processProgressDetails

					currentTime := time.Now().Unix()

					processLatestUpdatedTimesMap[processIdentifier] = currentTime
				}

				// Now we find the newest status to show to the user

				newestProgressDetails := ""
				newestDetailsUpdatedTime := int64(0)

				for index, processIdentifier := range processIdentifiersList{

					latestDetailsUpdateTime, exists := processLatestUpdatedTimesMap[processIdentifier]
					if (exists == false){
						// This should not happen
						// All processes should be added to this map during our first iteration through processes details
						return false, false, 0, 0, "", errors.New("processLatestUpdatedTimesMap missing process latest updated time")
					}

					if (index == 0 || latestDetailsUpdateTime > newestDetailsUpdatedTime){

						latestProcessDetails, exists := processLatestProgressDetailsMap[processIdentifier]
						if (exists == false){
							// This should not happen
							// All processes should be added to this map during our first iteration through processes details
							return false, false, 0, 0, "", errors.New("processLatestProgressDetailsMap missing process details")
						}

						newestProgressDetails = latestProcessDetails
					}
				}

				return allProcessesAreComplete, false, numberOfSuccessfulDownloads, numberOfHostsMissingContent, newestProgressDetails, nil
			}

			downloadIsComplete, downloadRanOutOfHosts, numberOfSuccessfulDownloads, numberOfHostsMissingContent, downloadProgressDetails, err := getDownloadInfo()
			if (err != nil){
				setDownloadProgressStatus(true, "ERROR: " + err.Error())
				downloadProgressDetailsBinding.Set("Report this error to the Seekia developers.")
				return
			}

			numberOfHostsContacted := numberOfSuccessfulDownloads + numberOfHostsMissingContent
			numberOfHostsContactedString := helpers.ConvertIntToString(numberOfHostsContacted)

			if (downloadIsComplete == true){

				// Download is complete.

				pageHasChanged := checkIfPageHasChangedFunction()
				if (pageHasChanged == true){
					return
				}

				expectedSuccessfulDownloads := len(processIdentifiersList) * expectedSuccessfulDownloadsPerProcess

				if (numberOfSuccessfulDownloads >= expectedSuccessfulDownloads){
					// We downloaded the content we wanted. Nothing left to do.
					fyne.Do(afterCompletionPage)
					return
				}

				// Download did not get required content.
				// We will show user option to retry.

				retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))

				exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), exitPage))

				checkConnectionDescription := getLabelCentered("Check if your internet connection is working below.")

				manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
						setManageNetworkConnectionPage(window, currentPage)
					}))

				if (downloadRanOutOfHosts == true){

					description2 := getLabelCentered("The download was unsuccessful.")
					description3 := getLabelCentered("All the hosts we contacted failed to respond.")
					description4 := getLabelCentered("You can exit or wait for more hosts to be found and retry.")

					page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, retryButton, exitButton, widget.NewSeparator(), checkConnectionDescription, manageConnectionButton)

					fyne.Do(func(){setPageContent(page, window)})
					return
				}

				description2 := getLabelCentered("The download was unsuccessful.")
				description3 := getLabelCentered("We contacted " + numberOfHostsContactedString + " hosts.")
				description4 := getLabelCentered("The " + downloadType + " may not exist on the network.")
				description5 := getLabelCentered("Retry the download?")

				page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, description5, retryButton, exitButton, widget.NewSeparator(), checkConnectionDescription, manageConnectionButton)

				fyne.Do(func(){setPageContent(page, window)})
				return
			}

			// Download is not complete

			numberOfSuccessfulDownloadsString := helpers.ConvertIntToString(numberOfSuccessfulDownloads)

			progressProgressStatusString := "Downloaded from " + numberOfSuccessfulDownloadsString + " hosts."
			setDownloadProgressStatus(downloadIsComplete, progressProgressStatusString)
			downloadProgressDetailsBinding.Set(downloadProgressDetails)

			downloadNumHostsContactedBinding.Set("Contacted " + numberOfHostsContactedString + " hosts.")

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == true){

				if (stopDownloadOnPageExit == true){
					for _, processIdentifier := range processIdentifiersList{
						manualDownloads.EndProcess(processIdentifier)
					}
				}

				return
			}

			time.Sleep(200 * time.Millisecond)	
		}
	}

	description2 := getBoldLabelCentered("Seekia is attempting to download the " + downloadType)
	description3 := getLabelCentered("The status of the download is displayed below.")

	downloadProgressStatusLabel := widget.NewLabelWithData(downloadProgressStatusBinding)
	downloadProgressStatusLabel.TextStyle = getFyneTextStyle_Bold()
	downloadProgressStatusLabelCentered := getWidgetCentered(downloadProgressStatusLabel)

	downloadNumHostsContactedLabel := getWidgetCentered(widget.NewLabelWithData(downloadNumHostsContactedBinding))
	downloadProgressDetailsLabel := getWidgetCentered(widget.NewLabelWithData(downloadProgressDetailsBinding))

	exitPageButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.MediaSkipNextIcon(), func(){

		for _, processIdentifier := range processIdentifiersList{

			manualDownloads.EndProcess(processIdentifier)
		}

		appMemory.DeleteMemoryEntry("CurrentViewedPage")
		exitPage()
	}))

	page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), downloadProgressStatusLabelCentered, downloadNumHostsContactedLabel, downloadProgressDetailsLabel, widget.NewSeparator(), exitPageButton)

	setPageContent(page, window)

	go updateBindingsFunction()
}




