package org.example.e3fxgaming.adventOfCode.aoc2023.day01

import org.example.e3fxgaming.adventOfCode.utility.Day
import org.example.e3fxgaming.adventOfCode.utility.IndividualLineInputParser
import org.example.e3fxgaming.adventOfCode.utility.InputParser
import org.example.e3fxgaming.adventOfCode.utility.StringAndIntTrieConstruct
import org.example.e3fxgaming.adventOfCode.utility.TrieConstruct


class Day01(inputLines: String) : Day<Int, Int> {
    companion object {
        val digitTrie = StringAndIntTrieConstruct().apply {
            listOf("one", "two", "three", "four", "five", "six", "seven", "eight", "nine")
                .forEachIndexed { index, s -> append(s, index + 1) }
        }
    }

    override val partOneParser: InputParser<Int> = object : IndividualLineInputParser<Int>(inputLines) {
        override fun parseLine(line: String): Int {
            var firstDigitChar: Char? = null
            var lastDigitChar: Char? = null
            line.forEach {
                if (it.isDigit()) {
                    if (firstDigitChar == null) {
                        firstDigitChar = it
                        lastDigitChar = it
                    } else {
                        lastDigitChar = it
                    }
                }
            }
            return "${checkNotNull(firstDigitChar)}${checkNotNull(lastDigitChar)}".toInt()
        }

    }

    override val partTwoParser: InputParser<Int> = object : IndividualLineInputParser<Int>(inputLines) {
        override fun parseLine(line: String): Int {
            var firstDigit: Int? = null
            var lastDigit: Int? = null
            fun foundDigit(currDigit: Int) {
                if (firstDigit == null) {
                    firstDigit = currDigit
                }
                lastDigit = currDigit
            }

            var currentTrieNodes: List<TrieConstruct.Node<Char, Int>> = emptyList()

            line.forEach {
                if (it.isDigit()) foundDigit(it.digitToInt())
                currentTrieNodes = buildList {
                    digitTrie.root.children[it]?.let(::add)
                    currentTrieNodes.mapNotNull { currentTrieNode ->
                        currentTrieNode.children[it]
                    }.let(::addAll)
                }.onEach { newTrieNode ->
                    newTrieNode.tag?.let(::foundDigit)
                }
            }
            return "$firstDigit$lastDigit".toInt()
        }

    }

    override fun solveFirst(given: List<Int>): String = given.sum().toString()
    override fun solveSecond(given: List<Int>): String = solveFirst(given)
}

fun main() = Day01(
    Day01::class.java.getResource("/2023/day01/realInput1.txt")!!.readText()
).runBoth()

package org.example.e3fxgaming.adventOfCode.aoc2023.day01

import org.example.e3fxgaming.adventOfCode.utility.Day
import org.example.e3fxgaming.adventOfCode.utility.IndividualLineInputParser
import org.example.e3fxgaming.adventOfCode.utility.InputParser
import org.example.e3fxgaming.adventOfCode.utility.StringAndIntTrieConstruct
import org.example.e3fxgaming.adventOfCode.utility.TrieConstruct


class Day01(inputLines: String) : Day<Int, Int> {
    companion object {
        val digitTrie = StringAndIntTrieConstruct().apply {
            listOf("one", "two", "three", "four", "five", "six", "seven", "eight", "nine")
                .forEachIndexed { index, s -> append(s, index + 1) }
        }
    }

    override val partOneParser: InputParser<Int> = object : IndividualLineInputParser<Int>(inputLines) {
        override fun parseLine(line: String): Int {
            var firstDigitChar: Char? = null
            var lastDigitChar: Char? = null
            line.forEach {
                if (it.isDigit()) {
                    if (firstDigitChar == null) {
                        firstDigitChar = it
                        lastDigitChar = it
                    } else {
                        lastDigitChar = it
                    }
                }
            }
            return "${checkNotNull(firstDigitChar)}${checkNotNull(lastDigitChar)}".toInt()
        }

    }

    override val partTwoParser: InputParser<Int> = object : IndividualLineInputParser<Int>(inputLines) {
        override fun parseLine(line: String): Int {
            var firstDigit: Int? = null
            var lastDigit: Int? = null
            fun foundDigit(currDigit: Int) {
                if (firstDigit == null) {
                    firstDigit = currDigit
                }
                lastDigit = currDigit
            }

            var currentTrieNodes: List<TrieConstruct.Node<Char, Int>> = emptyList()

            line.forEach {
                if (it.isDigit()) foundDigit(it.digitToInt())
                currentTrieNodes = buildList {
                    digitTrie.root.children[it]?.let(::add)
                    currentTrieNodes.mapNotNull { currentTrieNode ->
                        currentTrieNode.children[it]
                    }.let(::addAll)
                }.onEach { newTrieNode ->
                    newTrieNode.tag?.let(::foundDigit)
                }
            }
            return "$firstDigit$lastDigit".toInt()
        }

    }

    override fun solveFirst(given: List<Int>): String = given.sum().toString()
    override fun solveSecond(given: List<Int>): String = solveFirst(given)
}

fun main() = Day01(
    Day01::class.java.getResource("/2023/day01/realInput1.txt")!!.readText()
).runBoth()
