use std::io::Write;

use bicoro::*;
mod turnstile;
use turnstile::create;

// Our terminal just reads lines
pub struct Input(String);

// It can output to stdout, or stderr
pub enum Output {
    StdOut(String),
    StdErr(String),
    Flush,
}

/// Reads input until we get a valid turnstile input
/// Here we can handle if the inputs aren't correct
pub fn needs_input() -> Coroutine<'static, Input, Output, turnstile::Input> {
    // this is a recursive function. Any easy way to express a loop
    fn loop_(input: Input) -> Coroutine<'static, Input, Output, turnstile::Input> {
        match input.0.trim() {
            "push" => result(turnstile::Input::Push),
            "coin" => result(turnstile::Input::Coin),
            other => {
                let error = format!("Turnstile: 'I don't understand {}' \r\n", other);
                let prompt = "What do you try instead?".to_string();

                send(Output::StdErr(error))
                    .and_then(|()| send(Output::StdOut(prompt)))
                    .and_then(|()| send(Output::Flush))
                    .and_then(|()| receive())
                    .and_then(loop_) // we loop if it's invalid, we need to get that input for the turnstile!
            }
        }
    }
    // kicks off the loop
    let initial_prompt = format!("What do you do?: ");
    send(Output::StdOut(initial_prompt))
        .and_then(|()| send(Output::Flush))
        .and_then(|()| receive())
        .and_then(loop_)
}

/// Simply display the transition
pub fn on_output(o: turnstile::Output) -> Coroutine<'static, Input, Output, ()> {
    send(Output::StdOut(format!(
        "Turnstile responds with: '{}'\r\n",
        o.to_string()
    )))
}

pub fn main() {
    let turnstile = create();

    // we can run a child routine inside this routine, with the provided functiosn to convert
    // the inputs and outputs. Result remains the same.
    let mut composed = send(Output::StdOut(
        "You are stopped by a turnstile!\r\n".to_string(),
    ))
    .and_then(|()| send(Output::Flush))
    .and_then(|()| subroutine(needs_input, on_output, turnstile));

    // This is the main loop. Notice it's really only concerned
    // with handling inputs and outputs of the termninal.
    loop {
        match run_step(composed) {
            StepResult::Done(_) => unreachable!(), // just for this case, as its a non-exiting coroutine
            StepResult::Yield { output, next } => {
                match output {
                    Output::StdOut(o) => print!("{}", o),
                    Output::StdErr(e) => print!("{}", e),
                    Output::Flush => std::io::stdout().flush().unwrap(),
                }
                composed = *next;
            }
            StepResult::Next(fun) => {
                // Prompts only appear when needed here
                let mut buf = String::new();
                std::io::stdin().read_line(&mut buf).unwrap();
                let input = Input(buf);
                composed = fun(input);
            }
        }
    }
}
