//! This module contains a [`Rotate`] primitive which can be used in order to rotate [`Table`].
//!
//! It's also possible to transpose the table at the point of construction.
//! See [`Builder::index`].
//!
//! # Example
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! use tabled::{Table, settings::Rotate};
//!
//! let data = [[1, 2, 3], [4, 5, 6]];
//!
//! let table = Table::new(data).with(Rotate::Left).to_string();
//!
//! assert_eq!(
//!     table,
//!     concat!(
//!         "+---+---+---+\n",
//!         "| 2 | 3 | 6 |\n",
//!         "+---+---+---+\n",
//!         "| 1 | 2 | 5 |\n",
//!         "+---+---+---+\n",
//!         "| 0 | 1 | 4 |\n",
//!         "+---+---+---+",
//!     )
//! );
//! ```
//!
//! [`Table`]: crate::Table
//! [`Builder::index`]: crate::builder::Builder::index

// use core::cmp::max;
use core::cmp::max;

use crate::{
    grid::records::{ExactRecords, Records, Resizable},
    settings::TableOption,
};

/// Rotate can be used to rotate a table by 90 degrees.
#[derive(Debug)]
pub enum Rotate {
    /// Rotate [`Table`] to the left.
    ///
    /// [`Table`]: crate::Table
    Left,
    /// Rotate [`Table`] to the right.
    ///
    /// [`Table`]: crate::Table
    Right,
    /// Rotate [`Table`] to the top.
    ///
    /// So the top becomes the bottom.
    ///
    /// [`Table`]: crate::Table
    Top,
    /// Rotate [`Table`] to the bottom.
    ///
    /// So the top becomes the bottom.
    ///
    /// [`Table`]: crate::Table
    Bottom,
}

impl<R, D, C> TableOption<R, C, D> for Rotate
where
    R: Records + ExactRecords + Resizable,
{
    fn change(self, records: &mut R, _: &mut C, _: &mut D) {
        match self {
            Self::Left => rotate_left(records),
            Self::Right => rotate_right(records),
            Self::Bottom | Self::Top => rotate_horizontal(records),
        }
    }
}

fn rotate_horizontal<R>(records: &mut R)
where
    R: Records + ExactRecords + Resizable,
{
    let count_rows = records.count_rows();
    let count_cols = records.count_columns();

    for row in 0..count_rows / 2 {
        for col in 0..count_cols {
            let last_row = count_rows - row - 1;
            records.swap((last_row, col).into(), (row, col).into());
        }
    }
}

fn rotate_left<R>(records: &mut R)
where
    R: Records + ExactRecords + Resizable,
{
    let count_rows = records.count_rows();
    let count_cols = records.count_columns();
    let size = max(count_rows, count_cols);

    {
        for _ in count_rows..size {
            records.push_row();
        }

        for _ in count_cols..size {
            records.push_column();
        }
    }

    for col in 0..size {
        for row in col..size {
            records.swap((col, row).into(), (row, col).into());
        }
    }

    for row in 0..count_cols / 2 {
        records.swap_row(row, count_cols - row - 1);
    }

    {
        for (shift, row) in (count_rows..size).enumerate() {
            let row = row - shift;
            records.remove_column(row);
        }

        for (shift, col) in (count_cols..size).enumerate() {
            let col = col - shift;
            records.remove_row(col);
        }
    }
}

fn rotate_right<R>(records: &mut R)
where
    R: Records + ExactRecords + Resizable,
{
    let count_rows = records.count_rows();
    let count_cols = records.count_columns();
    let size = max(count_rows, count_cols);

    {
        for _ in count_rows..size {
            records.push_row();
        }

        for _ in count_cols..size {
            records.push_column();
        }
    }

    for col in 0..size {
        for row in col..size {
            records.swap((col, row).into(), (row, col).into());
        }
    }

    for col in 0..count_rows / 2 {
        records.swap_column(col, count_rows - col - 1);
    }

    {
        for (shift, row) in (count_rows..size).enumerate() {
            let row = row - shift;
            records.remove_column(row);
        }

        for (shift, col) in (count_cols..size).enumerate() {
            let col = col - shift;
            records.remove_row(col);
        }
    }
}
