package day12

import (
	"fmt"
	"strings"

	"adventofcode2023/utils"
	"adventofcode2023/utils/inputs"
)

var cache = make(map[string]int)

type memoized struct {
	f     func(int) int
	cache map[string]int
   }

func Part1(input string) int {
	ans := 0
	lines := strings.Split(input, "\n")
	for _, line := range lines {
		slices := strings.Split(line, " ")
		record := slices[0]
		groups := inputs.ToInts(slices[1], ",")
		ans += calc(record, groups)
	}
	return ans
}

func Part2(input string) int {
	ans := 0
	lines := strings.Split(input, "\n")
	for i, line := range lines {
		slices := strings.Split(line, " ")
		record := slices[0] + "?" + slices[0] + "?" + slices[0] + "?"  + slices[0] + "?"  + slices[0]
		groups := inputs.ToInts(slices[1] + "," + slices[1] + "," + slices[1] + "," + slices[1] + "," + slices[1] , ",")
		ans += calc(record, groups)
		fmt.Printf("running total after lines %v = %v\n",i, ans)
	}
	return ans
}

func calc(record string, groups []int) int {
	key := record + fmt.Sprint(groups)
	if v, ok := cache[key]; ok {
		return v
	}
	// ADD LOGIC HERE ... Base-case logic will go here
	// Did we run out of groups? We might still be valid
	if len(groups) == 0 {
		// Make sure there aren't any more damaged springs, if so, we're valid
		if ! strings.Contains(record, "#") {
			cache[key] = 1
			return 1
		} else {
			// Not valid to runout of groups and still have damaged springs "#"
			cache[key] = 0
			return 0
		}
	}

	if len(record) == 0 {
		// Already know there are more groups but no records
		cache[key] = 0
		return 0
	}

    // Look at the next element in each record and group
    next_character := record[0]
    next_group := groups[0]
	out := 0

	if next_character == '#' {
		// Test pound logic
		out = pound(record, groups, next_group)
	} else if next_character == '.' {
		// Test dot logic
		out = dot(record, groups)
	} else if next_character == '?' {
		// This character could be either character, so we'll explore both
		//possibilities
		out = dot(record, groups) + pound(record, groups, next_group)
	} else {
		utils.PanicOnErr(fmt.Errorf("RuntimeError"))
	
	}
	// Help with debugging
	// fmt.Println(record, groups, out)
	cache[key] = out
	return out
}

// Logic that treats the first character as pound-sign "#"
func pound(record string, groups []int, next_group int) int {

    // If the first is a pound, then the first n characters must be
    // able to be treated as a pound, where n is the first group number
	if next_group > len(record) {
		return 0
	}
	this_group := record[:next_group]
    this_group = strings.Replace(this_group, "?", "#", -1)

	// If the next group can't fit all the damaged springs, then abort
    if this_group != strings.Repeat("#", next_group) {
        return 0
	}

	// If the rest of the record is just the last group, then we're
    // done and there's only one possibility
    if len(record) == next_group {
        // Make sure this is the last group
        if len(groups) == 1 {
            //We are valid
            return 1
		} else {
            // There's more groups, we can't make it work
            return 0
		}
	}

	// Make sure the character that follows this group can be a seperator
    if record[next_group] == '?' || record[next_group] == '.' {
        // It can be seperator, so skip it and reduce to the next group
        return calc(record[next_group+1:], groups[1:])
	}
	// Can't be handled, there are no possibilites
	return 0
}

// Logic that treats the first character as dot "."
func dot(record string, groups []int) int {
	// ADD LOGIC HERE ... need to process this character and call
	// We just skip over the dot looking for the next pound
    return calc(record[1:], groups)
}