
// myAnalyses provides functions to manage genome analyses for People and Couples
// Analyses are created using the createPersonGeneticAnalysis and createCoupleGeneticAnalysis packagees

package myAnalyses

import "seekia/internal/appMemory"
import "seekia/internal/appValues"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/localFilesystem"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/genetics/createCoupleGeneticAnalysis"
import "seekia/internal/genetics/createPersonGeneticAnalysis"
import "seekia/internal/genetics/geneticAnalysis"
import "seekia/internal/genetics/prepareRawGenomes"
import "seekia/internal/genetics/readGeneticAnalysis"
import "seekia/internal/genetics/myGenomes"

import "path/filepath"
import "strings"
import "time"
import "sync"
import "errors"

// This will be locked anytime analyses are being added/deleted to the myMapList datastores
var updatingMyAnalysesMutex sync.Mutex

var myPersonAnalysesMapListDatastore *myMapList.MyMapList
var myCoupleAnalysesMapListDatastore *myMapList.MyMapList

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

	updatingMyAnalysesMutex.Lock()
	defer updatingMyAnalysesMutex.Unlock()

	newMyPersonAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyPersonAnalyses")
	if (err != nil) { return err }

	newMyCoupleAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyCoupleAnalyses")
	if (err != nil) { return err }

	myPersonAnalysesMapListDatastore = newMyPersonAnalysesMapListDatastore
	myCoupleAnalysesMapListDatastore = newMyCoupleAnalysesMapListDatastore

	return nil
}

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

	userDirectory, err := localFilesystem.GetAppUserFolderPath()
	if (err != nil) { return err }
	
	myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
	
	_, err = localFilesystem.CreateFolder(myAnalysesFolderPath)
	if (err != nil) { return err }
	
	return nil
}

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

	updatingMyAnalysesMutex.Lock()
	defer updatingMyAnalysesMutex.Unlock()

	//TODO: This function should delete analyses we don't need anymore
	// These include analyses for people who dont exist anymore, and analyses for people/couples whom have newer analyses

	return nil
}


func DeleteAllAnalysesForPerson(personIdentifier string)error{

	updatingMyAnalysesMutex.Lock()
	defer updatingMyAnalysesMutex.Unlock()

	mapToDelete := map[string]string{
		"PersonIdentifer": personIdentifier,
	}

	err := myPersonAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
	if (err != nil) { return err }

	mapToDelete = map[string]string{
		"Person1Identifier": personIdentifier,
	}

	err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
	if (err != nil) { return err }

	mapToDelete = map[string]string{
		"Person2Identifier": personIdentifier,
	}

	err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
	if (err != nil) { return err }

	return nil
}

func DeleteAllAnalysesForCouple(inputPerson1Identifier string, inputPerson2Identifier string)error{

	updatingMyAnalysesMutex.Lock()
	defer updatingMyAnalysesMutex.Unlock()

	person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
	if (err != nil) { return err }

	mapToDelete := map[string]string{
		"Person1Identifier": person1Identifier,
		"Person2Identifier": person2Identifier,
	}

	err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
	if (err != nil) { return err }	

	return nil
}

// This function is used to sort the person identifiers, so that each pair of identifiers will only map to a single couple
// Person1 and Person2 will always follow this order within a couple analysis
// The sorting method has no significance
func GetPeopleIdentifiersSortedForCouple(person1Identifier string, person2Identifier string)(string, string, error){

	if (person1Identifier == person2Identifier){
		return "", "", errors.New("GetPeopleIdentifiersSortedForCouple called with identical person identifiers: " + person1Identifier)
	}

	if (person1Identifier < person2Identifier){
		return person1Identifier, person2Identifier, nil
	}
	return person2Identifier, person1Identifier, nil
}


//Outputs:
//	-bool: Any analysis exists
//	-string: Newest analysis identifier
//	-int64: Time newest analysis was performed
//	-[][16]byte: List of genomes analyzed
//	-bool: Newer analysis version available
//		-This is not the same as new genomes being available. New version can be for the same genomes.
//		-To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
//	-error
func GetPersonNewestGeneticAnalysisInfo(personIdentifier string)(bool, string, int64, [][16]byte, bool, error){

	lookupMap := map[string]string{
		"PersonIdentifier": personIdentifier,
	}

	anyItemsFound, analysisItemsMapList, err := myPersonAnalysesMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, "", 0, nil, false, err }
	if (anyItemsFound == false){
		return false, "", 0, nil, false, nil
	}

	// Below is the newest analysis version, which would update if the user had updated their Seekia client
	appAnalysisVersion := appValues.GetGeneticAnalysisVersion()

	newestAnalysisFound := false
	newestAnalysisCreatedTime := int64(0)
	newestAnalysisIdentifier := ""
	newestAnalysisAnalyzedGenomesList := make([][16]byte, 0)
	newerAnalysisVersionAvailable := false

	for _, analysisMap := range analysisItemsMapList{

		currentPersonIdentifier, exists := analysisMap["PersonIdentifier"]
		if (exists == false) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonIdentifier")
		}

		if (personIdentifier != currentPersonIdentifier){
			return false, "", 0, nil, false, errors.New("GetMapListItems returning map with different personIdentifier.")
		}

		timeCreated, exists := analysisMap["TimeCreated"]
		if (exists == false) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
		}

		timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
		if (err != nil) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
		}

		if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
			continue
		}

		newestAnalysisFound = true
		
		analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
		if (exists == false) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
		}

		analyzedGenomes, exists := analysisMap["AnalyzedGenomes"]
		if (exists == false) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalyzedGenomes")
		}

		analyzedGenomesList := strings.Split(analyzedGenomes, "+")

		analyzedGenomesArrayList := make([][16]byte, 0)

		for _, genomeIdentifierHex := range analyzedGenomesList{

			genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
			if (err != nil){
				return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalyzedGenomes: " + analyzedGenomes)
			}

			analyzedGenomesArrayList = append(analyzedGenomesArrayList, genomeIdentifier)
		}

		analysisVersion, exists := analysisMap["AnalysisVersion"]
		if (exists == false) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
		}

		analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
		if (err != nil) {
			return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
		}

		if (appAnalysisVersion > analysisVersionInt){
			// This analysis is not of the newest analysis version
			// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
			newerAnalysisVersionAvailable = true
		} else {
			newerAnalysisVersionAvailable = false
		}

		newestAnalysisCreatedTime = timeCreatedInt64
		newestAnalysisIdentifier = analysisIdentifier
		newestAnalysisAnalyzedGenomesList = analyzedGenomesArrayList
	}
	
	if (newestAnalysisFound == false){
		return false, "", 0, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
	}
	
	return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisAnalyzedGenomesList, newerAnalysisVersionAvailable, nil
}

//Outputs:
//	-bool: Analysis exists
//	-string: Newest analysis identifier
//	-int64: Time newest analysis was performed
//	-[][16]byte: Person A list of genomes analyzed
//	-[][16]byte: Person B list of genomes analyzed
//	-bool: Newer analysis version available
//		-This is not the same as new genomes being available. New version can be for the same genomes.
//		-To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
//	-error
func GetCoupleNewestGeneticAnalysisInfo(inputPerson1Identifier string, inputPerson2Identifier string)(bool, string, int64, [][16]byte, [][16]byte, bool, error){

	person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
	if (err != nil) { return false, "", 0, nil, nil, false, err }

	lookupMap := map[string]string{
		"Person1Identifier": person1Identifier,
		"Person2Identifier": person2Identifier,
	}

	anyItemsFound, analysisItemsMapList, err := myCoupleAnalysesMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, "", 0, nil, nil, false, err }
	if (anyItemsFound == false){
		return false, "", 0, nil, nil, false, nil
	}

	// Below is the newest analysis version, which would update if the user had updated their Seekia client
	appAnalysisVersion := appValues.GetGeneticAnalysisVersion()

	newestAnalysisFound := false
	newestAnalysisCreatedTime := int64(0)
	newestAnalysisIdentifier := ""
	newestAnalysisPerson1AnalyzedGenomesList := [][16]byte{}
	newestAnalysisPerson2AnalyzedGenomesList := [][16]byte{}
	newerAnalysisVersionAvailable := false

	for _, analysisMap := range analysisItemsMapList{

		currentPerson1Identifier, exists := analysisMap["Person1Identifier"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person1Identifier")
		}

		if (person1Identifier != currentPerson1Identifier){
			return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different person1Identifier.")
		}

		currentPerson2Identifier, exists := analysisMap["Person2Identifier"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person2Identifier")
		}

		if (person2Identifier != currentPerson2Identifier){
			return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different person2Identifier.")
		}
	
		timeCreated, exists := analysisMap["TimeCreated"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
		}

		timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
		if (err != nil) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
		}

		if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
			continue
		}
		newestAnalysisFound = true
		
		analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
		}

		person1AnalyzedGenomes, exists := analysisMap["Person1AnalyzedGenomes"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person1AnalyzedGenomes")
		}

		person2AnalyzedGenomes, exists := analysisMap["Person2AnalyzedGenomes"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person2AnalyzedGenomes")
		}

		person1AnalyzedGenomesList := strings.Split(person1AnalyzedGenomes, "+")
		person2AnalyzedGenomesList := strings.Split(person2AnalyzedGenomes, "+")

		person1AnalyzedGenomesArrayList := make([][16]byte, 0)
		person2AnalyzedGenomesArrayList := make([][16]byte, 0)

		for _, genomeIdentifierHex := range person1AnalyzedGenomesList{

			genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
			if (err != nil){
				return false, "", 0, nil, nil, false, errors.New("Malformed myCoupleAnalysesMapListDatastore: Item contains invalid Person1AnalyzedGenomes: " + person1AnalyzedGenomes)
			}

			person1AnalyzedGenomesArrayList = append(person1AnalyzedGenomesArrayList, genomeIdentifier)
		}

		for _, genomeIdentifierHex := range person2AnalyzedGenomesList{

			genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
			if (err != nil){
				return false, "", 0, nil, nil, false, errors.New("Malformed myCoupleAnalysesMapListDatastore: Item contains invalid Person2AnalyzedGenomes: " + person1AnalyzedGenomes)
			}

			person2AnalyzedGenomesArrayList = append(person2AnalyzedGenomesArrayList, genomeIdentifier)
		}

		analysisVersion, exists := analysisMap["AnalysisVersion"]
		if (exists == false) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
		}

		analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
		if (err != nil) {
			return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
		}

		if (appAnalysisVersion > analysisVersionInt){
			// This analysis is not of the newest analysis version
			// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
			newerAnalysisVersionAvailable = true
		} else {
			newerAnalysisVersionAvailable = false
		}

		newestAnalysisCreatedTime = timeCreatedInt64
		newestAnalysisIdentifier = analysisIdentifier
		newestAnalysisPerson1AnalyzedGenomesList = person1AnalyzedGenomesArrayList
		newestAnalysisPerson2AnalyzedGenomesList = person2AnalyzedGenomesArrayList
	}
	
	if (newestAnalysisFound == false){
		return false, "", 0, nil, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
	}

	// We have to return the person1/Person2 analyzed genomes list in the same order that they came in

	if (inputPerson1Identifier == person1Identifier){
		// No swapping happened.
		return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPerson1AnalyzedGenomesList, newestAnalysisPerson2AnalyzedGenomesList, newerAnalysisVersionAvailable, nil
	}

	// We swap the person1/Person2 analyzed genomes lists

	return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPerson2AnalyzedGenomesList, newestAnalysisPerson1AnalyzedGenomesList, newerAnalysisVersionAvailable, nil
}


// This function should only be used to retrieve an existing analysis
//Outputs:
//	-bool: Analysis found
//	-geneticAnalysis.PersonAnalysis Analysis object
//	-error
func GetPersonGeneticAnalysis(analysisIdentifier string)(bool, geneticAnalysis.PersonAnalysis, error){

	analysisFound, analysisFileString, err := getGeneticAnalysisFileString(analysisIdentifier)
	if (err != nil) { return false, geneticAnalysis.PersonAnalysis{}, err }
	if (analysisFound == false){
		return false, geneticAnalysis.PersonAnalysis{}, nil
	}

	analysisObject, err := readGeneticAnalysis.ReadPersonGeneticAnalysisString(analysisFileString)
	if (err != nil) { return false, geneticAnalysis.PersonAnalysis{}, err }

	return true, analysisObject, nil
}

// This function should only be used to retrieve an existing analysis
//Outputs:
//	-bool: Analysis found
//	-geneticAnalysis.CoupleAnalysis Analysis object
//	-error
func GetCoupleGeneticAnalysis(analysisIdentifier string)(bool, geneticAnalysis.CoupleAnalysis, error){

	analysisFound, analysisFileString, err := getGeneticAnalysisFileString(analysisIdentifier)
	if (err != nil) { return false, geneticAnalysis.CoupleAnalysis{}, err }
	if (analysisFound == false){
		return false, geneticAnalysis.CoupleAnalysis{}, nil
	}

	analysisObject, err := readGeneticAnalysis.ReadCoupleGeneticAnalysisString(analysisFileString)
	if (err != nil) { return false, geneticAnalysis.CoupleAnalysis{}, err }

	return true, analysisObject, nil
}

func getGeneticAnalysisFileString(analysisIdentifier string)(bool, string, error){

	userDirectory, err := localFilesystem.GetAppUserFolderPath()
	if (err != nil) { return false, "", err }

	analysisFileName := analysisIdentifier + ".messagepack"

	analysisFilePath := filepath.Join(userDirectory, "MyAnalyses", analysisFileName)

	fileExists, fileBytes, err := localFilesystem.GetFileContents(analysisFilePath)
	if (err != nil) { return false, "", err }
	if (fileExists == false){
		return false, "", nil
	}

	fileString := string(fileBytes)

	return true, fileString, nil
}

// This map keeps track of current person analyses being generated
// Map structure: Person identifier -> Process identifier
var personAnalysisProcessesMap map[string]string = make(map[string]string)

var personAnalysisProcessesMapMutex sync.RWMutex

// This map keeps track of current couple analyses being generated
// Map structure: Person1Identifier + "+" + Person2Identifier -> Process identifier
var coupleAnalysisProcessesMap map[string]string = make(map[string]string)

var coupleAnalysisProcessesMapMutex sync.RWMutex


// Returns the process identifier for the current running genetic analyses for the person
// Outputs:
//	-bool: Any process found (The process may be complete)
//	-string: Process identifier
//	-error
func GetPersonGeneticAnalysisProcessIdentifier(personIdentifier string)(bool, string, error){

	personAnalysisProcessesMapMutex.RLock()
	processIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
	personAnalysisProcessesMapMutex.RUnlock()
	if (exists == false){
		return false, "", nil
	}

	return true, processIdentifier, nil
}

// Returns the process identifier for the current running genetic analyses for the couple
// Outputs:
//	-bool: Any process found (The process may be complete)
//	-string: Process identifier
//	-error
func GetCoupleGeneticAnalysisProcessIdentifier(inputPerson1Identifier string, inputPerson2Identifier string)(bool, string, error){

	person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
	if (err != nil) { return false, "", err }

	coupleIdentifier := person1Identifier + "+" + person2Identifier

	coupleAnalysisProcessesMapMutex.RLock()
	processIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
	coupleAnalysisProcessesMapMutex.RUnlock()
	if (exists == false){
		return false, "", nil
	}

	return true, processIdentifier, nil
}

//Outputs:
//	-bool: Process found
//	-bool: Process is complete
//	-bool: Process encountered error
//	-error: Error encountered by process
//	-int: Process percentage complete
//	-error
func GetAnalysisProcessInfo(processIdentifier string)(bool, bool, bool, error, int, error){

	processExists, processPercentageComplete := appMemory.GetMemoryEntry(processIdentifier + "_ProgressPercentageComplete")
	if (processExists == false){
		// This should not happen...
		return false, false, false, nil, 0, nil
	}

	exists, errorEncountered := appMemory.GetMemoryEntry(processIdentifier + "_ErrorEncountered")
	if (exists == true){

		errorEncounteredError := errors.New(errorEncountered)

		return true, true, true, errorEncounteredError, 100, nil
	}
	
	if (processPercentageComplete == "100"){
		return true, true, false, nil, 100, nil
	}

	processPercentageCompleteInt, err := helpers.ConvertStringToInt(processPercentageComplete)
	if (err != nil) {
		return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)
	}
	if (processPercentageCompleteInt < 0 || processPercentageCompleteInt > 100){
		return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)	
	}

	return true, false, false, nil, processPercentageCompleteInt, nil
}


func StopProcess(processIdentifier string){

	appMemory.SetMemoryEntry(processIdentifier + "_StopProcess", "Yes")
	appMemory.SetMemoryEntry(processIdentifier + "_ProgressPercentageComplete", "100")
}

// This funciton will stop any currently running processes for this person and start generating a new genetic analysis
//Outputs:
//	-string: New process identifier
//	-error
func StartCreateNewPersonGeneticAnalysis(personIdentifier string)(string, error){

	personAnalysisProcessesMapMutex.Lock()
	existingProcessIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
	if (exists == true){
		progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
		if (progressExists == false){
			personAnalysisProcessesMapMutex.Unlock()
			return "", errors.New("personAnalysisProcessesMap process percentageComplete value not found.")
		}
		if (processPercentageComplete != "100"){
			// There is another process running.
			// We will stop the current process for this person
			StopProcess(existingProcessIdentifier)
		}
	}

	newProcessIdentifier, err := helpers.GetNewRandomHexString(18)	
	if (err != nil) { return "", err }

	personAnalysisProcessesMap[personIdentifier] = newProcessIdentifier
	personAnalysisProcessesMapMutex.Unlock()

	updatePercentageCompleteFunction := func(newPercentage int)error{

		if (newPercentage < 0 || newPercentage > 100){
			return errors.New("Invalid person analysis generation progress percentage.")			
		}	

		newPercentageString := helpers.ConvertIntToString(newPercentage)	

		appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
		
		return nil
	}

	err = updatePercentageCompleteFunction(0)
	if (err != nil) { return "", err }

	createNewPersonGeneticAnalysis := func()error{

		checkIfProcessIsStopped := func()bool{

			exists, _ := appMemory.GetMemoryEntry(newProcessIdentifier + "_StopProcess")
			if (exists == true){
				return true
			}

			return false
		}

		personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
		if (err != nil){ return err }

		if (len(personGenomesMapList) == 0){
			return errors.New("Cannot create person genetic analysis: No genomes found for Person.")
		}

		genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))

		genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))

		finalIndex := len(personGenomesMapList) - 1

		for index, genomeMap := range personGenomesMapList{

			newPercentageProgress, err := helpers.ScaleIntProportionally(true, index, 0, finalIndex, 0, 10)
			if (err != nil) { return err }

			err = updatePercentageCompleteFunction(newPercentageProgress)
			if (err != nil) { return err }

			currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
			if (exists == false) {
				return errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
			}

			if (personIdentifier != currentPersonIdentifier){
				return errors.New("GetAllPersonGenomesMapList returning genome for different person.")
			}

			genomeIdentifierHex, exists := genomeMap["GenomeIdentifier"]
			if (exists == false) {
				return errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
			}

			genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
			if (err != nil) { return err }

			genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
			if (err != nil) { return err }

			rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
			if (err != nil) { return err }
			if (rawGenomeIsValid == false){
				return errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifierHex)
			}

			genomesList = append(genomesList, rawGenomeWithMetadata)
			genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifierHex)
		}

		analysisUpdatePercentageCompleteFunction := func(inputProgress int)error{

			newPercentageProgress, err := helpers.ScaleIntProportionally(true, inputProgress, 0, 100, 10, 10)
			if (err != nil) { return err }

			err = updatePercentageCompleteFunction(newPercentageProgress)
			if (err != nil) { return err }

			return nil
		}

		processCompleted, newGeneticAnalysisString, err := createPersonGeneticAnalysis.CreatePersonGeneticAnalysis(genomesList, analysisUpdatePercentageCompleteFunction, checkIfProcessIsStopped)
		if (err != nil) { return err }
		if (processCompleted == false){
			// User stopped the analysis mid-way
			return nil
		}

		analyzedGenomesListString := strings.Join(genomeIdentifiersList, "+")

		analysisIdentifier, err := helpers.GetNewRandomHexString(17)
		if (err != nil) { return err }

		currentTime := time.Now().Unix()
		currentTimeString := helpers.ConvertInt64ToString(currentTime)

		currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
		currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)

		newAnalysisMap := map[string]string{
			"PersonIdentifier": personIdentifier,
			"AnalysisIdentifier": analysisIdentifier,
			"TimeCreated": currentTimeString,
			"AnalyzedGenomes": analyzedGenomesListString,
			"AnalysisVersion": currentAnalysisVersionString,
		}

		updatingMyAnalysesMutex.Lock()
		defer updatingMyAnalysesMutex.Unlock()

		err = myPersonAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
		if (err != nil) { return err }

		userDirectory, err := localFilesystem.GetAppUserFolderPath()
		if (err != nil) { return err }
		
		myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")

		analysisFileName := analysisIdentifier + ".messagepack"

		err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
		if (err != nil) { return err }

		err = updatePercentageCompleteFunction(100)
		if (err != nil) { return err }

		personAnalysisProcessesMapMutex.Lock()
		delete(personAnalysisProcessesMap, personIdentifier)
		personAnalysisProcessesMapMutex.Unlock()	

		return nil
	}

	runAnalysis := func(){

		err := createNewPersonGeneticAnalysis()
		if (err != nil){
			appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", "100")
			appMemory.SetMemoryEntry(newProcessIdentifier + "_ErrorEncountered", err.Error())
			return
		}
	}

	go runAnalysis()

	return newProcessIdentifier, nil
}


// This will stop any currently running processes for this couple and start generating a new genetic analysis
//Outputs:
//	-string: New process identifier
//	-error
func StartCreateNewCoupleGeneticAnalysis(inputPerson1Identifier string, inputPerson2Identifier string)(string, error){

	person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
	if (err != nil) { return "", err }

	coupleIdentifier := person1Identifier + "+" + person2Identifier

	coupleAnalysisProcessesMapMutex.Lock()
	existingProcessIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
	if (exists == true){
		progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
		if (progressExists == false){
			coupleAnalysisProcessesMapMutex.Unlock()
			return "", errors.New("coupleAnalysisProcessesMap process percentageComplete value not found.")
		}
		if (processPercentageComplete != "100"){
			// There is another process running.
			// We will stop the current process for this person
			StopProcess(existingProcessIdentifier)
		}
	}

	newCoupleProcessIdentifier, err := helpers.GetNewRandomHexString(18)	
	if (err != nil) { return "", err }

	coupleAnalysisProcessesMap[coupleIdentifier] = newCoupleProcessIdentifier
	coupleAnalysisProcessesMapMutex.Unlock()

	updatePercentageCompleteFunction := func(newPercentage int)error{

		if (newPercentage < 0 || newPercentage > 100){
			return errors.New("Invalid couple analysis generation progress percentage.")			
		}

		newPercentageString := helpers.ConvertIntToString(newPercentage)	

		appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
		
		return nil
	}

	err = updatePercentageCompleteFunction(0)
	if (err != nil) { return "", err }

	createNewCoupleGeneticAnalysis := func()error{

		checkIfProcessIsStopped := func()bool{

			exists, _ := appMemory.GetMemoryEntry(newCoupleProcessIdentifier + "_StopProcess")
			if (exists == true){
				return true
			}
			return false
		}

		// We need both person1 and person2 to have an analysis before performing the couple analysis
		// We will see if an analysis is already running for each person
		// We will either start a new analysis for each person or monitor the existing one until it is done

		personIdentifiersList := []string{person1Identifier, person2Identifier}

		for index, personIdentifier := range personIdentifiersList{

			// This function will return the percentage progress that this person's analysis will use of the entire couple analysis
			getPercentageRangeForPersonAnalysis := func()(int, int){
				if (index == 0){
					return 0, 33
				}
				return 33, 66
			}

			personPercentageRangeStart, personPercentageRangeEnd := getPercentageRangeForPersonAnalysis()

			// Outputs:
			//	-bool: Person needs update
			//	-string: Person process identifier
			//	-error
			getPersonProcessIdentifier := func()(bool, string, error){

				runningProcessFound, runningProcessIdentifier, err := GetPersonGeneticAnalysisProcessIdentifier(personIdentifier)
				if (err != nil) { return false, "", err }
				if (runningProcessFound == true){
					// This person has a running analysis. We will show the progress in our current process
					return true, runningProcessIdentifier, nil
				}

				// We check if the person needs a new analysis.
				getPersonNewAnalysisNeededBool := func()(bool, error){

					anyPersonAnalysisFound, _, _, newestPersonAnalysisListOfGenomesAnalyzed, newerAnalysisVersionAvailable, err := GetPersonNewestGeneticAnalysisInfo(personIdentifier)
					if (err != nil) { return false, err }
					if (anyPersonAnalysisFound == false){
						// No analysis exists. We need to create a new analysis
						return true, nil
					}
					if (newerAnalysisVersionAvailable == true){
						// New analysis version is available.
						return true, nil
					}

					allPersonRawGenomeIdentifiersList, err := myGenomes.GetAllPersonRawGenomeIdentifiersList(person1Identifier)
					if (err != nil) { return false, err }

					genomesAreIdentical := helpers.CheckIfTwoListsContainIdenticalItems(allPersonRawGenomeIdentifiersList, newestPersonAnalysisListOfGenomesAnalyzed)
					if (genomesAreIdentical == false){
						// New genome exists/a genome was deleted. A new analysis is needed
						return true, nil
					}
					// Analysis is up to date. Nothing to do.
					return false, nil
				}

				personNewAnalysisIsNeeded, err := getPersonNewAnalysisNeededBool()
				if (err != nil) { return false, "", err }
				if (personNewAnalysisIsNeeded == false){
					return false, "", nil
				}

				// We start a new analysis

				newProcessIdentifier, err := StartCreateNewPersonGeneticAnalysis(personIdentifier)
				if (err != nil) { return false, "", err }			

				return true, newProcessIdentifier, nil
			}

			personNeedsUpdate, personProcessIdentifier, err := getPersonProcessIdentifier()
			if (err != nil) { return err }
			if (personNeedsUpdate == false){
				// No analysis needed
				err := updatePercentageCompleteFunction(personPercentageRangeEnd)
				if (err != nil) { return err }

				continue
			}

			for{

				processFound, processIsComplete, processEncounteredError, errorEncounteredByProcess, processPercentageComplete, err := GetAnalysisProcessInfo(personProcessIdentifier)
				if (err != nil){ return err }
				if (processFound == false){
					return errors.New("Person process not found after being found already.")
				}
				if (processIsComplete == true){
					if (processEncounteredError == true){
						return errorEncounteredByProcess
					}
					err := updatePercentageCompleteFunction(personPercentageRangeEnd)
					if (err != nil) { return err }

					break
				}

				personPercentageComplete, err := helpers.ScaleIntProportionally(true, processPercentageComplete, 0, 100, personPercentageRangeStart, personPercentageRangeEnd)
				if (err != nil) { return err }

				err = updatePercentageCompleteFunction(personPercentageComplete)
				if (err != nil) { return err }

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

		// Both people's analyses are complete.
		// The percentage progress is at 66%
		// Now we perform the couple analysis

		//Outputs:
		//	-[]prepareRawGenomes.RawGenomeWithMetadata: Genome Identifier -> Genome raw data string
		//	-[]string: Person raw genome identifiers list
		//	-error
		getPersonGenomesList := func(personIdentifier string)([]prepareRawGenomes.RawGenomeWithMetadata, []string, error){

			personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
			if (err != nil){ return nil, nil, err }

			if (len(personGenomesMapList) == 0){
				return nil, nil, errors.New("Cannot create person genetic analysis: No genomes found for Person.")
			}

			genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))

			// Map structure: Genome identifier -> Genome raw data string
			genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))

			for _, genomeMap := range personGenomesMapList{

				currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
				if (exists == false) {
					return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
				}

				if (personIdentifier != currentPersonIdentifier){
					return nil, nil, errors.New("GetAllPersonGenomesMapList returning genome for different person.")
				}

				genomeIdentifierHex, exists := genomeMap["GenomeIdentifier"]
				if (exists == false) {
					return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
				}

				genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
				if (err != nil) { return nil, nil, err }

				genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
				if (err != nil) { return nil, nil, err }

				rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
				if (err != nil) { return nil, nil, err }
				if (rawGenomeIsValid == false){
					return nil, nil, errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifierHex)
				}

				genomesList = append(genomesList, rawGenomeWithMetadata)
				genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifierHex)
			}

			return genomesList, genomeIdentifiersList, nil
		}

		person1GenomesList, person1RawGenomeIdentifiersList, err := getPersonGenomesList(person1Identifier)
		if (err != nil) { return err }

		updatePercentageCompleteFunction(70)

		person2GenomesList, person2RawGenomeIdentifiersList, err := getPersonGenomesList(person2Identifier)
		if (err != nil) { return err }

		updatePercentageCompleteFunction(74)

		updateCoupleAnalysisPercentageCompleteFunction := func(newPercentage int)error{

			personPercentageComplete, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 74, 100)
			if (err != nil) { return err }

			err = updatePercentageCompleteFunction(personPercentageComplete)
			if (err != nil) { return err }
		
			return nil
		}

		processCompleted, newGeneticAnalysisString, err := createCoupleGeneticAnalysis.CreateCoupleGeneticAnalysis(person1GenomesList, person2GenomesList, updateCoupleAnalysisPercentageCompleteFunction, checkIfProcessIsStopped)
		if (err != nil) { return err }
		if (processCompleted == false){
			// User stopped the analysis mid-way
			return nil
		}

		person1AnalyzedGenomesListString := strings.Join(person1RawGenomeIdentifiersList, "+")
		person2AnalyzedGenomesListString := strings.Join(person2RawGenomeIdentifiersList, "+")

		analysisIdentifier, err := helpers.GetNewRandomHexString(17)
		if (err != nil) { return err }

		currentTime := time.Now().Unix()
		currentTimeString := helpers.ConvertInt64ToString(currentTime)

		currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
		currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)

		newAnalysisMap := map[string]string{
			"Person1Identifier": person1Identifier,
			"Person2Identifier": person2Identifier,
			"AnalysisIdentifier": analysisIdentifier,
			"TimeCreated": currentTimeString,
			"Person1AnalyzedGenomes": person1AnalyzedGenomesListString,
			"Person2AnalyzedGenomes": person2AnalyzedGenomesListString,
			"AnalysisVersion": currentAnalysisVersionString,
		}

		updatingMyAnalysesMutex.Lock()
		defer updatingMyAnalysesMutex.Unlock()

		err = myCoupleAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
		if (err != nil) { return err }

		userDirectory, err := localFilesystem.GetAppUserFolderPath()
		if (err != nil) { return err }
		
		myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")

		analysisFileName := analysisIdentifier + ".messagepack"

		err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
		if (err != nil) { return err }

		err = updatePercentageCompleteFunction(100)
		if (err != nil) { return err }

		coupleAnalysisProcessesMapMutex.Lock()
		delete(coupleAnalysisProcessesMap, coupleIdentifier)
		coupleAnalysisProcessesMapMutex.Unlock()

		return nil
	}

	runAnalysis := func(){

		err := createNewCoupleGeneticAnalysis()
		if (err != nil){
			appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", "100")
			appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ErrorEncountered", err.Error())
			return
		}
	}

	go runAnalysis()

	return newCoupleProcessIdentifier, nil
}



