use raw::*;

const ENABLE_OPTIONS_INDICES: [EnableOption; 15] = [
    EnableOption::Blend,
    EnableOption::ColorLogicOperation,
    EnableOption::CullFace,
    EnableOption::DepthClamp,
    EnableOption::DepthTest,
    EnableOption::Dither,
    EnableOption::FramebufferSRGB,
    EnableOption::LineSmooth,
    EnableOption::Multisample,
    EnableOption::SampleShading,
    EnableOption::SampleMask,
    EnableOption::ScissorTest,
    EnableOption::StencilTest,
    EnableOption::TextureCubeMapSeamless,
    EnableOption::ProgramPointSize,
];


#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct FramebufferState {
    pub viewport: Viewport,

    pub options: [bool; 15],

    pub stencil_test: [StencilTest; 2],

    pub depth_test: DepthTest,
    pub depth_mask: DepthMask,
    pub depth_range: DepthRange,

    pub logic_operation: LogicOperation,

    pub blending_equation_rgb: BlendingEquation,
    pub blending_equation_alpha: BlendingEquation,
    pub linear_blending_factors: Option<[LinearBlendingFactor; 4]>,

    pub face_orientation: FaceOrientation,
    pub cull_face: Face,

    pub clear_color: ClearColor,
}

impl FramebufferState {
    pub fn sync(&mut self, other: &FramebufferState) {
        if self == other {
            return;
        }

        if self.viewport != other.viewport {
            viewport(other.viewport);
        }

        for i in 0..15 {
            if self.options[i] != other.options[i] {
                match other.options[i] {
                    true => enable(ENABLE_OPTIONS_INDICES[i]),
                    false => disable(ENABLE_OPTIONS_INDICES[i]),
                }
            }
        }

        if self.stencil_test[0] != other.stencil_test[0] {
            stencil_func_separate(Face::Front, other.stencil_test[0]);
        }

        if self.stencil_test[1] != other.stencil_test[1] {
            stencil_func_separate(Face::Front, other.stencil_test[1]);
        }

        if self.depth_test != other.depth_test {
            depth_function(other.depth_test);
        }

        if self.depth_mask != other.depth_mask {
            depth_mask(other.depth_mask);
        }

        if self.depth_range != other.depth_range {
            depth_range(other.depth_range);
        }

        if self.logic_operation != other.logic_operation {
            logic_op(other.logic_operation);
        }

        if self.blending_equation_rgb != other.blending_equation_rgb ||
            self.blending_equation_alpha != other.blending_equation_alpha
        {
            blend_equation_separate(other.blending_equation_rgb, other.blending_equation_alpha);
        }

        if self.linear_blending_factors != other.linear_blending_factors {
            if let Some(blending_factors) = other.linear_blending_factors {
                blend_func_separate(
                    blending_factors[0],
                    blending_factors[1],
                    blending_factors[2],
                    blending_factors[3],
                );
            }
        }

        if self.face_orientation != other.face_orientation {
            front_face(other.face_orientation);
        }

        if self.cull_face != other.cull_face {
            cull_face(other.cull_face);
        }

        if self.clear_color != other.clear_color {
            clear_color(other.clear_color);
        }

        *self = *other;
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct FramebufferStateBuilder {
    viewport: Viewport,

    options: [bool; 15],

    stencil_test: [StencilTest; 2],

    depth_test: DepthTest,
    depth_mask: DepthMask,
    depth_range: DepthRange,

    logic_operation: LogicOperation,

    blending_equation_rgb: BlendingEquation,
    blending_equation_alpha: BlendingEquation,
    linear_blending_factors: Option<[LinearBlendingFactor; 4]>,

    face_orientation: FaceOrientation,
    cull_face: Face,

    clear_color: ClearColor,
}

impl FramebufferStateBuilder {
    pub fn new() -> FramebufferStateBuilder {
        FramebufferStateBuilder::default()
    }

    pub fn viewport<'a>(&'a mut self, viewport: Viewport) -> &'a mut FramebufferStateBuilder {
        self.viewport = viewport;
        self
    }

    pub fn enable<'a>(&'a mut self, option: EnableOption) -> &'a mut FramebufferStateBuilder {
        self.options[usize::from(option)] = true;
        self
    }

    pub fn disable<'a>(&'a mut self, option: EnableOption) -> &'a mut FramebufferStateBuilder {
        self.options[usize::from(option)] = false;
        self
    }

    pub fn stencil_test<'a>(
        &'a mut self,
        face: Face,
        stencil_test: StencilTest,
    ) -> &'a mut FramebufferStateBuilder {
        match face {
            Face::Front => self.stencil_test[0] = stencil_test,
            Face::Back => self.stencil_test[1] = stencil_test,
            Face::FrontBack => {
                self.stencil_test[0] = stencil_test;
                self.stencil_test[1] = stencil_test;
            }
        }
        self
    }

    pub fn depth_test<'a>(&'a mut self, depth_test: DepthTest) -> &'a mut FramebufferStateBuilder {
        self.depth_test = depth_test;
        self
    }

    pub fn depth_mask<'a>(&'a mut self, depth_mask: DepthMask) -> &'a mut FramebufferStateBuilder {
        self.depth_mask = depth_mask;
        self
    }

    pub fn depth_range<'a>(
        &'a mut self,
        depth_range: DepthRange,
    ) -> &'a mut FramebufferStateBuilder {
        self.depth_range = depth_range;
        self
    }

    pub fn logic_operation<'a>(
        &'a mut self,
        logic_operation: LogicOperation,
    ) -> &'a mut FramebufferStateBuilder {
        self.logic_operation = logic_operation;
        self
    }

    pub fn blending_equation<'a>(
        &'a mut self,
        blending_equation_rgb: BlendingEquation,
        blending_equation_alpha: BlendingEquation,
    ) -> &'a mut FramebufferStateBuilder {
        self.blending_equation_rgb = blending_equation_rgb;
        self.blending_equation_alpha = blending_equation_alpha;
        self
    }

    pub fn linear_blending_factors<'a>(
        &'a mut self,
        source_rgb: LinearBlendingFactor,
        destination_rgb: LinearBlendingFactor,
        source_alpha: LinearBlendingFactor,
        destination_alpha: LinearBlendingFactor,
    ) -> &'a mut FramebufferStateBuilder {
        self.linear_blending_factors =
            Some(
                [source_rgb, destination_rgb, source_alpha, destination_alpha],
            );
        self
    }

    pub fn face_orientation<'a>(
        &'a mut self,
        face_orientation: FaceOrientation,
    ) -> &'a mut FramebufferStateBuilder {
        self.face_orientation = face_orientation;
        self
    }

    pub fn cull_face<'a>(&'a mut self, cull_face: Face) -> &'a mut FramebufferStateBuilder {
        self.cull_face = cull_face;
        self
    }

    pub fn clear_color<'a>(
        &'a mut self,
        clear_color: ClearColor,
    ) -> &'a mut FramebufferStateBuilder {
        self.clear_color = clear_color;
        self
    }

    pub fn build(&mut self) -> FramebufferState {
        FramebufferState {
            viewport: self.viewport,
            options: self.options,
            stencil_test: self.stencil_test,
            depth_test: self.depth_test,
            depth_mask: self.depth_mask,
            depth_range: self.depth_range,
            logic_operation: self.logic_operation,
            blending_equation_rgb: self.blending_equation_rgb,
            blending_equation_alpha: self.blending_equation_alpha,
            linear_blending_factors: self.linear_blending_factors,
            face_orientation: self.face_orientation,
            cull_face: self.cull_face,
            clear_color: self.clear_color,
        }
    }
}

impl Default for FramebufferState {
    fn default() -> FramebufferState {
        let mut options = [false; 15];
        options[usize::from(EnableOption::Dither)] = true;
        options[usize::from(EnableOption::Multisample)] = true;

        FramebufferState {
            viewport: Viewport::default(),

            options: options,

            stencil_test: [StencilTest::default(); 2],

            depth_test: DepthTest::default(),
            depth_mask: DepthMask::default(),
            depth_range: DepthRange::default(),

            logic_operation: LogicOperation::default(),

            blending_equation_rgb: BlendingEquation::default(),
            blending_equation_alpha: BlendingEquation::default(),
            linear_blending_factors: None,

            face_orientation: FaceOrientation::default(),
            cull_face: Face::default(),

            clear_color: ClearColor::default(),
        }
    }
}
