use std::{
    fmt::{Debug, Display},
    fs::File,
    io::{Cursor, Read, Write},
    path::PathBuf,
};

use binrw::{BinRead, BinWrite, BinWriterExt};
use diff::Diff;
use itertools::Itertools;
use log::debug;
use modular_bitfield::{
    bitfield,
    specifiers::{B1, B14},
};

use crate::{
    field_of::FieldOf,
    types::{Field, ObjectType, Point, WString, F64, U16, U32},
    FP,
};

use self::{
    circle::Circle, ellipse::Ellipse, hatch::Hatch, line::Lines, polygon::Polygon,
    rectangle::Rectangle,
};

pub mod circle;
pub mod ellipse;
pub mod hatch;
pub mod line;
pub mod polygon;
pub mod rectangle;

pub trait Translate {
    /// Move origin by absolute coordinates
    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>);
    /// Move origin by relative coordinates
    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>);
}

#[bitfield(bits = 16)]
#[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 ObjectFlags {
    pub disabled: B1,
    pub aspect_ratio_unlocked: B1,
    #[skip]
    __: B14,
}

/// Core properties defined for all object types
#[cfg_attr(feature = "default-debug", derive(Debug))]
#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
#[brw(magic(17u32))]
pub struct ObjectCore {
    pub pen: U32,
    pub obj_type: FieldOf<ObjectType>,
    pub flags: FieldOf<ObjectFlags>,
    pub name: WString,
    pub count: U32,
    pub _unknown_1: Field,
    pub io_control_enable_mask: U16,
    pub io_control_disable_mask: U16,
    pub _unknown_2: [Field; 5],
    pub origin: FieldOf<Point>,
    pub z: F64,
    pub a: F64,
    pub index: U32, // Increments for each hatch fill line (types 1/2) or 0xFFFF_FFFF (types 3/4/5/7/8)
}

impl Display for ObjectCore {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Type: {}, Enabled: {}, Pen: {}, Count: {}, Z: {:.FP$}",
            self.obj_type,
            (self.flags.disabled() == 0),
            self.pen,
            self.count,
            self.z
        )
    }
}

// Custom Debug implementation to only print known fields
#[cfg(not(feature = "default-debug"))]
impl Debug for ObjectCore {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ObjectCore")
            .field("pen", &self.pen)
            .field("obj_type", &self.obj_type)
            .field("flags", &self.flags)
            .field("name", &self.name)
            .field("count", &self.count)
            .field("io_control_enable_mask", &self.io_control_enable_mask)
            .field("io_control_disable_mask", &self.io_control_disable_mask)
            .field("origin", &self.origin)
            .field("z", &self.z)
            .field("a", &self.a)
            .field("index", &self.index)
            .finish()
    }
}

impl Translate for ObjectCore {
    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
        origin.map(|origin| *self.origin = origin);
        z.map(|z| self.z = z.into());
    }

    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
        delta.map(|delta| *self.origin += delta);
        z.map(|z| *self.z += z);
    }
}

impl ObjectCore {
    pub fn default(obj_type: ObjectType) -> Self {
        Self {
            pen: 0.into(),
            obj_type: obj_type.into(),
            flags: ObjectFlags::new()
                .with_disabled(0)
                .with_aspect_ratio_unlocked(0)
                .into(),
            name: "\0".to_string().into(),
            count: 1.into(),
            _unknown_1: vec![0, 0].into(), // 0_u16
            io_control_enable_mask: Default::default(),
            io_control_disable_mask: Default::default(),
            _unknown_2: [
                vec![0, 0].into(),                     // 0_u16
                vec![1, 0, 0, 0].into(),               // 1_u32
                vec![1, 0, 0, 0].into(),               // 1_u32
                vec![0, 0, 0, 0, 0, 0, 36, 64].into(), // 10.0_f64
                vec![0, 0, 0, 0, 0, 0, 36, 64].into(), // 10.0_f64
            ]
            .into(),
            origin: Point { x: 0.0, y: 0.0 }.into(),
            z: 0.0.into(),
            a: 0.0.into(),
            index: 0.into(),
        }
    }
}

#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub enum Object {
    #[brw(magic = 1u32)]
    Curve(Lines),
    #[brw(magic = 2u32)]
    Point(Lines),
    #[brw(magic = 3u32)]
    Rectangle(Rectangle),
    #[brw(magic = 4u32)]
    Circle(Circle),
    #[brw(magic = 5u32)]
    Ellipse(Ellipse),
    #[brw(magic = 6u32)]
    Polygon(Polygon),
    #[brw(magic = 32u32)]
    Hatch(Hatch),
}

impl Display for Object {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // write!(f, "{}", self.core)
        match self {
            Object::Curve(x) => write!(f, "{x}"),
            Object::Point(x) => write!(f, "{x}"),
            Object::Rectangle(x) => write!(f, "{x}"),
            Object::Circle(x) => write!(f, "{x}"),
            Object::Ellipse(x) => write!(f, "{x}"),
            Object::Polygon(x) => write!(f, "{x}"),
            Object::Hatch(x) => write!(f, "{x}"),
        }
    }
}

impl Translate for Object {
    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
        match self {
            Object::Curve(x) => x.move_absolute(origin, z),
            Object::Point(x) => x.move_absolute(origin, z),
            Object::Rectangle(x) => x.move_absolute(origin, z),
            Object::Circle(x) => x.move_absolute(origin, z),
            Object::Ellipse(x) => x.move_absolute(origin, z),
            Object::Polygon(x) => x.move_absolute(origin, z),
            Object::Hatch(x) => x.move_absolute(origin, z),
        }
    }

    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
        match self {
            Object::Curve(x) => x.move_relative(delta, z),
            Object::Point(x) => x.move_relative(delta, z),
            Object::Rectangle(x) => x.move_relative(delta, z),
            Object::Circle(x) => x.move_relative(delta, z),
            Object::Ellipse(x) => x.move_relative(delta, z),
            Object::Polygon(x) => x.move_relative(delta, z),
            Object::Hatch(x) => x.move_relative(delta, z),
        }
    }
}

impl Object {
    pub fn read_from_file(path: &PathBuf) -> Self {
        let mut input: File = File::open(path).expect("Failed to open input file");

        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
        input
            .read_to_end(buffer.get_mut())
            .expect("Failed to read input file");

        Object::read_le(&mut buffer).expect("Failed to deserialize input as object")
    }

    pub fn write_to_file(&self, path: &PathBuf) {
        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
        buffer.write_le(self).expect("Failed to serialize object");

        let mut output: File = File::create(path).expect("Failed to open output file");
        output
            .write_all(buffer.into_inner().as_slice())
            .expect("Failed to write to output file");
    }

    pub fn set_pen(&mut self, pen: u32) {
        match self {
            Object::Curve(x) => x.core.pen = pen.into(),
            Object::Point(x) => x.core.pen = pen.into(),
            Object::Rectangle(x) => x.core.pen = pen.into(),
            Object::Circle(x) => x.core.pen = pen.into(),
            Object::Ellipse(x) => x.core.pen = pen.into(),
            Object::Polygon(x) => x.core.pen = pen.into(),
            Object::Hatch(x) => {
                x.core.pen = pen.into();
                x.hatch_settings.iter_mut().for_each(|y| y.pen = pen.into());
                x.legacy_setting.hatch_0_pen = pen.into();
                x.legacy_setting.hatch_1_pen = pen.into();
                x.legacy_setting.hatch_2_pen = pen.into();
                x.hatches.as_mut().map(|x| x.set_pen(pen));
            }
        }
    }

    pub fn core_mut(&mut self) -> &mut ObjectCore {
        match self {
            Object::Curve(x) => &mut x.core,
            Object::Point(x) => &mut x.core,
            Object::Rectangle(x) => &mut x.core,
            Object::Circle(x) => &mut x.core,
            Object::Ellipse(x) => &mut x.core,
            Object::Polygon(x) => &mut x.core,
            Object::Hatch(x) => &mut x.core,
        }
    }

    pub fn core(&self) -> &ObjectCore {
        match self {
            Object::Curve(x) => &x.core,
            Object::Point(x) => &x.core,
            Object::Rectangle(x) => &x.core,
            Object::Circle(x) => &x.core,
            Object::Ellipse(x) => &x.core,
            Object::Polygon(x) => &x.core,
            Object::Hatch(x) => &x.core,
        }
    }
}

#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct ObjectModifier {
    pub default: U32, // 0 if values are default, 255 otherwise
    pub modifiers: FieldOf<ScaleValues>,
}

impl ObjectModifier {
    /// Apply relative move to origin correction vector
    pub fn move_relative(&mut self, delta: Point) {
        self.default = 255.into();
        self.modifiers.translate += delta;
    }

    /// Return correction to offset of object
    pub fn translation(&self) -> Point {
        self.modifiers.translate
    }

    /// Returns scaling vector of object
    pub fn scale(&self) -> Point {
        Point {
            x: self.modifiers.x_scale,
            y: self.modifiers.y_scale,
        }
    }

    /// Returns skewing vector of object
    pub fn skew(&self) -> Point {
        Point {
            x: self.modifiers.y_skew.asin(), // Radians from X axis
            y: self.modifiers.x_skew.asin(), // Radians from Y axis
        }
    }

    /// Returns origin, width, and height of bounding box after applying scale, skew, and  offset corrections
    pub fn corrected(&self, pt1: Point, pt2: Point) -> (Point, f64, f64) {
        // Calculate 4 points of drawn rectangle
        let corners: Vec<Point> = vec![
            Point {
                x: pt1.min(pt2).x,
                y: pt1.min(pt2).y,
            },
            Point {
                x: pt1.min(pt2).x,
                y: pt1.max(pt2).y,
            },
            Point {
                x: pt1.max(pt2).x,
                y: pt1.max(pt2).y,
            },
            Point {
                x: pt1.max(pt2).x,
                y: pt1.min(pt2).y,
            },
        ];

        debug!(
            "Drawn points: {}",
            corners
                .iter()
                .map(|x| format!("{x}"))
                .collect_vec()
                .join(", ")
        );

        // Apply skew, then scale and translation
        let corners: Vec<Point> = corners
            .iter()
            .map(|pt| {
                // Skewing along two axis is differenet from rotation, but how to handle
                // both skewing of an axis combined with a rotation?
                if self.modifiers.x_skew == 0.0 || self.modifiers.y_skew == 0.0 {
                    Point {
                        x: pt.x + pt.y * self.modifiers.x_skew,
                        y: pt.y + pt.x * self.modifiers.y_skew,
                    }
                } else {
                    Point {
                        x: pt.x + pt.y * self.modifiers.x_skew.asin().tan(),
                        y: pt.y + pt.x * self.modifiers.y_skew.asin().tan(),
                    }
                }
            })
            .inspect(|pt| debug!("pt: {pt}"))
            .map(|pt| pt * self.scale() + self.translation())
            .collect_vec();

        debug!(
            "Corrected points: {}",
            corners
                .iter()
                .map(|x| format!("{x}"))
                .collect_vec()
                .join(", ")
        );

        // Calculate resulting bounds
        let min_x: f64 = corners.iter().fold(f64::MAX, |acc, pt| acc.min(pt.x));
        let max_x: f64 = corners.iter().fold(f64::MIN, |acc, pt| acc.max(pt.x));
        let min_y: f64 = corners.iter().fold(f64::MAX, |acc, pt| acc.min(pt.y));
        let max_y: f64 = corners.iter().fold(f64::MIN, |acc, pt| acc.max(pt.y));

        // Compute min/max bounding box
        let bound_a: Point = Point { x: min_x, y: min_y };
        let bound_b: Point = Point { x: max_x, y: max_y };

        debug!("Bounding box: {}, {}", bound_a, bound_b);

        // Calculate origin of bounding box
        let origin: Point = (bound_a + bound_b) / Point::from(2.0);

        // Calculate width and height of bounding box
        let Point {
            x: width,
            y: height,
        } = bound_b - bound_a;

        (origin, width, height)
    }
}

impl Default for ObjectModifier {
    fn default() -> Self {
        Self {
            default: 0.into(),
            modifiers: ScaleValues::default().into(),
        }
    }
}

#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
#[diff(attr(
    #[derive(Debug, PartialEq)]
))]
pub struct ScaleValues {
    pub x_scale: f64, // X scaled ratio on drawn object
    pub y_skew: f64,
    #[br(assert(_unknown_2 == 0.0))]
    _unknown_2: f64,
    pub x_skew: f64,
    pub y_scale: f64, // Y scaled ratio on drawn object
    #[br(assert(_unknown_4 == 0.0))]
    _unknown_4: f64,
    translate: Point, // Correction factor due to scaling
    #[br(assert(_unknown_5 == 1.0))]
    _unknown_5: f64,
}

impl Default for ScaleValues {
    fn default() -> Self {
        Self {
            x_scale: 1.0.into(),
            y_skew: 0.0.into(),
            _unknown_2: 0.0.into(),
            x_skew: 0.0.into(),
            y_scale: 1.0.into(),
            _unknown_4: 0.0.into(),
            translate: (0.0, 0.0).into(),
            _unknown_5: 1.0.into(),
        }
    }
}

// Custom Debug implementation to only print known fields
impl Debug for ScaleValues {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ScaleValues")
            .field("x_scale", &self.x_scale)
            .field("y_scale", &self.y_scale)
            .field("x_skew", &self.x_skew)
            .field("y_skew", &self.y_skew)
            .field("translate", &self.translate)
            .finish()
    }
}
