use crate::object::*;
use myelin_geometry::*;
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct ObjectBuilderError {
    
    pub missing_shape: bool,
    
    pub missing_location: bool,
    
    pub missing_mobility: bool,
    
    pub missing_associated_data: bool,
}
#[derive(Debug)]
pub struct ObjectBuilder<T> {
    shape: Option<Polygon>,
    location: Option<Point>,
    rotation: Option<Radians>,
    mobility: Option<Mobility>,
    passable: bool,
    associated_data: Option<T>,
}
impl<T> Default for ObjectBuilder<T> {
    fn default() -> Self {
        Self {
            shape: None,
            location: None,
            rotation: None,
            mobility: None,
            passable: false,
            associated_data: None,
        }
    }
}
impl<T> ObjectBuilder<T> {
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn shape(&mut self, polygon: Polygon) -> &mut Self {
        self.shape = Some(polygon);
        self
    }
    
    
    
    
    
    
    pub fn location(&mut self, x: f64, y: f64) -> &mut Self {
        self.location = Some(Point { x, y });
        self
    }
    
    
    
    
    
    
    pub fn mobility(&mut self, mobility: Mobility) -> &mut Self {
        self.mobility = Some(mobility);
        self
    }
    
    
    
    
    
    
    pub fn rotation(&mut self, rotation: Radians) -> &mut Self {
        self.rotation = Some(rotation);
        self
    }
    
    
    
    
    
    
    pub fn passable(&mut self, passable: bool) -> &mut Self {
        self.passable = passable;
        self
    }
    
    
    
    
    
    
    pub fn associated_data(&mut self, associated_data: T) -> &mut Self {
        self.associated_data = Some(associated_data);
        self
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn build(&mut self) -> Result<ObjectDescription<T>, ObjectBuilderError> {
        let error = ObjectBuilderError {
            missing_shape: self.shape.is_none(),
            missing_location: self.location.is_none(),
            missing_mobility: self.mobility.is_none(),
            missing_associated_data: self.associated_data.is_none(),
        };
        let object = ObjectDescription {
            shape: self.shape.take().ok_or_else(|| error.clone())?,
            rotation: self.rotation.take().unwrap_or_else(Default::default),
            location: self.location.take().ok_or_else(|| error.clone())?,
            mobility: self.mobility.take().ok_or_else(|| error.clone())?,
            passable: self.passable,
            associated_data: self.take_associated_data().ok_or_else(|| error.clone())?,
        };
        Ok(object)
    }
}
trait TakeAssociatedData<T> {
    fn take_associated_data(&mut self) -> Option<T>;
}
impl<T> TakeAssociatedData<T> for ObjectBuilder<T> {
    default fn take_associated_data(&mut self) -> Option<T> {
        self.associated_data.take()
    }
}
impl<T> TakeAssociatedData<T> for ObjectBuilder<T>
where
    T: Default,
{
    default fn take_associated_data(&mut self) -> Option<T> {
        Some(self.associated_data.take().unwrap_or_default())
    }
}
impl<T> From<ObjectDescription<T>> for ObjectBuilder<T> {
    fn from(object_description: ObjectDescription<T>) -> Self {
        let ObjectDescription {
            shape,
            location,
            rotation,
            mobility,
            passable,
            associated_data,
        } = object_description;
        ObjectBuilder {
            shape: Some(shape),
            location: Some(location),
            rotation: Some(rotation),
            mobility: Some(mobility),
            passable,
            associated_data: Some(associated_data),
        }
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_object_builder_should_error_for_missing_shape() {
        let result = ObjectBuilder::<()>::default()
            .location(10.0, 10.0)
            .rotation(Radians::try_new(0.0).unwrap())
            .mobility(Mobility::Immovable)
            .associated_data(())
            .build();
        assert_eq!(
            Err(ObjectBuilderError {
                missing_shape: true,
                ..ObjectBuilderError::default()
            }),
            result
        );
    }
    #[test]
    fn test_object_builder_should_error_for_missing_location() {
        let result = ObjectBuilder::<()>::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .rotation(Radians::try_new(0.0).unwrap())
            .mobility(Mobility::Immovable)
            .associated_data(())
            .build();
        assert_eq!(
            Err(ObjectBuilderError {
                missing_location: true,
                ..ObjectBuilderError::default()
            }),
            result
        );
    }
    #[test]
    fn test_object_builder_should_error_for_missing_associated_data() {
        #[derive(Debug, PartialEq)]
        struct AssociatedData;
        let result = ObjectBuilder::<AssociatedData>::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .location(30.0, 40.0)
            .rotation(Radians::try_new(0.0).unwrap())
            .mobility(Mobility::Immovable)
            .build();
        assert_eq!(
            Err(ObjectBuilderError {
                missing_associated_data: true,
                ..ObjectBuilderError::default()
            }),
            result
        );
    }
    #[test]
    fn test_object_builder_uses_default_value_for_associated_data() {
        let result = ObjectBuilder::<String>::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .location(30.0, 40.0)
            .rotation(Radians::try_new(0.0).unwrap())
            .mobility(Mobility::Immovable)
            .build();
        let expected = ObjectDescription {
            shape: PolygonBuilder::default()
                .vertex(0.0, 0.0)
                .vertex(0.0, 1.0)
                .vertex(1.0, 0.0)
                .vertex(1.0, 1.0)
                .build()
                .unwrap(),
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(0.0).unwrap(),
            mobility: Mobility::Immovable,
            passable: false,
            associated_data: String::default(),
        };
        assert_eq!(Ok(expected), result);
    }
    #[test]
    fn test_object_builder_should_error_for_missing_mobility() {
        let result = ObjectBuilder::<()>::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .rotation(Radians::try_new(0.0).unwrap())
            .location(30.0, 40.0)
            .associated_data(())
            .build();
        assert_eq!(
            Err(ObjectBuilderError {
                missing_mobility: true,
                ..ObjectBuilderError::default()
            }),
            result
        );
    }
    #[test]
    fn test_object_builder_should_use_default_rotation() {
        let result = ObjectBuilder::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .location(30.0, 40.0)
            .mobility(Mobility::Immovable)
            .associated_data(())
            .build();
        let expected = ObjectDescription {
            shape: PolygonBuilder::default()
                .vertex(0.0, 0.0)
                .vertex(0.0, 1.0)
                .vertex(1.0, 0.0)
                .vertex(1.0, 1.0)
                .build()
                .unwrap(),
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(0.0).unwrap(),
            mobility: Mobility::Immovable,
            passable: false,
            associated_data: (),
        };
        assert_eq!(Ok(expected), result);
    }
    #[test]
    fn test_object_builder_uses_passable() {
        let result = ObjectBuilder::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .rotation(Radians::try_new(0.0).unwrap())
            .location(30.0, 40.0)
            .mobility(Mobility::Immovable)
            .passable(true)
            .associated_data(())
            .build();
        let expected = ObjectDescription {
            shape: Polygon::try_new(vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 0.0, y: 1.0 },
                Point { x: 1.0, y: 0.0 },
                Point { x: 1.0, y: 1.0 },
            ])
            .unwrap(),
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(0.0).unwrap(),
            mobility: Mobility::Immovable,
            passable: true,
            associated_data: (),
        };
        assert_eq!(Ok(expected), result);
    }
    #[test]
    fn test_object_builder_uses_associated_data() {
        let result = ObjectBuilder::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .rotation(Radians::try_new(0.0).unwrap())
            .location(30.0, 40.0)
            .mobility(Mobility::Immovable)
            .associated_data((10, "foo"))
            .build();
        let expected = ObjectDescription {
            shape: Polygon::try_new(vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 0.0, y: 1.0 },
                Point { x: 1.0, y: 0.0 },
                Point { x: 1.0, y: 1.0 },
            ])
            .unwrap(),
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(0.0).unwrap(),
            mobility: Mobility::Immovable,
            passable: false,
            associated_data: (10, "foo"),
        };
        assert_eq!(Ok(expected), result);
    }
    #[test]
    fn test_object_builder_should_error_with_everything_missing() {
        let result = ObjectBuilder::<()>::default().build();
        assert_eq!(
            Err(ObjectBuilderError {
                missing_shape: true,
                missing_location: true,
                missing_mobility: true,
                missing_associated_data: true,
            }),
            result
        );
    }
    #[test]
    fn test_object_builder_should_build_object() {
        let result = ObjectBuilder::default()
            .shape(
                PolygonBuilder::default()
                    .vertex(0.0, 0.0)
                    .vertex(0.0, 1.0)
                    .vertex(1.0, 0.0)
                    .vertex(1.0, 1.0)
                    .build()
                    .unwrap(),
            )
            .mobility(Mobility::Movable(Vector { x: -12.0, y: 5.0 }))
            .location(30.0, 40.0)
            .rotation(Radians::try_new(1.1).unwrap())
            .build();
        let expected = ObjectDescription {
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(1.1).unwrap(),
            mobility: Mobility::Movable(Vector { x: -12.0, y: 5.0 }),
            shape: Polygon::try_new(vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 0.0, y: 1.0 },
                Point { x: 1.0, y: 0.0 },
                Point { x: 1.0, y: 1.0 },
            ])
            .unwrap(),
            passable: false,
            associated_data: (),
        };
        assert_eq!(Ok(expected), result);
    }
    #[test]
    fn can_create_object_builder_from_object_description() {
        let object_description = ObjectDescription {
            location: Point { x: 30.0, y: 40.0 },
            rotation: Radians::try_new(1.1).unwrap(),
            mobility: Mobility::Movable(Vector { x: -12.0, y: 5.0 }),
            shape: Polygon::try_new(vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 0.0, y: 1.0 },
                Point { x: 1.0, y: 0.0 },
                Point { x: 1.0, y: 1.0 },
            ])
            .unwrap(),
            passable: true,
            associated_data: (),
        };
        assert_eq!(
            object_description,
            ObjectBuilder::from(object_description.clone())
                .build()
                .unwrap()
        );
    }
}