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

use crate::{
    array_of::ArrayOf,
    field_of::FieldOf,
    pen::Pen,
    types::{Field, Point, F64, U32},
    FP,
};
use binrw::{binrw, BinRead, BinWrite};
use diff::Diff;
use modular_bitfield::{
    bitfield,
    specifiers::{B1, B18},
};
use serde::{Deserialize, Serialize};

use super::{line::Lines, Object, ObjectCore, Translate};

#[bitfield(bits = 32)]
#[derive(BinRead, BinWrite, Copy, Clone, Debug, Default, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
#[br(map = Self::from_bytes)]
#[bw(map = |&x| Self::into_bytes(x))]
pub struct HatchFlag {
    pub all_calc: B1,
    pub follow_edge_once: B1,
    pub continuous_pattern: B1, // Hatch type 3 when combined with bidirectional_pattern
    pub bidirectional_pattern: B1, // Hatch type 1
    pub ring_pattern: B1,       // Hatch type 2
    #[skip]
    __: B1,
    pub auto_rotate_hatch_angle: B1,
    pub average_distribute_line: B1,
    #[skip]
    __: B1,
    pub gong_pattern: B1, // Hatch type 4 when combined with bidirectional_pattern
    pub cross_hatch: B1,
    pub background_pattern: B1, // Hatch type 5, must set all_calc as well
    pub fill_pattern: B1, // Hatch type 6 when combined with continuous_pattern and bidirectional_pattern
    pub zigzag_pattern: B1, // Hatch type 7
    #[skip]
    __: B18,
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, strum::Display)]
pub enum HatchPattern {
    Directional,
    Bidirectional,
    Ring,
    Continuous,
    Gong,
    Background,
    Fill,
    Zigzag,
}

impl From<HatchPattern> for HatchFlag {
    fn from(value: HatchPattern) -> Self {
        let mut ret = Self::new();
        match value {
            HatchPattern::Directional => (),
            HatchPattern::Bidirectional => ret.set_bidirectional_pattern(1),
            HatchPattern::Ring => ret.set_ring_pattern(1),
            HatchPattern::Continuous => {
                ret.set_bidirectional_pattern(1);
                ret.set_continuous_pattern(1);
            }
            HatchPattern::Gong => {
                ret.set_bidirectional_pattern(1);
                ret.set_gong_pattern(1);
            }
            HatchPattern::Background => {
                ret.set_background_pattern(1);
                ret.set_all_calc(1);
            }
            HatchPattern::Fill => ret.set_fill_pattern(1),
            HatchPattern::Zigzag => ret.set_zigzag_pattern(1),
        }
        ret
    }
}

impl From<HatchFlag> for HatchPattern {
    fn from(value: HatchFlag) -> Self {
        if value.ring_pattern() != 0 {
            HatchPattern::Ring
        } else if value.background_pattern() != 0 {
            HatchPattern::Background
        } else if value.fill_pattern() != 0 {
            HatchPattern::Fill
        } else if value.zigzag_pattern() != 0 {
            HatchPattern::Zigzag
        } else if value.gong_pattern() != 0 {
            HatchPattern::Gong
        } else if value.bidirectional_pattern() != 0 {
            if value.continuous_pattern() != 0 {
                HatchPattern::Continuous
            } else {
                HatchPattern::Bidirectional
            }
        } else {
            HatchPattern::Directional
        }
    }
}

#[cfg_attr(feature = "default-debug", derive(Debug))]
#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
// #[brw(magic(46u32))] // Number of fields in this struct
#[brw(magic(47u32))] // Number of fields in this struct
pub struct LegacyHatchSetting {
    pub mark_contour: U32,
    pub hatch_0_enable: U32,
    pub hatch_0_pen: U32,
    pub hatch_0_flags: FieldOf<HatchFlag>,
    pub hatch_0_edge_offset: F64,
    pub hatch_0_line_spacing: F64,
    pub hatch_0_start_offset: F64,
    pub hatch_0_end_offset: F64,
    pub hatch_0_angle: F64,
    pub hatch_1_enable: U32,
    pub hatch_1_pen: U32,
    pub hatch_1_flags: FieldOf<HatchFlag>,
    pub hatch_1_edge_offset: F64,
    pub hatch_1_line_spacing: F64,
    pub hatch_1_start_offset: F64,
    pub hatch_1_end_offset: F64,
    pub hatch_1_angle: F64,
    pub any_hatch_enabled: U32,
    pub hatch_0_rotate_angle: F64,
    pub hatch_1_rotate_angle: F64,
    pub hatch_2_enable: U32,
    pub hatch_2_pen: U32,
    pub hatch_2_flags: FieldOf<HatchFlag>,
    pub hatch_2_edge_offset: F64,
    pub hatch_2_line_spacing: F64,
    pub hatch_2_start_offset: F64,
    pub hatch_2_end_offset: F64,
    pub hatch_2_angle: F64,
    pub hatch_2_rotate_angle: F64,
    pub hatch_0_line_reduction: F64,
    pub hatch_1_line_reduction: F64,
    pub hatch_2_line_reduction: F64,
    pub hatch_0_loop_count: U32,
    pub hatch_1_loop_count: U32,
    pub hatch_2_loop_count: U32,
    pub hatch_0_loop_distance: F64,
    pub hatch_1_loop_distance: F64,
    pub hatch_2_loop_distance: F64,
    pub _unknown_1: [Field; 3],
    pub contour_priority: U32,
    pub hatch_0_count: U32,
    pub hatch_1_count: U32,
    pub hatch_2_count: U32,
}

// Custom Debug implementation to only print known fields
#[cfg(not(feature = "default-debug"))]
impl Debug for LegacyHatchSetting {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LegacyHatchSetting")
            .field("mark_contour", &self.mark_contour)
            .field("hatch_0_enable", &self.hatch_0_enable)
            .field("hatch_0_pen", &self.hatch_0_pen)
            .field("hatch_0_flags", &self.hatch_0_flags)
            .field("hatch_0_edge_offset", &self.hatch_0_edge_offset)
            .field("hatch_0_line_spacing", &self.hatch_0_line_spacing)
            .field("hatch_0_start_offset", &self.hatch_0_start_offset)
            .field("hatch_0_end_offset", &self.hatch_0_end_offset)
            .field("hatch_0_angle", &self.hatch_0_angle)
            .field("hatch_1_enable", &self.hatch_1_enable)
            .field("hatch_1_pen", &self.hatch_1_pen)
            .field("hatch_1_flags", &self.hatch_1_flags)
            .field("hatch_1_edge_offset", &self.hatch_1_edge_offset)
            .field("hatch_1_line_spacing", &self.hatch_1_line_spacing)
            .field("hatch_1_start_offset", &self.hatch_1_start_offset)
            .field("hatch_1_end_offset", &self.hatch_1_end_offset)
            .field("hatch_1_angle", &self.hatch_1_angle)
            .field("any_hatch_enabled", &self.any_hatch_enabled)
            .field("hatch_0_auto_rotate_angle", &self.hatch_0_rotate_angle)
            .field("hatch_1_auto_rotate_angle", &self.hatch_1_rotate_angle)
            .field("hatch_2_enable", &self.hatch_2_enable)
            .field("hatch_2_pen", &self.hatch_2_pen)
            .field("hatch_2_flags", &self.hatch_2_flags)
            .field("hatch_2_edge_offset", &self.hatch_2_edge_offset)
            .field("hatch_2_line_spacing", &self.hatch_2_line_spacing)
            .field("hatch_2_start_offset", &self.hatch_2_start_offset)
            .field("hatch_2_end_offset", &self.hatch_2_end_offset)
            .field("hatch_2_angle", &self.hatch_2_angle)
            .field("hatch_2_auto_rotate_angle", &self.hatch_2_rotate_angle)
            .field("hatch_0_line_reduction", &self.hatch_0_line_reduction)
            .field("hatch_1_line_reduction", &self.hatch_1_line_reduction)
            .field("hatch_2_line_reduction", &self.hatch_2_line_reduction)
            .field("hatch_0_loop_count", &self.hatch_0_loop_count)
            .field("hatch_1_loop_count", &self.hatch_1_loop_count)
            .field("hatch_2_loop_count", &self.hatch_2_loop_count)
            .field("hatch_0_loop_distance", &self.hatch_0_loop_distance)
            .field("hatch_1_loop_distance", &self.hatch_1_loop_distance)
            .field("hatch_2_loop_distance", &self.hatch_2_loop_distance)
            .field("contour_priority", &self.contour_priority)
            .field("hatch_0_count", &self.hatch_0_count)
            .field("hatch_1_count", &self.hatch_1_count)
            .field("hatch_2_count", &self.hatch_2_count)
            .finish()
    }
}

#[cfg_attr(feature = "default-debug", derive(Debug))]
#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
#[brw(magic(15u32))] // Number of fields in this struct
pub struct HatchSetting {
    pub count: U32,
    pub enabled: U32,
    pub pen: U32,
    pub flags: FieldOf<HatchFlag>,
    pub edge_offset: F64,
    pub line_spacing: F64,
    pub start_offset: F64,
    pub end_offset: F64,
    pub angle: F64,
    pub rotate_angle: F64,
    pub line_reduction: F64,
    pub loop_distance: F64,
    pub loop_count: U32,
    pub _unknown_1: [Field; 2],
}

// Custom Debug implementation to only print known fields
#[cfg(not(feature = "default-debug"))]
impl Debug for HatchSetting {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("HatchSettings")
            .field("count", &self.count)
            .field("enabled", &self.enabled)
            .field("pen", &self.pen)
            .field("flags", &self.flags)
            .field("edge_offset", &self.edge_offset)
            .field("line_spacing", &self.line_spacing)
            .field("start_offset", &self.start_offset)
            .field("end_offset", &self.end_offset)
            .field("angle", &self.angle)
            .field("auto_rotate_angle", &self.rotate_angle)
            .field("line_reduction", &self.line_reduction)
            .field("loop_distance", &self.loop_distance)
            .field("loop_count", &self.loop_count)
            .finish()
    }
}

impl Default for HatchSetting {
    fn default() -> Self {
        Self {
            count: 1.into(),
            enabled: false.into(),
            pen: 0.into(),
            flags: HatchFlag::new().into(),
            edge_offset: 0.0.into(),
            line_spacing: 1.0.into(),
            start_offset: 0.0.into(),
            end_offset: 0.0.into(),
            angle: 0.0.into(),
            rotate_angle: 10.0.into(),
            line_reduction: 0.0.into(),
            loop_distance: 0.5.into(),
            loop_count: 0.into(),
            _unknown_1: [vec![0, 0, 0, 0, 0, 0, 0, 0].into(), vec![0, 0, 0, 0].into()].into(),
        }
    }
}

impl From<Vec<HatchSetting>> for LegacyHatchSetting {
    fn from(value: Vec<HatchSetting>) -> Self {
        LegacyHatchSetting {
            mark_contour: false.into(),
            hatch_0_enable: value.get(0).map(|x| x.enabled).unwrap_or_default(),
            hatch_0_pen: value.get(0).map(|x| x.pen).unwrap_or_default(),
            hatch_0_flags: value.get(0).map(|x| x.flags).unwrap_or_default(),
            hatch_0_edge_offset: value.get(0).map(|x| x.edge_offset).unwrap_or_default(),
            hatch_0_line_spacing: value.get(0).map(|x| x.line_spacing).unwrap_or_default(),
            hatch_0_start_offset: value.get(0).map(|x| x.start_offset).unwrap_or_default(),
            hatch_0_end_offset: value.get(0).map(|x| x.end_offset).unwrap_or_default(),
            hatch_0_angle: value.get(0).map(|x| x.angle).unwrap_or_default(),
            hatch_1_enable: value.get(1).map(|x| x.enabled).unwrap_or_default(),
            hatch_1_pen: value.get(1).map(|x| x.pen).unwrap_or_default(),
            hatch_1_flags: value.get(1).map(|x| x.flags).unwrap_or_default(),
            hatch_1_edge_offset: value.get(1).map(|x| x.edge_offset).unwrap_or_default(),
            hatch_1_line_spacing: value.get(1).map(|x| x.line_spacing).unwrap_or_default(),
            hatch_1_start_offset: value.get(1).map(|x| x.start_offset).unwrap_or_default(),
            hatch_1_end_offset: value.get(1).map(|x| x.end_offset).unwrap_or_default(),
            hatch_1_angle: value.get(1).map(|x| x.angle).unwrap_or_default(),
            any_hatch_enabled: value.iter().any(|x| x.enabled.into()).into(),
            hatch_0_rotate_angle: value.get(0).map(|x| x.rotate_angle).unwrap_or_default(),
            hatch_1_rotate_angle: value.get(1).map(|x| x.rotate_angle).unwrap_or_default(),
            hatch_2_enable: value.get(2).map(|x| x.enabled).unwrap_or_default(),
            hatch_2_pen: value.get(2).map(|x| x.pen).unwrap_or_default(),
            hatch_2_flags: value.get(2).map(|x| x.flags).unwrap_or_default(),
            hatch_2_edge_offset: value.get(2).map(|x| x.edge_offset).unwrap_or_default(),
            hatch_2_line_spacing: value.get(2).map(|x| x.line_spacing).unwrap_or_default(),
            hatch_2_start_offset: value.get(2).map(|x| x.start_offset).unwrap_or_default(),
            hatch_2_end_offset: value.get(2).map(|x| x.end_offset).unwrap_or_default(),
            hatch_2_angle: value.get(2).map(|x| x.angle).unwrap_or_default(),
            hatch_2_rotate_angle: value.get(2).map(|x| x.rotate_angle).unwrap_or_default(),
            hatch_0_line_reduction: value.get(0).map(|x| x.line_reduction).unwrap_or_default(),
            hatch_1_line_reduction: value.get(1).map(|x| x.line_reduction).unwrap_or_default(),
            hatch_2_line_reduction: value.get(2).map(|x| x.line_reduction).unwrap_or_default(),
            hatch_0_loop_count: value.get(0).map(|x| x.loop_count).unwrap_or_default(),
            hatch_1_loop_count: value.get(1).map(|x| x.loop_count).unwrap_or_default(),
            hatch_2_loop_count: value.get(2).map(|x| x.loop_count).unwrap_or_default(),
            hatch_0_loop_distance: value.get(0).map(|x| x.loop_distance).unwrap_or_default(),
            hatch_1_loop_distance: value.get(1).map(|x| x.loop_distance).unwrap_or_default(),
            hatch_2_loop_distance: value.get(2).map(|x| x.loop_distance).unwrap_or_default(),
            _unknown_1: [
                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
            ]
            .into(),
            contour_priority: false.into(),
            hatch_0_count: value.get(0).map(|x| x.count).unwrap_or_default(),
            hatch_1_count: value.get(1).map(|x| x.count).unwrap_or_default(),
            hatch_2_count: value.get(2).map(|x| x.count).unwrap_or_default(),
        }
    }
}

#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct HatchLine {
    pub lines: ArrayOf<Lines>,
}

#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
#[brw(magic(16_u32))] // ObjectType::HatchLine
pub struct HatchLines {
    pub core: ObjectCore, // HatchType::HatchLine
    pub hatch_line: ArrayOf<HatchLine>,
}

#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct Hatches {
    pub core: ObjectCore, // HatchType::HatchLine
    pub hatch_lines: ArrayOf<HatchLines>,
}

impl Hatches {
    pub fn set_pen(&mut self, pen: u32) {
        // Ignore self.core.pen
        self.hatch_lines.iter_mut().for_each(|x| {
            // Ignore HatchLines.core.pen
            x.hatch_line.iter_mut().for_each(|x| {
                x.lines.iter_mut().for_each(|x| {
                    x.core.pen = pen.into();
                })
            })
        });
    }
}

#[binrw]
#[derive(Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct Hatch {
    pub core: ObjectCore,
    pub outline: ArrayOf<Object>,
    pub legacy_setting: LegacyHatchSetting,

    #[br(temp)]
    #[bw(try_calc(u32::try_from(hatch_settings.len()).unwrap().try_into()))]
    pub num_settings: U32,

    pub _unknown_1: Field,

    #[br(count = num_settings.value)]
    pub hatch_settings: Vec<HatchSetting>,

    #[brw(if(legacy_setting.any_hatch_enabled.value == 1))]
    pub hatches: Option<Hatches>,
}

impl Display for Hatch {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.core)?;
        for object in self.outline.iter() {
            write!(f, "\nOutline: {}", object)?;
        }
        write!(
            f,
            "\nMark Contour: {}, Contour Priority: {}",
            *self.legacy_setting.mark_contour != 0,
            *self.legacy_setting.contour_priority != 0
        )?;
        for (index, setting) in self.hatch_settings.iter().enumerate() {
            if setting.enabled.into() {
                write!(
                    f,
                    "\nHatch #{}: All Calc: {}, Follow Edge Once: {}, Cross Hatch: {}, Pattern: {}, Angle: {:.FP$}, Pen: {}, Count: {}, Line Space: {:.FP$}, Avg Distribte Line: {}",
                    index,
                    setting.flags.all_calc() != 0,
                    setting.flags.follow_edge_once() != 0,
                    setting.flags.cross_hatch() != 0,
                    HatchPattern::from(*setting.flags),
                    *setting.angle,
                    *setting.pen,
                    *setting.count,
                    *setting.line_spacing,
                    setting.flags.average_distribute_line() != 0,
                )?;
                write!(
                    f,
                    "\n          Edge Offset: {:.FP$}, Start Offset: {:.FP$}, End Offset: {:.FP$}, Line Reduction: {:.FP$}, Loop Count: {}, Loop Distance: {:.FP$}, Auto Rotate: {}, Auto Rotate Angle: {:.FP$}",
                    *setting.edge_offset,
                    *setting.start_offset,
                    *setting.end_offset,
                    *setting.line_reduction,
                    *setting.loop_count,
                    *setting.loop_distance,
                    setting.flags.auto_rotate_hatch_angle() != 0,
                    *setting.rotate_angle,
                )?;
            }
        }
        Ok(())
    }
}

impl Translate for Hatch {
    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
        self.outline
            .iter_mut()
            .for_each(|x| x.move_absolute(origin, z));
        origin.map(|origin| {
            let delta: Point = origin - *self.core.origin;
            self.hatches.iter_mut().for_each(|x| {
                // Ignore Hatches.core.origin
                x.hatch_lines.iter_mut().for_each(|x| {
                    // Ignore HatchLines.core.origin
                    x.hatch_line.iter_mut().for_each(|x| {
                        x.lines
                            .iter_mut()
                            .for_each(|x| x.move_relative(Some(delta), None));
                    })
                })
            });
        });
        self.core.move_absolute(origin, z);
    }

    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
        self.outline
            .iter_mut()
            .for_each(|x| x.move_relative(delta, z));
        delta.map(|delta| {
            self.hatches.iter_mut().for_each(|x| {
                // Ignore Hatches.core.origin
                x.hatch_lines.iter_mut().for_each(|x| {
                    // Ignore HatchLines.core.origin
                    x.hatch_line.iter_mut().for_each(|x| {
                        x.lines
                            .iter_mut()
                            .for_each(|x| x.move_relative(Some(delta), None));
                    })
                })
            });
        });
        self.core.move_relative(delta, z);
    }
}

impl Hatch {
    /// Estimate the power density from the hatch's line spacing and pen speed/frequency/power/pulse
    pub fn calc_power_density(&self, pens: &Vec<Pen>) -> u64 {
        let pen: &Pen = pens
            .get(*self.core.pen as usize)
            .expect("Invalid pen index");
        let line_spacing: f64 = *self
            .hatch_settings
            .iter()
            .find(|h| h.enabled.into())
            .expect("Hatch object does not have enabled settings")
            .line_spacing;

        (1.0 / line_spacing) as u64 * pen.power_density()
    }
}
