
// viewedHosts provides functions to generate and retrieve the viewedHosts list
// This list is used to browse hosts on the View Hosts page

package viewedHosts

import "seekia/internal/appMemory"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/myDatastores/myList"
import "seekia/internal/myDatastores/myMap"
import "seekia/internal/mySettings"
import "seekia/internal/network/enabledHosts"
import "seekia/internal/profiles/viewableProfiles"

import "slices"
import "sync"
import "errors"

// This mutex will be locked whenever the viewed hosts list is being updated
var updatingViewedHostsMutex sync.Mutex

var viewedHostsListDatastore *myList.MyList

var viewedHostsFiltersMapDatastore *myMap.MyMap

// This function must be called whenever an app user signs in
func InitializeViewedHostsDatastores()error{

	updatingViewedHostsMutex.Lock()
	defer updatingViewedHostsMutex.Unlock()

	newViewedHostsListDatastore, err := myList.CreateNewList("ViewedHosts")
	if (err != nil) { return err }

	newViewedHostsFiltersMapDatastore, err := myMap.CreateNewMap("ViewedHostsFilters")
	if (err != nil) { return err }

	viewedHostsListDatastore = newViewedHostsListDatastore

	viewedHostsFiltersMapDatastore = newViewedHostsFiltersMapDatastore

	return nil
}


func GetViewedHostsSortByAttribute()(string, error){

	exists, currentAttribute, err := mySettings.GetSetting("ViewedHostsSortByAttribute")
	if (err != nil) { return "", err }
	if (exists == false){
		return "BanAdvocates", nil
	} 

	return currentAttribute, nil
}

func GetViewedHostsSortDirection()(string, error){
	
	exists, sortDirection, err := mySettings.GetSetting("ViewedHostsSortDirection")
	if (err != nil) { return "", err }
	if (exists == false){
		return "Descending", nil
	} 
	if (sortDirection != "Ascending" && sortDirection != "Descending"){
		return "", errors.New("MySettings contains invalid ViewedHostsSortDirection: " + sortDirection)
	}

	return sortDirection, nil
}

// Will need a refresh any time a new host profile is downloaded
func CheckIfViewedHostsNeedsRefresh()(bool, error){

	exists, needsRefresh, err := mySettings.GetSetting("ViewedHostsNeedsRefreshYesNo")
	if (err != nil) { return true, err }
	if (exists == true && needsRefresh == "No") {
		return false, nil
	}

	return true, nil
}


func GetViewedHostsAreReadyStatus(networkType byte)(bool, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, errors.New("GetViewedHostsAreReadyStatus called with invalid networkType: " + networkTypeString)
	}

	exists, hostsGeneratedStatus, err := mySettings.GetSetting("ViewedHostsGeneratedStatus")
	if (err != nil) { return false, err }
	if (exists == false || hostsGeneratedStatus != "Yes"){
		return false, nil
	}

	exists, hostsSortedStatus, err := mySettings.GetSetting("ViewedHostsSortedStatus")
	if (err != nil) { return false, err }
	if (exists == false || hostsSortedStatus != "Yes"){
		return false, nil
	}

	exists, viewedHostsNetworkTypeString, err := mySettings.GetSetting("ViewedHostsNetworkType")
	if (err != nil) { return false, err }
	if (exists == false){
		// This should not happen, because ViewedHostsNetworkType is created whenever viewedHosts are generated
		return false, errors.New("mySettings missing ViewedHostsNetworkType when ViewedHostsGeneratedStatus exists.")
	}

	viewedHostsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedHostsNetworkTypeString)
	if (err != nil) {
		return false, errors.New("mySettings contains invalid ViewedHostsNetworkType: " + viewedHostsNetworkTypeString)
	}
	if (viewedHostsNetworkType != networkType){
		// ViewedHosts were generated for a different networkType
		// This should never happen, because we will always set ViewedHostsGeneratedStatus to No when we switch network types,
		//		and we will always call the GetViewedHostsAreReadyStatus function with the current appNetworkType

		//TODO: Log this.

		err := mySettings.SetSetting("ViewedHostsGeneratedStatus", "No")
		if (err != nil) { return false, err }

		return false, nil
	}

	return true, nil
}

// Outputs:
//	-bool: Viewed hosts list is ready
//	-[][16]byte: Viewed host identity hashes list
//	-error
func GetViewedHostsList(networkType byte)(bool, [][16]byte, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, nil, errors.New("GetViewedHostsList called with invalid networkType: " + networkTypeString)
	}

	areReady, err := GetViewedHostsAreReadyStatus(networkType)
	if (err != nil) { return false, nil, err }
	if (areReady == false){
		return false, nil, nil
	}

	readyHostIdentityHashesList, err := viewedHostsListDatastore.GetList()
	if (err != nil) { return false, nil, err }

	viewedHostsList := make([][16]byte, 0, len(readyHostIdentityHashesList))

	for _, hostIdentityHashString := range readyHostIdentityHashesList{

		hostIdentityHash, identityType, err := identity.ReadIdentityHashString(hostIdentityHashString)
		if (err != nil){
			return false, nil, errors.New("viewedHostsListDatastore contains invalid identity hash: " + hostIdentityHashString)
		}
		if (identityType != "Host"){
			return false, nil, errors.New("viewedHostsListDatastore contains non-Host identity hash: " + identityType)
		}

		viewedHostsList = append(viewedHostsList, hostIdentityHash)
	}

	return true, viewedHostsList, nil
}

// This function returns the number of viewed hosts
// It can be called before the list is ready (after being generated, but before being sorted)
func GetNumberOfGeneratedViewedHosts()(int, error){

	currentViewedHostsList, err := viewedHostsListDatastore.GetList()
	if (err != nil) { return 0, err }

	lengthInt := len(currentViewedHostsList)

	return lengthInt, nil
}

//Outputs:
//	-bool: Build encountered error
//	-string: Error encountered
//	-bool: Build is stopped (will be stopped if user went to different page)
//	-bool: Viewed hosts are ready
//	-float64: Progress status (0 - 1)
//	-error
func GetViewedHostsBuildStatus(networkType byte)(bool, string, bool, bool, float64, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, "", false, false, 0, errors.New("GetViewedHostsBuildStatus called with invalid networkType: " + networkTypeString)
	}

	exists, encounteredError := appMemory.GetMemoryEntry("ViewedHostsBuildEncounteredError")
	if (exists == false){
		// No build exists. A build has not been started since Seekia was started
		return false, "", false, false, 0, nil
	}
	if (encounteredError == "Yes"){
		exists, errorEncountered := appMemory.GetMemoryEntry("ViewedHostsBuildError")
		if (exists == false){
			return false, "", false, false, 0, errors.New("Viewed Hosts build encountered error is yes, but no error exists.")
		}

		return true, errorEncountered, false, false, 0, nil
	}

	isStopped := CheckIfBuildViewedHostsIsStopped()
	if (isStopped == true){
		return false, "", true, false, 0, nil
	}

	hostsAreReadyBool, err := GetViewedHostsAreReadyStatus(networkType)
	if (err != nil) { return false, "", false, false, 0, err }
	if (hostsAreReadyBool == true){

		return false, "", false, true, 1, nil	
	}

	exists, currentPercentageString := appMemory.GetMemoryEntry("ViewedHostsReadyProgressStatus")
	if (exists == false){
		// No build exists. A build has not been started since Seekia was started
		return false, "", false, false, 0, nil
	}

	currentPercentageFloat, err := helpers.ConvertStringToFloat64(currentPercentageString)
	if (err != nil){
		return false, "", false, false, 0, errors.New("ViewedHostsReadyProgressStatus is not a float: " + currentPercentageString)
	}

	return false, "", false, false, currentPercentageFloat, nil
}

func CheckIfBuildViewedHostsIsStopped()bool{

	exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildViewedHostsYesNo")
	if (exists == false || buildStoppedStatus != "No") {
		return true
	}

	return false
}


// This function will cancel the current build (if one is running)
// It will then start updating our viewed hosts
func StartUpdatingViewedHosts(networkType byte)error{

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("StartUpdatingViewedHosts called with invalid networkType: " + networkTypeString)
	}

	appMemory.SetMemoryEntry("StopBuildViewedHostsYesNo", "Yes")

	// We wait for any existing build to stop
	updatingViewedHostsMutex.Lock()

	appMemory.SetMemoryEntry("ViewedHostsBuildEncounteredError", "No")
	appMemory.SetMemoryEntry("ViewedHostsBuildError", "")
	appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "0")

	appMemory.SetMemoryEntry("StopBuildViewedHostsYesNo", "No")

	updateViewedHosts := func()error{

		getViewedHostsNeedToBeGeneratedStatus := func()(bool, error){

			exists, hostsGeneratedStatus, err := mySettings.GetSetting("ViewedHostsGeneratedStatus")
			if (err != nil) { return false, err }
			if (exists == false || hostsGeneratedStatus != "Yes"){
				return true, nil
			}

			exists, viewedHostsNetworkTypeString, err := mySettings.GetSetting("ViewedHostsNetworkType")
			if (err != nil) { return false, err }
			if (exists == false){
				// This should not happen, because ViewedHostsNetworkType is set to Yes whenever viewed hosts are generated
				return false, errors.New("ViewedHostsNetworkType missing when ViewedHostsGeneratedStatus exists.")
			}

			viewedHostsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedHostsNetworkTypeString)
			if (err != nil) {
				return false, errors.New("mySettings contains invalid ViewedHostsNetworkType: " + viewedHostsNetworkTypeString)
			}
			if (viewedHostsNetworkType != networkType){
				// This should not happen, because ViewedHostsGeneratedStatus should be set to No whenever app network type is changed,
				//		and StartUpdatingViewedHosts should only be called with the current app network type.
				return true, nil
			}

			return false, nil
		}

		viewedHostsNeedToBeGenerated, err := getViewedHostsNeedToBeGeneratedStatus()
		if (err != nil) { return err }
		if (viewedHostsNeedToBeGenerated == true){

			err := mySettings.SetSetting("ViewedHostsSortedStatus", "No")
			if (err != nil) { return err }

			enabledHostsList, err := enabledHosts.GetEnabledHostsList(false, networkType)
			if (err != nil) { return err }

			numberOfEnabledHosts := len(enabledHostsList)

			generatedHostIdentityHashesList := make([]string, 0)

			for index, hostIdentityHash := range enabledHostsList{

				hostPassesFilters, err := checkIfHostPassesFilters(hostIdentityHash)
				if (err != nil) { return err }
				if (hostPassesFilters == true){

					hostIdentityHashString, identityType, err := identity.EncodeIdentityHashBytesToString(hostIdentityHash)
					if (err != nil) {
						hostIdentityHashHex := encoding.EncodeBytesToHexString(hostIdentityHash[:])
						return errors.New("enabledHostsList contains invalid identity hash: " + hostIdentityHashHex)
					}
					if (identityType != "Host"){
						return errors.New("enabledHostsList contains non-host identity hash: " + identityType)
					}

					generatedHostIdentityHashesList = append(generatedHostIdentityHashesList, hostIdentityHashString)
				}

				isStopped := CheckIfBuildViewedHostsIsStopped()
				if (isStopped == true){
					return nil
				}

				progressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, numberOfEnabledHosts-1, 0, 50)
				if (err != nil) { return err }

				progressFloat := float64(progressPercentage)/100
				hostsReadyProgressPercentage := helpers.ConvertFloat64ToString(progressFloat)

				appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", hostsReadyProgressPercentage)
			}

			err = viewedHostsListDatastore.OverwriteList(generatedHostIdentityHashesList)
			if (err != nil) { return err }

			err = mySettings.SetSetting("ViewedHostsGeneratedStatus", "Yes")
			if (err != nil) { return err }

			networkTypeString := helpers.ConvertByteToString(networkType)

			err = mySettings.SetSetting("ViewedHostsNetworkType", networkTypeString)
			if (err != nil) { return err }
		}

		appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "0.50")

		isStopped := CheckIfBuildViewedHostsIsStopped()
		if (isStopped == true){
			return nil
		}

		hostsReadyStatus, err := GetViewedHostsAreReadyStatus(networkType)
		if (err != nil) { return err }
		if (hostsReadyStatus == false){

			// Now we sort hosts

			currentSortByAttribute, err := GetViewedHostsSortByAttribute()
			if (err != nil) { return err }
			currentSortDirection, err := GetViewedHostsSortDirection()
			if (err != nil) { return err }

			currentViewedHostsList, err := viewedHostsListDatastore.GetList()
			if (err != nil) { return err }

			// We use this map to make sure there are no duplicate hosts
			// This should never happen, unless the user's stored list was edited or there is a bug
			allHostsMap := make(map[[16]byte]struct{})

			// Map structure: Host Identity Hash -> Sort By Attribute Value
			hostAttributeValuesMap := make(map[string]float64)

			maximumIndex := len(currentViewedHostsList) - 1

			for index, hostIdentityHashString := range currentViewedHostsList{

				hostIdentityHash, identityType, err := identity.ReadIdentityHashString(hostIdentityHashString)
				if (err != nil){
					return errors.New("currentViewedHostsList contains invalid identity hash: " + hostIdentityHashString)
				}
				if (identityType != "Host"){
					return errors.New("currentViewedHostsList contains invalid non-Host identity: " + identityType)
				}

				_, exists := allHostsMap[hostIdentityHash]
				if (exists == true){
					return errors.New("currentViewedHostsList contains duplicate host identity hash.")
				}
				allHostsMap[hostIdentityHash] = struct{}{}

				profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(hostIdentityHash, networkType, currentSortByAttribute, true, true, true)
				if (err != nil) { return err }
				if (profileExists == true && attributeExists == true){

					attributeValueFloat, err := helpers.ConvertStringToFloat64(attributeValue)
					if (err != nil) {
						return errors.New("Viewed hosts attribute cannot be converted to float.")
					}

					hostAttributeValuesMap[hostIdentityHashString] = attributeValueFloat
				}

				isStopped := CheckIfBuildViewedHostsIsStopped()
				if (isStopped == true){
					return nil
				}

				newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
				if (err != nil) { return err }

				newProgressFloat := float64(newScaledPercentageInt)/100

				newProgressString := helpers.ConvertFloat64ToString(newProgressFloat)

				appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", newProgressString)
			}

			compareHostsFunction := func(identityHash1 string, identityHash2 string)int{

				if (identityHash1 == identityHash2){
					panic("compareHostsFunction called with duplicate hosts.")
				}

				attributeValue1, attributeValue1Exists := hostAttributeValuesMap[identityHash1]

				attributeValue2, attributeValue2Exists := hostAttributeValuesMap[identityHash2]

				if (attributeValue1Exists == false && attributeValue2Exists == false){

					// We don't know the attribute value for either host
					// We sort hosts in unicode order
					if (identityHash1 < identityHash2){
						return -1
					}
					return 1

				} else if (attributeValue1Exists == true && attributeValue2Exists == false){

					// We sort unknown attribute hosts to the back of the list

					return -1

				} else if (attributeValue1Exists == false && attributeValue2Exists == true){

					return 1
				}

				// Both attribute values exist

				if (attributeValue1 == attributeValue2){
					// We sort identity hashes in unicode order
					if (identityHash1 < identityHash2){
						return -1
					}
					return 1
				}

				if (attributeValue1 < attributeValue2){

					if (currentSortDirection == "Ascending"){
						return -1
					}
					return 1
				}
				if (currentSortDirection == "Ascending"){
					return 1
				}
				return -1
			}

			slices.SortFunc(currentViewedHostsList, compareHostsFunction)

			err = viewedHostsListDatastore.OverwriteList(currentViewedHostsList)
			if (err != nil) { return err }

			err = mySettings.SetSetting("ViewedHostsSortedStatus", "Yes")
			if (err != nil) { return err }
		}

		appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "1")
		
		err = mySettings.SetSetting("ViewedHostsNeedsRefreshYesNo", "No")
		if (err != nil) { return err }

		return nil
	}

	updateFunction := func(){

		err := updateViewedHosts()
		if (err != nil){
			appMemory.SetMemoryEntry("ViewedHostsBuildEncounteredError", "Yes")
			appMemory.SetMemoryEntry("ViewedHostsBuildError", err.Error())
		}

		updatingViewedHostsMutex.Unlock()
	}

	go updateFunction()

	return nil
}


func checkIfHostPassesFilters(inputHostIdentityHash [16]byte)(bool, error){

	//TODO

	return true, nil
}



func GetNumberOfActiveHostFilters()(int, error){

	numberOfActiveFilters := 0

	//TODO
	
	return numberOfActiveFilters, nil
}


func SetHostFilterOnOffStatus(filterName string, filterOnOffStatus bool)error{

	filterOnOffStatusString := helpers.ConvertBoolToYesOrNoString(filterOnOffStatus)

	err := viewedHostsFiltersMapDatastore.SetMapEntry(filterName, filterOnOffStatusString)
	if (err != nil) { return err }

	err = mySettings.SetSetting("ViewedHostsGeneratedStatus", "No")
	if (err != nil) { return err }

	return nil
}


func GetHostFilterOnOffStatus(filterName string)(bool, error){

	filterStatusExists, currentFilterStatus, err := viewedHostsFiltersMapDatastore.GetMapEntry(filterName)
	if (err != nil) { return false, err }
	if (filterStatusExists == false){
		return false, nil
	}

	filterOnOffStatusBool, err := helpers.ConvertYesOrNoStringToBool(currentFilterStatus)
	if (err != nil) { return false, err }

	return filterOnOffStatusBool, nil
}





