
// myPeople provides functions to manage a user's genome people
// Each person can have many genomes and a genetic analysis
// Each person's genomes are stored in myGenomes
// This package stores each person's identifier, name, sex, and createdTime

package myPeople

import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/helpers"
import "seekia/internal/genetics/myGenomes"
import "seekia/internal/genetics/myCouples"
import "seekia/internal/genetics/myAnalyses"

import "time"
import "sync"
import "errors"

// This will be locked anytime People are being added/deleted
var updatingMyPeopleMutex sync.Mutex

var myGenomePeopleMapListDatastore *myMapList.MyMapList

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

	updatingMyPeopleMutex.Lock()
	defer updatingMyPeopleMutex.Unlock()

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

	myGenomePeopleMapListDatastore = newMyGenomePeopleMapListDatastore

	return nil
}

//Outputs:
//	-[]map[string]string
//		-PersonIdentifier -> Person Identifier
//		-PersonName -> Person Name
//		-CreatedTime -> Time the person was created
//	-error
func GetMyGenomePeopleMapList()([]map[string]string, error){

	peopleMapList, err := myGenomePeopleMapListDatastore.GetMapList()
	if (err != nil) { return nil, err }

	return peopleMapList, nil
}

//Outputs:
//	-bool: Person with this name already exists
//	-error
func AddPerson(personName string, personSex string)(bool, error){
	
	if (personName == ""){
		return false, errors.New("AddPerson called with empty personName")
	}
	if (personSex != "Male" && personSex != "Female" && personSex != "Intersex"){
		return false, errors.New("AddPerson called with invalid personSex: " + personSex)
	}

	personIdentifier, err := helpers.GetNewRandomHexString(15)
	if (err != nil) { return false, err }

	createdTime := time.Now().Unix()
	createdTimeString := helpers.ConvertInt64ToString(createdTime)

	updatingMyPeopleMutex.Lock()
	defer updatingMyPeopleMutex.Unlock()

	lookupMap := map[string]string{
		"PersonName": personName,
	}

	anyExist, _, err := myGenomePeopleMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, err }
	if (anyExist == true){
		return true, nil
	}

	peopleMapList, err := myGenomePeopleMapListDatastore.GetMapList()
	if (err != nil) { return false, err }

	newPersonMap := map[string]string{
		"PersonIdentifier": personIdentifier,
		"PersonName": personName,
		"CreatedTime": createdTimeString,
		"PersonSex": personSex,
	}

	newPeopleMapList := append(peopleMapList, newPersonMap)

	err = myGenomePeopleMapListDatastore.OverwriteMapList(newPeopleMapList)
	if (err != nil) { return false, err }

	return false, nil
}

//Outputs:
//	-bool: Person with this name already exists
//	-error
func EditPerson(personIdentifier string, newPersonName string, newPersonSex string)(bool, error){

	if (newPersonName == ""){
		return false, errors.New("EditPerson called with empty newPersonName")
	}
	if (newPersonSex != "Male" && newPersonSex != "Female" && newPersonSex != "Intersex"){
		return false, errors.New("EditPerson called with invalid newPersonSex: " + newPersonSex)
	}

	updatingMyPeopleMutex.Lock()
	defer updatingMyPeopleMutex.Unlock()

	lookupMap := map[string]string{
		"PersonName": newPersonName,
	}

	anyExist, _, err := myGenomePeopleMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, err }
	if (anyExist == true){
		// Someone else already has this name
		return true, nil
	}

	peopleMapList, err := myGenomePeopleMapListDatastore.GetMapList()
	if (err != nil) { return false, err }

	for _, personMap := range peopleMapList{

		currentPersonIdentifier, exists := personMap["PersonIdentifier"]
		if (exists == false){
			return false, errors.New("MyGenomePeopleMapList malformed: contains entry missing PersonIdentifier")
		}

		if (currentPersonIdentifier == personIdentifier){

			personMap["PersonName"] = newPersonName
			personMap["PersonSex"] = newPersonSex
		}
	}

	err = myGenomePeopleMapListDatastore.OverwriteMapList(peopleMapList)
	if (err != nil) { return false, err }

	return false, nil
}

// This function will delete the person, any couples with the person, and all of the person's genomes
func DeletePerson(personIdentifier string)error{

	updatingMyPeopleMutex.Lock()
	defer updatingMyPeopleMutex.Unlock()

	err := myGenomes.DeleteAllPersonGenomes(personIdentifier)
	if (err != nil) { return err }

	err = myCouples.DeleteAllCouplesForPerson(personIdentifier)
	if (err != nil) { return err }

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

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

	err = myAnalyses.DeleteAllAnalysesForPerson(personIdentifier)
	if (err != nil) { return err }

	return nil
}

//Outputs:
//	-bool: Person found
//	-string: Person name
//	-int64: Created time
//	-string: Sex ("Male"/"Female"/"Intersex")
//	-error
func GetPersonInfo(personIdentifier string)(bool, string, int64, string, error){

	if (personIdentifier == "111111111111111111111111111111" || personIdentifier == "222222222222222222222222222222"){
		// These are the identifiers we use for sample analyses
		// These are analyses the user can view if they want to see what an analysis will look like

		getPersonName := func()string{
			if (personIdentifier == "111111111111111111111111111111"){
				return "Person 1"
			}
			return "Person 2"
		}

		personName := getPersonName()

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

		getPersonSex := func()string{
			if (personIdentifier == "111111111111111111111111111111"){
				return "Male"
			}
			return "Female"
		}

		personSex := getPersonSex()

		return true, personName, currentTime, personSex, nil
	}

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

	anyItemFound, retrievedItemsList, err := myGenomePeopleMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, "", 0, "", err }
	if (anyItemFound == false){
		return false, "", 0, "", nil
	}
	if (len(retrievedItemsList) != 1){
		return false, "", 0, "", errors.New("myGenomePeopleMapList malformed: Multiple entries exist for 1 person")
	}

	personMap := retrievedItemsList[0]

	createdTime, exists := personMap["CreatedTime"]
	if (exists == false) {
		return false, "", 0, "", errors.New("myGenomePeopleMapList malformed: Entry missing CreatedTime")
	}

	createdTimeInt64, err := helpers.ConvertStringToInt64(createdTime)
	if (err != nil) {
		return false, "", 0, "", errors.New("myGenomePeopleMapList malformed: Invalid CreatedTime: " + createdTime)
	}

	personName, exists := personMap["PersonName"]
	if (exists == false) {
		return false, "", 0, "", errors.New("myGenomePeopleMapList malformed: Entry missing PersonName")
	}

	personSex, exists := personMap["PersonSex"]
	if (exists == false) {
		return false, "", 0, "", errors.New("myGenomePeopleMapList malformed: Entry missing PersonSex")
	}

	return true, personName, createdTimeInt64, personSex, nil
}


// This will return true if a person's analysis is ready
// This means it was created with all of the person's genomes using the newest available analysis version
// Outputs:
//	-bool: Any Genomes exist
//	-bool: Person analysis is ready
//	-string: Newest ready person genetic analysis 
//	-error
func CheckIfPersonAnalysisIsReady(personIdentifier string)(bool, bool, string, error){

	allPersonRawGenomeIdentifiersList, err := myGenomes.GetAllPersonRawGenomeIdentifiersList(personIdentifier)
	if (err != nil){ return false, false, "", err }
	if (len(allPersonRawGenomeIdentifiersList) == 0){
		return false, false, "", nil
	}

	anyAnalysisFound, newestAnalysisIdentifier, _, newestAnalysisListOfGenomesAnalyzed, newerAnalysisVersionAvailable, err := myAnalyses.GetPersonNewestGeneticAnalysisInfo(personIdentifier)
	if (err != nil){ return false, false, "", err }
	if (anyAnalysisFound == false){
		return true, false, "", nil
	}
	if (newerAnalysisVersionAvailable == true){
		return true, false, "", nil
	}

	genomesAreIdentical := helpers.CheckIfTwoListsContainIdenticalItems(allPersonRawGenomeIdentifiersList, newestAnalysisListOfGenomesAnalyzed)
	if (genomesAreIdentical == false){
		// A newer genome has been added
		return true, false, "", nil	
	}

	return true, true, newestAnalysisIdentifier, nil
}



