// Number Guessing Game
// The computer picks a random number, you try to guess it!

// Game configuration using ADTs
type Difficulty =
    | Easy      // 1-50, unlimited guesses
    | Medium    // 1-100, 10 guesses
    | Hard      // 1-200, 7 guesses

// GameConfig: minNum, maxNum, maxGuesses
type GameConfig =
    | GameConfig(Int, Int, Option<Int>)

// GameState: secret, guesses, config
type GameState =
    | GameState(Int, Int, GameConfig)

type GuessResult =
    | TooLow
    | TooHigh
    | Correct

// Config helpers
fn getMinNum(config: GameConfig): Int =
    match config { GameConfig(min, _, _) => min }

fn getMaxNum(config: GameConfig): Int =
    match config { GameConfig(_, max, _) => max }

fn getMaxGuesses(config: GameConfig): Option<Int> =
    match config { GameConfig(_, _, maxG) => maxG }

// State helpers
fn getSecret(state: GameState): Int =
    match state { GameState(s, _, _) => s }

fn getGuessCount(state: GameState): Int =
    match state { GameState(_, g, _) => g }

fn getConfig(state: GameState): GameConfig =
    match state { GameState(_, _, c) => c }

fn difficultyConfig(d: Difficulty): GameConfig =
    match d {
        Easy => GameConfig(1, 50, None),
        Medium => GameConfig(1, 100, Some(10)),
        Hard => GameConfig(1, 200, Some(7))
    }

fn difficultyName(d: Difficulty): String =
    match d {
        Easy => "Easy (1-50, unlimited guesses)",
        Medium => "Medium (1-100, 10 guesses)",
        Hard => "Hard (1-200, 7 guesses)"
    }

// Core game logic (pure functions)
fn checkGuess(guess: Int, secret: Int): GuessResult =
    if guess < secret then TooLow
    else if guess > secret then TooHigh
    else Correct

fn isGameOver(state: GameState, result: GuessResult): Bool =
    match result {
        Correct => true,
        _ => match getMaxGuesses(getConfig(state)) {
            Some(max) => getGuessCount(state) >= max,
            None => false
        }
    }

fn guessesRemaining(state: GameState): Option<Int> =
    match getMaxGuesses(getConfig(state)) {
        Some(max) => Some(max - getGuessCount(state)),
        None => None
    }

// Game effects
fn printWelcome(): Unit with {Console} = {
    Console.print("")
    Console.print("========================================")
    Console.print("       NUMBER GUESSING GAME")
    Console.print("========================================")
    Console.print("")
}

fn chooseDifficulty(): Difficulty with {Console} = {
    Console.print("Choose difficulty:")
    Console.print("  1. " + difficultyName(Easy))
    Console.print("  2. " + difficultyName(Medium))
    Console.print("  3. " + difficultyName(Hard))
    Console.print("")
    Console.print("Enter choice (1-3): ")

    let choice = Console.readInt()
    match choice {
        1 => Easy,
        2 => Medium,
        3 => Hard,
        _ => {
            Console.print("Invalid choice, defaulting to Medium")
            Medium
        }
    }
}

fn initGame(difficulty: Difficulty): GameState with {Random} = {
    let config = difficultyConfig(difficulty)
    let secret = Random.int(getMinNum(config), getMaxNum(config))
    GameState(secret, 0, config)
}

fn printGameStart(state: GameState): Unit with {Console} = {
    let config = getConfig(state)
    Console.print("")
    Console.print("I'm thinking of a number between " +
        toString(getMinNum(config)) + " and " +
        toString(getMaxNum(config)) + "...")
    match getMaxGuesses(config) {
        Some(max) => Console.print("You have " + toString(max) + " guesses."),
        None => Console.print("You have unlimited guesses.")
    }
    Console.print("")
}

fn getGuess(state: GameState): Int with {Console} = {
    match guessesRemaining(state) {
        Some(remaining) =>
            Console.print("Guesses remaining: " + toString(remaining)),
        None => ()
    }
    Console.print("Enter your guess: ")
    Console.readInt()
}

fn printResult(result: GuessResult): Unit with {Console} =
    match result {
        TooLow => Console.print("Too low! Try higher."),
        TooHigh => Console.print("Too high! Try lower."),
        Correct => Console.print("")
    }

fn printVictory(state: GameState): Unit with {Console} = {
    Console.print("========================================")
    Console.print("  CONGRATULATIONS! You got it!")
    Console.print("  The number was: " + toString(getSecret(state)))
    Console.print("  Guesses used: " + toString(getGuessCount(state)))
    Console.print("========================================")
}

fn printDefeat(state: GameState): Unit with {Console} = {
    Console.print("========================================")
    Console.print("  GAME OVER - Out of guesses!")
    Console.print("  The number was: " + toString(getSecret(state)))
    Console.print("========================================")
}

// Main game loop
fn gameLoop(state: GameState): GameState with {Console} = {
    let guess = getGuess(state)
    let result = checkGuess(guess, getSecret(state))
    let newState = GameState(getSecret(state), getGuessCount(state) + 1, getConfig(state))

    printResult(result)

    if isGameOver(newState, result) then {
        match result {
            Correct => printVictory(newState),
            _ => printDefeat(newState)
        }
        newState
    } else {
        gameLoop(newState)
    }
}

fn askPlayAgain(): Bool with {Console} = {
    Console.print("")
    Console.print("Play again? (1 = yes, 2 = no): ")
    let choice = Console.readInt()
    choice == 1
}

fn playGame(): Unit with {Console, Random} = {
    let difficulty = chooseDifficulty()
    let state = initGame(difficulty)
    printGameStart(state)
    let finalState = gameLoop(state)
    ()
}

fn mainLoop(): Unit with {Console, Random} = {
    playGame()
    if askPlayAgain() then mainLoop()
    else Console.print("Thanks for playing! Goodbye!")
}

fn main(): Unit with {Console, Random} = {
    printWelcome()
    mainLoop()
}

let output = run main() with {}
