//! Define table formatting utilities

use std::io::{Write, Error};

use encode_unicode::Utf8Char;

use super::utils::NEWLINE;

/// Alignment for cell's content
#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
pub enum Alignment {
    /// Align left
    LEFT,
    /// Align in the center
    CENTER,
    /// Align right
    RIGHT,
}

/// Position of a line separator in a table
#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
pub enum LinePosition {
    /// Table's border on top
    Top,
    /// Line separator between the titles row,
    /// and the first data row
    Title,
    /// Line separator between data rows
    Intern,
    /// Bottom table's border
    Bottom,
}

/// Position of a column separator in a row
#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
pub enum ColumnPosition {
    /// Left table's border
    Left,
    /// Internal column separators
    Intern,
    /// Rigth table's border
    Right,
}

/// Contains the character used for printing a line separator
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
pub struct LineSeparator {
    /// Line separator
    line: char,
    /// Internal junction separator
    junc: char,
    /// Left junction separator
    ljunc: char,
    /// Right junction separator
    rjunc: char,
}

impl LineSeparator {
    /// Create a new line separator instance where `line` is the character used to separate 2 lines
    /// and `junc` is the one used for junctions between columns and lines
    pub fn new(line: char, junc: char, ljunc: char, rjunc: char) -> LineSeparator {
        LineSeparator {
            line: line,
            junc: junc,
            ljunc: ljunc,
            rjunc: rjunc,
        }
    }

    /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column.
    /// Returns the number of printed lines
    fn print<T: Write + ?Sized>(&self,
                                 out: &mut T,
                                 col_width: &[usize],
                                 padding: (usize, usize),
                                 colsep: bool,
                                 lborder: bool,
                                 rborder: bool)
                                 -> Result<usize, Error> {
        if lborder {
            out.write_all(Utf8Char::from(self.ljunc).as_bytes())?;
        }
        let mut iter = col_width.into_iter().peekable();
        while let Some(width) = iter.next() {
            for _ in 0..width + padding.0 + padding.1 {
                out.write_all(Utf8Char::from(self.line).as_bytes())?;
            }
            if colsep && iter.peek().is_some() {
                out.write_all(Utf8Char::from(self.junc).as_bytes())?;
            }
        }
        if rborder {
            out.write_all(Utf8Char::from(self.rjunc).as_bytes())?;
        }
        out.write_all(NEWLINE)?;
        Ok(1)
    }
}

impl Default for LineSeparator {
    fn default() -> Self {
        LineSeparator::new('-', '+', '+', '+')
    }
}

/// Contains the table formatting rules
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
pub struct TableFormat {
    /// Optional column separator character
    csep: Option<char>,
    /// Optional left border character
    lborder: Option<char>,
    /// Optional right border character
    rborder: Option<char>,
    /// Optional internal line separator
    lsep: Option<LineSeparator>,
    /// Optional title line separator
    tsep: Option<LineSeparator>,
    /// Optional top line separator
    top_sep: Option<LineSeparator>,
    /// Optional bottom line separator
    bottom_sep: Option<LineSeparator>,
    /// Left padding
    pad_left: usize,
    /// Right padding
    pad_right: usize,
    /// Global indentation when rendering the table
    indent: usize,
}

impl TableFormat {
    /// Create a new empty TableFormat.
    pub fn new() -> TableFormat {
        TableFormat {
            csep: None,
            lborder: None,
            rborder: None,
            lsep: None,
            tsep: None,
            top_sep: None,
            bottom_sep: None,
            pad_left: 0,
            pad_right: 0,
            indent: 0,
        }
    }

    /// Return a tuple with left and right padding
    pub fn get_padding(&self) -> (usize, usize) {
        (self.pad_left, self.pad_right)
    }

    /// Set left and right padding
    pub fn padding(&mut self, left: usize, right: usize) {
        self.pad_left = left;
        self.pad_right = right;
    }

    /// Set the character used for internal column separation
    pub fn column_separator(&mut self, separator: char) {
        self.csep = Some(separator);
    }

    /// Set the character used for table borders
    pub fn borders(&mut self, border: char) {
        self.lborder = Some(border);
        self.rborder = Some(border);
    }

    /// Set the character used for left table border
    pub fn left_border(&mut self, border: char) {
        self.lborder = Some(border);
    }

    /// Set the character used for right table border
    pub fn right_border(&mut self, border: char) {
        self.rborder = Some(border);
    }

    /// Set a line separator
    pub fn separator(&mut self, what: LinePosition, separator: LineSeparator) {
        *match what {
             LinePosition::Top => &mut self.top_sep,
             LinePosition::Bottom => &mut self.bottom_sep,
             LinePosition::Title => &mut self.tsep,
             LinePosition::Intern => &mut self.lsep,
         } = Some(separator);
    }

    /// Set format for multiple kind of line separator
    pub fn separators(&mut self, what: &[LinePosition], separator: LineSeparator) {
        for pos in what {
            self.separator(*pos, separator);
        }
    }

    fn get_sep_for_line(&self, pos: LinePosition) -> &Option<LineSeparator> {
        match pos {
            LinePosition::Intern => &self.lsep,
            LinePosition::Top => &self.top_sep,
            LinePosition::Bottom => &self.bottom_sep,
            LinePosition::Title => {
                match &self.tsep {
                    s @ &Some(_) => s,
                    &None => &self.lsep,
                }
            }
        }
    }

    /// Set global indentation in spaces used when rendering a table
    pub fn indent(&mut self, spaces: usize) {
        self.indent = spaces;
    }

    /// Get global indentation in spaces used when rendering a table
    pub fn get_indent(&self) -> usize {
        self.indent
    }

    /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column.
    /// Returns the number of printed lines
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn print_line_separator<T: Write + ?Sized>(&self,
                                                   out: &mut T,
                                                   col_width: &[usize],
                                                   pos: LinePosition)
                                                   -> Result<usize, Error> {
        match *self.get_sep_for_line(pos) {
            Some(ref l) => {
                //TODO: Wrap this into dedicated function one day
                out.write_all(&vec![b' '; self.get_indent()])?;
                l.print(out,
                         col_width,
                         self.get_padding(),
                         self.csep.is_some(),
                         self.lborder.is_some(),
                         self.rborder.is_some())
            }
            None => Ok(0),
        }
    }

    /// Returns the character used to separate columns.
    /// `pos` specify if the separator is left/right final or internal to the table
    pub fn get_column_separator(&self, pos: ColumnPosition) -> Option<char> {
        match pos {
            ColumnPosition::Left => self.lborder,
            ColumnPosition::Intern => self.csep,
            ColumnPosition::Right => self.rborder,
        }
    }

    /// Print a column separator or a table border
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn print_column_separator<T: Write + ?Sized>(&self,
                                                     out: &mut T,
                                                     pos: ColumnPosition)
                                                     -> Result<(), Error> {
        match self.get_column_separator(pos) {
            Some(s) => out.write_all(Utf8Char::from(s).as_bytes()),
            None => Ok(()),
        }
    }
}

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

/// A builder to create a `TableFormat`
pub struct FormatBuilder {
    format: Box<TableFormat>,
}

impl FormatBuilder {
    /// Creates a new builder
    pub fn new() -> FormatBuilder {
        FormatBuilder { format: Box::new(TableFormat::new()) }
    }

    /// Set left and right padding
    pub fn padding(mut self, left: usize, right: usize) -> Self {
        self.format.padding(left, right);
        self
    }

    /// Set the character used for internal column separation
    pub fn column_separator(mut self, separator: char) -> Self {
        self.format.column_separator(separator);
        self
    }

    /// Set the character used for table borders
    pub fn borders(mut self, border: char) -> Self {
        self.format.borders(border);
        self
    }

    /// Set the character used for left table border
    pub fn left_border(mut self, border: char) -> Self {
        self.format.left_border(border);
        self
    }

    /// Set the character used for right table border
    pub fn right_border(mut self, border: char) -> Self {
        self.format.right_border(border);
        self
    }

    /// Set a line separator format
    pub fn separator(mut self, what: LinePosition, separator: LineSeparator) -> Self {
        self.format.separator(what, separator);
        self
    }

    /// Set separator format for multiple kind of line separators
    pub fn separators(mut self, what: &[LinePosition], separator: LineSeparator) -> Self {
        self.format.separators(what, separator);
        self
    }

    /// Set global indentation in spaces used when rendering a table
    pub fn indent(mut self, spaces: usize) -> Self {
        self.format.indent(spaces);
        self
    }

    /// Return the generated `TableFormat`
    pub fn build(&self) -> TableFormat {
        *self.format
    }
}

impl Into<TableFormat> for FormatBuilder {
    fn into(self) -> TableFormat {
        *self.format
    }
}

impl From<TableFormat> for FormatBuilder {
    fn from(fmt: TableFormat) -> Self {
        FormatBuilder { format: Box::new(fmt) }
    }
}

/// Predifined formats. Those constants are lazily evaluated when
/// the corresponding struct is dereferenced
pub mod consts {
    use super::{TableFormat, LineSeparator, FormatBuilder, LinePosition};

    lazy_static! {
        /// A line separator made of `-` and `+`
        static ref MINUS_PLUS_SEP: LineSeparator = LineSeparator::new('-', '+', '+', '+');
        /// A line separator made of `=` and `+`
        static ref EQU_PLUS_SEP: LineSeparator = LineSeparator::new('=', '+', '+', '+');

        /// Default table format
        ///
        /// # Example
        /// ```text
        /// +----+----+
        /// | T1 | T2 |
        /// +====+====+
        /// | a  | b  |
        /// +----+----+
        /// | d  | c  |
        /// +----+----+
        /// ```
        pub static ref FORMAT_DEFAULT: TableFormat = FormatBuilder::new()
                                                                    .column_separator('|')
                                                                    .borders('|')
                                                                    .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Title, *EQU_PLUS_SEP)
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .padding(1, 1)
                                                                    .build();

        /// Similar to `FORMAT_DEFAULT` but without special separator after title line
        ///
        /// # Example
        /// ```text
        /// +----+----+
        /// | T1 | T2 |
        /// +----+----+
        /// | a  | b  |
        /// +----+----+
        /// | c  | d  |
        /// +----+----+
        /// ```
        pub static ref FORMAT_NO_TITLE: TableFormat = FormatBuilder::new()
                                                                    .column_separator('|')
                                                                    .borders('|')
                                                                    .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Title, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .padding(1, 1)
                                                                    .build();

        /// With no line separator, but with title separator
        ///
        /// # Example
        /// ```text
        /// +----+----+
        /// | T1 | T2 |
        /// +----+----+
        /// | a  | b  |
        /// | c  | d  |
        /// +----+----+
        /// ```
        pub static ref FORMAT_NO_LINESEP_WITH_TITLE: TableFormat = FormatBuilder::new()
                                                                    .column_separator('|')
                                                                    .borders('|')
                                                                    .separator(LinePosition::Title, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .padding(1, 1)
                                                                    .build();

        /// With no line or title separator
        ///
        /// # Example
        /// ```text
        /// +----+----+
        /// | T1 | T2 |
        /// | a  | b  |
        /// | c  | d  |
        /// +----+----+
        /// ```
        pub static ref FORMAT_NO_LINESEP: TableFormat = FormatBuilder::new()
                                                                    .column_separator('|')
                                                                    .borders('|')
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .padding(1, 1)
                                                                    .build();

        /// No column separator
        ///
        /// # Example
        /// ```text
        /// --------
        ///  T1  T2
        /// ========
        ///  a   b
        /// --------
        ///  d   c
        /// --------
        /// ```
        pub static ref FORMAT_NO_COLSEP: TableFormat = FormatBuilder::new()
                                                                    .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Title, *EQU_PLUS_SEP)
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .padding(1, 1)
                                                                    .build();

        /// Format for printing a table without any separators (only alignment)
        ///
        /// # Example
        /// ```text
        ///  T1  T2
        ///  a   b
        ///  d   c
        /// ```
        pub static ref FORMAT_CLEAN: TableFormat = FormatBuilder::new()
                                                                    .padding(1, 1)
                                                                    .build();

        /// Format for a table with only external borders and title separator
        ///
        /// # Example
        /// ```text
        /// +--------+
        /// | T1  T2 |
        /// +========+
        /// | a   b  |
        /// | c   d  |
        /// +--------+
        /// ```
        pub static ref FORMAT_BORDERS_ONLY: TableFormat = FormatBuilder::new()
                                                                    .padding(1, 1)
                                                                    .separator(LinePosition::Title, *EQU_PLUS_SEP)
                                                                    .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Top, *MINUS_PLUS_SEP)
                                                                    .borders('|')
                                                                    .build();

        /// A table with no external border
        ///
        /// # Example
        /// ```text
        ///  T1 | T2
        /// ====+====
        ///  a  | b
        /// ----+----
        ///  c  | d
        /// ```
        pub static ref FORMAT_NO_BORDER: TableFormat = FormatBuilder::new()
                                                                    .padding(1, 1)
                                                                    .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
                                                                    .separator(LinePosition::Title, *EQU_PLUS_SEP)
                                                                    .column_separator('|')
                                                                    .build();

        /// A table with no external border and no line separation
        ///
        /// # Example
        /// ```text
        ///  T1 | T2
        /// ----+----
        ///  a  | b
        ///  c  | d
        /// ```
        pub static ref FORMAT_NO_BORDER_LINE_SEPARATOR: TableFormat = FormatBuilder::new()
                                                                    .padding(1, 1)
                                                                    .separator(LinePosition::Title, *MINUS_PLUS_SEP)
                                                                    .column_separator('|')
                                                                    .build();

        /// A table with borders and delimiters made with box characters
        /// 
        /// # Example
        /// ```text
        /// ┌────┬────┬────┐
        /// │ t1 │ t2 │ t3 │
        /// ├────┼────┼────┤
        /// │ 1  │ 1  │ 1  │
        /// ├────┼────┼────┤
        /// │ 2  │ 2  │ 2  │
        /// └────┴────┴────┘
        /// ```
        pub static ref FORMAT_BOX_CHARS: TableFormat = FormatBuilder::new()
                             .column_separator('│')
                             .borders('│')
                             .separators(&[LinePosition::Top],
                                         LineSeparator::new('─',
                                                            '┬',
                                                            '┌',
                                                            '┐'))
                             .separators(&[LinePosition::Intern],
                                         LineSeparator::new('─',
                                                            '┼',
                                                            '├',
                                                            '┤'))
                             .separators(&[LinePosition::Bottom],
                                         LineSeparator::new('─',
                                                            '┴',
                                                            '└',
                                                            '┘'))
                             .padding(1, 1)
                             .build();
    }
}
