package day22

import (
	"fmt"
	"strconv"
	"strings"
)

type Queue struct {
	data []int
	size int
}

func NewQueue(size int) *Queue {
	return &Queue{
		data: make([]int, 0, size),
		size: size,
	}
}

func (queue *Queue) Push(value int) {
	if len(queue.data) == queue.size {
		queue.data = queue.data[1:]
	}
	queue.data = append(queue.data, value)
}

func (queue *Queue) Get() []int {
	return queue.data
}

func (queue *Queue) String() string {
	strs := make([]string, len(queue.data))
	for i, val := range queue.data {
		strs[i] = fmt.Sprintf("%d", val)
	}
	return strings.Join(strs, ", ")
}

func Part1(input string) int {
	secretNumbers := parseInput(input)

	total := 0
	for _, number := range secretNumbers {
		for i := 0; i < 2000; i++ {
			number = getNextNumber(number)
		}
		total += number
	}
	return total
}

func Part2(input string) int {
	secretNumbers := parseInput(input)
	memo := make(map[string]int)

	for _, number := range secretNumbers {
		seen := make(map[string]bool)
		queue := NewQueue(4)
		prevNumber := secretNumbers[0] % 10

		for i := 0; i < 2000; i++ {
			singleNumber := number % 10
			cost := singleNumber - prevNumber
			queue.Push(cost)
			if i > 3 {
				if _, ok := seen[queue.String()]; !ok {
					memo[queue.String()] += singleNumber
					seen[queue.String()] = true
				}
			}
			prevNumber = singleNumber
			number = getNextNumber(number)
		}
	}
	return maxMapValue(memo)
}

func parseInput(input string) []int {
	parts := strings.Split(input, "\n")
	secretNumbers := []int{}
	for _, part := range parts {
		num, _ := strconv.Atoi(part)
		secretNumbers = append(secretNumbers, num)
	}

	return secretNumbers
}

func getNextNumber(number int) int {
	step1 := (number * 64) ^ number
	step1 = step1 % 16777216

	step2 := (step1 / 32) ^ step1
	step2 = step2 % 16777216

	step3 := (step2 * 2048) ^ step2
	step3 = step3 % 16777216

	return step3
}

func maxMapValue(memo map[string]int) int {
	maxValue := 0

	for _, value := range memo {
		if value > maxValue {
			maxValue = value
		}
	}
	return maxValue
}
