use crate::geometry::Point;
use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GeometricTolerance {
    pub feature_control_frame: FeatureControlFrame,
    pub placement: TolerancePlacement,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeatureControlFrame {
    pub frames: Vec<ToleranceFrame>,
    pub composite_frame: Option<CompositeFrame>,
    pub material_condition: MaterialCondition,
    pub datum_reference_frame: Vec<DatumReference>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToleranceFrame {
    pub symbol: ToleranceSymbol,
    pub tolerance_value: f64,
    pub datum_references: Vec<DatumReference>,
    pub modifier: ToleranceModifier,
    pub secondary_tolerance: Option<f64>,
    pub projected_tolerance: Option<ProjectedTolerance>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompositeFrame {
    pub primary_tolerance: ToleranceFrame,
    pub secondary_tolerance: ToleranceFrame,
    pub position_symbol: ToleranceSymbol,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ToleranceSymbol {
    Straightness,
    Flatness,
    Circularity,
    Cylindricity,
    ProfileOfLine,
    ProfileOfSurface,
    Angularity,
    Perpendicularity,
    Parallelism,
    Position,
    Symmetry,
    Concentricity,
    CircularRunout,
    TotalRunout,
}

impl fmt::Display for ToleranceSymbol {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ToleranceSymbol::Straightness => write!(f, "⌖"),
            ToleranceSymbol::Flatness => write!(f, "⏥"),
            ToleranceSymbol::Circularity => write!(f, "○"),
            ToleranceSymbol::Cylindricity => write!(f, "⏦"),
            ToleranceSymbol::ProfileOfLine => write!(f, "⌒"),
            ToleranceSymbol::ProfileOfSurface => write!(f, "⌔"),
            ToleranceSymbol::Angularity => write!(f, "∠"),
            ToleranceSymbol::Perpendicularity => write!(f, "⊥"),
            ToleranceSymbol::Parallelism => write!(f, "∥"),
            ToleranceSymbol::Position => write!(f, "Ⓟ"),
            ToleranceSymbol::Symmetry => write!(f, "⌖"),
            ToleranceSymbol::Concentricity => write!(f, "◎"),
            ToleranceSymbol::CircularRunout => write!(f, "⇵"),
            ToleranceSymbol::TotalRunout => write!(f, "⬒"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DatumReference {
    pub datum: String,
    pub modifier: DatumModifier,
    pub material_condition: MaterialCondition,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum DatumModifier {
    None,
    MaximumMaterialCondition,
    LeastMaterialCondition,
    RegardlessOfFeatureSize,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum MaterialCondition {
    None,
    MaximumMaterialCondition,
    LeastMaterialCondition,
    RegardlessOfFeatureSize,
}

impl Default for MaterialCondition {
    fn default() -> Self {
        MaterialCondition::None
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ToleranceModifier {
    None,
    MaximumMaterialCondition,
    LeastMaterialCondition,
    ProjectedTolerance,
    FreeState,
    Envelope,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProjectedTolerance {
    pub zone_height: f64,
    pub zone_diameter: Option<f64>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TolerancePlacement {
    OnFeature,
    Projected,
    RegardlessOfFeatureSize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToleranceValue {
    pub diameter_symbol: bool,
    pub value: f64,
    pub tolerance_zone_shape: ToleranceZoneShape,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ToleranceZoneShape {
    Cylinder,
    Sphere,
    Circle,
    BetweenTwoLines,
    BetweenTwoPlanes,
    Polygon,
}

impl GeometricTolerance {
    pub fn new(symbol: ToleranceSymbol, value: f64) -> Self {
        let frame = ToleranceFrame {
            symbol,
            tolerance_value: value,
            datum_references: Vec::new(),
            modifier: ToleranceModifier::None,
            secondary_tolerance: None,
            projected_tolerance: None,
        };
        
        let control_frame = FeatureControlFrame {
            frames: vec![frame],
            composite_frame: None,
            material_condition: MaterialCondition::None,
            datum_reference_frame: Vec::new(),
        };
        
        Self {
            feature_control_frame: control_frame,
            placement: TolerancePlacement::OnFeature,
        }
    }
    
    pub fn add_datum(&mut self, datum: String, modifier: DatumModifier) {
        let reference = DatumReference {
            datum,
            modifier,
            material_condition: self.feature_control_frame.material_condition,
        };
        
        if let Some(frame) = self.feature_control_frame.frames.first_mut() {
            frame.datum_references.push(reference);
        }
        
        self.feature_control_frame.datum_reference_frame.push(reference.clone());
    }
    
    pub fn set_material_condition(&mut self, condition: MaterialCondition) {
        self.feature_control_frame.material_condition = condition;
    }
    
    pub fn set_composite(&mut self, primary_value: f64, secondary_value: f64, datum_references: Vec<DatumReference>) {
        let primary_frame = ToleranceFrame {
            symbol: ToleranceSymbol::Position,
            tolerance_value: primary_value,
            datum_references: datum_references.clone(),
            modifier: ToleranceModifier::None,
            secondary_tolerance: None,
            projected_tolerance: None,
        };
        
        let secondary_frame = ToleranceFrame {
            symbol: ToleranceSymbol::Position,
            tolerance_value: secondary_value,
            datum_references,
            modifier: ToleranceModifier::None,
            secondary_tolerance: None,
            projected_tolerance: None,
        };
        
        self.feature_control_frame.composite_frame = Some(CompositeFrame {
            primary_tolerance: primary_frame,
            secondary_tolerance: secondary_frame,
            position_symbol: ToleranceSymbol::Position,
        });
    }
    
    pub fn add_projected_tolerance(&mut self, height: f64) {
        if let Some(frame) = self.feature_control_frame.frames.first_mut() {
            frame.projected_tolerance = Some(ProjectedTolerance {
                zone_height: height,
                zone_diameter: None,
            });
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DatumTarget {
    pub target_point: Option<Point>,
    pub target_line: Option<(Point, Point)>,
    pub target_area: Option<(Point, f64)>,
    pub target_type: DatumTargetType,
    pub identifier: String,
    pub size: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum DatumTargetType {
    Point,
    Line,
    Area,
}

impl DatumTarget {
    pub fn new_point(identifier: String, point: Point) -> Self {
        Self {
            target_point: Some(point),
            target_line: None,
            target_area: None,
            target_type: DatumTargetType::Point,
            identifier,
            size: 3.0,
        }
    }
    
    pub fn new_line(identifier: String, p1: Point, p2: Point) -> Self {
        Self {
            target_point: None,
            target_line: Some((p1, p2)),
            target_area: None,
            target_type: DatumTargetType::Line,
            identifier,
            size: 3.0,
        }
    }
    
    pub fn new_area(identifier: String, center: Point, diameter: f64) -> Self {
        Self {
            target_point: None,
            target_line: None,
            target_area: Some((center, diameter)),
            target_type: DatumTargetType::Area,
            identifier,
            size: diameter,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DatumReferenceFrame {
    pub designation: Vec<Datum>,
    pub order: usize,
    pub material_condition: MaterialCondition,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Datum {
    pub identifier: String,
    pub type_id: String,
    pub targets: Vec<DatumTarget>,
    pub material_condition: MaterialCondition,
    pub shift: Option<(f64, f64, f64)>,
}

impl Datum {
    pub fn new(identifier: String) -> Self {
        Self {
            identifier,
            type_id: String::new(),
            targets: Vec::new(),
            material_condition: MaterialCondition::None,
            shift: None,
        }
    }
    
    pub fn add_target(&mut self, target: DatumTarget) {
        self.targets.push(target);
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DatumSystem {
    pub primary_datum: Datum,
    pub secondary_datum: Option<Datum>,
    pub tertiary_datum: Option<Datum>,
    pub references: Vec<DatumReference>,
}

impl DatumSystem {
    pub fn new(primary: Datum) -> Self {
        Self {
            primary_datum: primary,
            secondary_datum: None,
            tertiary_datum: None,
            references: Vec::new(),
        }
    }
    
    pub fn add_secondary(&mut self, datum: Datum) {
        self.secondary_datum = Some(datum);
    }
    
    pub fn add_tertiary(&mut self, datum: Datum) {
        self.tertiary_datum = Some(datum);
    }
    
    pub fn build_references(&mut self) {
        self.references.clear();
        
        if let Some(datum) = &self.primary_datum {
            self.references.push(DatumReference {
                datum: datum.identifier.clone(),
                modifier: DatumModifier::None,
                material_condition: datum.material_condition,
            });
        }
        
        if let Some(datum) = &self.secondary_datum {
            self.references.push(DatumReference {
                datum: datum.identifier.clone(),
                modifier: DatumModifier::None,
                material_condition: datum.material_condition,
            });
        }
        
        if let Some(datum) = &self.tertiary_datum {
            self.references.push(DatumReference {
                datum: datum.identifier.clone(),
                modifier: DatumModifier::None,
                material_condition: datum.material_condition,
            });
        }
    }
}

impl From<GeometricTolerance> for crate::data_structure::Entity {
    fn from(tol: GeometricTolerance) -> Self {
        crate::data_structure::Entity::new(
            crate::data_structure::EntityType::GeometricTolerance,
            crate::data_structure::EntityGeometry::GeometricTolerance(tol),
        )
    }
}
