use crate::{grid::records::vec_records::Text, Table};

use super::Builder;

/// [`IndexBuilder`] helps to add an index to the table.
///
/// Index is a column on the left of the table.
///
/// It also can be used to transpose the table.
///
/// Creates a new [`IndexBuilder`] instance.
///
/// It creates a default index a range from 0 to N. (N - count rows)
/// It also sets a default columns to the range 0 .. N (N - count columns).
///nfo<'a>
/// # Example
///
/// ```
/// use tabled::builder::Builder;
///
/// let mut builder = Builder::default();
/// builder.push_record(["i", "col-1", "col-2"]);
/// builder.push_record(["0", "value-1", "value-2"]);
///
/// let table = builder.index().build().to_string();
///
/// assert_eq!(
///     table,
///     "+---+---+---------+---------+\n\
///      |   | i | col-1   | col-2   |\n\
///      +---+---+---------+---------+\n\
///      | 0 | 0 | value-1 | value-2 |\n\
///      +---+---+---------+---------+"
/// )
/// ```
///
/// # Example
///
/// ```
/// use tabled::builder::Builder;
///
/// let table = Builder::default()
///     .index()
///     .build();
/// ```
#[derive(Debug, Clone)]
pub struct IndexBuilder {
    /// Index is an index data.
    /// It's always set.
    index: Vec<Text<String>>,
    /// Name of an index
    name: Option<Text<String>>,
    /// A flag which checks if we need to actually use index.
    ///
    /// It might happen when it's only necessary to [`Self::transpose`] table.
    print_index: bool,
    /// A flag which checks if table was transposed.
    transposed: bool,
    /// Data originated in [`Builder`].
    data: Vec<Vec<Text<String>>>,
    /// A size of columns
    count_columns: usize,
}

impl IndexBuilder {
    /// No flag makes builder to not use an index.
    ///
    /// It may be useful when only [`Self::transpose`] need to be used.
    ///
    /// ```
    /// use tabled::builder::Builder;
    ///
    /// let mut builder = Builder::default();
    /// builder.push_record(["i", "col-1", "col-2"]);
    /// builder.push_record(["0", "value-1", "value-2"]);
    /// builder.push_record(["2", "value-3", "value-4"]);
    ///
    /// let table = builder.index().hide().build().to_string();
    ///
    /// assert_eq!(
    ///     table,
    ///     "+---+---------+---------+\n\
    ///      | i | col-1   | col-2   |\n\
    ///      +---+---------+---------+\n\
    ///      | 0 | value-1 | value-2 |\n\
    ///      +---+---------+---------+\n\
    ///      | 2 | value-3 | value-4 |\n\
    ///      +---+---------+---------+"
    /// )
    /// ```
    pub fn hide(mut self) -> Self {
        self.print_index = false;
        self
    }

    /// Set an index name.
    ///
    /// When [`None`] the name won't be used.
    ///
    /// # Example
    ///
    /// ```
    /// use tabled::builder::Builder;
    ///
    /// let mut builder = Builder::default();
    /// builder.push_record(["i", "column1", "column2"]);
    /// builder.push_record(["0", "value1", "value2"]);
    ///
    /// let table = builder.index()
    ///     .column(1)
    ///     .name(Some(String::from("index")))
    ///     .build();
    ///
    /// assert_eq!(
    ///     table.to_string(),
    ///     "+--------+---+---------+\n\
    ///      |        | i | column2 |\n\
    ///      +--------+---+---------+\n\
    ///      | index  |   |         |\n\
    ///      +--------+---+---------+\n\
    ///      | value1 | 0 | value2  |\n\
    ///      +--------+---+---------+"
    /// )
    /// ```
    pub fn name(mut self, name: Option<String>) -> Self {
        self.name = name.map(Text::new);
        self
    }

    /// Sets a index to the chosen column.
    ///
    /// Also sets a name of the index to the column name.
    ///
    /// # Example
    ///
    /// ```
    /// use tabled::builder::Builder;
    ///
    /// let mut builder = Builder::default();
    /// builder.push_record(["i", "column1", "column2"]);
    /// builder.push_record(["0", "value1", "value2"]);
    ///
    /// let table = builder.index().column(1).build();
    ///
    /// assert_eq!(
    ///     table.to_string(),
    ///     "+---------+---+---------+\n\
    ///      |         | i | column2 |\n\
    ///      +---------+---+---------+\n\
    ///      | column1 |   |         |\n\
    ///      +---------+---+---------+\n\
    ///      | value1  | 0 | value2  |\n\
    ///      +---------+---+---------+"
    /// )
    /// ```
    pub fn column(mut self, column: usize) -> Self {
        if column >= self.count_columns {
            return self;
        }

        self.index = get_column(&mut self.data, column);

        let name = self.index.remove(0);
        self.name = Some(name);

        self
    }

    /// Transpose index and columns.
    ///
    /// # Example
    ///
    /// ```
    /// use tabled::builder::Builder;
    ///
    /// let mut builder = Builder::default();
    /// builder.push_record(["i", "column-1", "column-2", "column-3"]);
    /// builder.push_record(["0", "value-1", "value-2", "value-3"]);
    /// builder.push_record(["1", "value-4", "value-5", "value-6"]);
    /// builder.push_record(["2", "value-7", "value-8", "value-9"]);
    ///
    /// let table = builder.index().column(1).transpose().build();
    ///
    /// assert_eq!(
    ///     table.to_string(),
    ///     "+----------+---------+---------+---------+\n\
    ///      | column-1 | value-1 | value-4 | value-7 |\n\
    ///      +----------+---------+---------+---------+\n\
    ///      | i        | 0       | 1       | 2       |\n\
    ///      +----------+---------+---------+---------+\n\
    ///      | column-2 | value-2 | value-5 | value-8 |\n\
    ///      +----------+---------+---------+---------+\n\
    ///      | column-3 | value-3 | value-6 | value-9 |\n\
    ///      +----------+---------+---------+---------+"
    /// )
    /// ```
    pub fn transpose(mut self) -> Self {
        if self.data.is_empty() {
            return self;
        }

        let mut columns = self.data.remove(0);
        std::mem::swap(&mut self.index, &mut columns);

        let count_columns = columns.len();
        rotate_vector(&mut self.data, self.index.len());

        self.data.insert(0, columns);

        self.transposed = !self.transposed;
        self.count_columns = count_columns;

        self
    }

    /// Builds a table.
    pub fn build(self) -> Table {
        let builder: Builder = self.into();
        builder.build()
    }
}

impl From<Builder> for IndexBuilder {
    fn from(builder: Builder) -> Self {
        let count_columns = builder.count_columns();
        let data: Vec<Vec<_>> = builder.into();

        let mut index = Vec::new();
        if !data.is_empty() {
            // we exclude first row which contains a header
            let count_rows = data.len() - 1;
            index = build_range_index(count_rows);
        }

        Self {
            index,
            data,
            count_columns,
            name: None,
            print_index: true,
            transposed: false,
        }
    }
}

impl From<IndexBuilder> for Builder {
    fn from(b: IndexBuilder) -> Self {
        build_index(b)
    }
}

fn build_index(mut b: IndexBuilder) -> Builder {
    // we can skip the conversion if this builder has neither data rows nor header row
    if b.index.is_empty() && b.count_columns == 0 {
        return Builder::default();
    }

    // add index column
    if b.print_index {
        b.index.insert(0, Text::default());
        insert_column(&mut b.data, b.index, 0);
    }

    if let Some(name) = b.name {
        if b.transposed && b.print_index {
            b.data[0][0] = name;
        } else {
            let count_columns = b.data[0].len();
            let mut name_row = vec![Text::default(); count_columns];
            name_row[0] = name;

            b.data.insert(1, name_row);
        }
    }

    Builder::from_vec(b.data)
}

fn build_range_index(n: usize) -> Vec<Text<String>> {
    (0..n).map(|i| i.to_string()).map(Text::new).collect()
}

// note: Pretty heavy operation.
fn rotate_vector<T>(rows: &mut Vec<Vec<T>>, count_columns: usize)
where
    T: Default + Clone,
{
    let count_rows = rows.len();
    let mut columns = vec![vec![T::default(); count_rows]; count_columns];
    for col in 0..count_columns {
        for (row, data) in rows.iter_mut().enumerate() {
            let value = data.pop().expect("expected to be controlled");
            let col = count_columns - col - 1;
            columns[col][row] = value;
        }
    }

    *rows = columns;
}

fn insert_column<T: Default>(v: &mut [Vec<T>], mut column: Vec<T>, col: usize) {
    for row in v.iter_mut() {
        let value = column.remove(col);
        row.insert(col, value);
    }
}

fn get_column<T>(v: &mut [Vec<T>], col: usize) -> Vec<T>
where
    T: Default,
{
    let mut column = Vec::with_capacity(v.len());
    for row in v.iter_mut() {
        let value = row.remove(col);
        column.push(value);
    }

    column
}
