use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PaperOrientation {
    Portrait,
    Landscape,
}

impl Default for PaperOrientation {
    fn default() -> Self {
        PaperOrientation::Portrait
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PaperUnit {
    Inches,
    Millimeters,
    Pixels,
}

impl Default for PaperUnit {
    fn default() -> Self {
        PaperUnit::Millimeters
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PaperSize {
    A0,
    A1,
    A2,
    A3,
    A4,
    A5,
    Letter,
    Legal,
    Tabloid,
    ArchA,
    ArchB,
    ArchC,
    ArchD,
    ArchE,
    Custom,
}

impl PaperSize {
    #[inline]
    pub fn width_mm(&self) -> f64 {
        match self {
            PaperSize::A0 => 841.0,
            PaperSize::A1 => 594.0,
            PaperSize::A2 => 420.0,
            PaperSize::A3 => 297.0,
            PaperSize::A4 => 210.0,
            PaperSize::A5 => 148.0,
            PaperSize::Letter => 215.9,
            PaperSize::Legal => 215.9,
            PaperSize::Tabloid => 279.4,
            PaperSize::ArchA => 228.6,
            PaperSize::ArchB => 304.8,
            PaperSize::ArchC => 457.2,
            PaperSize::ArchD => 609.6,
            PaperSize::ArchE => 914.4,
            PaperSize::Custom => 210.0,
        }
    }

    #[inline]
    pub fn height_mm(&self) -> f64 {
        match self {
            PaperSize::A0 => 1189.0,
            PaperSize::A1 => 841.0,
            PaperSize::A2 => 594.0,
            PaperSize::A3 => 420.0,
            PaperSize::A4 => 297.0,
            PaperSize::A5 => 210.0,
            PaperSize::Letter => 279.4,
            PaperSize::Legal => 355.6,
            PaperSize::Tabloid => 431.8,
            PaperSize::ArchA => 304.8,
            PaperSize::ArchB => 457.2,
            PaperSize::ArchC => 609.6,
            PaperSize::ArchD => 914.4,
            PaperSize::ArchE => 1219.2,
            PaperSize::Custom => 297.0,
        }
    }

    #[inline]
    pub fn name(&self) -> String {
        match self {
            PaperSize::A0 => "ISO A0 (841 x 1189 mm)".to_string(),
            PaperSize::A1 => "ISO A1 (594 x 841 mm)".to_string(),
            PaperSize::A2 => "ISO A2 (420 x 594 mm)".to_string(),
            PaperSize::A3 => "ISO A3 (297 x 420 mm)".to_string(),
            PaperSize::A4 => "ISO A4 (210 x 297 mm)".to_string(),
            PaperSize::A5 => "ISO A5 (148 x 210 mm)".to_string(),
            PaperSize::Letter => "Letter (8.5 x 11 in)".to_string(),
            PaperSize::Legal => "Legal (8.5 x 14 in)".to_string(),
            PaperSize::Tabloid => "Tabloid (11 x 17 in)".to_string(),
            PaperSize::ArchA => "Arch A (9 x 12 in)".to_string(),
            PaperSize::ArchB => "Arch B (12 x 18 in)".to_string(),
            PaperSize::ArchC => "Arch C (18 x 24 in)".to_string(),
            PaperSize::ArchD => "Arch D (24 x 36 in)".to_string(),
            PaperSize::ArchE => "Arch E (36 x 48 in)".to_string(),
            PaperSize::Custom => "Custom".to_string(),
        }
    }
}

impl Default for PaperSize {
    fn default() -> Self {
        PaperSize::A4
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PlotRotation {
    Degrees0,
    Degrees90,
    Degrees180,
    Degrees270,
}

impl Default for PlotRotation {
    fn default() -> Self {
        PlotRotation::Degrees0
    }
}

impl PlotRotation {
    #[inline]
    pub fn from_degrees(degrees: f64) -> Self {
        let normalized = degrees % 360.0;
        if normalized >= -45.0 && normalized < 45.0 {
            PlotRotation::Degrees0
        } else if normalized >= 45.0 && normalized < 135.0 {
            PlotRotation::Degrees90
        } else if normalized >= 135.0 || normalized < -135.0 {
            PlotRotation::Degrees180
        } else {
            PlotRotation::Degrees270
        }
    }

    #[inline]
    pub fn to_degrees(&self) -> f64 {
        match self {
            PlotRotation::Degrees0 => 0.0,
            PlotRotation::Degrees90 => 90.0,
            PlotRotation::Degrees180 => 180.0,
            PlotRotation::Degrees270 => 270.0,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PlotType {
    Layout,
    Extents,
    Limits,
    View,
    Window,
    Display,
}

impl Default for PlotType {
    fn default() -> Self {
        PlotType::Layout
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ShadePlotMode {
    AsDisplayed,
    Wireframe,
    Hidden,
    Rendered,
}

impl Default for ShadePlotMode {
    fn default() -> Self {
        ShadePlotMode::AsDisplayed
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScaleType {
    ScaleToFit,
    Scale1To128,
    Scale1To64,
    Scale1To32,
    Scale1To16,
    Scale3To32,
    Scale1To8,
    Scale3To16,
    Scale1To4,
    Scale3To8,
    Scale1To2,
    Scale3To4,
    Scale1To1,
    Scale3To2,
    Scale2To1,
    Custom,
}

impl ScaleType {
    #[inline]
    pub fn from_ratio(numerator: f64, denominator: f64) -> Self {
        let ratio = numerator / denominator;
        let tolerance = 0.001;

        if (ratio - 1.0).abs() < tolerance {
            ScaleType::ScaleToFit
        } else if (ratio - 1.0 / 128.0).abs() < tolerance {
            ScaleType::Scale1To128
        } else if (ratio - 1.0 / 64.0).abs() < tolerance {
            ScaleType::Scale1To64
        } else if (ratio - 1.0 / 32.0).abs() < tolerance {
            ScaleType::Scale1To32
        } else if (ratio - 1.0 / 16.0).abs() < tolerance {
            ScaleType::Scale1To16
        } else if (ratio - 3.0 / 32.0).abs() < tolerance {
            ScaleType::Scale3To32
        } else if (ratio - 1.0 / 8.0).abs() < tolerance {
            ScaleType::Scale1To8
        } else if (ratio - 3.0 / 16.0).abs() < tolerance {
            ScaleType::Scale3To16
        } else if (ratio - 1.0 / 4.0).abs() < tolerance {
            ScaleType::Scale1To4
        } else if (ratio - 3.0 / 8.0).abs() < tolerance {
            ScaleType::Scale3To8
        } else if (ratio - 1.0 / 2.0).abs() < tolerance {
            ScaleType::Scale1To2
        } else if (ratio - 3.0 / 4.0).abs() < tolerance {
            ScaleType::Scale3To4
        } else if (ratio - 1.0).abs() < tolerance {
            ScaleType::Scale1To1
        } else if (ratio - 3.0 / 2.0).abs() < tolerance {
            ScaleType::Scale3To2
        } else if (ratio - 2.0).abs() < tolerance {
            ScaleType::Scale2To1
        } else {
            ScaleType::Custom
        }
    }

    #[inline]
    pub fn scale_factor(&self) -> (f64, f64) {
        match self {
            ScaleType::ScaleToFit => (1.0, 1.0),
            ScaleType::Scale1To128 => (1.0, 128.0),
            ScaleType::Scale1To64 => (1.0, 64.0),
            ScaleType::Scale1To32 => (1.0, 32.0),
            ScaleType::Scale1To16 => (1.0, 16.0),
            ScaleType::Scale3To32 => (3.0, 32.0),
            ScaleType::Scale1To8 => (1.0, 8.0),
            ScaleType::Scale3To16 => (3.0, 16.0),
            ScaleType::Scale1To4 => (1.0, 4.0),
            ScaleType::Scale3To8 => (3.0, 8.0),
            ScaleType::Scale1To2 => (1.0, 2.0),
            ScaleType::Scale3To4 => (3.0, 4.0),
            ScaleType::Scale1To1 => (1.0, 1.0),
            ScaleType::Scale3To2 => (3.0, 2.0),
            ScaleType::Scale2To1 => (2.0, 1.0),
            ScaleType::Custom => (1.0, 1.0),
        }
    }

    #[inline]
    pub fn as_ratio(&self) -> String {
        let (num, den) = self.scale_factor();
        match self {
            ScaleType::ScaleToFit => "Scale to fit".to_string(),
            ScaleType::Custom => format!("{}:{}", num, den),
            _ => format!("1:{}", den as u32),
        }
    }
}

impl Default for ScaleType {
    fn default() -> Self {
        ScaleType::ScaleToFit
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineCapStyle {
    Butt,
    Round,
    Square,
}

impl Default for LineCapStyle {
    fn default() -> Self {
        LineCapStyle::Round
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineJoinStyle {
    Miter,
    Round,
    Bevel,
}

impl Default for LineJoinStyle {
    fn default() -> Self {
        LineJoinStyle::Round
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FillMode {
    UseColor,
    UseObject,
    Outlined,
    Tiled,
    Solid,
}

impl Default for FillMode {
    fn default() -> Self {
        FillMode::UseObject
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlotStyle {
    pub name: String,
    pub color: Option<u32>,
    pub grayscale: bool,
    pub screening: u8,
    pub end_cap_style: LineCapStyle,
    pub join_style: LineJoinStyle,
    pub lineweight: super::line_type::Lineweight,
    pub linetype_name: String,
    pub fill_mode: FillMode,
    pub style_name: String,
}

impl Default for PlotStyle {
    fn default() -> Self {
        Self {
            name: "Normal".to_string(),
            color: None,
            grayscale: false,
            screening: 100,
            end_cap_style: LineCapStyle::Round,
            join_style: LineJoinStyle::Round,
            lineweight: super::line_type::Lineweight::Default,
            linetype_name: "Continuous".to_string(),
            fill_mode: FillMode::UseObject,
            style_name: "Normal".to_string(),
        }
    }
}

impl PlotStyle {
    #[inline]
    pub fn from_color(color: u32) -> Self {
        Self {
            name: format!("Color {}", color),
            color: Some(color),
            ..Default::default()
        }
    }

    #[inline]
    pub fn is_override(&self) -> bool {
        self.color.is_some() ||
        self.lineweight != super::line_type::Lineweight::ByBlock ||
        !self.linetype_name.is_empty()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlotStyleTable {
    pub name: String,
    pub description: String,
    pub styles: Vec<PlotStyle>,
}

impl Default for PlotStyleTable {
    fn default() -> Self {
        let mut table = Self {
            name: "acad".to_string(),
            description: "AutoCAD 打印样式表".to_string(),
            styles: Vec::new(),
        };
        for color in 0..=255 {
            table.styles.push(PlotStyle::from_color(color));
        }
        table
    }
}

impl PlotStyleTable {
    #[inline]
    pub fn by_color(&self, color: u32) -> Option<&PlotStyle> {
        self.styles.get(color as usize)
    }

    #[inline]
    pub fn by_color_mut(&mut self, color: u32) -> Option<&mut PlotStyle> {
        self.styles.get_mut(color as usize)
    }

    #[inline]
    pub fn add_style(&mut self, style: PlotStyle) {
        self.styles.push(style);
    }

    #[inline]
    pub fn remove_style(&mut self, index: usize) -> bool {
        if index < self.styles.len() {
            self.styles.remove(index);
            true
        } else {
            false
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlotWindowArea {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

impl Default for PlotWindowArea {
    fn default() -> Self {
        Self {
            x: 0.0,
            y: 0.0,
            width: 210.0,
            height: 297.0,
        }
    }
}

impl PlotWindowArea {
    #[inline]
    pub fn from_points(p1: super::geometry::Point, p2: super::geometry::Point) -> Self {
        let min_x = p1.x.min(p2.x);
        let min_y = p1.y.min(p2.y);
        let max_x = p1.x.max(p2.x);
        let max_y = p1.y.max(p2.y);
        Self {
            x: min_x,
            y: min_y,
            width: max_x - min_x,
            height: max_y - min_y,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlotSettings {
    pub plot_device: String,
    pub paper_size: PaperSize,
    pub canonical_media_name: String,
    pub plot_origin: (f64, f64),
    pub plot_window_area: PlotWindowArea,
    pub paper_units: PaperUnit,
    pub plot_rotation: PlotRotation,
    pub plot_type: PlotType,
    pub current_style_sheet: String,
    pub shade_plot_mode: ShadePlotMode,
    pub scale_type: ScaleType,
    pub scale_factor: f64,
    pub custom_scale: (f64, f64),
    pub print_lineweights: bool,
    pub scale_lineweights_with_plot: bool,
    pub plot_with_plot_styles: bool,
    pub plot_with_orig_lineweights: bool,
    pub plot_object_lineweights: bool,
}

impl Default for PlotSettings {
    fn default() -> Self {
        Self {
            plot_device: "None".to_string(),
            paper_size: PaperSize::A4,
            canonical_media_name: "A4 (210.00 x 297.00 mm)".to_string(),
            plot_origin: (5.0, 5.0),
            plot_window_area: PlotWindowArea::default(),
            paper_units: PaperUnit::Millimeters,
            plot_rotation: PlotRotation::Degrees0,
            plot_type: PlotType::Layout,
            current_style_sheet: "acad.stb".to_string(),
            shade_plot_mode: ShadePlotMode::AsDisplayed,
            scale_type: ScaleType::ScaleToFit,
            scale_factor: 1.0,
            custom_scale: (1.0, 1.0),
            print_lineweights: false,
            scale_lineweights_with_plot: false,
            plot_with_plot_styles: true,
            plot_with_orig_lineweights: false,
            plot_object_lineweights: true,
        }
    }
}

impl PlotSettings {
    #[inline]
    pub fn set_scale(&mut self, scale_type: ScaleType, custom: Option<(f64, f64)>) {
        self.scale_type = scale_type;
        if let Some(custom_scale) = custom {
            self.custom_scale = custom_scale;
        }
        let (num, den) = scale_type.scale_factor();
        self.scale_factor = if den > 0.0 { num / den } else { 1.0 };
    }

    #[inline]
    pub fn effective_scale(&self) -> f64 {
        match self.scale_type {
            ScaleType::ScaleToFit => self.scale_factor,
            ScaleType::Custom => {
                if self.custom_scale.1 != 0.0 {
                    self.custom_scale.0 / self.custom_scale.1
                } else {
                    1.0
                }
            }
            _ => {
                let (num, den) = self.scale_type.scale_factor();
                if den != 0.0 { num / den } else { 1.0 }
            }
        }
    }

    #[inline]
    pub fn paper_to_model_scale(&self) -> f64 {
        self.effective_scale()
    }

    #[inline]
    pub fn model_to_paper_length(&self, model_length: f64) -> f64 {
        model_length / self.effective_scale()
    }

    #[inline]
    pub fn paper_to_model_length(&self, paper_length: f64) -> f64 {
        paper_length * self.effective_scale()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Layout {
    pub name: String,
    pub paper_size: PaperSize,
    pub margins: (f64, f64, f64, f64),
    pub printable_area: (f64, f64),
    pub orientation: PaperOrientation,
    pub plot_style: Option<String>,
    pub canonical_media_name: String,
    pub tab_order: u32,
    pub is_current: bool,
    pub is_plottable: bool,
}

impl Default for Layout {
    fn default() -> Self {
        Self::new("Layout1")
    }
}

impl Layout {
    #[inline]
    pub fn new(name: &str) -> Self {
        let paper_size = PaperSize::A4;
        let margins = (5.0, 5.0, 5.0, 5.0);
        let printable_width = paper_size.width_mm() - margins.0 - margins.1;
        let printable_height = paper_size.height_mm() - margins.2 - margins.3;

        Self {
            name: name.to_string(),
            paper_size,
            margins,
            printable_area: (printable_width, printable_height),
            orientation: PaperOrientation::Portrait,
            plot_style: None,
            canonical_media_name: paper_size.name(),
            tab_order: 1,
            is_current: false,
            is_plottable: true,
        }
    }

    #[inline]
    pub fn set_paper_size(&mut self, size: PaperSize) {
        self.paper_size = size;
        self.update_printable_area();
    }

    #[inline]
    pub fn set_margins(&mut self, left: f64, right: f64, top: f64, bottom: f64) {
        self.margins = (left, right, top, bottom);
        self.update_printable_area();
    }

    #[inline]
    pub fn set_orientation(&mut self, orientation: PaperOrientation) {
        self.orientation = orientation;
    }

    fn update_printable_area(&mut self) {
        let width = self.paper_size.width_mm() - self.margins.0 - self.margins.1;
        let height = self.paper_size.height_mm() - self.margins.2 - self.margins.3;
        self.printable_area = (width.max(0.0), height.max(0.0));
    }

    #[inline]
    pub fn paper_width(&self) -> f64 {
        match self.orientation {
            PaperOrientation::Portrait => self.paper_size.width_mm(),
            PaperOrientation::Landscape => self.paper_size.height_mm(),
        }
    }

    #[inline]
    pub fn paper_height(&self) -> f64 {
        match self.orientation {
            PaperOrientation::Portrait => self.paper_size.height_mm(),
            PaperOrientation::Landscape => self.paper_size.width_mm(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Viewport {
    pub center: super::geometry::Point,
    pub width: f64,
    pub height: f64,
    pub view_center: super::geometry::Point,
    pub view_height: f64,
    pub view_scale: f64,
    pub snap_base: super::geometry::Point,
    pub snap_spacing: (f64, f64),
    pub grid_spacing: (f64, f64),
    pub is_grid_display: bool,
    pub is_grid_snap: bool,
    pub is_locked: bool,
    pub display_locked: bool,
    pub id: u32,
}

impl Default for Viewport {
    fn default() -> Self {
        Self::new()
    }
}

impl Viewport {
    #[inline]
    pub fn new() -> Self {
        Self {
            center: super::geometry::Point::origin(),
            width: 100.0,
            height: 100.0,
            view_center: super::geometry::Point::origin(),
            view_height: 100.0,
            view_scale: 1.0,
            snap_base: super::geometry::Point::origin(),
            snap_spacing: (1.0, 1.0),
            grid_spacing: (10.0, 10.0),
            is_grid_display: false,
            is_grid_snap: false,
            is_locked: false,
            display_locked: false,
            id: 0,
        }
    }

    #[inline]
    pub fn zoom_to_extents(&mut self, extents: &(super::geometry::Point, super::geometry::Point)) {
        let width = (extents.1.x - extents.0.x).abs();
        let height = (extents.1.y - extents.0.y).abs();
        let center_x = (extents.0.x + extents.1.x) / 2.0;
        let center_y = (extents.0.y + extents.1.y) / 2.0;

        self.view_center = super::geometry::Point::new(center_x, center_y, 0.0);
        self.view_height = height * 1.1;

        if width / height > self.width / self.height {
            self.view_height = width * self.height / self.width / 0.9;
        }
    }

    #[inline]
    pub fn zoom(&mut self, factor: f64) {
        self.view_height /= factor;
        self.view_scale /= factor;
    }

    #[inline]
    pub fn pan(&mut self, delta_x: f64, delta_y: f64) {
        self.view_center.x += delta_x / self.view_scale;
        self.view_center.y += delta_y / self.view_scale;
    }

    #[inline]
    pub fn set_scale(&mut self, scale: f64) {
        self.view_scale = scale;
    }

    #[inline]
    pub fn border_points(&self) -> (super::geometry::Point, super::geometry::Point) {
        let half_width = self.width / 2.0;
        let half_height = self.height / 2.0;
        (
            super::geometry::Point::new(
                self.center.x - half_width,
                self.center.y - half_height,
                0.0,
            ),
            super::geometry::Point::new(
                self.center.x + half_width,
                self.center.y + half_height,
                0.0,
            ),
        )
    }

    #[inline]
    pub fn contains(&self, point: &super::geometry::Point) -> bool {
        let (min, max) = self.border_points();
        point.x >= min.x && point.x <= max.x && point.y >= min.y && point.y <= max.y
    }
}

#[derive(Debug, Clone)]
pub struct LayoutManager {
    layouts: Vec<Layout>,
    model_space: Layout,
    current_layout: usize,
    viewports: std::collections::HashMap<usize, Viewport>,
    active_viewport: u32,
    next_viewport_id: u32,
}

impl Default for LayoutManager {
    fn default() -> Self {
        Self::new()
    }
}

impl LayoutManager {
    #[inline]
    pub fn new() -> Self {
        let mut manager = Self {
            layouts: Vec::new(),
            model_space: Layout::new("Model"),
            current_layout: usize::MAX,
            viewports: std::collections::HashMap::new(),
            active_viewport: 0,
            next_viewport_id: 1,
        };
        manager.model_space.is_plottable = false;
        manager.layouts.push(Layout::new("Layout1"));
        manager.layouts.push(Layout::new("Layout2"));
        manager.set_current(0);
        manager
    }

    #[inline]
    pub fn add_layout(&mut self, name: &str) -> usize {
        let layout = Layout::new(name);
        let index = self.layouts.len();
        self.layouts.push(layout);
        self.set_current(index);
        index
    }

    #[inline]
    pub fn remove_layout(&mut self, index: usize) -> bool {
        if index >= self.layouts.len() || index == self.current_layout {
            return false;
        }
        self.layouts.remove(index);
        if self.current_layout > index {
            self.current_layout -= 1;
        }
        true
    }

    #[inline]
    pub fn set_current(&mut self, index: usize) -> bool {
        if index < self.layouts.len() {
            self.current_layout = index;
            self.layouts[index].is_current = true;
            true
        } else {
            false
        }
    }

    #[inline]
    pub fn current(&self) -> &Layout {
        if self.current_layout < self.layouts.len() {
            &self.layouts[self.current_layout]
        } else {
            &self.model_space
        }
    }

    #[inline]
    pub fn current_mut(&mut self) -> &mut Layout {
        if self.current_layout < self.layouts.len() {
            &mut self.layouts[self.current_layout]
        } else {
            &mut self.model_space
        }
    }

    #[inline]
    pub fn model_space(&self) -> &Layout {
        &self.model_space
    }

    #[inline]
    pub fn model_space_mut(&mut self) -> &mut Layout {
        &mut self.model_space
    }

    #[inline]
    pub fn get_layout(&self, index: usize) -> Option<&Layout> {
        self.layouts.get(index)
    }

    #[inline]
    pub fn get_layout_mut(&mut self, index: usize) -> Option<&mut Layout> {
        self.layouts.get_mut(index)
    }

    #[inline]
    pub fn layout_count(&self) -> usize {
        self.layouts.len()
    }

    #[inline]
    pub fn layouts(&self) -> &[Layout] {
        &self.layouts
    }

    #[inline]
    pub fn add_viewport(&mut self, mut viewport: Viewport) -> u32 {
        let id = self.next_viewport_id;
        self.next_viewport_id += 1;
        viewport.id = id;
        self.viewports.insert(id as usize, viewport);
        id
    }

    #[inline]
    pub fn remove_viewport(&mut self, id: u32) -> bool {
        self.viewports.remove(&(id as usize)).is_some()
    }

    #[inline]
    pub fn get_viewport(&self, id: u32) -> Option<&Viewport> {
        self.viewports.get(&(id as usize))
    }

    #[inline]
    pub fn get_viewport_mut(&mut self, id: u32) -> Option<&mut Viewport> {
        self.viewports.get_mut(&(id as usize))
    }

    #[inline]
    pub fn viewports(&self) -> &std::collections::HashMap<usize, Viewport> {
        &self.viewports
    }

    #[inline]
    pub fn set_active_viewport(&mut self, id: u32) {
        if self.viewports.contains_key(&(id as usize)) {
            self.active_viewport = id;
        }
    }

    #[inline]
    pub fn active_viewport(&self) -> u32 {
        self.active_viewport
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::geometry::Point;

    #[test]
    fn test_paper_size_dimensions() {
        assert!((PaperSize::A4.width_mm() - 210.0).abs() < 1e-10);
        assert!((PaperSize::A4.height_mm() - 297.0).abs() < 1e-10);
        assert!((PaperSize::A3.width_mm() - 297.0).abs() < 1e-10);
        assert!((PaperSize::A3.height_mm() - 420.0).abs() < 1e-10);
    }

    #[test]
    fn test_plot_rotation() {
        assert_eq!(PlotRotation::from_degrees(0.0), PlotRotation::Degrees0);
        assert_eq!(PlotRotation::from_degrees(90.0), PlotRotation::Degrees90);
        assert_eq!(PlotRotation::from_degrees(180.0), PlotRotation::Degrees180);
        assert_eq!(PlotRotation::from_degrees(270.0), PlotRotation::Degrees270);
    }

    #[test]
    fn test_scale_type() {
        assert_eq!(ScaleType::Scale1To128.scale_factor(), (1.0, 128.0));
        assert_eq!(ScaleType::ScaleToFit.scale_factor(), (1.0, 1.0));
    }

    #[test]
    fn test_plot_settings_scale() {
        let mut settings = PlotSettings::default();
        settings.set_scale(ScaleType::Scale1To64, None);
        assert!((settings.effective_scale() - 0.015625).abs() < 1e-10);
    }

    #[test]
    fn test_layout_printable_area() {
        let mut layout = Layout::new("Test");
        layout.set_margins(10.0, 10.0, 10.0, 10.0);
        let width = layout.paper_size.width_mm() - 20.0;
        let height = layout.paper_size.height_mm() - 20.0;
        assert!((layout.printable_area.0 - width).abs() < 1e-10);
        assert!((layout.printable_area.1 - height).abs() < 1e-10);
    }

    #[test]
    fn test_viewport_zoom() {
        let mut viewport = Viewport::new();
        viewport.view_height = 100.0;
        viewport.zoom(2.0);
        assert!((viewport.view_height - 50.0).abs() < 1e-10);
    }

    #[test]
    fn test_layout_manager() {
        let manager = LayoutManager::new();
        assert_eq!(manager.layout_count(), 2);
        assert!(manager.current().name.contains("Layout"));
    }

    #[test]
    fn test_plot_style_table() {
        let table = PlotStyleTable::default();
        assert_eq!(table.styles.len(), 256);
        assert!(table.by_color(5).is_some());
    }

    #[test]
    fn test_viewport_contains() {
        let viewport = Viewport {
            center: Point::new(100.0, 100.0, 0.0),
            width: 100.0,
            height: 100.0,
            ..Default::default()
        };
        assert!(viewport.contains(&Point::new(100.0, 100.0, 0.0)));
        assert!(viewport.contains(&Point::new(50.0, 50.0, 0.0)));
        assert!(!viewport.contains(&Point::new(200.0, 200.0, 0.0)));
    }

    #[test]
    fn test_viewport_border_points() {
        let viewport = Viewport {
            center: Point::new(100.0, 100.0, 0.0),
            width: 50.0,
            height: 50.0,
            ..Default::default()
        };
        let (min, max) = viewport.border_points();
        assert!((min.x - 75.0).abs() < 1e-10);
        assert!((min.y - 75.0).abs() < 1e-10);
        assert!((max.x - 125.0).abs() < 1e-10);
        assert!((max.y - 125.0).abs() < 1e-10);
    }
}
