// Use the same global constants
pub mod constants;

use std::fmt::Display;

use constants::{AVAILABLEPLANKS, CUTADJACENCY, PLANKMAX, PLANKMIN, PLAY, ROOMLENGTH, SAWBLADE};

// Define a plank piece structure
#[derive(Debug)]
pub struct MaterialStorage {
    pub total_planks: u32,
    pub planks_new: Vec<Plank>,
    pub planks_used: Vec<Plank>,
    pub planks_too_short: Vec<Plank>,
}

impl Default for MaterialStorage {
    /// Create a new, default storage
    fn default() -> Self {
        Self {
            total_planks: AVAILABLEPLANKS,
            planks_new: vec![Plank::default(); AVAILABLEPLANKS as usize],
            planks_used: vec![],
            planks_too_short: vec![],
        }
    }
}
impl MaterialStorage {
    pub fn new(&self) -> Self {
        MaterialStorage::default()
    }
    /// Does there exist used/cut planks?
    pub fn exists_used(&self) -> bool {
        !self.planks_used.is_empty()
    }
    /// Get a used plank
    pub fn get_used(&mut self) -> Option<Plank> {
        self.planks_used.pop()
    }
    /// Store a used plank
    pub fn store_used(&mut self, plank: Plank) {
        self.planks_used.push(plank)
    }
    /// Try get a used plank, fallback by getting a new
    pub fn try_get_used(&mut self) -> Option<Plank> {
        if let Some(plank) = self.planks_used.pop() {
            Some(plank)
        } else {
            self.get_new()
        }
    }
    /// Get how many cut planks exists
    pub fn get_used_count(&self) -> u32 {
        self.planks_used.len() as u32
    }
    /// Does there exist new planks
    pub fn exists_new(&self) -> bool {
        !self.planks_new.is_empty()
    }
    /// Get a new plank
    pub fn get_new(&mut self) -> Option<Plank> {
        self.planks_new.pop()
    }
    /// Put a too short plank aside
    pub fn discard_unusable(&mut self, plank: Plank) {
        self.planks_too_short.push(plank)
    }
}

// Define a plank cut structure
#[derive(Debug, Clone, Copy)]
pub struct Cut {
    pub coordinate: u32,
}

impl Cut {
    pub fn new(coordinate: u32) -> Self {
        Cut { coordinate }
    }
}

impl Display for Cut {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.coordinate)
    }
}

// Define a plank piece structure
#[derive(Debug, Clone, Copy)]
pub struct Plank {
    pub length: u32,
    pub endpiece: bool,
    pub new: bool,
}

impl Default for Plank {
    /// Create a new, default plank
    fn default() -> Self {
        Self {
            length: PLANKMAX,
            endpiece: false,
            new: true,
        }
    }
}

impl Display for Plank {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.length)
    }
}

impl Plank {
    /// Create a new plank
    pub fn new(&self) -> Self {
        Plank::default()
    }
    /// Is this a new plank?
    pub fn is_new(&self) -> bool {
        self.new
    }
    /// Is this at the end of a row
    pub fn get_endpiece(&self) -> bool {
        self.endpiece
    }
    /// Make this plank an endpiece
    pub fn set_endpiece(&mut self) {
        self.endpiece = true;
    }

    /// Return the length of the plank
    pub fn length(&self) -> u32 {
        self.length
    }

    /// Return the original plank with the new length along with leftover
    pub fn cut_to_length(mut self, measure: u32) -> (Plank, Plank) {
        if measure > PLANKMAX {
            panic!("Can't cut a plank longer than it is!");
        } else if measure == 0 {
            panic!("Cutting a plank to length 0 does not make sense!");
        }

        // Create the leftover plank, correct for material loss due to sawing
        let leftover_plank;
        let leftover_length;
        if self.length() < PLANKMIN {
            // Create a new plank instead
            let new_plank: Plank = Default::default();
            leftover_plank = new_plank;
        } else {
            leftover_length = self.length - (measure + SAWBLADE);
            leftover_plank = Plank {
                length: leftover_length,
                ..Default::default()
            };
        }

        // Trim self to the new length
        self.length = measure;
        // In case the plank was new, mark it as cut
        if self.new {
            self.new = false;
        }

        // Return the original plank and the leftover
        (self, leftover_plank)
    }
}

// Define a row structure
#[derive(Debug)]
pub struct Row {
    planks: Vec<Plank>,
    cut_coordinates: Vec<Cut>,
    coverage: u32,
    full: bool,
    row_max_length: u32,
}
impl Default for Row {
    /// Create a new, empty row
    fn default() -> Self {
        Self {
            planks: vec![],
            cut_coordinates: vec![],
            coverage: 0,
            full: false,
            row_max_length: ROOMLENGTH - 2 * PLAY,
        }
    }
}

impl Display for Row {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let scalefactor = 50;
        // Try to compensate for the extra size taken by
        // the number printout
        let comp = 50 * self.planks_count();
        let mut length_sum = 0;
        write!(f, "   ")?; // Prefix space for play
        for plank in self.planks().iter() {
            length_sum += plank.length;
            write!(f, "|")?;

            // Print the relative length of the plank
            for _ in 0..((plank.length()) / scalefactor) {
                write!(f, "-")?;
            }
        }
        write!(f, "|")?;
        write!(f, " Cut coordinates: ")?;
        for cut in self.get_cut_coordinates() {
            write!(f, " {cut: ^6}, ")?;
        }
        writeln!(f)?;

        write!(f, "+{PLAY}")?;
        for plank in self.planks().iter() {
            for _ in 0..((plank.length() - comp) / scalefactor) / 2 {
                write!(f, " ")?;
            }
            // Print the numeric length
            write!(f, "{: ^6}", plank.length())?;

            // Remainder of the plank
            for _ in 0..((plank.length() - comp) / scalefactor) / 2 {
                write!(f, " ")?;
            }
        }

        write!(f, "+{PLAY}")?;
        writeln!(f, "\t Row length: {length_sum}")?;

        write!(f, "   ")?; // Prefix space for play
        for plank in self.planks().iter() {
            write!(f, "|")?;
            // Print the bottom self of the plank
            for _ in 0..(plank.length() / scalefactor) {
                write!(f, "-")?;
            }
        }
        writeln!(f, "|")
    }
}

impl Row {
    /// Is this row considered full?
    pub fn get_full(&self) -> bool {
        self.full
    }

    /// Mark this row as full
    pub fn set_full(&mut self) {
        self.full = true;
    }

    pub fn set_first_and_last_as_endpieces(&mut self) {
        // #TODO Remove unwrap
        self.planks.first_mut().unwrap().set_endpiece();
        self.planks.last_mut().unwrap().set_endpiece();
    }

    /// Get the number of planks for this floor
    pub fn planks_count(&self) -> u32 {
        self.planks.len() as u32
    }

    /// Get a mutable reference to the planks
    pub fn planks_mut(&mut self) -> &mut Vec<Plank> {
        &mut self.planks
    }

    /// Get a mutable reference to the planks
    pub fn planks(&self) -> &Vec<Plank> {
        &self.planks
    }

    /// Return how many planks/pieces are in this row
    pub fn get_used_planks(&mut self) {
        self.planks.len();
    }

    /// Increase how much of the row is filled
    fn add_coverage(&mut self, coverage: u32) {
        self.coverage += coverage;
    }

    /// Get the coverage so far of this row
    pub fn get_coverage(&self) -> u32 {
        self.coverage
    }

    /// Get the maximum length allowed
    pub fn get_max_length(&self) -> u32 {
        self.row_max_length
    }

    /// Add a plank to this row
    pub fn add(&mut self, plank: Plank) {
        // Add the new plank to coverage, coverage is
        // thus the coordinate from origin
        self.add_coverage(plank.length());

        // There is always a cut at the end of a row
        // only add Cut if less than this
        let coordinate = self.get_coverage();
        if coordinate < self.row_max_length {
            self.cut_coordinates.push(Cut::new(coordinate));
        }
        self.planks.push(plank);
    }

    /// Swap the topmost plank
    pub fn swap_latest(&mut self, ms: &mut MaterialStorage) {
        let number_of_planks = self.planks.len();
        if number_of_planks >= 2 {
            self.pop(ms);
            self.pop(ms);
            if let Some(plank) = ms.get_used() {
                self.add(plank);
            }
            if let Some(plank) = ms.get_used() {
                self.add(plank);
            }
        }
    }

    /// Remove a plank from this row
    pub fn pop(&mut self, ms: &mut MaterialStorage) {
        // Remove the plank
        if let Some(plank) = self.planks.pop() {
            ms.store_used(plank);
            // Update the row coverage
            self.coverage -= plank.length();
        }
        // Remove the cut
        if let Some(cut) = self.cut_coordinates.pop() {
            log::info!("Removed cut at {}", cut.coordinate);
        }
    }

    pub fn get_cut_coordinates(&self) -> Vec<Cut> {
        self.cut_coordinates.clone()
    }

    pub fn check_if_cut_is_valid(&self, new_cut: Cut) -> bool {
        // If we have no cuts, no need to check them
        if !self.cut_coordinates.is_empty() {
            for cut in &self.cut_coordinates {
                if cut.coordinate.abs_diff(new_cut.coordinate) < CUTADJACENCY {
                    // Invalid, cut is too close!
                    return false;
                }
            }
        }
        true
    }
}

// Define a floor structure
#[derive(Debug)]
pub struct Floor {
    rows: Vec<Row>,
    coverage: u32,
    complete: bool,
}
impl Default for Floor {
    /// Create a new, empty row
    fn default() -> Self {
        Self {
            rows: vec![],
            coverage: 0,
            complete: false,
        }
    }
}

impl Display for Floor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (num, row) in self.rows().iter().enumerate() {
            writeln!(f, "Row: {:#?}", num + 1)?;
            writeln!(f, "{row}")?;
        }
        Ok(())
    }
}

impl Floor {
    /// Is this floor considered complete?
    pub fn get_complete(&self) -> bool {
        self.complete
    }

    /// Mark this floor as complete
    pub fn set_complete(&mut self) {
        self.complete = true;
    }

    /// Get the number of plank rows for this floor
    pub fn rows_count(&self) -> usize {
        self.rows.len()
    }

    /// Get a mutable reference to the rows
    pub fn rows_mut(&mut self) -> &mut Vec<Row> {
        &mut self.rows
    }

    /// Get a reference to the rows
    pub fn rows(&self) -> &Vec<Row> {
        &self.rows
    }

    /// Get a reference to the previous row
    pub fn last_row(&self) -> Option<&Row> {
        self.rows.last()
    }

    /// Add another row to this floor
    pub fn add(&mut self, row: Row) {
        self.rows.push(row);
    }

    /// Get the number of plank rows for this floor
    pub fn get_coverage(&self) -> u32 {
        self.coverage
    }

    /// Increase how much of the row is filled
    pub fn add_coverage(&mut self, coverage: u32) {
        self.coverage += coverage;
    }
}