use std::env;
use std::io::{stdin, stdout, Write};
use std::path::Path;
use std::process::{Child, Command, Stdio};


fn main(){
    let mut status=0;
    let user = env::var("USER").unwrap();
    let home_dir = format!("{}{}", "/home/", user);
    let mut pwd = env::current_dir().unwrap();
    loop {
        // use the `>` character as the prompt
        // need to explicitly flush this to ensure it prints before read_line
        print!("$ ");
        stdout().flush().unwrap();

        let mut input = String::new();
        stdin().read_line(&mut input).unwrap();

        // read_line leaves a trailing newline, which trim removes
        // this needs to be peekable so we can determine when we are on then last command
        let mut commands = input.trim().split(" | ").peekable();
        let mut previous_command = None;

        while let Some(command) = commands.next()  {
            // Other shell variables that can be changed

            // everything after the first whitespace character is interpreted as args to the command
            let mut parts = command.trim().split_whitespace();
            let command = parts.next().unwrap();
            let args = parts;

            match command {
                "true" | ":"  => {
                    status = 0;
                },
                "false" => {
                    status = 1;
                },
                "echo" => {
                    let args: Vec<&str> = args/*.peekable().peek().map_or("", |x| *x).split(" ")*/.collect();
                    for arg in args {
                        if arg.contains("$") {
                            match arg {
                                "$?" => {
                                    print!("{}", status);
                                },
                                "$PWD" => {
                                    print!("{}", pwd.display());
                                },
                                "$USER" => {
                                    print!("{}", user);
                                },
                                "$HOME" => {
                                    print!("{}", home_dir);
                                },
                                arg => {
                                    print!("\n");            
                                }
                            }
                            print!(" ");
                        } else {
                            print!("{} ", arg);
                        }
                    }
                    print!(" ");
                    stdout().flush().unwrap();
                    println!();
                },
                "cd" => {
                    let new_dir = args.peekable().peek().map_or(home_dir.clone(), |x| (*x).to_string());
                    let root = Path::new(&new_dir);
                    if let Err(e) = env::set_current_dir(&root) {
                        eprintln!("{}", e);
                    }

                    previous_command = None;
                    pwd=env::current_dir().unwrap();
                },
                "pwd" => {
                    println!("{}", pwd.display());
                },
                "exit" => return,
                "#" => {},
                command => {
                    let stdin = previous_command
                        .map_or(Stdio::inherit(),
                                |output: Child| Stdio::from(output.stdout.unwrap()));

                    let stdout = if commands.peek().is_some() {
                        // there is another command piped behind this one
                        // prepare to send output to the next command
                        Stdio::piped()
                    } else {
                        // there are no more commands piped behind this one
                        // send output to shell stdout
                        Stdio::inherit()
                    };

                    let output = Command::new(command)
                        .args(args)
                        .stdin(stdin)
                        .stdout(stdout)
                        .spawn();

                    match output {
                        Ok(output) => { previous_command = Some(output); },
                        Err(e) => {
                            previous_command = None;
                            eprintln!("{}", e);
                        },
                    };
                },
            }
        }

        if let Some(mut final_command) = previous_command {
            // block until the final command has finished
            final_command.wait().unwrap();
        }

    }
}
