import Foundation
import PokerCore
import PokerAI

/// Measures opponents against each other so the skill dial can be checked by eye as well as
/// by the test suite.
func runShootout(hands: Int, seed: UInt64, stack: Int = 10_000, structure: BettingStructure = .noLimit) {
    print("Duplicate matches — each hand dealt to both players, so the cards cancel out. \(structure.name).")
    print("Positive means the first player wins, in big blinds per 100 hands.")
    print("")

    func match(_ label: String, _ a: Bot, _ b: Bot) {
        let result = Shootout.duplicate(a, b, hands: hands, seed: seed, startingStack: stack, structure: structure)
        let bar = String(repeating: "█", count: min(40, Int(abs(result.bigBlindsPer100) / 2)))
        print(String(format: "  %-46@ %+8.2f  %@", label as NSString, result.bigBlindsPer100, bar))
    }

    print("Style against style")
    match("Solid regular (90) vs calling station (5)",
          Bot(id: "a", personality: .solidRegular, skill: 90, seed: 1),
          Bot(id: "b", personality: .callingStation, skill: 5, seed: 2))
    match("Solid regular (90) vs nit (20)",
          Bot(id: "a", personality: .solidRegular, skill: 90, seed: 3),
          Bot(id: "b", personality: .nit, skill: 20, seed: 4))
    match("Solid regular (90) vs maniac (15)",
          Bot(id: "a", personality: .solidRegular, skill: 90, seed: 5),
          Bot(id: "b", personality: .maniac, skill: 15, seed: 6))
    match("Tight aggressive (70) vs loose passive (15)",
          Bot(id: "a", personality: .tightAggressive, skill: 70, seed: 7),
          Bot(id: "b", personality: .loosePassive, skill: 15, seed: 8))

    print("")
    print("Same style, different skill — this is the dial that has to be real")
    for personality in [Personality.tightAggressive, .solidRegular, .looseAggressive, .nit] {
        let band = personality.skillRange
        let expert = Bot(id: "a", personality: personality, skill: band.upperBound, seed: 11)
        let novice = Bot(id: "b", personality: personality, skill: band.lowerBound, seed: 12)
        match("\(personality.name) \(band.upperBound) vs \(band.lowerBound)", expert, novice)
    }

    print("")
    print("Six-handed, same style at two skill levels — the dial in the format the app uses")
    for personality in [Personality.tightAggressive, .solidRegular, .looseAggressive, .nit, .callingStation] {
        let band = personality.skillRange
        let expert = Bot(id: "hero", personality: personality, skill: band.upperBound, seed: 11)
        let r = Shootout.ring(expert, versus: personality, villainSkill: band.lowerBound, seats: 6,
                              hands: max(200, hands / 6), seed: seed, startingStack: stack, structure: structure)
        print(String(format: "  %-24@ %3d vs %3d   %+8.2f bb/100", personality.name as NSString,
                     band.upperBound, band.lowerBound, r.bigBlindsPer100))
    }

    print("")
    print("Six-handed, one style against a table of another")
    for (label, style, skill) in [("calling station", Personality.callingStation, 15),
                                  ("loose passive", Personality.loosePassive, 15),
                                  ("maniac", Personality.maniac, 15),
                                  ("nit", Personality.nit, 25),
                                  ("tight aggressive", Personality.tightAggressive, 55)] {
        let hero = Bot(id: "hero", personality: .solidRegular, skill: 90, seed: 31)
        let r = Shootout.ring(hero, versus: style, villainSkill: skill, seats: 6, hands: max(200, hands / 6), seed: seed, startingStack: stack, structure: structure)
        print(String(format: "  solid regular (90) vs a table of %-18@ %+8.2f bb/100", label as NSString, r.bigBlindsPer100))
    }

    print("")
    print("Climbing the ladder — one strong player against tables of rising difficulty")
    let hero = Bot(id: "hero", personality: .solidRegular, skill: 95, seed: 21)
    for difficulty in [10, 30, 50, 70, 90] {
        var rng = SeededRandom(seed: 500 &+ UInt64(difficulty))
        let lineup = TableComposition.lineup(difficulty: difficulty, opponents: 5, rng: &rng)
        let villains = lineup.enumerated().map {
            Bot(id: "V\($0.offset)", personality: $0.element.personality, skill: $0.element.skill,
                seed: 900 &+ UInt64($0.offset))
        }
        let result = ringMatch(hero: hero, villains: villains, hands: hands, seed: seed &+ UInt64(difficulty), structure: structure)
        let names = Set(lineup.map { $0.personality.name }).sorted().joined(separator: ", ")
        print(String(format: "  difficulty %3d  %+8.2f bb/100   table: %@", difficulty, result, names as NSString))
    }
}

private func ringMatch(hero: Bot, villains: [Bot], hands: Int, seed: UInt64, structure: BettingStructure = .noLimit) -> Double {
    let level = BlindLevel(smallBlind: 50, bigBlind: 100)
    let all = [hero] + villains
    let table = BotTable(bots: all, seed: seed)
    var rng = SeededRandom(seed: seed &* 13 &+ 5)
    var net = 0
    for hand in 0..<hands {
        let seats = all.enumerated().map { SeatState(seat: $0.offset, playerID: $0.element.id, stack: 10_000) }
        let config = HandConfig(level: level, buttonSeat: hand % all.count, seed: rng.next(), structure: structure)
        let engine = HandEngine(seats: seats, config: config)
        table.play(engine)
        net += engine.result?.net[0] ?? 0
    }
    return Double(net) / Double(level.bigBlind) / Double(hands) * 100
}
