use crate::*;

#[derive(Clone, PartialEq, Eq)]
pub struct Path(pub(crate) ID2D1PathGeometry);

impl Fill for Path {
    #[inline]
    fn fill(&self, dc: &ID2D1DeviceContext, brush: &ID2D1Brush) {
        unsafe {
            dc.FillGeometry(&self.0, brush, None);
        }
    }
}

impl Stroke for Path {
    #[inline]
    fn stroke(
        &self,
        dc: &ID2D1DeviceContext,
        brush: &ID2D1Brush,
        width: f32,
        style: Option<ID2D1StrokeStyle>,
    ) {
        unsafe {
            dc.DrawGeometry(&self.0, brush, width, style);
        }
    }
}

unsafe impl Send for Path {}
unsafe impl Sync for Path {}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum FigureEnd {
    Open = D2D1_FIGURE_END_OPEN.0,
    Closed = D2D1_FIGURE_END_CLOSED.0,
}

pub struct PathBuilder {
    geometry: ID2D1PathGeometry,
    sink: ID2D1GeometrySink,
}

pub struct Figure {
    geometry: ID2D1PathGeometry,
    sink: ID2D1GeometrySink,
}

impl PathBuilder {
    #[inline]
    pub(crate) fn new(geometry: ID2D1PathGeometry) -> Self {
        unsafe {
            let sink = { geometry.Open().unwrap() };
            Self { geometry, sink }
        }
    }

    #[inline]
    pub fn begin(self, point: impl Into<Point>) -> Figure {
        unsafe {
            let point: D2D_POINT_2F = Inner(point.into()).into();
            self.sink.BeginFigure(&point, D2D1_FIGURE_BEGIN_FILLED);
            Figure {
                geometry: self.geometry,
                sink: self.sink,
            }
        }
    }

    #[inline]
    pub fn build(self) -> Path {
        unsafe {
            self.sink.Close().unwrap();
            Path(self.geometry)
        }
    }
}

impl Figure {
    #[inline]
    pub fn line_to(self, point: impl Into<Point>) -> Self {
        unsafe {
            let point: D2D_POINT_2F = Inner(point.into()).into();
            self.sink.AddLine(point);
            self
        }
    }

    #[inline]
    pub fn quadratic_bezier_to(self, ctrl: impl Into<Point>, to: impl Into<Point>) -> Self {
        unsafe {
            let ctrl: D2D_POINT_2F = Inner(ctrl.into()).into();
            let to: D2D_POINT_2F = Inner(to.into()).into();
            self.sink
                .AddQuadraticBezier(&D2D1_QUADRATIC_BEZIER_SEGMENT {
                    point1: ctrl,
                    point2: to,
                });
            self
        }
    }

    #[inline]
    pub fn cubic_bezier_to(
        self,
        c0: impl Into<Point>,
        c1: impl Into<Point>,
        to: impl Into<Point>,
    ) -> Self {
        unsafe {
            let c0: D2D_POINT_2F = Inner(c0.into()).into();
            let c1: D2D_POINT_2F = Inner(c1.into()).into();
            let to: D2D_POINT_2F = Inner(to.into()).into();
            self.sink.AddBezier(&D2D1_BEZIER_SEGMENT {
                point1: c0,
                point2: c1,
                point3: to,
            });
            self
        }
    }

    #[inline]
    pub fn end(self, t: FigureEnd) -> PathBuilder {
        unsafe {
            self.sink.EndFigure(D2D1_FIGURE_END(t as u32));
            PathBuilder {
                geometry: self.geometry,
                sink: self.sink,
            }
        }
    }
}
