package main
import (
	"bufio"
	"fmt"
	"log"
	"math/rand"
	"os"
	"strconv"
	"time"
)
// getCommaPositions returns the indices where commas occur in the line
func getCommaPositions(line string) []int {
	var commas []int
		for i := 0; i < len(line); i++ {
			if line[i] == ',' {
				commas = append(commas, i)
			}
		}
		return commas
	}
// substringBetweenCommasFast returns substring between nth and mth commas (1-based), using cached comma positions
// Returns error if indices invalid
func substringBetweenCommasFast(line string, commas []int, nth, mth int) (string, error) {
	if nth >= mth {
		return "", fmt.Errorf("Invalid indices: nth >= mth")
	}
	if mth > len(commas) {
		return "", fmt.Errorf("Not enough commas: needed %d got %d", mth, len(commas))
	}
	// Calculate start index
	// If nth == 0, start from beginning of line
	start := 0
	if nth > 0 {
		start = commas[nth-1] + 1
	}
	end := 0
	if mth-1 < len(commas) {
		end = commas[mth-1]
	} else {
		end = len(line)
	}
	if start > end || start < 0 || end > len(line) {
		return "", fmt.Errorf("Invalid substring indices")
	}
	return line[start:end], nil
}
// extractPartsFast extracts multiple parts defined by pairs of comma indices
// Each pair [n,m] means substring between nth and mth commas
func extractPartsFast(line string, commas []int, pairs [][2]int) ([]string, error) {
	parts := make([]string, len(pairs))
	for i, pair := range pairs {
		part, err := substringBetweenCommasFast(line, commas, pair[0], pair[1])
		if err != nil {
			return nil, err
		}
		parts[i] = part
	}
	return parts, nil
}
// pickOffspringParts1 picks offspring parts from parents with controlled swapping logic
func pickOffspringParts1(p1G, p1g, p2G, p2g string) (string, string) {
	if rand.Intn(2) == 0 {
		// Offspring first part from parent1's p1G
		if rand.Intn(2) == 0 {
			return p1G, p2G
		}
		return p1G, p2g
	}
	// Offspring first part from parent2's p2G
	if rand.Intn(2) == 0 {
		return p2G, p1G
	}
	return p2G, p1g
}
// pickOffspringParts2 picks each offspring part randomly from either parent parts slice (of equal length)
func pickOffspringParts2(parent1Parts, parent2Parts []string) ([]string, error) {
	if len(parent1Parts) != len(parent2Parts) {
		return nil, fmt.Errorf("Parent parts slices must have the same length")
	}
	offspringParts := make([]string, len(parent1Parts))
	for i := range parent1Parts {
		if rand.Intn(2) == 0 {
			offspringParts[i] = parent1Parts[i]
		} else {
			offspringParts[i] = parent2Parts[i]
		}
	}
	return offspringParts, nil
}
// offspringWithRandomExclusion produces offspring parts by randomly picking parts from parents except for 2 random parts replaced by random int strings between 1-10 excluding parents' values
func offspringWithRandomExclusion(parent1Parts, parent2Parts []string) ([]string, error) {
	if len(parent1Parts) != len(parent2Parts) {
		return nil, fmt.Errorf("Parent parts slices must have the same length")
	}
	n := len(parent1Parts)
	if n < 2 {
		return nil, fmt.Errorf("Need at least two parts for replacement")
	}
	offspring := make([]string, n)
	chosenRandomIdx := make(map[int]bool)
	// Pick 2 distinct indexes randomly for replacement
	perm := rand.Perm(n)
	chosenRandomIdx[perm[0]] = true
	chosenRandomIdx[perm[1]] = true
	for i := 0; i < n; i++ {
		if chosenRandomIdx[i] {
			// Parse parent parts to ints if possible
			p1v, err1 := strconv.Atoi(parent1Parts[i])
			p2v, err2 := strconv.Atoi(parent2Parts[i])
			// Collect forbidden values (only if both parses succeeded)
			forbidden := map[int]bool{}
			if err1 == nil {
				forbidden[p1v] = true
			}
			if err2 == nil {
				forbidden[p2v] = true
			}
			var val int
			tries := 0
			// Randomly select int 1-10 excluding forbidden
			for {
				// 1-10
				val = rand.Intn(10) + 1
				if !forbidden[val] {
					break
				}
				tries++
				if tries > 100 {
					return nil, fmt.Errorf("Cannot find suitable random integer excluding parents at index %d", i)
				}
			}
			offspring[i] = strconv.Itoa(val)
			} else {
			// Pick randomly from parent1 or parent2
			if rand.Intn(2) == 0 {
				offspring[i] = parent1Parts[i]
			} else {
				offspring[i] = parent2Parts[i]
			}
		}
	}
	return offspring, nil
}
// clampInt clamps x between min and max inclusive
func clampInt(x, min, max int) int {
	if x < min {
		return min
	}
	if x > max {
		return max
	}
	return x
}
// fitsMorandi checks if color fits Morandi logic:
// R,G,B in [70,200] inclusive, A==0.5
func fitsMorandi(r, g, b, a int) bool {
	return r >= 70 && r <= 200 &&
		g >= 70 && g <= 200 &&
		b >= 70 && b <= 200 &&
		a == 0.5
}
// fitsMacaron checks if color fits Macaron logic:
// R,G,B in [128,255] inclusive, A==1 but NOT Morandi
func fitsMacaron(r, g, b, a int) bool {
	if a != 1 {
		return false
	}
	inMacaron := r >= 128 && r <= 255 &&
		g >= 128 && g <= 255 &&
		b >= 128 && b <= 255
	if !inMacaron {
		return false
	}
	// must NOT be Morandi
	if fitsMorandi(r, g, b, a) {
		return false
	}
	return true
}
// adjustColorToMorandi clamps R,G,B in 70-200, A=0.5
func adjustColorToMorandi(r, g, b, a int) (int, int, int, int) {
	r = clampInt(r, 70, 200)
	g = clampInt(g, 70, 200)
	b = clampInt(b, 70, 200)
	a = 0.5
	return r, g, b, a
}
// adjustColorToMacaron clamps R,G,B in 128-255, A=1
// If clamping produces Morandi overlap, bump value above Morandi upper bound
func adjustColorToMacaron(r, g, b, a int) (int, int, int, int) {
	a = 1
	// Helper to bump value to avoid Morandi overlap
	bump := func(val int) int {
		if val < 128 {
			val = 128
		}
		// Avoid Morandi overlap in [70,200]
		if val >= 70 && val <= 200 {
			// just above Morandi upper bound
			val = 201
		}
		if val > 255 {
			val = 255
		}
		return val
	}
	r = bump(r)
	g = bump(g)
	b = bump(b)
	return r, g, b, a
}
// parseColorPart converts string to int, returns 0 on error
func parseColorPart(s string) int {
	v, err := strconv.Atoi(s)
	if err != nil {
		return 0
	}
	return v
}
// generateOffspringHairAndBrowColors inherits hair and brow color RGBA values fully from one parent, then adjusts the offspring color according to whether (offspringPart1, offspringPart2) matches (1,1) or (2,2) for Morandi, else adjusted for Macaron
func generateOffspringHairAndBrowColors(
	p1Parts []string,
	p2Parts []string,
	// number between 1-2 commas in offspring (string)
	offspringPart1,
	// number between 2-3 commas in offspring (string)
	offspringPart2 string,
) (string, string, string, string, error) {
	// Validate at least 13 parts (commas 1...13)
	if len(p1Parts) <= 12 || len(p2Parts) <= 12 {
		return "", "", "", "", fmt.Errorf("Parent parts length less than 13, cannot extract hair and brow color parts")
	}
	// Randomly choose one parent to inherit from (commas 9-10 to 12-13)
	chosenParent := 1
	if rand.Intn(2) == 0 {
		chosenParent = 2
	}
	var rHaB, gHaB, bHaB, aHaB string
	if chosenParent == 1 {
		rHaB = p1Parts[9]
		gHaB = p1Parts[10]
		bHaB = p1Parts[11]
		aHaB = p1Parts[12]
	} else {
		rHaB = p2Parts[9]
		gHaB = p2Parts[10]
		bHaB = p2Parts[11]
		aHaB = p2Parts[12]
	}
	// Parse strings to int; fallback to 0 on error
	r := parseColorPart(rHaB)
	g := parseColorPart(gHaB)
	b := parseColorPart(bHaB)
	// A is often alpha channel; ensure integer
	a := parseColorPart(aHaB)
	// Decide whether to use Morandi or Macaron adjustment based on offspringPart1 and offspringPart2
	useMorandi := (offspringPart1 == "1" && offspringPart2 == "1") || (offspringPart1 == "2" && offspringPart2 == "2")
	// Adjust color accordingly
	if useMorandi {
		r, g, b, a = adjustColorToMorandi(r, g, b, a)
	} else {
		r, g, b, a = adjustColorToMacaron(r, g, b, a)
	}
	// Convert back to string
	return strconv.Itoa(r), strconv.Itoa(g), strconv.Itoa(b), strconv.Itoa(a), nil
}
// countLines counts the number of lines in a file quickly (only scanning once)
func countLines(filepath string) (int, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	count := 0
	for scanner.Scan() {
		count++
	}
	if err := scanner.Err(); err != nil {
		return 0, err
	}
	return count, nil
}
// getLineAt reads the specified line (0-based) from the file
func getLineAt(filepath string, lineNum int) (string, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	current := 0
	for scanner.Scan() {
		if current == lineNum {
			return scanner.Text(), nil
		}
		current++
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}
	return "", fmt.Errorf("line %d not found", lineNum)
}
// findParents scans the file and finds:
// - a parent1 line where parts between commas 1-2 and 2-3 equal ("1","1")
// - a parent2 line where those parts are NOT ("1","1")
// Returns line indices and their contents
func findParents(filePath string) (int, string, int, string, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return 0, "", 0, "", err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	var parent1Candidates []struct {
		index int
		line  string
	}
	var parent2Candidates []struct {
		index int
		line  string
	}
	lineNum := 0
	for scanner.Scan() {
		line := scanner.Text()
		commas := getCommaPositions(line)
		part1, err1 := substringBetweenCommasFast(line, commas, 1, 2)
		part2, err2 := substringBetweenCommasFast(line, commas, 2, 3)
		if err1 != nil || err2 != nil {
			lineNum++
			// Skip malformed lines
			continue
		}
		if part1 == "1" && part2 == "1" {
			parent1Candidates = append(parent1Candidates, struct {
			index int
			line  string
		}{lineNum, line})
		} else {
			parent2Candidates = append(parent2Candidates, struct {
				index int
				line  string
			}{lineNum, line})
		}
		lineNum++
	}
	if err := scanner.Err(); err != nil {
		return 0, "", 0, "", err
	}
	if len(parent1Candidates) == 0 {
		return 0, "", 0, "", fmt.Errorf("no parent1 line with parts (1,1) found")
	}
	if len(parent2Candidates) == 0 {
		return 0, "", 0, "", fmt.Errorf("no parent2 line with parts != (1,1) found")
	}
	// Pick one random candidate from each group
	p1 := parent1Candidates[rand.Intn(len(parent1Candidates))]
	p2 := parent2Candidates[rand.Intn(len(parent2Candidates))]
	return p1.index, p1.line, p2.index, p2.line, nil
}
// extractAllParts extracts all parts between commas from 1 up to maxComma (inclusive)
func extractAllParts(line string, maxComma int) ([]string, error) {
	commas := getCommaPositions(line)
	if maxComma+1 > len(commas)+1 {
		return nil, fmt.Errorf("Line does not have enough commas: need at least %d commas, got %d", maxComma+1, len(commas)+1)
	}
	groups := make([][2]int, maxComma)
	for i := 1; i <= maxComma; i++ {
		groups[i-1] = [2]int{i, i + 1}
	}
	return extractPartsFast(line, commas, groups)
}
func main() {
	rand.Seed(time.Now().UnixNano())
	filePath := "CharacterAppearanceData.txt"
	// Find suitable parent1 and parent2 lines
	i1, line1, i2, line2, err := findParents(filePath)
	if err != nil {
		log.Fatalf("Error finding parents: %v", err)
	}
	fmt.Printf("Selected Parent1 Line %d: %s\nSelected Parent2 Line %d: %s\n\n", i1+1, line1, i2+1, line2)
	// Cache commas
	c1 := getCommaPositions(line1)
	c2 := getCommaPositions(line2)
	// Extract parts between commas 1-2 and 2-3 for pickOffspringParts1, offspringParts2
	parts1_grp1, err := extractPartsFast(line1, c1, [][2]int{{1, 2}, {2, 3}})
	if err != nil {
		log.Fatalf("Error extracting parent1 group1 parts: %v", err)
	}
	parts2_grp1, err := extractPartsFast(line2, c2, [][2]int{{1, 2}, {2, 3}})
	if err != nil {
		log.Fatalf("Error extracting parent2 group1 parts: %v", err)
	}
	offspringPart1, offspringPart2 := pickOffspringParts1(parts1_grp1[0], parts1_grp1[1], parts2_grp1[0], parts2_grp1[1])
	// Extract parts between commas 3-4 and 4-5 for pickOffspringParts3, offspringParts4
	parts1_grp2, err := extractPartsFast(line1, c1, [][2]int{{3, 4}, {4, 5}})
	if err != nil {
		log.Fatalf("Error extracting parent1 group2 parts: %v", err)
	}
	parts2_grp2, err := extractPartsFast(line2, c2, [][2]int{{3, 4}, {4, 5}})
	if err != nil {
		log.Fatalf("Error extracting parent2 group2 parts: %v", err)
	}
	offspringPart3, offspringPart4 := pickOffspringParts1(parts1_grp2[0], parts1_grp2[1], parts2_grp2[0], parts2_grp2[1])
	fmt.Printf("Offspring parts between commas 1-2 and 2-3: %s, %s\n", offspringPart1, offspringPart2)
	fmt.Printf("Offspring parts between commas 3-4 and 4-5: %s, %s\n", offspringPart3, offspringPart4)
	// Extract parts between commas 33-41 (inclusive) for offspringWithRandomExclusion logic
	partsRange := [][2]int{{33, 34}, {34, 35}, {35, 36}, {36, 37}, {37, 38}, {38, 39}, {39, 40}, {40, 41}}
	parts1_grp3, err := extractPartsFast(line1, c1, partsRange)
	if err != nil {
		log.Fatalf("Error extracting parent1 parts 33-41: %v", err)
	}
	parts2_grp3, err := extractPartsFast(line2, c2, partsRange)
	if err != nil {
		log.Fatalf("Error extracting parent2 parts 33-41: %v", err)
	}
	offspringPartsGrp3, err := offspringWithRandomExclusion(parts1_grp3, parts2_grp3)
	if err != nil {
		log.Fatalf("Error generating offspring parts with random exclusion: %v", err)
	}
	for i, part := range offspringPartsGrp3 {
		fmt.Printf("Offspring part between commas %d-%d: %s\n", partsRange[i][0], partsRange[i][1], part)
	}
	// Extract all parts up to comma 13 to get hair and brow color parts (indexes 9-12)
	allParts1, err := extractAllParts(line1, 13)
	if err != nil {
		log.Fatalf("Error extracting all needed parts from parent1: %v", err)
	}
	allParts2, err := extractAllParts(line2, 13)
	if err != nil {
		log.Fatalf("Error extracting all needed parts from parent2: %v", err)
	}
	offspringHairR, offspringHairG, offspringHairB, offspringHairA, err := generateOffspringHairAndBrowColors(allParts1, allParts2, offspringPart1, offspringPart2)
	if err != nil {
		log.Fatalf("Error generating offspring hair and brow colors: %v", err)
	}
	fmt.Printf("\nOffspring hairandbrowColors (R, G, B, A): %s, %s, %s, %s\n", offspringHairR, offspringHairG, offspringHairB, offspringHairA)
}
Can you make it shorter, cleaner, memories friendlier?