use crate::visual::colors::Color;
use crate::visual::colors::IntoColor;
use crate::visual::renderer::{
    affine::Affine,
    brushes::{Brush, Extend, ImageSampling},
    colors::RGBA,
    styles::ImageFitMode,
    Bitmap, Scene,
};
use psydk_proc::{FromPyStr, StimulusParams};
use std::sync::Arc;
use strum::EnumString;
use uuid::Uuid;

unsafe impl Send for PatternStimulus {}

use super::{
    animations::Animation, helpers, impl_pystimulus_for_wrapper, PyStimulus, Stimulus, StimulusParamValue,
    StimulusParams, StrokeStyle,
};
use crate::{
    context::ExperimentContext,
    visual::{
        geometry::{Shape, Size, Transformation2D},
        window::{Frame, WindowState, WindowStateSnapshot},
    },
};

#[derive(EnumString, Debug, Clone, Copy, PartialEq, FromPyStr)]
#[strum(serialize_all = "snake_case")]
pub enum FillPattern {
    Uniform,
    Stripes,
    Sinosoidal,
    Checkerboard,
}

#[derive(StimulusParams, Clone, Debug)]
pub struct PatternParams {
    pub shape: Shape,
    pub x: Size,
    pub y: Size,
    pub phase_x: f32,
    pub phase_y: f32,
    pub pattern_size: Size,
    pub fill_color: Color,
    pub background_color: Color,
    pub pattern_rotation: f32,
    pub stroke_style: StrokeStyle,
    pub stroke_color: Color,
    pub stroke_width: Size,
    pub alpha: Option<f32>,
    pub flag_clicked: bool,
}

#[derive(Debug)]
pub struct PatternStimulus {
    id: uuid::Uuid,
    params: PatternParams,
    fill_pattern: FillPattern,

    gradient_colors: Option<Vec<Color>>,
    pattern_image: Option<Bitmap>,
    transform: Transformation2D,
    animations: Vec<Animation>,
    visible: bool,
}

impl PatternStimulus {
    pub fn new(
        context: &ExperimentContext,
        shape: Shape,
        x: Size,
        y: Size,
        phase_x: f32,
        phase_y: f32,
        pattern_size: Size,
        fill_color: Color,
        background_color: Color,
        pattern: FillPattern,
        pattern_rotation: f32,
        stroke_style: StrokeStyle,
        stroke_color: Color,
        stroke_width: Size,
        alpha: Option<f32>,
        transform: Transformation2D,
    ) -> Self {
        let mut stim = Self {
            id: Uuid::new_v4(),
            params: PatternParams {
                shape,
                x,
                y,
                phase_x,
                phase_y,
                pattern_size,
                fill_color,
                background_color,
                pattern_rotation,
                stroke_style,
                stroke_color,
                stroke_width,
                alpha,
                flag_clicked: false,
            },
            fill_pattern: pattern,
            gradient_colors: None,
            pattern_image: None,
            transform,
            animations: Vec::new(),
            visible: true,
        };

        let fg = fill_color;
        let bg = background_color;

        match pattern {
            FillPattern::Uniform => {}
            FillPattern::Stripes => {
                panic!("Stripes pattern not implemented yet");
                // let image_2x1_data = vec![fg.r(), fg.g(), fg.b(), fg.a(), bg.r(), bg.g(), bg.b(), bg.a()];
                // let image_2x1 = crate::visual::renderer::image::ImageBuffer::from_raw(2, 1, image_2x1_data)
                //     .expect("Failed to create image. This should never happen.");

                // let pattern_image = context
                //     .renderer_factory()
                //     .create_bitmap_f32(image_2x1, crate::visual::renderer::color_formats::ColorEncoding::Linear);
                // stim.pattern_image = Some(pattern_image);
            }
            FillPattern::Sinosoidal => todo!(),
            FillPattern::Checkerboard => {
                panic!("Checkerboard pattern not implemented yet");
                // let image_2x2_data = vec![
                //     fg.r(),
                //     fg.g(),
                //     fg.b(),
                //     fg.a(),
                //     bg.r(),
                //     bg.g(),
                //     bg.b(),
                //     bg.a(),
                //     bg.r(),
                //     bg.g(),
                //     bg.b(),
                //     bg.a(),
                //     fg.r(),
                //     fg.g(),
                //     fg.b(),
                //     fg.a(),
                // ];
                // let image_2x2 = crate::visual::renderer::image::ImageBuffer::from_raw(2, 2, image_2x2_data)
                //     .expect("Failed to create image. This should never happen.");

                // let pattern_image = context
                //     .renderer_factory()
                //     .create_bitmap_f32(image_2x2, crate::visual::renderer::color_formats::ColorEncoding::Linear);
                // stim.pattern_image = Some(pattern_image);
            }
        }

        stim
    }
}

#[derive(Debug, Clone)]
#[pyclass(name = "PatternStimulus", extends=PyStimulus)]
/// A stimulus that displays a shape.
///
/// Parameters
/// ----------
/// shape : Shape
///     The shape to display.
/// x : Size, optional
///     The x-coordinate of the center of the shape.
/// y : Size, optional
///     The y-coordinate of the center of the shape.
/// fill_color : Union[Color, (float, float, float), (float, float, float, float), str], optional
///    The fill color of the shape.
/// stroke_style : StrokeStyle, optional
///    The stroke style of the shape.
/// stroke_color : Union[Color, (float, float, float), (float, float, float, float), str], optional
///   The stroke color of the shape.
/// stroke_width : Union[Size, float], optional
///  The stroke width of the shape.
/// alpha : float, optional
///  The alpha channel of the shape.
/// transform : Transformation2D, optional
/// The transformation of the shape.
pub struct PyPatternStimulus();

#[pymethods]
impl PyPatternStimulus {
    #[new]
    #[pyo3(signature = (
        context,
        shape,
        x = IntoSize(Size::Pixels(0.0)),
        y = IntoSize(Size::Pixels(0.0)),
        phase_x = 0.0,
        phase_y = 0.0,
        pattern_size = IntoSize(Size::Pixels(100.0)),
        fill_color = IntoColor(Color::new_srgba(1.0, 1.0, 1.0, 1.0)),
        background_color = IntoColor(Color::new_srgba(0.0, 0.0, 0.0, 1.0)),
        pattern = FillPattern::Uniform,
        pattern_rotation = 0.0,
        stroke_style = StrokeStyle::default(),
        stroke_color = IntoColor(Color::new_srgba(0.0, 0.0, 0.0, 1.0)),
        stroke_width = IntoSize(Size::Pixels(0.0)),
        alpha = None,
        transform = Transformation2D::Identity(),
    ))]
    /// A stimulus that displays a shape.
    ///
    /// Parameters
    /// ----------
    /// shape : Shape
    ///     The shape to display.
    /// x : Size, optional
    ///     The x-coordinate of the center of the shape.
    /// y : Size, optional
    ///     The y-coordinate of the center of the shape.
    /// fill_color : Union[Color, (float, float, float), (float, float, float, float), str], optional
    ///    The fill color of the shape.
    /// stroke_style : StrokeStyle, optional
    ///    The stroke style of the shape.
    /// stroke_color : Union[Color, (float, float, float), (float, float, float, float), str], optional
    ///   The stroke color of the shape.
    /// stroke_width : Union[Size, float], optional
    ///    The stroke width of the shape.
    /// alpha : float, optional
    ///    The alpha channel of the shape.
    /// transform : Transformation2D, optional
    ///    The transformation of the shape.
    fn __new__(
        py: Python,
        context: ExperimentContext,
        shape: Shape,
        x: IntoSize,
        y: IntoSize,
        phase_x: f32,
        phase_y: f32,
        pattern_size: IntoSize,
        fill_color: IntoColor,
        background_color: IntoColor,
        pattern: FillPattern,
        pattern_rotation: f32,
        stroke_style: StrokeStyle,
        stroke_color: IntoColor,
        stroke_width: IntoSize,
        alpha: Option<f32>,
        transform: Transformation2D,
    ) -> (Self, PyStimulus) {
        (
            Self(),
            PyStimulus::new(PatternStimulus::new(
                &context,
                shape,
                x.into(),
                y.into(),
                phase_x,
                phase_y,
                pattern_size.into(),
                fill_color.into(),
                background_color.into(),
                pattern,
                pattern_rotation,
                stroke_style,
                stroke_color.into(),
                stroke_width.into(),
                alpha,
                transform,
            )),
        )
    }

    fn clicked(slf: PyRef<'_, Self>) -> bool {
        let mut stim = slf.as_ref().0.lock();
        if let Some(stim) = stim.downcast_mut::<PatternStimulus>() {
            stim.clicked()
        } else {
            unreachable!("PyPatternStimulus always wraps PatternStimulus");
        }
    }
}

impl_pystimulus_for_wrapper!(PyPatternStimulus, PatternStimulus);

impl Stimulus for PatternStimulus {
    fn uuid(&self) -> Uuid {
        self.id
    }

    fn animations(&mut self) -> Option<&mut Vec<Animation>> {
        Some(&mut self.animations)
    }

    fn add_animation(&mut self, animation: Animation) {
        self.animations.push(animation);
    }

    fn draw(&mut self, scene: &mut crate::visual::renderer::wrapped::Scene, window_state: &WindowStateSnapshot) {
        if !self.visible {
            return;
        }

        let windows_size = window_state.size;
        let screen_props = window_state.physical_screen;
        let dc = &*window_state.display_characteristics;

        let x_origin = self.params.x.eval(windows_size, screen_props);
        let y_origin = self.params.y.eval(windows_size, screen_props);

        let pattern_size = self.params.pattern_size.eval(windows_size, screen_props);

        let shift_x = (self.params.phase_x % 360.0) / 360.0 * pattern_size;
        let shift_y = (self.params.phase_y % 360.0) / 360.0 * pattern_size;

        let fill_color = self.params.fill_color.to_display_rgba(dc);

        let pattern_transform = Affine::rotate(self.params.pattern_rotation);

        let fill_brush = match self.fill_pattern {
            FillPattern::Uniform => Brush::Solid(fill_color.into()),
            FillPattern::Sinosoidal => todo!(),
            FillPattern::Checkerboard | FillPattern::Stripes => Brush::Image {
                image: self
                    .pattern_image
                    .clone()
                    .expect("Pattern image should be set for stripes and checkerboard patterns"),
                start: (x_origin + shift_x, y_origin + shift_y).into(),
                fit_mode: ImageFitMode::Exact {
                    width: pattern_size,
                    height: pattern_size,
                },
                sampling: ImageSampling::Nearest,
                edge_mode: (Extend::Repeat, Extend::Repeat),
                transform: Some(pattern_transform),
                alpha: self.params.alpha.map(|a| a as f32),
            },
        };

        let stroke_color = self.params.stroke_color.to_display_rgba(dc);

        let stroke_brush = crate::visual::renderer::brushes::Brush::Solid(stroke_color.into());

        let stroke_width = self.params.stroke_width.eval(windows_size, screen_props);

        let stroke_options = crate::visual::renderer::styles::StrokeStyle::new(stroke_width);

        scene.draw_shape_filled(&window_state, &self.params.shape, &fill_brush.into(), None);

        // match &self.params.shape {
        //     Shape::Circle { x, y, radius } => {
        //         let x = x.eval(windows_size, screen_props);
        //         let y = y.eval(windows_size, screen_props);
        //         let radius = radius.eval(windows_size, screen_props);

        //         // move by x_origin and y_origin
        //         let x = x + x_origin;
        //         let y = y + y_origin;

        //         let shape = crate::visual::renderer::shapes::Shape::circle((x, y), radius);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Rectangle { x, y, width, height } => {
        //         let x = x.eval(windows_size, screen_props);
        //         let y = y.eval(windows_size, screen_props);
        //         let width = width.eval(windows_size, screen_props);
        //         let height = height.eval(windows_size, screen_props);

        //         // move by x_origin and y_origin
        //         let x = x + x_origin;
        //         let y = y + y_origin;

        //         let shape = crate::visual::renderer::shapes::Shape::rectangle((x, y), width, height);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Ellipse {
        //         x,
        //         y,
        //         radius_x,
        //         radius_y,
        //     } => {
        //         let x = x.eval(windows_size, screen_props);
        //         let y = y.eval(windows_size, screen_props);
        //         let radius_x = radius_x.eval(windows_size, screen_props);
        //         let radius_y = radius_y.eval(windows_size, screen_props);

        //         // move by x_origin and y_origin
        //         let x = x + x_origin;
        //         let y = y + y_origin;

        //         let shape = crate::visual::renderer::shapes::Shape::ellipse((x, y), radius_x, radius_y, 0.0);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Line { x1, y1, x2, y2 } => {
        //         let x1 = x1.eval(windows_size, screen_props);
        //         let y1 = y1.eval(windows_size, screen_props);
        //         let x2 = x2.eval(windows_size, screen_props);
        //         let y2 = y2.eval(windows_size, screen_props);

        //         // move by x_origin and y_origin
        //         let x1 = x1 + x_origin;
        //         let y1 = y1 + y_origin;
        //         let x2 = x2 + x_origin;
        //         let y2 = y2 + y_origin;

        //         let shape = crate::visual::renderer::shapes::Shape::line((x1, y1), (x2, y2));

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Polygon { points } => {
        //         let points = points
        //             .iter()
        //             .map(|p| {
        //                 let x = p.0.eval(windows_size, screen_props);
        //                 let y = p.1.eval(windows_size, screen_props);

        //                 // move by x_origin and y_origin
        //                 (x + x_origin, y + y_origin).into()
        //             })
        //             .collect::<Vec<(f32, f32)>>();

        //         let shape = crate::visual::renderer::shapes::Shape::polygon(points);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Triangle { a, b, c } => {
        //         let a = (
        //             a.0.eval(windows_size, screen_props) + x_origin,
        //             a.1.eval(windows_size, screen_props) + y_origin,
        //         );
        //         let b = (
        //             b.0.eval(windows_size, screen_props) + x_origin,
        //             b.1.eval(windows_size, screen_props) + y_origin,
        //         );
        //         let c = (
        //             c.0.eval(windows_size, screen_props) + x_origin,
        //             c.1.eval(windows_size, screen_props) + y_origin,
        //         );
        //         let shape = crate::visual::renderer::shapes::Shape::triangle(a, b, c);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        //     Shape::Path { points } => {
        //         let points = points
        //             .iter()
        //             .map(|p| {
        //                 let x = p.0.eval(windows_size, screen_props);
        //                 let y = p.1.eval(windows_size, screen_props);

        //                 // move by x_origin and y_origin
        //                 (x + x_origin, y + y_origin)
        //             })
        //             .collect::<Vec<(f32, f32)>>();

        //         let shape = crate::visual::renderer::shapes::Shape::path(points);

        //         scene.draw_shape_fill(shape.clone(), fill_brush.clone(), None, None);

        //         scene.draw_shape_stroke(shape, stroke_brush, stroke_options, None, None);
        //     }
        // };
    }
    fn set_visible(&mut self, visible: bool) {
        self.visible = visible;
    }

    fn visible(&self) -> bool {
        self.visible
    }

    fn set_transformation(&mut self, transformation: crate::visual::geometry::Transformation2D) {
        self.transform = transformation;
    }

    fn transformation(&self) -> crate::visual::geometry::Transformation2D {
        self.transform.clone()
    }

    fn get_param(&self, name: &str) -> Option<StimulusParamValue> {
        self.params.get_param(name)
    }

    fn set_param(&mut self, name: &str, value: StimulusParamValue) {
        self.params.set_param(name, value)
    }

    fn dispatch_event(&mut self, event: &crate::input::Event, window_state: &WindowStateSnapshot) -> bool {
        // handle mouse click events
        return match event {
            crate::input::Event::MouseButtonPress { position, .. }
            | crate::input::Event::TouchStart { position, .. } => {
                let (x, y) = (Size::Pixels(position.0 as f32), Size::Pixels(position.1 as f32));
                if self.contains(x, y, window_state.into()) {
                    self.params.flag_clicked = true;
                    true
                } else {
                    false
                }
            }
            _ => false,
        };
    }

    fn contains(&self, x: Size, y: Size, window_state: &WindowStateSnapshot) -> bool {
        let windows_size = window_state.size;
        let screen_props = window_state.physical_screen;

        // translate x and y by the stimulus position
        let x = -x + self.params.x.clone();
        let y = -y + self.params.y.clone();

        // check if the shape contains a point
        self.params.shape.contains_point((x, y), windows_size, screen_props)
    }

    fn clicked(&mut self) -> bool {
        if self.params.flag_clicked {
            self.params.flag_clicked = false; // reset the flag
            true // return true if the button was clicked
        } else {
            false // return false if the button was not clicked
        }
    }
}
