import java.io.File
import java.math.BigInteger
import java.security.MessageDigest

tailrec fun Int.gcd(b: Int): Int = if (b == 0) this else b.gcd(this % b)
tailrec fun Long.gcd(b: Long): Long = if (b == 0L) this else b.gcd(this % b)
fun Int.lcm(b: Int): Int = this / gcd(b) * b
fun Long.lcm(b: Long): Long = this / gcd(b) * b
fun Iterable<Int>.lcm(): Int = reduce(Int::lcm)
fun Iterable<Long>.lcm(): Long = reduce(Long::lcm)

fun Iterable<Pair<Long, Long>>.chineseRemainder(): Long {
    val prod = map { it.second.toBigInteger() }.reduce(BigInteger::times)
    var sum = BigInteger.ZERO
    for ((rem, coprime) in this) {
        val cobig = coprime.toBigInteger()
        val p = prod / cobig
        sum += rem.toBigInteger() * p.modInverse(cobig) * p
    }
    return (sum % prod).toLong()
}

/**
 * Reads lines from the given input txt file.
 */
fun readInput(name: String) = File("src", "$name.txt")
    .readLines()

fun String.splitInts(vararg del: String = arrayOf(" ")): List<Int> = split(*del).filter(String::isNotBlank).map(String::toInt)
fun String.splitLongs(vararg del: String = arrayOf(" ")): List<Long> = split(*del).filter(String::isNotBlank).map(String::toLong)

fun listOfIntegerLists(input: List<String>): ArrayList<List<Int>> {
    val output = ArrayList<List<Int>>()
    var currList = ArrayList<Int>()
    for (i in input) {
        if (i.isEmpty()) {
            output.add(currList)
            currList = ArrayList()
        } else {
            currList.add(i.toInt())
        }
    }
    if (currList.isNotEmpty()) {
        output.add(currList)
    }
    return output
}

data class RelPos(val dc: Int, val dr: Int) {
    fun translate(c: Int, r: Int) = RelPos(c + dc, r + dr)
    fun translate(rp: RelPos) = RelPos(rp.dc + dc, rp.dr + dr)
}

class CharGrid {

    companion object {
        val PLUS_POS = listOf(RelPos(0, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(0, 1))
        val PLUS_CENTER_POS = listOf(RelPos(0, -1), RelPos(-1, 0), RelPos(0, 0), RelPos(1, 0), RelPos(0, 1))
        val CROSS_POS = listOf(RelPos(-1, -1), RelPos(1, -1), RelPos(-1, 1), RelPos(1, 1))
        val CROSS_CENTER_POS = listOf(RelPos(-1, -1), RelPos(1, -1), RelPos(0, 0), RelPos(-1, 1), RelPos(1, 1))
        val BOX_POS = listOf(
            RelPos(-1, -1), RelPos(0, -1), RelPos(1, -1),
            RelPos(-1, 0), RelPos(1, 0),
            RelPos(-1, 1), RelPos(0, 1), RelPos(1, 1)
        )
        val NINE_POS = listOf(
            RelPos(-1, -1), RelPos(0, -1), RelPos(1, -1),
            RelPos(-1, 0), RelPos(0, 0), RelPos(1, 0),
            RelPos(-1, 1), RelPos(0, 1), RelPos(1, 1)
        )
    }

    val width: Int
    val height: Int
    val data: Array<CharArray>
    val bChar: Char

    constructor(width: Int, height: Int, borderChar: Char = ' ', fillChar: Char = borderChar) {
        bChar = borderChar
        this.width = width
        this.height = height
        data = Array(height) { CharArray(width) { fillChar } }
    }

    constructor(input: List<String>, borderChar: Char = ' ') {
        bChar = borderChar
        width = input[0].length
        height = input.size
        data = Array(height) { input[it].toCharArray() }
    }

    constructor(inputGrid: Array<CharArray>, borderChar: Char = ' ') {
        bChar = borderChar
        this.data = inputGrid
        width = inputGrid[0].size
        height = inputGrid.size
    }

    operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar

    operator fun set(col: Int, row: Int, newChar: Char?) {
        if (newChar != null && isInside(col, row)) data[row][col] = newChar
    }

    operator fun get(pos: RelPos) = getOrNull(pos) ?: bChar
    operator fun set(pos: RelPos, newChar: Char?) {
        if (newChar != null && isInside(pos)) data[pos.dr][pos.dc] = newChar
    }


    @Deprecated("Use RelPos version instead")
    fun setOrThrow(col: Int, row: Int, newChar: Char) {
        if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds")
        set(col, row, newChar)
    }

    @Deprecated("Use RelPos version instead")
    fun getOrNull(col: Int, row: Int) = if (isInside(col, row)) data[row][col] else null
    fun getOrNull(pos: RelPos) = if (isInside(pos)) data[pos.dr][pos.dc] else null

    @Deprecated("Use RelPos version instead")
    fun isInside(col: Int, row: Int) = (col in 0 until width) && (row in 0 until height)
    fun isInside(pos: RelPos) = (pos.dc in 0 until width) && (pos.dr in 0 until height)

    fun getHorizNumberValueAt(pos: RelPos): Pair<Int, List<RelPos>> {
        val relPos = getHorizNumberRelPosAt(pos.dc, pos.dr)
        return relPos.map { this[pos.translate(it)].digitToInt() }
            .reduce { acc, digit -> acc * 10 + digit } to relPos
    }

    fun getHorizNumberRelPosAt(col: Int, row: Int): List<RelPos> {
        val relPos = ArrayList<RelPos>(5)
        var c = 0
        while (col + c > 0 && get(col + c - 1, row).isDigit()) c--
        while (col + c < width && get(col + c, row).isDigit()) {
            relPos.add(RelPos(c, 0))
            c++
        }
        return relPos
    }

    fun copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar)

    fun generateGridPos() = generateSequence(RelPos(0, 0)) {
        if (it.dc + 1 < width) RelPos(it.dc + 1, it.dr) else if (it.dr + 1 < height) RelPos(0, it.dr + 1) else null
    }

    @Deprecated("Use RelPos version instead")
    fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) =
        generateGridPos().forEach { this[it] = op(this, it.dc, it.dr) }

    fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) =
        generateGridPos().forEach { this[it] = op(this, it) }

    fun apply(op: (content: Char) -> Char?) =
        applyWithPos { grid: CharGrid, pos -> op(grid[pos]) }

    fun grow(
        relposes: Iterable<RelPos>, predicate: (content: Char) -> Boolean,
        op: (orgContent: Char, oldContent: Char, pos: RelPos) -> Char? = { orgContent: Char, _: Char, _: RelPos -> orgContent }
    ): CharGrid {
        val newGrid = copyOf()
        generateGridPos().filter { predicate(get(it)) }
            .forEach { pos ->
                relposes
                    .map { it.translate(pos) }
                    .filter { isInside(it) }
                    .forEach { newGrid[it] = op(this[pos], newGrid[it], it) }
            }
        return newGrid
    }

    fun collectRelative(c: Int, r: Int, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
        collectRelative(RelPos(c, r), relposes, skipBorder)

    fun collectRelative(pos: RelPos, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
        if (skipBorder) {
            relposes.mapNotNull { getOrNull(pos.translate(it)) }
        } else {
            relposes.map { get(pos.translate(it)) }
        }

    fun countStrings(relposeList: Iterable<Iterable<RelPos>>, string: String) =
        findMatches(relposeList) { it == string }.count()

    fun findMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: String) -> Boolean): List<Pair<String, RelPos>> {
        return generateGridPos().flatMap { pos ->
            relposeList.mapNotNull { rp ->
                val st = collectRelative(pos, rp).joinToString("")
                if (predicate(st)) st to pos else null
            }
        }.toList()
    }

    fun findCombinedMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: List<String>) -> Boolean): List<RelPos> {
        return generateGridPos().filter { pos ->
            predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") })
        }.toList()
    }

    fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
        relposes.filter { predicate(get(c + it.dc, r + it.dr)) }

    fun matchRelativeRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
        relposes.filter { predicate(get(pos.translate(it))) }

    fun matchAbsoluteRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
        relposes.map { pos.translate(it) }.filter { predicate(get(it)) }

    fun marchMatching(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
        relposes.map { pos.translate(it) }.filter { predicate(get(it)) }

    fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
        return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList()
    }

    fun findMatchesRelPos(predicate: (char: Char) -> Boolean): List<RelPos> {
        return generateGridPos().filter { predicate(get(it)) }.toList()
    }

    fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> {
        return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList()
    }

    fun addBorder(borderChar: Char = bChar): CharGrid {
        val topBottom = CharArray(width + 2) { borderChar }
        return CharGrid(Array(height + 2) {
            if (it == 0 || it == height + 1) topBottom else
                CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(r - 1, it - 1) }
        }, borderChar)
    }

    fun debug() {
        data.joinToString("\n") { it.concatToString() }.println()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as CharGrid

        if (width != other.width) return false
        if (height != other.height) return false
        if (!data.contentDeepEquals(other.data)) return false

        return true
    }

    override fun hashCode(): Int {
        var result = width
        result = 31 * result + height
        result = 31 * result + data.contentDeepHashCode()
        return result
    }
}

/**
 * Converts string to md5 hash.
 */
fun String.md5() = BigInteger(1, MessageDigest.getInstance("MD5").digest(toByteArray()))
    .toString(16)
    .padStart(32, '0')

/**
 * The cleaner shorthand for printing output.
 */
fun Any?.println() = println(this)
