use std::fmt::{Debug, Display};

use binrw::{binrw, BinRead, BinWrite};
use diff::Diff;

use crate::{array_of::ArrayOf, types::Point};

use super::{ObjectCore, Translate};

#[cfg_attr(feature = "default-debug", derive(Debug))]
#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub enum LineType {
    #[brw(magic = 0x0001u16)]
    Point { _zero: u32, point: Point },
    #[brw(magic = 0x0100u16)]
    Line { _zero: u32, points: ArrayOf<Point> },
    #[brw(magic = 0x0300u16)]
    Bezier { _zero: u32, points: ArrayOf<Point> },
}

impl Display for LineType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LineType::Point { _zero, point } => {
                write!(f, "Point: {}", point)
            }
            LineType::Line { _zero, points } => {
                write!(
                    f,
                    "Line: {}",
                    points
                        .iter()
                        .map(|x| format!("{x}"))
                        .collect::<Vec<String>>()
                        .join(", ")
                )
            }
            LineType::Bezier { _zero, points } => {
                write!(
                    f,
                    "Bezier: {}",
                    points
                        .iter()
                        .map(|x| format!("{x}"))
                        .collect::<Vec<String>>()
                        .join(", ")
                )
            }
        }
    }
}

// Custom Debug implementation to only print known fields
#[cfg(not(feature = "default-debug"))]
impl Debug for LineType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Point { _zero, point } => f.debug_struct("Point").field("point", point).finish(),
            Self::Line { _zero, points } => f.debug_struct("Line").field("points", points).finish(),
            Self::Bezier { _zero, points } => {
                f.debug_struct("Bezier").field("points", points).finish()
            }
        }
    }
}

impl LineType {
    fn move_relative(&mut self, delta: Point) {
        match self {
            LineType::Point { _zero, point } => {
                *point += delta;
            }
            LineType::Line { _zero, points } => {
                points.iter_mut().for_each(|pt| *pt += delta);
            }
            LineType::Bezier { _zero, points } => {
                points.iter_mut().for_each(|pt| *pt += delta);
            }
        }
    }
}

#[binrw]
#[derive(Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct Lines {
    pub core: ObjectCore,

    #[br(temp)]
    #[bw(try_calc(u32::try_from(lines.len())))]
    pub num_lines: u32,

    pub _unknown_1: u32,

    #[br(count = num_lines)]
    pub lines: Vec<LineType>,
}

impl Display for Lines {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.core)?;
        for line in &self.lines {
            write!(f, "\n{}", line)?;
        }
        Ok(())
    }
}

impl Translate for Lines {
    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
        origin.map(|origin| {
            let delta: Point = *self.core.origin - origin;
            self.lines.iter_mut().for_each(|x| x.move_relative(delta));
        });
        self.core.move_absolute(origin, z);
    }

    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
        delta.map(|delta| {
            self.lines.iter_mut().for_each(|x| x.move_relative(delta));
        });
        self.core.move_relative(delta, z);
    }
}
