hunter 1.0.3

Fast, lag-free terminal file browser
use termion::event::Key;
use failure::Fail;
use chrono::{DateTime, Local};

use crate::term;
use crate::widget::Widget;
use crate::listview::{ListView, Listable};
use crate::fail::{HResult, HError};
use crate::dirty::Dirtyable;

pub type LogView = ListView<Vec<LogEntry>>;


#[derive(Debug)]
pub struct LogEntry {
    description: String,
    content: Option<String>,
    lines: usize,
    folded: bool
}


impl Foldable for LogEntry {
    fn description(&self) -> &String {
        &self.description
    }
    fn content(&self) -> Option<&String> {
        self.content.as_ref()
    }
    fn lines(&self) -> usize {
        if self.is_folded() { 1 } else {
            self.lines
        }
    }
    fn toggle_fold(&mut self) {
        self.folded = !self.folded;
    }
    fn is_folded(&self) -> bool {
        self.folded
    }
}


impl From<&HError> for LogEntry {
    fn from(from: &HError) -> LogEntry {
        let time: DateTime<Local> = Local::now();

        let logcolor = match from {
            HError::Log(_) => term::normal_color(),
            _ => term::color_red()
        };

        let description = format!("{}{}{}: {}",
                                  term::color_green(),
                                  time.format("%F %R"),
                                  logcolor,
                                  from).lines().take(1).collect();
        let mut content = format!("{}{}{}: {}\n",
                                  term::color_green(),
                                  time.format("%F %R"),
                                  logcolor,
                                  from);


        if let Some(cause) = from.cause() {
            content += &format!("{}\n", cause);
        }

        if let Some(backtrace) = from.backtrace() {
            content += &format!("{}\n", backtrace);
        }

        let lines = content.lines().count();

        LogEntry {
            description: description,
            content: Some(content),
            lines: lines,
            folded: true
        }
    }
}



pub trait FoldableWidgetExt {
    fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
    fn render_header(&self) -> HResult<String> { Ok("".to_string()) }
    fn render_footer(&self) -> HResult<String> { Ok("".to_string()) }
}

impl FoldableWidgetExt for  ListView<Vec<LogEntry>> {
    fn on_refresh(&mut self) -> HResult<()> {
        if self.content.refresh_logs()? > 0 {
            self.core.set_dirty();
        }
        Ok(())
    }

    fn render_header(&self) -> HResult<String> {
        let (xsize, _) = self.core.coordinates.size_u();
        let current = self.current_fold().map(|n| n+1).unwrap_or(0);
        let num = self.content.len();
        let hint = format!("{} / {}", current, num);
        let hint_xpos = xsize - hint.len();
        let header = format!("Logged entries: {}{}{}",
                             num,
                             term::goto_xy_u(hint_xpos, 0),
                             hint);
        Ok(header)
    }

    fn render_footer(&self) -> HResult<String> {
        let current = self.current_fold()?;
        if let Some(logentry) = self.content.get(current) {
            let (xsize, ysize) = self.core.coordinates.size_u();
            let (_, ypos) = self.core.coordinates.position_u();
            let description = logentry.description();
            let lines = logentry.lines();
            let start_pos = self.fold_start_pos(current);
            let selection = self.get_selection();
            let current_line = (selection - start_pos) + 1;
            let line_hint = format!("{} / {}", current_line, lines);
            let hint_xpos = xsize - line_hint.len();
            let hint_ypos = ysize + ypos + 1;

            let sized_description = term::sized_string_u(&description,
                                                         xsize
                                                         - (line_hint.len()+2));

            let footer = format!("{}{}{}{}{}",
                                 sized_description,
                                 term::reset(),
                                 term::status_bg(),
                                 term::goto_xy_u(hint_xpos, hint_ypos),
                                 line_hint);

            Ok(footer)
        } else { Ok("No log entries".to_string()) }
    }
}

trait LogList {
    fn refresh_logs(&mut self) -> HResult<usize>;
}

impl LogList for Vec<LogEntry> {
    fn refresh_logs(&mut self) -> HResult<usize> {
        let logs = crate::fail::get_logs()?;

        let mut logentries = logs.into_iter().map(|log| {
            LogEntry::from(log)
        }).collect::<Vec<_>>();

        let n = logentries.len();

        self.append(&mut logentries);

        Ok(n)
    }
}


pub trait Foldable {
    fn description(&self) -> &String;
    fn content(&self) -> Option<&String>;
    fn lines(&self) -> usize;
    fn toggle_fold(&mut self);
    fn is_folded(&self) -> bool;

    fn text(&self) -> &String {
        if !self.is_folded() && self.content().is_some() {
            self.content().unwrap()
        } else { self.description() }
    }

    fn render_description(&self) -> String {
        self.description().to_string()
    }

    fn render_content(&self) -> Vec<String> {
        if let Some(content) = self.content() {
            content
                .lines()
                .map(|line| line.to_string())
                .collect()
        } else { vec![self.render_description()] }
    }

    fn render(&self) -> Vec<String> {
        if self.is_folded() {
            vec![self.render_description()]
        } else {
            self.render_content()
        }
    }
}

impl<F: Foldable> ListView<Vec<F>>
where
    ListView<Vec<F>>: FoldableWidgetExt {

    fn toggle_fold(&mut self) -> HResult<()> {
        let fold = self.current_fold()?;
        let fold_pos = self.fold_start_pos(fold);

        self.content[fold].toggle_fold();

        if self.content[fold].is_folded() {
            self.set_selection(fold_pos);
        }

        self.core.set_dirty();
        Ok(())
    }

    fn fold_start_pos(&self, fold: usize) -> usize {
        self.content
            .iter()
            .take(fold)
            .fold(0, |pos, foldable| {
                pos + (foldable.lines())
            })
    }

    fn current_fold(&self) -> Option<usize> {
        let pos = self.get_selection();

        let fold_lines = self
            .content
            .iter()
            .map(|f| f.lines())
            .collect::<Vec<usize>>();

        fold_lines
            .iter()
            .enumerate()
            .fold((0, None), |(lines, fold_pos), (i, current_fold_lines)| {
                if fold_pos.is_some() {
                    (lines, fold_pos)
                } else {
                    if lines + current_fold_lines > pos {
                        (lines, Some(i))
                    } else {
                        (lines + current_fold_lines, None)
                    }
                }}).1
    }
}


impl<F: Foldable> Listable for ListView<Vec<F>>
where
    ListView<Vec<F>>: FoldableWidgetExt {

    fn len(&self) -> usize {
        self.content.iter().map(|f| f.lines()).sum()
    }

    fn render(&self) -> Vec<String> {
        let (xsize, _) = self.core.coordinates.size_u();
        self.content
            .iter()
            .map(|foldable|
                 foldable
                 .render()
                 .iter()
                 .map(|line| term::sized_string_u(line, xsize))
                 .collect::<Vec<_>>())
            .flatten()
            .collect()
    }

    fn render_header(&self) -> HResult<String> {
        FoldableWidgetExt::render_header(self)
    }

    fn render_footer(&self) -> HResult<String> {
        FoldableWidgetExt::render_footer(self)
    }

    fn on_refresh(&mut self) -> HResult<()> {
        FoldableWidgetExt::on_refresh(self)
    }

    fn on_key(&mut self, key: Key) -> HResult<()> {
        match key {
            Key::Up | Key::Char('p') => self.move_up(),
            Key::Char('P') => for _ in 0..10 { self.move_up() },
            Key::Char('N') => for _ in 0..10 { self.move_down() },
            Key::Down | Key::Char('n') => self.move_down(),
            Key::Char('t') => self.toggle_fold()?,
            Key::Char('l') => self.popup_finnished()?,
            _ => {}
        }
        Ok(())
    }
}