package main

import (
	"fmt"
	"strings"

	"../../"
)

func main() {
	pw := "fbgdceah" // Part 2 Puzzle input
	input := aoc.StdinToStringSlice()
	if aoc.ArgIsSet("-1") {
		pw = "abcdefgh" // Part 1 Puzzle input
		pw = scramblePassword(pw, input)
		fmt.Println(scramblePassword(pw, input))
		return
	}
	fmt.Println(unscramblePassword(pw, input))
}

func unscramblePassword(pw string, inst []string) string {
	// Brute force it.
	// Just get all permutations of the runes and return the one
	// for which the instructions return the input
	tst := aoc.StringPermutations(pw)
	for i := range tst {
		if scramblePassword(tst[i], inst) == pw {
			return tst[i]
		}
	}
	return ""
}

func scramblePassword(pw string, inst []string) string {
	for i := range inst {
		pts := strings.Fields(inst[i])
		switch pts[0] + " " + pts[1] {
		case "swap position":
			pw = swapPos(pw, aoc.Atoi(pts[2]), aoc.Atoi(pts[5]))
		case "swap letter":
			pw = swapLetter(pw, pts[2], pts[5])
		case "reverse positions":
			pw = reverse(pw, aoc.Atoi(pts[2]), aoc.Atoi(pts[4]))
		case "rotate left":
			pw = rotate(pw, aoc.Atoi(pts[2])*-1)
		case "rotate right":
			pw = rotate(pw, aoc.Atoi(pts[2]))
		case "rotate based":
			rotIdx := strings.Index(pw, pts[6])
			if rotIdx >= 4 {
				rotIdx++
			}
			rotIdx++
			pw = rotate(pw, rotIdx)
		case "move position":
			pw = move(pw, aoc.Atoi(pts[2]), aoc.Atoi(pts[5]))
		}
	}
	return pw
}

func swapPos(pw string, x, y int) string {
	r := []rune(pw)
	r[x], r[y] = r[y], r[x]
	return string(r)
}

func swapLetter(pw string, x, y string) string {
	xPos := strings.Index(pw, x)
	yPos := strings.Index(pw, y)
	return swapPos(pw, xPos, yPos)
}

func rotate(pw string, steps int) string {
	r := []rune(pw)
	var x rune
	for steps < 0 {
		// Left rotate (shift & push)
		x, r = r[0], r[1:]
		r = append(r, x)
		steps++
	}
	for steps > 0 {
		// Right rotate (pop & unshift)
		x, r = r[len(r)-1], r[:len(r)-1]
		r = append([]rune{x}, r...)
		steps--
	}
	return string(r)
}

func reverse(pw string, beg, end int) string {
	revStr := pw[beg : end+1]
	runes := []rune(revStr)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return pw[:beg] + string(runes) + pw[end+1:]
}

func move(pw string, x, y int) string {
	r := []rune(pw)
	// Cut element at x
	mov := r[x]
	r = append(r[:x], r[x+1:]...)
	// Insert at y
	r = append(r[:y], append([]rune{mov}, r[y:]...)...)
	return string(r)
}
