use crate::file::File;
use crate::preview::preview;
use memedb_core::{read_tags, write_tags, Error as MemeError};
use rayon::prelude::*;
use std::{
    env::{args_os, current_dir},
    io::{Error as IoError, Write},
    mem::replace,
    path::PathBuf,
    sync::mpsc::{channel, Sender},
};
use termion::{
    clear::{All, CurrentLine},
    cursor::{Goto, Hide, Show},
    event::Key,
    style::{Bold, Invert, Reset},
    terminal_size,
};

#[derive(PartialEq)]
enum State {
    Browse,
    Add,
    Remove,
    Toggle,
}

pub enum Signal {
    Nothing,
    Redraw,
    Exit,
}

pub struct App {
    files: Vec<File>,
    selected: Vec<usize>,
    index: usize,
    scroll: usize,
    state: State,
    buffer: String,
    sender: Option<Sender<()>>,
}

impl App {
    pub fn new() -> Result<App, IoError> {
        Ok(App {
            files: args_os()
                .nth(1)
                .map(PathBuf::from)
                .unwrap_or(current_dir()?)
                .read_dir()?
                .collect::<Result<Vec<_>, _>>()?
                .into_par_iter()
                .filter_map(|e| {
                    Some(File {
                        name: String::from(e.file_name().to_string_lossy()),
                        path: e.path(),
                        tags: read_tags(&e.path()).ok()?,
                    })
                })
                .collect(),
            selected: Vec::new(),
            index: 0,
            scroll: 0,
            state: State::Browse,
            buffer: String::new(),
            sender: None,
        })
    }

    pub fn draw(
        &mut self,
        stdout: &mut termion::raw::RawTerminal<std::io::Stdout>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let (_, rows) = terminal_size()?;
        if self.state == State::Browse {
            if let Some(sender) = &mut self.sender {
                let _ = sender.send(());
            }
            write!(stdout, "{}{}", All, Hide)?;
            let mut text_width = 0;
            for (y, (i, file)) in self
                .files
                .iter()
                .enumerate()
                .skip(self.scroll)
                .take(rows as usize - 1)
                .enumerate()
            {
                if self.index == i {
                    write!(stdout, "{}", Invert)?;
                }
                write!(stdout, "{}", Goto(1, y as u16 + 1))?;
                if self.selected.iter().any(|s| s == &i) {
                    write!(stdout, "{}> ", Bold)?;
                }
                text_width = text_width.max(file.draw(stdout)?);
                write!(stdout, "{}", Reset)?;
            }

            write!(
                stdout,
                "{}q: quit; a: add; r: remove; t: toggle; esc: cancel",
                Goto(1, rows)
            )?;

            stdout.flush()?;
            let path = self.files[self.index].path.clone();
            let (sender, reciever) = channel();
            self.sender = Some(sender);
            std::thread::spawn(move || {
                use std::io::stdout;
                use termion::raw::IntoRawMode;
                let mut stdout = stdout().into_raw_mode().unwrap();
                preview(path, text_width as u16, &mut stdout, reciever).unwrap();
            });
        } else {
            use State::*;
            write!(stdout, "{}{}{}", Goto(1, rows), CurrentLine, Show)?;
            match self.state {
                Browse => (),
                Add => write!(stdout, "add: {}", self.buffer)?,
                Remove => write!(stdout, "remove: {}", self.buffer)?,
                Toggle => write!(stdout, "toggle: {}", self.buffer)?,
            }
            stdout.flush()?;
        }
        Ok(())
    }

    fn up(&mut self) {
        if self.index <= 0 {
            return;
        }
        self.index -= 1;
        if self.index < self.scroll {
            self.scroll -= 1;
        }
    }

    fn down(&mut self) -> Result<(), IoError> {
        if self.index >= self.files.len() - 1 {
            return Ok(());
        }
        let (_, h) = terminal_size()?;
        self.index += 1;
        if self.index - self.scroll >= h as usize - 1 {
            self.scroll += 1;
        }
        Ok(())
    }

    fn select(&mut self) -> Result<(), IoError> {
        if let Some(i) = self.selected.iter().position(|s| *s == self.index) {
            self.selected.remove(i);
        } else {
            self.selected.push(self.index);
        }
        self.down()
    }

    fn do_action(&mut self, files: &[usize]) -> Result<(), MemeError> {
        use State::*;
        for file in files {
            let file = &mut self.files[*file];
            match self.state {
                Add => {
                    file.tags.insert(self.buffer.clone());
                }
                Remove => {
                    file.tags.remove(&self.buffer);
                }
                Toggle => {
                    if file.tags.contains(&self.buffer) {
                        file.tags.remove(&self.buffer);
                    } else {
                        file.tags.insert(self.buffer.clone());
                    }
                }
                _ => (),
            }
            write_tags(&file.path, &file.tags)?;
        }
        self.buffer.clear();
        Ok(())
    }

    pub fn handle_key(&mut self, key: Key) -> Result<Signal, MemeError> {
        use Key::*;
        use State::*;
        Ok(match self.state {
            Browse => match key {
                Char('q') => Signal::Exit,
                Char('t') => {
                    self.state = Toggle;
                    Signal::Redraw
                }
                Char('a') => {
                    self.state = Add;
                    Signal::Redraw
                }
                Char('r') => {
                    self.state = Remove;
                    Signal::Redraw
                }
                Char(' ') => {
                    self.select()?;
                    Signal::Redraw
                }
                Up => {
                    self.up();
                    Signal::Redraw
                }
                Down => {
                    self.down()?;
                    Signal::Redraw
                }
                _ => Signal::Nothing,
            },
            Add | Remove | Toggle => match key {
                Char('\n') => {
                    if self.selected.is_empty() {
                        self.do_action(&[self.index])?;
                    } else {
                        let selected = replace(&mut self.selected, Vec::new());
                        self.do_action(&selected)?;
                    }
                    self.state = Browse;
                    Signal::Redraw
                }
                Char(c) => {
                    self.buffer.push(c);
                    Signal::Redraw
                }
                Backspace => {
                    self.buffer.pop();
                    Signal::Redraw
                }
                Esc => {
                    self.buffer.clear();
                    self.state = Browse;
                    Signal::Redraw
                }
                _ => Signal::Nothing,
            },
        })
    }
}
