package com.jsjdesigns.poker.core

/**
 * Where a table gets its cards.
 *
 * Abstracted so tests can stack the deck deterministically, and so hand replay
 * can later re-deal a recorded hand exactly.
 */
interface CardSource {
    fun shuffle()
    fun deal(): Int
    fun deal(count: Int): IntArray = IntArray(count) { deal() }
}

/**
 * A fixed deal order, for tests and replays.
 *
 * Deal order matches [com.jsjdesigns.poker.game.Table]: two hole cards per seat in seat
 * order, then burn + flop, burn + turn, burn + river.
 */
class StackedDeck(private val order: IntArray) : CardSource {
    private var next = 0
    override fun shuffle() { next = 0 }
    override fun deal(): Int {
        check(next < order.size) { "stacked deck exhausted after $next cards" }
        return order[next++]
    }

    companion object {
        /** Builds a stacked deck from readable text, e.g. `holes = listOf("Ah Ad", "Kc Ks")`. */
        fun of(holes: List<String>, board: String = "", filler: String = ""): StackedDeck {
            val cards = ArrayList<Int>()
            for (h in holes) cards.addAll(cardsOf(h).toList())
            val boardCards = if (board.isBlank()) IntArray(0) else cardsOf(board)
            val fillerCards = if (filler.isBlank()) IntArray(0) else cardsOf(filler)

            // burn + flop, burn + turn, burn + river
            val used = (cards + boardCards.toList()).toSet()
            val burns = (0 until Card.DECK_SIZE).filter { it !in used }.iterator()
            fun burn(): Int = burns.next()

            if (boardCards.isNotEmpty()) {
                cards.add(burn())
                for (i in 0 until minOf(3, boardCards.size)) cards.add(boardCards[i])
                if (boardCards.size > 3) { cards.add(burn()); cards.add(boardCards[3]) }
                if (boardCards.size > 4) { cards.add(burn()); cards.add(boardCards[4]) }
            }
            cards.addAll(fillerCards.toList())
            // Pad with whatever is left so the deck never runs dry mid-hand.
            val chosen = cards.toSet()
            for (c in 0 until Card.DECK_SIZE) if (c !in chosen) cards.add(c)
            return StackedDeck(cards.toIntArray())
        }
    }
}
