#[derive(Debug)]
pub struct CommandBox {
    pub history: History<String>,
    pub hist_idx: Option<usize>,
    pub text: String,
    pub cursor: usize,
}

impl CommandBox {
    pub fn new() -> Self {
        CommandBox {
            history: History::new(64),
            hist_idx: None,
            text: String::new(),
            cursor: 0,
        }
    }

    pub fn forward(&mut self) {
        if self.cursor < self.text.len() {
            self.cursor += 1;
        }
    }

    pub fn cursor_end(&mut self) {
        self.cursor = self.text.len();
    }

    pub fn cursor_start(&mut self) {
        self.cursor = 0;
    }

    pub fn cursor_back_word(&mut self) {
        let mut prev_word_idx = 0;
        {
            let sl = &self.text[0..self.cursor];
            let idx = sl.rfind(|c: char| !c.is_alphanumeric() && c != '_');
            if let Some(i) = idx {
                prev_word_idx = i;
            }
        }
        self.cursor = prev_word_idx;
    }

    pub fn cursor_forward_word(&mut self) {
        let mut next_word_idx = self.cursor;
        {
            if self.cursor != self.text.len() {
                let sl = &self.text[self.cursor..];
                let idx = sl.find(|c: char| !c.is_alphanumeric() && c != '_');
                if let Some(i) = idx {
                    next_word_idx = i;
                }
            }
        }
        self.cursor = next_word_idx;
    }

    pub fn backward(&mut self) {
        self.cursor = self.cursor.saturating_sub(1);
    }

    pub fn backspace(&mut self) {
        if self.cursor != 0 {
            self.text.remove(self.cursor - 1);
            self.backward();
        }
    }

    pub fn delete(&mut self) {
        if self.cursor < self.text.len() {
            self.text.remove(self.cursor);
        }
    }

    pub fn delete_to_end(&mut self) {
        self.text.truncate(self.cursor);
    }

    pub fn delete_to_start(&mut self) {
        self.text = self.text.chars().skip(self.cursor).collect();
        self.cursor = 0;
    }

    pub fn push_str(&mut self, v: &str) {
        self.text.insert_str(self.cursor, v);
        self.cursor += v.len();
    }

    pub fn is_empty(&self) -> bool {
        self.text.is_empty()
    }

    pub fn clear(&mut self) {
        self.text.clear();
        self.cursor = 0;
    }

    pub fn hist_append(&mut self) {
        self.history.append(self.text.drain(..).collect());
        self.cursor_start();
    }

    fn get_from_hist(&self) -> String {
        let size = self.history.items.len();
        self.history.items[size - 1 - self.hist_idx.unwrap()].clone()
    }

    pub fn hist_prev(&mut self) {
        if self.history.items.is_empty() {
            return;
        }
        if let Some(idx) = self.hist_idx {
            if idx + 1 < self.history.items.len() {
                self.hist_idx = Some(idx + 1);
                self.text = self.get_from_hist();
                self.cursor_end();
            }
        } else {
            self.hist_idx = Some(0);
            self.text = self.get_from_hist();
            self.cursor_end();
        }
    }

    pub fn hist_next(&mut self) {
        if let Some(idx) = self.hist_idx {
            // most recent hist item, reset command box
            if idx == 0 {
                self.hist_idx = None;
                self.text = "(".into();
            } else {
                self.hist_idx = Some(idx - 1);
                self.text = self.get_from_hist();
            }
            self.cursor_end();
        }
    }
}

impl std::default::Default for CommandBox {
    fn default() -> Self {
        CommandBox::new()
    }
}

#[derive(Debug)]
pub struct History<T> {
    pub items: Vec<T>,
    pub max_size: usize,
}

impl<T> History<T> {
    pub fn new(max_size: usize) -> Self {
        if max_size == 0 {
            panic!();
        }
        Self {
            items: vec![],
            max_size,
        }
    }

    pub fn append(&mut self, item: T) {
        if self.items.len() >= self.max_size {
            self.items.remove(0);
        }
        self.items.push(item);
    }
}

#[cfg(test)]
mod command_tests {
    use super::*;

    fn setup_with(text: &str) -> CommandBox {
        let mut cmd = CommandBox::new();
        cmd.push_str(text);
        cmd
    }

    #[test]
    fn entering_text() {
        let cmd = setup_with("save as file.png");
        assert_eq!(&cmd.text, "save as file.png");
        assert_eq!(cmd.cursor, 16)
    }

    #[test]
    fn entering_text_between() {
        let mut cmd = setup_with("save as file.png");
        cmd.backward();
        cmd.backward();
        cmd.backward();
        cmd.push_str("ext");
        assert_eq!(&cmd.text, "save as file.extpng");
    }

    #[test]
    fn delete_to_end() {
        let mut cmd = setup_with("save as file.png");
        cmd.backward();
        cmd.backward();
        cmd.backward();
        cmd.delete_to_end();
        assert_eq!(&cmd.text, "save as file.");
    }

    #[test]
    fn delete_to_start() {
        let mut cmd = setup_with("save as file.png");
        cmd.backward();
        cmd.backward();
        cmd.backward();
        cmd.delete_to_start();
        assert_eq!(&cmd.text, "png");
    }

    #[test]
    fn backspacing_from_end() {
        let mut cmd = setup_with("save");
        cmd.backspace();
        assert_eq!(&cmd.text, "sav");
        assert_eq!(cmd.cursor, 3);
    }

    #[test]
    fn backspacing_from_middle() {
        let mut cmd = setup_with("save");
        cmd.backward();
        cmd.backspace();
        assert_eq!(&cmd.text, "sae");
        assert_eq!(cmd.cursor, 2);
    }

    #[test]
    fn delete() {
        let mut cmd = setup_with("save");
        cmd.backward();
        cmd.delete();
        assert_eq!(&cmd.text, "sav");
        assert_eq!(cmd.cursor, 3);
    }

    #[test]
    fn delete_end() {
        let mut cmd = setup_with("save");
        cmd.delete();
        assert_eq!(&cmd.text, "save");
    }

    #[test]
    fn delete_all() {
        let mut cmd = setup_with("save");
        for _ in 0..4 {
            cmd.backward();
        }
        for _ in 0..4 {
            cmd.delete();
        }
        assert_eq!(&cmd.text, "");
        assert_eq!(cmd.cursor, 0);
    }

    #[test]
    fn seeking() {
        let mut cmd = setup_with("save");
        for _ in 0..4 {
            cmd.backward();
        }
        assert_eq!(cmd.cursor, 0);
        cmd.forward();
        assert_eq!(cmd.cursor, 1);
    }

    #[test]
    fn hist_append() {
        let mut cmd = setup_with("hello");
        cmd.hist_append();
        cmd.push_str("another");
        cmd.hist_append();
        cmd.push_str("one");
        cmd.hist_append();
        assert_eq!(cmd.history.items.len(), 3);
    }

    #[test]
    fn hist_prev() {
        let mut cmd = setup_with("hello");
        cmd.hist_append();
        cmd.push_str("another");
        cmd.hist_append();
        cmd.push_str("one");
        cmd.hist_append();

        cmd.hist_prev();
        assert_eq!(&cmd.text, "one");
        cmd.hist_prev();
        assert_eq!(&cmd.text, "another");
    }

    #[test]
    fn hist_next() {
        let mut cmd = setup_with("hello");
        cmd.hist_append();
        cmd.push_str("another");
        cmd.hist_append();
        cmd.push_str("one");
        cmd.hist_append();

        cmd.hist_prev();
        cmd.hist_prev();
        cmd.hist_next();
        assert_eq!(&cmd.text, "one");
    }
}

#[cfg(test)]
mod history_tests {
    use super::*;
    #[test]
    fn append() {
        let mut h = History::<u32>::new(4);
        h.append(5);
        h.append(6);
        h.append(7);
        h.append(8);
        h.append(9);
        assert_eq!(h.items, vec![6, 7, 8, 9]);
    }
}