// Originally generated by the template in CodeDAO
package kotlinadventofcode.`2016`

import com.github.h0tk3y.betterParse.combinators.*
import com.github.h0tk3y.betterParse.grammar.*
import com.github.h0tk3y.betterParse.lexer.*
import kotlinadventofcode.Day
import kotlin.math.abs

class `2016-01` : Day {

    /**
     * After verifying your solution on the AoC site, run `./ka continue` to add a test for it.
     */
    override fun runPartOneNoUI(input: String): String {
        var state = State(0, 0, CardinalDirection.NORTH)
        parseInstructions(input).forEach { state = state.getStateAfter(it) }
        return (abs(state.x) + abs(state.y)).toString()
    }

    /**
     * After verifying your solution on the AoC site, run `./ka continue` to add a test for it.
     */
    override fun runPartTwoNoUI(input: String): String {
        var state = State(0, 0, CardinalDirection.NORTH)
        val visited = mutableSetOf(0 to 0)
        parseInstructions(input).forEach { instruction ->
            val oldState = state
            state = state.getStateAfter(instruction)
            val allVisitedCoordinates = when (state.direction) {
                CardinalDirection.NORTH -> ((oldState.y + 1)..state.y).map { oldState.x to it }
                CardinalDirection.EAST -> ((oldState.x + 1)..state.x).map { it to oldState.y }
                CardinalDirection.SOUTH -> ((oldState.y - 1) downTo state.y).map { oldState.x to it }
                CardinalDirection.WEST -> ((oldState.x - 1) downTo state.x).map { it to oldState.y }
            }

            allVisitedCoordinates.forEach { coordinate ->
                if (visited.contains(coordinate)) {
                    return (abs(coordinate.first) + abs(coordinate.second)).toString()
                } else {
                    visited.add(coordinate)
                }
            }
        }

        throw IllegalStateException("No location visited twice")
    }

    data class State(val x: Int, val y: Int, val direction: CardinalDirection) {
        fun getStateAfter(instruction: Instruction): State {
            val newDirection = when (instruction.turn) {
                Turn.CLOCKWISE -> when (direction) {
                    CardinalDirection.NORTH -> CardinalDirection.EAST
                    CardinalDirection.EAST -> CardinalDirection.SOUTH
                    CardinalDirection.SOUTH -> CardinalDirection.WEST
                    CardinalDirection.WEST -> CardinalDirection.NORTH
                }
                Turn.COUNTERCLOCKWISE -> when (direction) {
                    CardinalDirection.NORTH -> CardinalDirection.WEST
                    CardinalDirection.WEST -> CardinalDirection.SOUTH
                    CardinalDirection.SOUTH -> CardinalDirection.EAST
                    CardinalDirection.EAST -> CardinalDirection.NORTH
                }
            }

            val newCoordinates = when (newDirection) {
                CardinalDirection.NORTH -> x to y + instruction.distance
                CardinalDirection.EAST -> x + instruction.distance to y
                CardinalDirection.SOUTH -> x to y - instruction.distance
                CardinalDirection.WEST -> x - instruction.distance to y
            }

            return State(newCoordinates.first, newCoordinates.second, newDirection)
        }
    }

    enum class CardinalDirection {
        NORTH,
        EAST,
        SOUTH,
        WEST
    }

    enum class Turn {
        CLOCKWISE,
        COUNTERCLOCKWISE
    }

    data class Instruction(val turn: Turn, val distance: Int)

    private fun parseInstructions(input: String): List<Instruction> {
        val grammar = object : Grammar<List<Instruction>>() {
            val commaLit by literalToken(",")
            val spaceLit by literalToken(" ")
            val rLit by literalToken("R")
            val lLit by literalToken("L")
            val positiveIntRegex by regexToken("\\d+")

            val positiveInt by positiveIntRegex use { text.toInt() }
            val turn by (rLit or lLit) map { if (it.text == "R") Turn.CLOCKWISE else Turn.COUNTERCLOCKWISE }
            val instruction by (turn and positiveInt) map { Instruction(it.t1, it.t2) }
            val separator by commaLit and spaceLit

            override val rootParser by separatedTerms(instruction, separator)
        }

        return grammar.parseToEnd(input)
    }

    override val defaultInput = """R5, R4, R2, L3, R1, R1, L4, L5, R3, L1, L1, R4, L2, R1, R4, R4, L2, L2, R4, L4, R1, R3, L3, L1, L2, R1, R5, L5, L1, L1, R3, R5, L1, R4, L5, R5, R1, L185, R4, L1, R51, R3, L2, R78, R1, L4, R188, R1, L5, R5, R2, R3, L5, R3, R4, L1, R2, R2, L4, L4, L5, R5, R4, L4, R2, L5, R2, L1, L4, R4, L4, R2, L3, L4, R2, L3, R3, R2, L2, L3, R4, R3, R1, L4, L2, L5, R4, R4, L1, R1, L5, L1, R3, R1, L2, R1, R1, R3, L4, L1, L3, R2, R4, R2, L2, R1, L5, R3, L3, R3, L1, R4, L3, L3, R4, L2, L1, L3, R2, R3, L2, L1, R4, L3, L5, L2, L4, R1, L4, L4, R3, R5, L4, L1, L1, R4, L2, R5, R1, R1, R2, R1, R5, L1, L3, L5, R2"""
}