use ansi_term::Style;
use diff::LineChange;
use printer::{Colors, InteractivePrinter};

#[derive(Clone)]
pub struct DecorationText {
    pub width: usize,
    pub text: String,
}

pub trait Decoration {
    fn generate(
        &self,
        line_number: usize,
        continuation: bool,
        printer: &InteractivePrinter,
    ) -> DecorationText;
    fn width(&self) -> usize;
}

pub struct LineNumberDecoration {
    color: Style,
    cached_wrap: DecorationText,
    cached_wrap_invalid_at: usize,
}

impl LineNumberDecoration {
    pub fn new(colors: &Colors) -> Self {
        LineNumberDecoration {
            color: colors.line_number,
            cached_wrap_invalid_at: 10000,
            cached_wrap: DecorationText {
                text: colors.line_number.paint(" ".repeat(4)).to_string(),
                width: 4,
            },
        }
    }
}

impl Decoration for LineNumberDecoration {
    fn generate(
        &self,
        line_number: usize,
        continuation: bool,
        _printer: &InteractivePrinter,
    ) -> DecorationText {
        if continuation {
            if line_number > self.cached_wrap_invalid_at {
                let new_width = self.cached_wrap.width + 1;
                return DecorationText {
                    text: self.color.paint(" ".repeat(new_width)).to_string(),
                    width: new_width,
                };
            }

            self.cached_wrap.clone()
        } else {
            let plain: String = format!("{:4}", line_number);
            DecorationText {
                width: plain.len(),
                text: self.color.paint(plain).to_string(),
            }
        }
    }

    fn width(&self) -> usize {
        4
    }
}

pub struct LineChangesDecoration {
    cached_none: DecorationText,
    cached_added: DecorationText,
    cached_removed_above: DecorationText,
    cached_removed_below: DecorationText,
    cached_modified: DecorationText,
}

impl LineChangesDecoration {
    #[inline]
    fn generate_cached(style: Style, text: &str) -> DecorationText {
        DecorationText {
            text: style.paint(text).to_string(),
            width: text.chars().count(),
        }
    }

    pub fn new(colors: &Colors) -> Self {
        LineChangesDecoration {
            cached_none: Self::generate_cached(Style::default(), " "),
            cached_added: Self::generate_cached(colors.git_added, "+"),
            cached_removed_above: Self::generate_cached(colors.git_removed, "‾"),
            cached_removed_below: Self::generate_cached(colors.git_removed, "_"),
            cached_modified: Self::generate_cached(colors.git_modified, "~"),
        }
    }
}

impl Decoration for LineChangesDecoration {
    fn generate(
        &self,
        line_number: usize,
        continuation: bool,
        printer: &InteractivePrinter,
    ) -> DecorationText {
        if !continuation {
            if let Some(ref changes) = printer.line_changes {
                return match changes.get(&(line_number as u32)) {
                    Some(&LineChange::Added) => self.cached_added.clone(),
                    Some(&LineChange::RemovedAbove) => self.cached_removed_above.clone(),
                    Some(&LineChange::RemovedBelow) => self.cached_removed_below.clone(),
                    Some(&LineChange::Modified) => self.cached_modified.clone(),
                    _ => self.cached_none.clone(),
                };
            }
        }

        self.cached_none.clone()
    }

    fn width(&self) -> usize {
        self.cached_none.width
    }
}

pub struct GridBorderDecoration {
    cached: DecorationText,
}

impl GridBorderDecoration {
    pub fn new(colors: &Colors) -> Self {
        GridBorderDecoration {
            cached: DecorationText {
                text: colors.grid.paint("│").to_string(),
                width: 1,
            },
        }
    }
}

impl Decoration for GridBorderDecoration {
    fn generate(
        &self,
        _line_number: usize,
        _continuation: bool,
        _printer: &InteractivePrinter,
    ) -> DecorationText {
        self.cached.clone()
    }

    fn width(&self) -> usize {
        self.cached.width
    }
}
