use std::{
    sync::{Arc, RwLock},
    thread,
    io::{stdout, Write},
    time::Duration
};
use crossterm::{
    execute,
    cursor,
    terminal,
    screen::RawScreen,
    input::{input, InputEvent, KeyEvent}
};
type TerminalMenu = Arc<RwLock<TerminalMenuStruct>>;
#[derive(Eq, PartialEq)]
enum TMIKind {
    Button,
    ScrollSelection,
    ListSelection,
    Numeric,
}
pub struct TerminalMenuItem {
    name: String,
    kind: TMIKind,
    s_values: Vec<String>,
    s_selected: usize,
    n_value: f64,
    n_step: f64,
    n_min: f64,
    n_max: f64
}
pub fn button(name: &str) -> TerminalMenuItem {
    TerminalMenuItem {
        name: name.to_owned(),
        kind: TMIKind::Button,
        s_values: vec![],
        s_selected: 0,
        n_value: 0.0,
        n_step: 0.0,
        n_min: 0.0,
        n_max: 0.0,
    }
}
pub fn scroll_selection(name: &str, values: Vec<&str>) -> TerminalMenuItem {
    if values.len() == 0 {
        panic!("values cannot be empty");
    }
    TerminalMenuItem {
        name: name.to_owned(),
        kind: TMIKind::ScrollSelection,
        s_values: values.iter().map(|&s| s.to_owned()).collect(),
        s_selected: 0,
        n_value: 0.0,
        n_step: 0.0,
        n_min: 0.0,
        n_max: 0.0,
    }
}
pub fn list_selection(name: &str, values: Vec<&str>) -> TerminalMenuItem {
    if values.len() == 0 {
        panic!("values cannot be empty");
    }
    TerminalMenuItem {
        name: name.to_owned(),
        kind: TMIKind::ListSelection,
        s_values: values.iter().map(|&s| s.to_owned()).collect(),
        s_selected: 0,
        n_value: 0.0,
        n_step: 0.0,
        n_min: 0.0,
        n_max: 0.0,
    }
}
pub fn numeric(name: &str, default: f64, step: f64, min: f64, max: f64) -> TerminalMenuItem {
    TerminalMenuItem {
        name: name.to_owned(),
        kind: TMIKind::Numeric,
        s_values: vec![],
        s_selected: 0,
        n_value: default,
        n_step: step,
        n_min: min,
        n_max: max,
    }
}
pub struct TerminalMenuStruct {
    items: Vec<TerminalMenuItem>,
    selected: usize,
    active: bool,
    exited: bool,
}
impl TerminalMenuStruct {
    
    
    
    
    
    pub fn is_active(&self) -> bool {
        !self.exited
    }
    
    
    
    
    
    pub fn selected_item(&self) -> &str {
        &self.items[self.selected].name
    }
    
    
    
    
    
    pub fn selection_value(&self, name: &str) -> Option<&str> {
        for item in &self.items {
            if    (item.kind == TMIKind::ListSelection
                || item.kind == TMIKind::ScrollSelection)
                && item.name.eq(name) {
                return Some(&item.s_values[item.s_selected]);
            }
        }
        None
    }
    
    
    
    
    
    pub fn numeric_value(&self, name: &str) -> Option<f64> {
        for item in &self.items {
            if item.kind == TMIKind::Numeric && item.name.eq(name) {
                return Some(item.n_value);
            }
        }
        None
    }
}
pub fn menu(items: Vec<TerminalMenuItem>) -> TerminalMenu {
    if items.len() == 0 {
        panic!("items cannot be empty");
    }
    Arc::new(RwLock::new(TerminalMenuStruct {
        items,
        selected: 0,
        active: false,
        exited: true
    }))
}
pub fn selected_item(menu: &TerminalMenu) -> String {
    menu.read().unwrap().selected_item().to_owned()
}
pub fn selection_value(menu: &TerminalMenu, item: &str) -> Option<String> {
    menu.read().unwrap().selection_value(item).map(|s| s.to_owned())
}
pub fn numeric_value(menu: &TerminalMenu, item: &str) -> Option<f64> {
    menu.read().unwrap().numeric_value(item)
}
fn move_up(a: u16) {
    if a != 0 {
        execute!(stdout(), cursor::MoveUp(a)).unwrap();
    }
}
fn move_down(a: u16) {
    if a != 0 {
        execute!(stdout(), cursor::MoveDown(a)).unwrap();
    }
}
fn move_left(a: u16) {
    if a != 0 {
        execute!(stdout(), cursor::MoveLeft(a)).unwrap();
    }
}
fn move_right(a: u16) {
    if a != 0 {
        execute!(stdout(), cursor::MoveRight(a)).unwrap();
    }
}
fn move_to_beginning() {
    for _ in 0..terminal::size().unwrap().0 {
        print!("\u{8}");
    }
}
fn clear_rest_of_line() {
    execute!(stdout(), terminal::Clear(terminal::ClearType::UntilNewLine)).unwrap();
}
fn save_pos() {
    execute!(stdout(), cursor::SavePosition).unwrap();
}
fn restore_pos() {
    execute!(stdout(), cursor::RestorePosition).unwrap();
}
fn print_menu(menu: &TerminalMenuStruct, longest_name: usize, selected: usize) {
    for i in 0..menu.items.len() {
        print!("{} {}    ", if i == selected { '>' } else { ' ' }, menu.items[i].name);
        for _ in menu.items[i].name.len()..longest_name {
            print!(" ");
        }
        match menu.items[i].kind {
            TMIKind::Button => {},
            TMIKind::ScrollSelection => print!("{}", menu.items[i].s_values[menu.items[i].s_selected]),
            TMIKind::ListSelection => {
                move_left(1);
                for j in 0..menu.items[i].s_values.len() {
                    print!("{}{}{}",
                           if j == menu.items[i].s_selected {'['} else {' '},
                           menu.items[i].s_values[j],
                           if j == menu.items[i].s_selected {']'} else {' '},
                    );
                }
            }
            TMIKind::Numeric => print!("{}", menu.items[i].n_value)
        }
        if i != menu.items.len() - 1 {
            println!();
        } else {
            move_to_beginning();
            stdout().flush().unwrap();
        }
    }
}
fn run_menu(menu: TerminalMenu) {
    
    {
        let mut menu = menu.write().unwrap();
        menu.active = true;
        menu.exited = false;
    }
    execute!(stdout(), cursor::Hide).unwrap();
    
    let mut longest_name = 0;
    {
        let menu = menu.read().unwrap();
        for item in &menu.items {
            if item.name.len() > longest_name {
                longest_name = item.name.len();
            }
        }
        print_menu(&menu, longest_name, menu.selected);
    }
    let _raw = RawScreen::into_raw_mode().unwrap();
    let input = input();
    let mut stdin = input.read_async();
    use KeyEvent::*;
    while menu.read().unwrap().active {
        if let Some(InputEvent::Keyboard(k)) = stdin.next() {
            match k {
                Up | Char('w') => {
                    let mut menu = menu.write().unwrap();
                    save_pos();
                    move_up((menu.items.len() - menu.selected - 1) as u16);
                    print!(" ");
                    if menu.selected == 0 {
                        menu.selected = menu.items.len() - 1;
                        move_down(menu.items.len() as u16 - 1);
                    }
                    else {
                        menu.selected -= 1;
                        move_up(1);
                    }
                    print!("\u{8}>");
                    restore_pos();
                }
                Down | Char('s') => {
                    let mut menu = menu.write().unwrap();
                    save_pos();
                    move_up((menu.items.len() - menu.selected - 1) as u16);
                    print!(" ");
                    if menu.selected == menu.items.len() - 1 {
                        menu.selected = 0;
                        move_up(menu.items.len() as u16 - 1);
                    }
                    else {
                        menu.selected += 1;
                        move_down(1);
                    }
                    print!("\u{8}>");
                    restore_pos();
                }
                Left | Char('a') => {
                    let mut menu = menu.write().unwrap();
                    let s = menu.selected;
                    save_pos();
                    move_up((menu.items.len() - s - 1) as u16);
                    move_right(longest_name as u16 + 6);
                    clear_rest_of_line();
                    match menu.items[s].kind {
                        TMIKind::Button => {}
                        TMIKind::ScrollSelection => {
                            if menu.items[s].s_selected == 0 {
                                menu.items[s].s_selected =
                                    menu.items[s].s_values.len() - 1;
                            }
                            else {
                                menu.items[s].s_selected -= 1;
                            }
                            print!("{}", menu.items[s].s_values[
                                menu.items[s].s_selected
                            ]);
                        }
                        TMIKind::ListSelection => {
                            if menu.items[s].s_selected == 0 {
                                menu.items[s].s_selected =
                                    menu.items[s].s_values.len() - 1;
                            }
                            else {
                                menu.items[s].s_selected -= 1;
                            }
                            move_left(1);
                            for i in 0..menu.items[s].s_values.len() {
                                print!("{}{}{}",
                                    if i == menu.items[s].s_selected {'['} else {' '},
                                    menu.items[s].s_values[i],
                                    if i == menu.items[s].s_selected {']'} else {' '},
                                );
                            }
                        }
                        TMIKind::Numeric => {
                            menu.items[s].n_value -=
                                menu.items[s].n_step;
                            if menu.items[s].n_value <
                                menu.items[s].n_min {
                                menu.items[s].n_value =
                                    menu.items[s].n_min;
                            }
                            print!("{}", menu.items[s].n_value);
                        }
                    }
                    restore_pos();
                }
                Right | Char('d') => {
                    let mut menu = menu.write().unwrap();
                    let s = menu.selected;
                    save_pos();
                    move_up((menu.items.len() - s - 1) as u16);
                    move_right(longest_name as u16 + 6);
                    clear_rest_of_line();
                    match menu.items[s].kind {
                        TMIKind::Button => {}
                        TMIKind::ScrollSelection => {
                            if menu.items[s].s_selected ==
                                menu.items[s].s_values.len() - 1 {
                                menu.items[s].s_selected = 0;
                            }
                            else {
                                menu.items[s].s_selected += 1;
                            }
                            print!("{}", menu.items[s].s_values[
                                menu.items[s].s_selected
                            ]);
                        }
                        TMIKind::ListSelection => {
                            if menu.items[s].s_selected ==
                                menu.items[s].s_values.len() - 1 {
                                menu.items[s].s_selected = 0;
                            }
                            else {
                                menu.items[s].s_selected += 1;
                            }
                            move_left(1);
                            for i in 0..menu.items[s].s_values.len() {
                                print!("{}{}{}",
                                       if i == menu.items[s].s_selected {'['} else {' '},
                                       menu.items[s].s_values[i],
                                       if i == menu.items[s].s_selected {']'} else {' '},
                                );
                            }
                        }
                        TMIKind::Numeric => {
                            menu.items[s].n_value +=
                                menu.items[s].n_step;
                            if menu.items[s].n_value >
                                menu.items[s].n_max {
                                menu.items[s].n_value =
                                    menu.items[s].n_max;
                            }
                            print!("{}", menu.items[s].n_value);
                        }
                    }
                    restore_pos();
                }
                Enter => {
                    let mut menu = menu.write().unwrap();
                    let s = menu.selected;
                    match menu.items[s].kind {
                        TMIKind::Button => {
                            menu.active = false;
                        }
                        TMIKind::ScrollSelection => {
                            save_pos();
                            move_up((menu.items.len() - s - 1) as u16);
                            move_right(longest_name as u16 + 6);
                            clear_rest_of_line();
                            if menu.items[s].s_selected ==
                                menu.items[s].s_values.len() - 1 {
                                menu.items[s].s_selected = 0;
                            } else {
                                menu.items[s].s_selected += 1;
                            }
                            print!("{}", menu.items[s].s_values[
                                menu.items[s].s_selected
                            ]);
                            restore_pos();
                        }
                        TMIKind::ListSelection => {
                            save_pos();
                            move_up((menu.items.len() - s - 1) as u16);
                            move_right(longest_name as u16 + 6);
                            clear_rest_of_line();
                            if menu.items[s].s_selected ==
                                menu.items[s].s_values.len() - 1 {
                                menu.items[s].s_selected = 0;
                            }
                            else {
                                menu.items[s].s_selected += 1;
                            }
                            move_left(1);
                            for i in 0..menu.items[s].s_values.len() {
                                print!("{}{}{}",
                                       if i == menu.items[s].s_selected {'['} else {' '},
                                       menu.items[s].s_values[i],
                                       if i == menu.items[s].s_selected {']'} else {' '},
                                );
                            }
                            restore_pos();
                        }
                        _ => ()
                    }
                }
                _ => ()
            }
        }
        thread::sleep(Duration::from_millis(10));
    }
    execute!(stdout(),
            cursor::MoveUp(menu.read().unwrap().items.len() as u16 - 1),
            terminal::Clear(terminal::ClearType::FromCursorDown),
            cursor::Show
        ).unwrap();
    menu.write().unwrap().exited = true;
}
pub fn activate(menu: &TerminalMenu) {
    let menu = menu.clone();
    thread::spawn(move || {
        run_menu(menu);
    });
}
pub fn deactivate(menu: &TerminalMenu) {
    menu.write().unwrap().active = false;
    wait_for_exit(menu);
}
pub fn wait_for_exit(menu: &TerminalMenu) {
    loop {
        thread::sleep(Duration::from_millis(10));
        if menu.read().unwrap().exited {
            break;
        }
    }
}
pub fn run(menu: &TerminalMenu) {
    run_menu(menu.clone());
}