package main

import (
	"AoC2020/helper"
	"fmt"
	"os"
	"strconv"
	"strings"
)

var Mem = make(map[int]int64)
var Mask string

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	execute(input, run)
	Mem = make(map[int]int64)
	execute(input, runPart2)
}

func execute(input []string, f func(a string)) {
	for _, val := range input {
		f(val)
	}
	count := 0
	for  _, value := range Mem {
		count = count + int(value)
	}
	fmt.Println(count)
}

func run(row string) {
	command := strings.Split(row," = ")
	if command[0] == "mask" {
		Mask = command[1]
	} else {
		location, _ := strconv.Atoi(command[0][4:len(command[0])-1])
		number, _ := strconv.Atoi(command[1])
		save(number, Mask, location)
	}
}

func save(number int, mask string, location int) {
	bitNumber := strconv.FormatInt(int64(number), 2)
	bitNumber = addPrecedingZeroes(bitNumber)
	bitNumber = applyMask(bitNumber,mask)
	saveToM(bitNumber, location)
}

func applyMask(number string, mask string) string{
	var newNumber string
	for i, val := range mask {
		switch val {
		case 'X': newNumber = newNumber + string(number[i])
		case '1': newNumber = newNumber + "1"
		case '0': newNumber = newNumber + "0"
		}
	}
	return newNumber
}

func runPart2(row string) {
	command := strings.Split(row," = ")
	if command[0] == "mask" {
		Mask = command[1]
	} else {
		location, _ := strconv.Atoi(command[0][4:len(command[0])-1])
		number, _ := strconv.Atoi(command[1])
		savePart2(number, Mask, location)
	}
}

func savePart2(number int, mask string, location int) {
	bitNumber := strconv.FormatInt(int64(location), 2)
	bitNumber = addPrecedingZeroes(bitNumber)
	bitNumbers := applyMaskPart2(bitNumber,mask)
	for _,val := range bitNumbers {
		Mem[int(getIntFromBitNumber(val))] = int64(number)
	}
}

func saveToM(bitNumber string, location int) {
	Mem[location] = getIntFromBitNumber(bitNumber)
}

func getIntFromBitNumber(bitNumber string) int64 {
	if i, err := strconv.ParseInt(bitNumber, 2, 64); err != nil {
		fmt.Println(err)
	} else {
		return i
	}
	return -1
}

func applyMaskPart2(number string, mask string) []string{
	var newNumber string
	for i, val := range mask {
		switch val {
		case 'X': newNumber = newNumber + "X"
		case '1': newNumber = newNumber + "1"
		case '0': newNumber = newNumber + string(number[i])
		}
	}
	return getVariations(0,newNumber)
}

func getVariations(pointer int ,number string) []string{
	var variations []string
	for i := pointer; i < len(number); i++ {
		if number[i] == 'X' {
			number1 := number + ""
			number1 = replaceAtIndex(number1,'0',i)
			number2 := number + ""
			number2 = replaceAtIndex(number1,'1',i)
			variations = append(variations, getVariations(i, number1) ...)
			variations = append(variations, getVariations(i, number2) ...)
		}
	}
	if strings.Index(number, "X") == -1 {
		variations = append(variations, number)
	}
	return variations
}

func addPrecedingZeroes(number string) string {
	return strings.Repeat("0", 36-len(number)) + number
}

func replaceAtIndex(in string, r rune, i int) string {
	out := []rune(in)
	out[i] = r
	return string(out)
}

