
// viewedModerators provides functions to generate and retrieve the viewedModerators list
// This list is used to browse moderators on the View Moderators page

package viewedModerators

import "seekia/internal/appMemory"
import "seekia/internal/badgerDatabase"
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/profiles/viewableProfiles"

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

// This mutex will be locked whenever the moderators list is being updated
var updatingModeratorsMutex sync.Mutex

var viewedModeratorsListDatastore *myList.MyList

var viewedModeratorsFiltersMapDatastore *myMap.MyMap

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

	updatingModeratorsMutex.Lock()
	defer updatingModeratorsMutex.Unlock()

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

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

	viewedModeratorsListDatastore = newViewedModeratorsListDatastore

	viewedModeratorsFiltersMapDatastore = newViewedModeratorsFiltersMapDatastore

	return nil
}


func GetViewedModeratorsSortByAttribute()(string, error){

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

	return currentAttribute, nil
}

func GetViewedModeratorsSortDirection()(string, error){
	
	exists, sortDirection, err := mySettings.GetSetting("ViewedModeratorsSortDirection")
	if (err != nil) { return "", err }
	if (exists == false){
		return "Descending", nil
	} 
	if (sortDirection != "Ascending" && sortDirection != "Descending"){
		return "", errors.New("mySettings malformed: Contains invalid ViewedModeratorsSortDirection: " + sortDirection)
	}

	return sortDirection, nil
}

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

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

	return true, nil
}

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

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

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

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

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

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

		//TODO: Log this.

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

		return false, nil
	}

	return true, nil
}

//Outputs:
//	-bool: List is ready
//	-[][16]byte: Viewed moderators list
//	-error
func GetViewedModeratorsList(networkType byte)(bool, [][16]byte, error){

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

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

	readyModeratorIdentityHashesList, err := viewedModeratorsListDatastore.GetList()
	if (err != nil) { return false, nil, err }

	viewedModeratorsList := make([][16]byte, 0, len(readyModeratorIdentityHashesList))

	for _, moderatorIdentityHashString := range readyModeratorIdentityHashesList{

		moderatorIdentityHash, identityType, err := identity.ReadIdentityHashString(moderatorIdentityHashString)
		if (err != nil){
			return false, nil, errors.New("sortedViewedModeratorsListDatastore contains invalid identityHash: " + moderatorIdentityHashString)
		}
		if (identityType != "Moderator"){
			return false, nil, errors.New("sortedViewedModeratorsListDatastore contains non-Moderator identity hash: " + identityType)
		}

		viewedModeratorsList = append(viewedModeratorsList, moderatorIdentityHash)
	}

	return true, viewedModeratorsList, nil
}

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

	currentViewedModeratorsList, err := viewedModeratorsListDatastore.GetList()
	if (err != nil) { return 0, err }

	numberOfModerators := len(currentViewedModeratorsList)
	
	return numberOfModerators, nil
}

//Outputs:
//	-bool: Build encountered error
//	-string: Error encountered
//	-bool: Build is stopped (will be stopped if user went to different page)
//	-bool: Viewed moderators are ready
//	-float64: Percentage Progress (0 - 1)
//	-error
func GetViewedModeratorsBuildStatus(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("GetViewedModeratorsBuildStatus called with invalid networkType: " + networkTypeString)
	}

	exists, encounteredError := appMemory.GetMemoryEntry("ViewedModeratorsBuildEncounteredError")
	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("ViewedModeratorsBuildError")
		if (exists == false){
			return false, "", false, false, 0, errors.New("ViewedModerators build encountered error is yes, but no error exists.")
		}

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

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

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

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

	exists, currentPercentageString := appMemory.GetMemoryEntry("ViewedModeratorsReadyProgressStatus")
	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("ViewedModeratorsReadyProgressStatus is invalid: Not a float: " + currentPercentageString)
	}

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


func CheckIfBuildViewedModeratorsIsStopped()bool{

	exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildViewedModeratorsYesNo")
	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 moderators
func StartUpdatingViewedModerators(networkType byte)error{

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

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

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

	appMemory.SetMemoryEntry("ViewedModeratorsBuildEncounteredError", "No")
	appMemory.SetMemoryEntry("ViewedModeratorsBuildError", "")
	appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "0")

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

	updateViewedModerators := func()error{

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

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

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

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

			return false, nil
		}

		viewedModeratorsNeedToBeGenerated, err := getViewedModeratorsNeedToBeGeneratedStatus()
		if (err != nil) { return err }
		if (viewedModeratorsNeedToBeGenerated == true){

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

			// We use a map to avoid duplicates
			allModeratorsMap := make(map[[16]byte]struct{})
			
			allModeratorIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Moderator")
			if (err != nil) { return err }

			appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".05")

			for _, moderatorIdentityHash := range allModeratorIdentityHashesList{
				allModeratorsMap[moderatorIdentityHash] = struct{}{}
			}

			appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".10")

			// We also get all reviewed moderator identity hashes
			// This is because moderators who are banned will have their profiles deleted

			allReviewedIdentityHashes, err := badgerDatabase.GetAllReviewedIdentityHashes()
			if (err != nil) { return err }

			appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".15")

			for _, identityHash := range allReviewedIdentityHashes{

				identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
				if (err != nil) { return err }

				if (identityType == "Moderator"){
					allModeratorsMap[identityHash] = struct{}{}
				}
			}

			appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".20")

			numberOfModerators := len(allModeratorsMap)

			generatedModeratorIdentityHashesList := make([]string, 0)

			index := 0

			for moderatorIdentityHash, _ := range allModeratorsMap{

				moderatorPassesFilters, err := checkIfModeratorPassesFilters(moderatorIdentityHash, networkType)
				if (err != nil) { return err }
				if (moderatorPassesFilters == true){

					moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
					if (err != nil) {
						moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
						return errors.New("allModeratorsMap contains invalid identity hash: " + moderatorIdentityHashHex)
					}

					generatedModeratorIdentityHashesList = append(generatedModeratorIdentityHashesList, moderatorIdentityHashString)
				}

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

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

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

				appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", moderatorsReadyProgressPercentage)

				index += 1
			}

			err = viewedModeratorsListDatastore.OverwriteList(generatedModeratorIdentityHashesList)
			if (err != nil) { return err }

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

			networkTypeString := helpers.ConvertByteToString(networkType)

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

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

		appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "0.50")
		
		moderatorsReadyStatus, err := GetViewedModeratorsAreReadyStatus(networkType)
		if (err != nil) { return err }
		if (moderatorsReadyStatus == false){

			// Now we sort moderators

			currentSortByAttribute, err := GetViewedModeratorsSortByAttribute()
			if (err != nil) { return err }
			currentSortDirection, err := GetViewedModeratorsSortDirection()
			if (err != nil) { return err }

			currentViewedModeratorsList, err := viewedModeratorsListDatastore.GetList()
			if (err != nil) { return err }

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

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

			maximumIndex := len(currentViewedModeratorsList) - 1

			for index, moderatorIdentityHashString := range currentViewedModeratorsList{

				moderatorIdentityHash, identityType, err := identity.ReadIdentityHashString(moderatorIdentityHashString)
				if (err != nil) {
					return errors.New("currentViewedModeratorsList contains invalid moderator identity hash: " + moderatorIdentityHashString)
				}
				if (identityType != "Moderator"){
					return errors.New("currentViewedModeratorsList contains non-moderator identity hash: " + identityType)
				}

				_, exists := allModeratorsMap[moderatorIdentityHash]
				if (exists == true){
					return errors.New("currentViewedModeratorsList contains duplicate identity hash.")
				}

				allModeratorsMap[moderatorIdentityHash] = struct{}{}

				profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(moderatorIdentityHash, 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 moderators attribute cannot be converted to float: " + currentSortByAttribute)
					}

					moderatorAttributeValuesMap[moderatorIdentityHashString] = attributeValueFloat
				}

				isStopped := CheckIfBuildViewedModeratorsIsStopped()
				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("ViewedModeratorsReadyProgressStatus", newProgressString)
			}

			compareModeratorsFunction := func(identityHashA string, identityHashB string)int{

				if (identityHashA == identityHashB){
					panic("Duplicate moderator identity hashes called during sort.")
				}

				attributeValueA, attributeValueAExists := moderatorAttributeValuesMap[identityHashA]

				attributeValueB, attributeValueBExists := moderatorAttributeValuesMap[identityHashB]

				if (attributeValueAExists == false && attributeValueBExists == false){

					// We don't know the attribute value for either moderator
					// We sort moderators in unicode order
					if (identityHashA < identityHashB){
						return -1
					}
					return 1

				} else if (attributeValueAExists == true && attributeValueBExists == false){

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

					return -1

				} else if (attributeValueAExists == false && attributeValueBExists == true){

					return 1
				}

				// Both attribute values exist

				if (attributeValueA == attributeValueB){
					// We sort identity hashes in unicode order
					if (identityHashA < identityHashB){
						return -1
					}
					return 1
				}

				if (attributeValueA < attributeValueB){

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

			slices.SortFunc(currentViewedModeratorsList, compareModeratorsFunction)

			err = viewedModeratorsListDatastore.OverwriteList(currentViewedModeratorsList)
			if (err != nil) { return err }

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

		appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "1")

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

		return nil
	}

	updateFunction := func(){

		err := updateViewedModerators()
		if (err != nil) {
			appMemory.SetMemoryEntry("ViewedModeratorsBuildEncounteredError", "Yes")
			appMemory.SetMemoryEntry("ViewedModeratorsBuildError", err.Error())
		}
	
		updatingModeratorsMutex.Unlock()
	}

	go updateFunction()

	return nil
}


func checkIfModeratorPassesFilters(inputModeratorIdentityHash [16]byte, networkType byte)(bool, error){

	//TODO

	return true, nil
}



func GetNumberOfActiveModeratorFilters()(int, error){

	numberOfActiveFilters := 0

	//TODO

	return numberOfActiveFilters, nil
}


func SetModeratorFilterOnOffStatus(filterName string, filterStatus bool)error{

	filterOnOffStatusString := helpers.ConvertBoolToYesOrNoString(filterStatus)

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

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

	return nil
}


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

	filterStatusExists, currentFilterStatus, err := viewedModeratorsFiltersMapDatastore.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
}



