extern crate rustbox;
extern crate regex;

use std::process::*;
use std::error::Error;
use std::default::Default;
use std::time::Duration;
use std::thread;

use self::rustbox::{Color, RustBox};
use self::rustbox::Key;
use self::regex::Regex;

use git::git::*;
use git::branch::*;

struct Context {
    rustbox: RustBox,
    branches: Branches,
    input: String,
    selected_index: usize,
}

impl Context {
    fn input(&mut self, c: char) {
        self.input.push(c);
        self.adjust_selected_index();
    }

    fn pop(&mut self) {
        self.input.pop();
        self.adjust_selected_index();
    }

    fn adjust_selected_index(&mut self) {
        let list_size = self.branch_list().len();
        if list_size <= 1 {
            self.selected_index = 0;
            return
        }

        let max_index = list_size - 1;
        if self.selected_index > max_index {
            self.selected_index = max_index;
        }
    }

    fn up_selected(&mut self) {
        if self.selected_index == 0 {
            self.selected_index = self.branch_list().len() - 1;
        } else {
            self.selected_index -= 1;
        }
    }

    fn down_selected(&mut self) {
        if self.selected_index == self.branch_list().len() - 1 {
            self.selected_index = 0;
        } else {
            self.selected_index += 1;
        }
    }

    fn branch_list(&self) -> Vec<Branch> {
        let mut list = self.branches.list();
        list.sort();
        if self.input.len() == 0 {
            return list
        }

        match Regex::new(format!("(?i){}", self.input).as_ref()) {
            Ok(regex) => {
                list.into_iter().filter(|x| {
                    regex.is_match(x.name.as_ref())
                }).collect()
            },
            Err(_) => list,
        }
    }

    fn selected_branch(&self) -> Option<Branch> {
        self.branch_list().get(self.selected_index).map(|b| b.clone())
    }
}


pub fn exec() {
    let rustbox = RustBox::init(Default::default()).unwrap_or_else(|e| panic!(e));

    let git = Git::new();
    let branches = git.branches();

    let mut context = Context{
        rustbox: rustbox,
        branches: branches,
        input: String::new(),
        selected_index: 0,
    };

    print(&context);

    loop {
        match context.rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Esc | Key::Ctrl('c') => { break; },
                    Key::Char(c) => {
                        context.input(c);
                    },
                    Key::Ctrl('h') | Key::Backspace | Key::Delete => {
                        context.pop();
                    },
                    Key::Ctrl('n') | Key::Down => {
                        context.down_selected();
                    },
                    Key::Ctrl('p') | Key::Up => {
                        context.up_selected();
                    },
                    Key::Enter => {
                        match context.selected_branch() {
                            Some(branch) => {
                                let output = git.checkout(&branch).unwrap();

                                if output.status.success() {
                                    println!("{}", String::from_utf8_lossy(&output.stdout));
                                    break;
                                } else {
                                    print_err(output, &context);
                                }
                            },
                            _ => {},
                        }
                    },
                    _ => { },
                }
            },
            Err(e) => panic!("{}", e.description()),
            _ => { }
        }
        print(&context);
    }
}

fn print(context: &Context) {
    context.rustbox.clear();

    context.rustbox.print(0, 0, rustbox::RB_BOLD, Color::White, Color::Default, format!("QUERY > {}", context.input).as_ref());
    context.rustbox.print(0, 1, rustbox::RB_BOLD, Color::Green, Color::Default, "Press ESC or Ctrl+c to exit.");

    let list = context.branch_list();
    let horizontal_offset = 2;

    for (i, branch) in list.iter().enumerate() {

        let text =
            if context.branches.is_current(branch) {
                format!("{:2}: * {}", i, branch.name)
            } else {
                format!("{:2}:  {}",  i, branch.name)
            };

        if i == context.selected_index {
            context.rustbox.print(1, i+horizontal_offset, rustbox::RB_BOLD, Color::Green, Color::Magenta, text.as_ref());
        } else if context.branches.is_current(branch) {
            context.rustbox.print(1, i+horizontal_offset, rustbox::RB_BOLD, Color::Green, Color::Default, text.as_ref());
        } else {
            context.rustbox.print(1, i+horizontal_offset, rustbox::RB_BOLD, Color::White, Color::Default, text.as_ref());
        }
    }

    context.rustbox.present();
}

fn print_err(output: Output, context: &Context) {
    context.rustbox.clear();

    context.rustbox.print(
        0,
        0,
        rustbox::RB_BOLD,
        Color::Magenta,
        Color::Default,
        String::from_utf8_lossy(&output.stderr).as_ref(),
    );
    context.rustbox.present();

    thread::sleep(Duration::from_millis(2000));

    print(context);
}