package companyAnalysis

import "seekia/internal/allowedText"
import "seekia/internal/helpers"

import "slices"
import "strings"
import "errors"
import "maps"


func VerifyNeanderthalVariants_23andMe(numberOfVariants int)bool{

	if (numberOfVariants < 0 || numberOfVariants > 7462){
		return false
	}

	return true
}

// These are the maternal and paternal halpogroups we know
// There are more that should be documented
// If a user submits one of these, the attribute will be canonical
// Otherwise, moderators must approve it
// TODO: Someone needs to figure out all of 23andMe's haplogroups.

func GetKnownMaternalHaplogroupsList_23andMe()[]string{

	return knownMaternalHaplogroupsList
}

func GetKnownPaternalHaplogroupsList_23andMe()[]string{

	return knownPaternalHaplogroupsList
}

var knownMaternalHaplogroupsList []string = []string{
	"H3",
	"U4a1a",
	"J1",
	"U9a",
	"H1a",
	"U5b1b1a1",
}

var knownPaternalHaplogroupsList []string = []string{
	"R-L1335",
	"E-P147",
	"E-L29",
	"T-CTS8512",
	"R-P311",
}

//Outputs:
//	-bool: Is Valid
//	-bool: Is Canonical
func VerifyMaternalHaplogroup_23AndMe(inputValue string)(bool, bool){

	isKnown := slices.Contains(knownMaternalHaplogroupsList, inputValue)
	if (isKnown == true){
		return true, true
	}

	if (len(inputValue) > 25){
		return false, false
	}

	isAllowed := allowedText.VerifyStringIsAllowed(inputValue)
	if (isAllowed == false){
		return false, false
	}

	containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(inputValue)
	if (containsTabsOrNewlines == true){
		return false, false
	}

	return true, true
}

//Outputs:
//	-bool: Is Valid
//	-bool: Is Canonical
func VerifyPaternalHaplogroup_23AndMe(inputValue string)(bool, bool){

	isKnown := slices.Contains(knownPaternalHaplogroupsList, inputValue)
	if (isKnown == true){
		return true, true
	}

	if (len(inputValue) > 25){
		return false, false
	}

	isAllowed := allowedText.VerifyStringIsAllowed(inputValue)
	if (isAllowed == false){
		return false, false
	}

	containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(inputValue)
	if (containsTabsOrNewlines == true){
		return false, false
	}

	return true, true
}

//TODO: Replace float64 with float32 everywhere where ancestry composition percentage maps exist?
// We don't need more than 1 decimal of precision, and values only span 0-100

//Outputs:
//	-bool: Inputs are valid (all locations are valid and sum to the correct amounts)
//	-string: Composition attribute
//	-error: There is a bug in the function
func CreateAncestryCompositionAttribute_23andMe(continentPercentagesMap map[string]float64, regionPercentagesMap map[string]float64, subregionPercentagesMap map[string]float64)(bool, string, error){

	// We will round down all percentages to 1 decimal, because that is the highest precision that 23andMe offers

	getLocationSection := func(locationPercentagesMap map[string]float64)string{

		if (len(locationPercentagesMap) == 0){
			return "None"
		}

		locationItemsList := make([]string, 0)

		for locationName, locationPercentage := range locationPercentagesMap{

			if (locationPercentage == 0){
				continue
			}

			percentageString := helpers.ConvertFloat64ToStringRounded(locationPercentage, 1)

			locationItem := locationName + "$" + percentageString

			locationItemsList = append(locationItemsList, locationItem)
		}

		if (len(locationItemsList) == 0){
			return "None"
		}

		locationSection := strings.Join(locationItemsList, "#")

		return locationSection
	}

	continentsSection := getLocationSection(continentPercentagesMap)
	regionsSection := getLocationSection(regionPercentagesMap)
	subregionsSection := getLocationSection(subregionPercentagesMap)

	compositionAttribute := continentsSection + "+" + regionsSection + "+" + subregionsSection

	attributeIsValid, _, _, _, err := ReadAncestryCompositionAttribute_23andMe(true, compositionAttribute)
	if (err != nil){ return false, "", err }
	if (attributeIsValid == false){
		return false, "", nil
	}

	return true, compositionAttribute, nil
}

// This function's maps only contains locations which have no sublocations
//Outputs:
//	-bool: Attribute is valid
//	-map[string]float64: Continent percentages map (continent name -> Percentage of total ancestry)
//	-map[string]float64: Region percentages map (region name -> Percentage of total ancestry)
//	-map[string]float64: Subregion percentages map (Subregion name -> Percentage of total ancestry)
//	-error (if there is a bug in this function)
func ReadAncestryCompositionAttribute_23andMe(verifyData bool, attributeValue string)(bool, map[string]float64, map[string]float64, map[string]float64, error){

	// Attribute is formatted as follows:

	// Continents section + "+" + Regions Section + "+" + Sub-Regions section

	// Continents section is made up of continent items, or "None"
	// Region section is made up of region items, or "None"
	// Subregion section is made up of subregion items, or "None"

	// Each section's items are separated by "#"

	// Continent Item: Continent name + "$" + Percentage of whole 
	// Region Item: Region name + "$" + Percentage of whole
	// Subregion Item: Subregion name + "$" + Percentage of whole

	// Only locations with no sub-locations are included
	// Example: Unassigned has no sub-locations, so it is a valid continent to include
	// Thus, all percentage values for all continent, region and subregion locations must sum to 100

	attributeList := strings.Split(attributeValue, "+")

	if (len(attributeList) != 3){
		return false, nil, nil, nil, nil
	}

	//Map Structure: Continent name -> Percentage of total ancestry
	continentPercentagesMap := make(map[string]float64)

	//Map Structure: Region name -> Percentage of total ancestry
	regionPercentagesMap := make(map[string]float64)

	//Map Structure: Subregion name -> Percentage of total ancestry
	subregionPercentagesMap := make(map[string]float64)

	// Outputs:
	//	-error: If attribute is invalid
	addLocationItemsToMap := func(locationSection string, locationMap map[string]float64)error{

		if (locationSection == "None"){
			return nil
		}

		locationItemsList := strings.Split(locationSection, "#")

		for _, locationItem := range locationItemsList{

			locationName, locationPercentage, delimiterFound := strings.Cut(locationItem, "$")
			if (delimiterFound == false){
				return errors.New("Attribute is invalid: contains invalid locationItem")
			}

			locationPercentageFloat64, err := helpers.ConvertStringToFloat64(locationPercentage)
			if (err != nil){
				return errors.New("Attribute is invalid: location percentage is not float.")
			}

			if (locationPercentageFloat64 <= 0 || locationPercentageFloat64 > 100){
				return errors.New("Attribute is invalid: location percentage is out of range.")
			}

			_, exists := locationMap[locationName]
			if (exists == true){
				return errors.New("Attribute is invalid: Contains duplicate locationName")
			}

			locationMap[locationName] = locationPercentageFloat64
		}

		return nil
	}

	continentsSection := attributeList[0]
	regionsSection := attributeList[1]
	subregionsSection := attributeList[2]

	err := addLocationItemsToMap(continentsSection, continentPercentagesMap)
	if (err != nil){
		return false, nil, nil, nil, nil
	}

	err = addLocationItemsToMap(regionsSection, regionPercentagesMap)
	if (err != nil){
		return false, nil, nil, nil, nil
	}

	err = addLocationItemsToMap(subregionsSection, subregionPercentagesMap)
	if (err != nil){
		return false, nil, nil, nil, nil
	}

	if (verifyData == false){
		return true, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, nil
	}

	// First we make sure all percentages sum to 100
	// These should all be locations which have no sublocations

	totalSum := float64(0)

	for _, continentPercentage := range continentPercentagesMap{
		totalSum += continentPercentage
	}
	for _, regionPercentage := range regionPercentagesMap{
		totalSum += regionPercentage
	}
	for _, subregionPercentage := range subregionPercentagesMap{
		totalSum += subregionPercentage
	}

	if (totalSum != 100){
		return false, nil, nil, nil, nil
	}

	// Now we add all parent locations

	mapsAreValid, filledContinentPercentagesMap, _, _, err := AddMissingParentsToAncestryCompositionMaps_23andMe(continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap)
	if (err != nil){ return false, nil, nil, nil, err }
	if (mapsAreValid == false){
		return false, nil, nil, nil, nil
	}

	// Now we make sure continents total is valid
	// If continents total is valid, the region/subregion totals should also be valid

	continentsTotal := float64(0)

	for _, continentPercentage := range filledContinentPercentagesMap{
		continentsTotal += continentPercentage
	}

	if (continentsTotal != 100){
		// Attribute is invalid: Continents do not sum to 100
		return false, nil, nil, nil, nil
	}

	return true, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, nil
}

// This function will return new maps with the parent locations and their percentages added
// It takes as input location maps that only contain locations which have no sublocations
// It is used to read an ancestry composition attribute, and to show a user's progress when they are building their composition
// It does not verify that the percentages sum to 100, because it receives partially completed maps during the build process
// It verifies all location names are valid
//Outputs:
//	-bool: Input maps are valid
//	-map[string]float64: Continent percentages map (with parents added)
//	-map[string]float64: Region percentages map (with parents added)
//	-map[string]float64: Subregion percentages map (with parents added)
//	-error (a bug exists in the function)
func AddMissingParentsToAncestryCompositionMaps_23andMe(inputContinentPercentagesMap map[string]float64, inputRegionPercentagesMap map[string]float64, inputSubregionPercentagesMap map[string]float64)(bool, map[string]float64, map[string]float64, map[string]float64, error){

	// We copy the input maps because the we only need to add the missing parents for display, not for encoding
	// We need to maintain the integrity of the input maps
	// The missing parents are only added whenever the composition attribute is displayed in the GUI.
	// This allows the attribute to be smaller in size, reducing the size of profiles.

	continentPercentagesMap := maps.Clone(inputContinentPercentagesMap)
	regionPercentagesMap := maps.Clone(inputRegionPercentagesMap)
	subregionPercentagesMap := maps.Clone(inputSubregionPercentagesMap)

	allContinentsList := GetAncestryContinentsList_23andMe()

	// We make sure all continent names are valid

	for continentName, _ := range continentPercentagesMap{

		isValid := slices.Contains(allContinentsList, continentName)
		if (isValid == false){
			// Continent name is unknown
			return false, nil, nil, nil, nil
		}
	}

	// This list will store the number of regions we should have when complete
	// This enables us to detect unknown region names
	expectedRegionsCount := 0

	// This list will store the number of subregions we should have when complete
	// This enables us to detect unknown subregion names
	expectedSubregionsCount := 0

	for _, continentName := range allContinentsList{

		continentRegionsList, err := GetAncestryContinentRegionsList_23andMe(continentName)
		if (err != nil){
			return false, nil, nil, nil, errors.New("GetAncestryContinentRegionsList_23andMe missing continent regions: " + continentName)
		}

		if (len(continentRegionsList) == 0){
			// This continent has no sublocations
			// If it exists, it should be included
			continue
		}

		_, exists := continentPercentagesMap[continentName]
		if (exists == true){
			// Ancestry composition is invalid: Contains continent with sublocations
			// All provided locations should have no sublocations
			return false, nil, nil, nil, nil
		}

		continentPercentage := float64(0)

		for _, regionName := range continentRegionsList{

			regionSubregionsList, err := GetAncestryRegionSubregionsList_23andMe(continentName, regionName)
			if (err != nil){
				return false, nil, nil, nil, errors.New("GetAncestryRegionSubregionsList_23andMe missing region subregions: " + regionName)
			}

			if (len(regionSubregionsList) == 0){

				regionPercentage, exists := regionPercentagesMap[regionName]
				if (exists == true){
					continentPercentage += regionPercentage
					expectedRegionsCount += 1		
				}

				continue
			}

			_, exists := regionPercentagesMap[regionName]
			if (exists == true){
				// Ancestry composition is invalid: Contains region with sublocations
				// All provided locations should have no sublocations
				return false, nil, nil, nil, nil
			}

			regionPercentage := float64(0)

			for _, subregionName := range regionSubregionsList{

				subregionPercentage, exists := subregionPercentagesMap[subregionName]
				if (exists == true){
					regionPercentage += subregionPercentage
					expectedSubregionsCount += 1
				}
			}

			if (regionPercentage != 0){
				regionPercentagesMap[regionName] = regionPercentage
				expectedRegionsCount += 1
				continentPercentage += regionPercentage
			}
		}

		if (continentPercentage != 0){
			continentPercentagesMap[continentName] = continentPercentage
		}
	}

	// We make sure no unknown region/subregion names exist

	if (len(regionPercentagesMap) != expectedRegionsCount){
		// An unknown region must exist
		return false, nil, nil, nil, nil
	}
	if (len(subregionPercentagesMap) != expectedSubregionsCount){
		// An unknown subregion must exist
		return false, nil, nil, nil, nil
	}

	return true, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, nil
}

// This function does not add the missing parent locations
//Outputs:
//	-map[string]float64: Continent percentages map
//	-map[string]float64: Region percentages map
//	-map[string]float64: Subregion percentages map
//	-error
func GetOffspringAncestryComposition_23andMe(person1AncestryCompositionAttribute string, person2AncestryCompositionAttribute string)(map[string]float64, map[string]float64, map[string]float64, error){

	person1AttributeIsValid, person1ContinentPercentagesMap, person1RegionPercentagesMap, person1SubregionPercentagesMap, err := ReadAncestryCompositionAttribute_23andMe(true, person1AncestryCompositionAttribute)
	if (err != nil) { return nil, nil, nil, err }
	if (person1AttributeIsValid == false){
		return nil, nil, nil, errors.New("GetOffspringAncestryComposition_23andMe called with invalid person A ancestry composition attribute: " + person1AncestryCompositionAttribute)
	}

	person2AttributeIsValid, person2ContinentPercentagesMap, person2RegionPercentagesMap, person2SubregionPercentagesMap, err := ReadAncestryCompositionAttribute_23andMe(true, person2AncestryCompositionAttribute)
	if (err != nil) { return nil, nil, nil, err }
	if (person2AttributeIsValid == false){
		return nil, nil, nil, errors.New("GetOffspringAncestryComposition_23andMe called with invalid person B ancestry composition attribute: " + person2AncestryCompositionAttribute)
	}
	
	offspringContinentPercentagesMap := make(map[string]float64)

	for continentName, continentPercentage := range person1ContinentPercentagesMap{

		person1Percentage := continentPercentage/2

		offspringContinentPercentagesMap[continentName] = person1Percentage
	}
	
	for continentName, continentPercentage := range person2ContinentPercentagesMap{

		person2Percentage := continentPercentage/2

		offspringContinentPercentagesMap[continentName] += person2Percentage
	}

	offspringRegionPercentagesMap := make(map[string]float64)

	for regionName, regionPercentage := range person1RegionPercentagesMap{

		person1Percentage := regionPercentage/2

		offspringRegionPercentagesMap[regionName] = person1Percentage
	}

	for regionName, regionPercentage := range person2RegionPercentagesMap{

		person2Percentage := regionPercentage/2

		offspringRegionPercentagesMap[regionName] += person2Percentage
	}

	offspringSubregionPercentagesMap := make(map[string]float64)

	for subregionName, subregionPercentage := range person1SubregionPercentagesMap{

		person1Percentage := subregionPercentage/2

		offspringSubregionPercentagesMap[subregionName] = person1Percentage
	}

	for subregionName, subregionPercentage := range person2SubregionPercentagesMap{

		person2Percentage := subregionPercentage/2

		offspringSubregionPercentagesMap[subregionName] += person2Percentage
	}

	return offspringContinentPercentagesMap, offspringRegionPercentagesMap, offspringSubregionPercentagesMap, nil
}


// Ancestral similarity aims to compare how closely related a 2 user's ancestors are.

//Outputs:
//	-int: A percentage between 0 and 100
//	-error
func GetAncestralSimilarity_23andMe(verifyAttributes bool, person1AncestryCompositionAttribute string, person2AncestryCompositionAttribute string)(int, error){

	person1AttributeIsValid, person1ContinentPercentagesMap, person1RegionPercentagesMap, person1SubregionPercentagesMap, err := ReadAncestryCompositionAttribute_23andMe(verifyAttributes, person1AncestryCompositionAttribute)
	if (err != nil) { return 0, err }
	if (person1AttributeIsValid == false){
		return 0, errors.New("GetAncestralSimilarity_23andMe called with invalid person1 23andMe_AncestryComposition attribute: " + person1AncestryCompositionAttribute)
	}

	person2AttributeIsValid, person2ContinentPercentagesMap, person2RegionPercentagesMap, person2SubregionPercentagesMap, err := ReadAncestryCompositionAttribute_23andMe(verifyAttributes, person2AncestryCompositionAttribute)
	if (err != nil) { return 0, err }
	if (person2AttributeIsValid == false){
		return 0, errors.New("GetAncestralSimilarity_23andMe called with invalid person2 23andMe_AncestryComposition attribute: " + person2AncestryCompositionAttribute)
	}

	continentsAreEqual := maps.Equal(person1ContinentPercentagesMap, person2ContinentPercentagesMap)
	if (continentsAreEqual == true){

		regionsAreEqual := maps.Equal(person1RegionPercentagesMap, person2RegionPercentagesMap)
		if (regionsAreEqual == true){

			subregionsAreEqual := maps.Equal(person1SubregionPercentagesMap, person2SubregionPercentagesMap)
			if (subregionsAreEqual == true){

				return 100, nil
			}
		}
	}

	totalSimilarity := float64(0)

	// Different continents are counted as having no similarity.
	// Different regions within the same continent are counted as having 70% similarity
	// Different subregions within the same region are counted as having 80% similarity

	for continentName, person1ContinentPercentage := range person1ContinentPercentagesMap{

		person2ContinentPercentage, exists := person2ContinentPercentagesMap[continentName]
		if (exists == true){

			continentSimilarity := min(person1ContinentPercentage, person2ContinentPercentage)
			totalSimilarity += continentSimilarity
		}
	}

	// We iterate through the region/subregion maps twice
	// First we account for exact region/subregion similarity
	// Next, we account for same-parent-location region/subregion similarity

	person1RegionsList := helpers.GetListOfMapKeys(person1RegionPercentagesMap)

	for _, regionName := range person1RegionsList{

		person1RegionPercentage, exists := person1RegionPercentagesMap[regionName]
		if (exists == false){
			return 0, errors.New("person1RegionPercentagesMap missing regionName: " + regionName)
		}

		person2RegionPercentage, exists := person2RegionPercentagesMap[regionName]
		if (exists == true){
			regionSimilarity := min(person1RegionPercentage, person2RegionPercentage)
			totalSimilarity += regionSimilarity

			// We subtract the region similarity so we don't
			// 		count it twice when we account for same-parent-location location similarity
			person1RegionPercentagesMap[regionName] -= regionSimilarity
			person2RegionPercentagesMap[regionName] -= regionSimilarity
		}
	}

	for person1RegionName, person1RegionPercentage := range person1RegionPercentagesMap{

		if (person1RegionPercentage == 0){
			continue
		}

		person1RegionContinent, err := GetAncestryRegionParentContinent_23andMe(person1RegionName)
		if (err != nil){ return 0, err }

		// We iterate through person2 regions and find other regions belonging to the same continent

		for person2RegionName, person2RegionPercentage := range person2RegionPercentagesMap{

			person2RegionContinent, err := GetAncestryRegionParentContinent_23andMe(person2RegionName)
			if (err != nil){ return 0, err }

			if (person1RegionContinent == person2RegionContinent){

				regionSimilarity := min(person1RegionPercentage, person2RegionPercentage)

				// We say regions from the same continent are 70% similar
				totalSimilarity += (regionSimilarity * 0.7)

				person1RegionPercentage -= regionSimilarity
				person2RegionPercentagesMap[person2RegionName] -= regionSimilarity
			}

			if (person1RegionPercentage == 0){
				// We have accounted for any similar regions person2 has
				// We will continue on to the next region in our composition
				break
			}
		}
	}

	person1SubregionsList := helpers.GetListOfMapKeys(person1SubregionPercentagesMap)

	for _, subregionName := range person1SubregionsList{

		person1SubregionPercentage, exists := person1SubregionPercentagesMap[subregionName]
		if (exists == false){
			return 0, errors.New("person1SubregionPercentagesMap missing subregionName: " + subregionName)
		}

		person2SubregionPercentage, exists := person2SubregionPercentagesMap[subregionName]
		if (exists == true){
			subregionSimilarity := min(person1SubregionPercentage, person2SubregionPercentage)
			totalSimilarity += subregionSimilarity

			// We subtract the subregion similarity so we don't
			// 		count it twice when we account for same-parent-location location similarity
			person1SubregionPercentagesMap[subregionName] -= subregionSimilarity
			person2SubregionPercentagesMap[subregionName] -= subregionSimilarity
		}
	}

	for person1SubregionName, person1SubregionPercentage := range person1SubregionPercentagesMap{

		if (person1SubregionPercentage == 0){
			continue
		}

		person1SubregionRegion, err := GetAncestrySubregionParentRegion_23andMe(person1SubregionName)
		if (err != nil){ return 0, err }

		// We iterate through person2 subregions and find other subregions belonging to the same region

		for person2SubregionName, person2SubregionPercentage := range person2SubregionPercentagesMap{

			person2SubregionRegion, err := GetAncestrySubregionParentRegion_23andMe(person2SubregionName)
			if (err != nil){ return 0, err }

			if (person1SubregionRegion == person2SubregionRegion){

				subregionSimilarity := min(person1SubregionPercentage, person2SubregionPercentage)

				// We say subregions from the same region are 80% similar
				totalSimilarity += (subregionSimilarity * 0.8)

				person1SubregionPercentage -= subregionSimilarity
				person2SubregionPercentagesMap[person2SubregionName] -= subregionSimilarity
			}

			if (person1SubregionPercentage == 0){
				// We have accounted for any similar subregions person2 has
				// We will continue on to the next subregion in our composition
				break
			}
		}
	}

	totalSimilarityInt, err := helpers.FloorFloat64ToInt(totalSimilarity)
	if (err != nil) { return 0, err }

	if (totalSimilarityInt < 0 || totalSimilarityInt > 100){
		totalSimilarityString := helpers.ConvertIntToString(totalSimilarityInt)
		return 0, errors.New("totalSimilarity is out of bounds after calculating ancestral similarity: " + totalSimilarityString)
	}

	return totalSimilarityInt, nil
}

// This is used to get all the names needed for translations
func GetAllAncestryLocationsList_23andMe()([]string, error){

	continentsList := GetAncestryContinentsList_23andMe()

	allLocationsList := make([]string, 0, len(continentsList))

	for _, continentName := range continentsList{

		allLocationsList = append(allLocationsList, continentName)

		continentRegionsList, err := GetAncestryContinentRegionsList_23andMe(continentName)
		if (err != nil) { return nil, err }

		for _, regionName := range continentRegionsList{

			allLocationsList = append(allLocationsList, regionName)

			subregionsList, err := GetAncestryRegionSubregionsList_23andMe(continentName, regionName)
			if (err != nil) { return nil, err }

			allLocationsList = append(allLocationsList, subregionsList...)
		}
	}

	return allLocationsList, nil
}

// Categories are structured as follows: Continent -> Region -> Subregion
func GetAncestryContinentsList_23andMe()[]string{

	continentsList := []string{
		"Central & South Asian",
		"East Asian",
		"European",
		"Indigenous American",
		"Melanesian",
		"Sub-Saharan African",
		"Western Asian & North African",
		"Unassigned"}

	return continentsList
}

func GetAncestryContinentRegionsList_23andMe(continentName string)([]string, error){

	switch continentName{

		case "Sub-Saharan African":{

			regionsList := []string{"West African",
									"Northern East African",
									"Congolese & Southern East African",
									"African Hunter-Gatherer",
									"Broadly Sub-Saharan African"}

			return regionsList, nil
		}
		case "East Asian":{

			regionsList := []string{"North Asian",
									"Chinese",
									"Vietnamese",
									"Filipino & Austronesian",
									"Indonesian, Khmer, Thai & Myanma",
									"Chinese Dai",
									"Japanese",
									"Korean",
									"Broadly East Asian"}
			return regionsList, nil
		}
		case "European":{

			regionsList := []string{"Northwestern European",
									"Southern European",
									"Eastern European",
									"Ashkenazi Jewish",
									"Broadly European"}

			return regionsList, nil
		}
		case "Western Asian & North African":{

			regionsList := []string{"Northern West Asian",
									"Arab, Egyptian & Levantine",
									"North African",
									"Broadly Western Asian & North African"}
			return regionsList, nil
		}
		case "Central & South Asian":{

			regionsList := []string{"Central Asian",
									"Northern Indian & Pakistani",
									"Bengali & Northeast Indian",
									"Gujarati Patidar",
									"Southern Indian Subgroup",
									"Southern Indian & Sri Lankan",
									"Malayali Subgroup",
									"Broadly Central & South Asian"}

			return regionsList, nil
		}
		case "Melanesian", "Indigenous American", "Unassigned":{

			regionsList := make([]string, 0)

			return regionsList, nil
		}
	}

	return nil, errors.New("GetAncestryContinentRegionsList_23andMe called with unknown continentName: " + continentName)
}


func GetAncestryRegionSubregionsList_23andMe(continentName string, regionName string)([]string, error){

	switch continentName{

		case "Sub-Saharan African":{

			switch regionName{

				case "West African":{

					subregionsList := []string{"Senegambian & Guinean",
												"Ghanaian, Liberian & Sierra Leonean",
												"Nigerian",
												"Broadly West African"}

					return subregionsList, nil
				}
				case "Northern East African":{

					subregionsList := []string{"Sudanese",
												"Ethiopian & Eritrean",
												"Somali",
												"Broadly Northern East African"}

					return subregionsList, nil
				}
				case "Congolese & Southern East African":{

					subregionsList := []string{"Angolan & Congolese",
												"Southern East African",
												"Broadly Congolese & Southern East African"}

					return subregionsList, nil
				}

				case "African Hunter-Gatherer", "Broadly Sub-Saharan African":{
					emptyList := make([]string, 0)

					return emptyList, nil
				}
			}
			return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown region for " + continentName + ": " + regionName)
		}
		case "East Asian":{

			switch regionName{

				case "North Asian":{

					subregionsList := []string{"Siberian",
												"Manchurian & Mongolian",	
												"Broadly North Asian"}

					return subregionsList, nil
				}
				case "Chinese":{

					subregionsList := []string{"Northern Chinese & Tibetan",
												"Southern Chinese & Taiwanese",
												"South Chinese",
												"Broadly Chinese"}
					return subregionsList, nil
				}
				case "Vietnamese",
					"Filipino & Austronesian",
					"Indonesian, Khmer, Thai & Myanma",
					"Chinese Dai",
					"Japanese",
					"Korean",
					"Broadly East Asian":{

					subregionsList := make([]string, 0)

					return subregionsList, nil
				}
			}

			return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown region for " + continentName + ": " + regionName)
		}

		case "European":{

			switch regionName {

				case "Northwestern European":{

					subregionsList := []string{"British & Irish",
												"Finnish",
												"French & German",
												"Scandinavian",
												"Broadly Northwestern European"}

					return subregionsList, nil
				}
				case "Southern European":{

					subregionsList := []string{"Greek & Balkan",
												"Spanish & Portuguese",
												"Italian",
												"Sardinian",
												"Broadly Southern European"}

					return subregionsList, nil
				}

				case "Eastern European", "Ashkenazi Jewish", "Broadly European":{

					emptyList := make([]string, 0)

					return emptyList, nil
				}
			}

			return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown region for " + continentName + ": " + regionName)
		}

		case "Western Asian & North African":{

			switch regionName{

				case "Northern West Asian":{

					subregionsList := []string{"Cypriot",
												"Anatolian",
												"Iranian, Caucasian & Mesopotamian",
												"Broadly Northern West Asian"}

					return subregionsList, nil
				}
				case "Arab, Egyptian & Levantine":{

					subregionsList := []string{"Peninsular Arab",
												"Levantine",
												"Egyptian",
												"Coptic Egyptian",
												"Broadly Arab, Egyptian, & Levantine"}

					return subregionsList, nil
				}
				case "North African", "Broadly Western Asian & North African":{

					emptyList := make([]string, 0)

					return emptyList, nil
				}
			}

			return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown region for " + continentName + ": " + regionName)
		}

		case "Central & South Asian":{

			switch regionName{

				case "Central Asian",
					"Northern Indian & Pakistani",
					"Bengali & Northeast Indian",
					"Gujarati Patidar",
					"Southern Indian Subgroup",
					"Southern Indian & Sri Lankan",
					"Malayali Subgroup",
					"Broadly Central & South Asian":{

					emptyList := make([]string, 0)
					
					return emptyList, nil
				}
			}

			return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown region for " + continentName + ": " + regionName)
		}
	}

	return nil, errors.New("GetAncestryRegionSubregionsList_23andMe called with unknown continent name: " + continentName)
}



// This returns the continent that a region belongs to
func GetAncestryRegionParentContinent_23andMe(regionName string)(string, error){

	switch regionName{

		case "West African",
			"Northern East African",
			"Congolese & Southern East African",
			"African Hunter-Gatherer",
			"Broadly Sub-Saharan African":{

			return "Sub-Saharan African", nil
		}

		case "North Asian",
			"Chinese",
			"Vietnamese",
			"Filipino & Austronesian",
			"Indonesian, Khmer, Thai & Myanma",
			"Chinese Dai",
			"Japanese",
			"Korean",
			"Broadly East Asian":{

			return "East Asian", nil
		}

		case "Northwestern European",
			"Southern European",
			"Eastern European",
			"Ashkenazi Jewish",
			"Broadly European":{

			return "European", nil
		}
		case "Northern West Asian",
			"Arab, Egyptian & Levantine",
			"North African",
			"Broadly Western Asian & North African":{

			return "Western Asian & North African", nil
		}
		case "Central Asian",
			"Northern Indian & Pakistani",
			"Bengali & Northeast Indian",
			"Gujarati Patidar",
			"Southern Indian Subgroup",
			"Southern Indian & Sri Lankan",
			"Malayali Subgroup",
			"Broadly Central & South Asian":{

			return "Central & South Asian", nil
		}
	}

	return "", errors.New("GetAncestryRegionContinent_23andMe called with unknown region: " + regionName)
}

// This returns the region that a subregion belongs to
func GetAncestrySubregionParentRegion_23andMe(subregionName string)(string, error){

	switch subregionName{

		// Continent == "Sub-Saharan African"

		case "Senegambian & Guinean",
			"Ghanaian, Liberian & Sierra Leonean",
			"Nigerian",
			"Broadly West African":{

			return "West African", nil
		}
		case "Sudanese",
			"Ethiopian & Eritrean",
			"Somali",
			"Broadly Northern East African":{

			return "Northern East African", nil
		}
		case "Angolan & Congolese",
			"Southern East African",
			"Broadly Congolese & Southern East African":{

			return "Congolese & Southern East African", nil
		}

		// Continent == "East Asian"

		case "Siberian",
			"Manchurian & Mongolian",	
			"Broadly North Asian":{

			return "North Asian", nil
		}
		case "Northern Chinese & Tibetan",
				"Southern Chinese & Taiwanese",
				"South Chinese",
				"Broadly Chinese":{

			return "Chinese", nil
		}

		// Continent == "European"

		case "British & Irish",
			"Finnish",
			"French & German",
			"Scandinavian",
			"Broadly Northwestern European":{

			return "Northwestern European", nil
		}
		case "Greek & Balkan",
				"Spanish & Portuguese",
				"Italian",
				"Sardinian",
				"Broadly Southern European":{

			return "Southern European", nil
		}

		// Continent == "Western Asian & North African"

		case "Cypriot",
				"Anatolian",
				"Iranian, Caucasian & Mesopotamian",
				"Broadly Northern West Asian":{

			return "Northern West Asian", nil
		}
		case "Peninsular Arab",
			"Levantine",
			"Egyptian",
			"Coptic Egyptian",
			"Broadly Arab, Egyptian, & Levantine":{

			return "Arab, Egyptian & Levantine", nil
		}
	}

	return "", errors.New("GetAncestrySubregionRegion_23andMe called with unknown subregion: " + subregionName)
}


