use crate::{
    builder::validator::buttoninfo::ButtonInfo, guihandler::gui::iconizable::IconizablePositioning,
    prelude::*,
};

use anyhow::Result;
use assetpath::AssetPath;
use utilities::prelude::*;
use vulkan_rs::prelude::*;

use super::{
    fill_type::{FillType, InnerFillType},
    wrapper::{IconizableWrapper, TextableWrapper},
    IconBuilderType,
};
use cgmath::{vec2, vec4};

use std::sync::{
    atomic::{AtomicBool, Ordering::SeqCst},
    Arc, Mutex,
};

pub struct ButtonBuilder {
    icon: Option<Arc<Image>>,
    margin: u32,

    text: String,
    text_color: Color,
    ratio: f32,
    text_alignment: TextAlignment,

    button_select_mode: ButtonSelectMode,

    #[cfg(feature = "audio")]
    click_sound: Option<AssetPath>,

    #[cfg(feature = "audio")]
    hover_sound: Option<AssetPath>,

    normal: Option<FillTypeInfo>,
    selected: Option<FillTypeInfo>,
}

impl ButtonBuilder {
    pub fn set_select_mode(mut self, select_mode: ButtonSelectMode) -> Self {
        self.button_select_mode = select_mode;

        self
    }

    pub fn set_normal(mut self, normal: impl Into<FillTypeInfo>) -> Self {
        self.normal = Some(normal.into());

        self
    }

    pub fn set_selected(mut self, selected: impl Into<FillTypeInfo>) -> Self {
        self.selected = Some(selected.into());

        self
    }

    pub fn set_icon(mut self, icon: Option<Arc<Image>>, margin: u32) -> Self {
        self.icon = icon;
        self.margin = margin;

        self
    }

    pub fn set_text_ratio(mut self, ratio: f32) -> Self {
        self.ratio = ratio;

        self
    }

    pub fn set_text_alignment(mut self, text_alignment: TextAlignment) -> Self {
        self.text_alignment = text_alignment;

        self
    }

    pub fn set_text_color(mut self, text_color: Color) -> Self {
        self.text_color = text_color;

        self
    }

    pub fn set_text(mut self, text: impl Into<String>) -> Self {
        self.text = text.into();

        self
    }

    #[cfg(feature = "audio")]
    pub fn set_click_sound(mut self, path: AssetPath) -> Self {
        self.click_sound = Some(path);

        self
    }

    #[cfg(feature = "audio")]
    pub fn set_hover_sound(mut self, path: AssetPath) -> Self {
        self.hover_sound = Some(path);

        self
    }

    pub fn build(self, gui_handler: Arc<GuiHandler>) -> Result<Arc<Button>> {
        let framable = Framable::new(gui_handler.clone(), false)?;

        let normal = FillType::new(
            framable.clone(),
            match self.normal {
                Some(info) => info,
                None => FillTypeInfo::from(gui_handler.menu_button().clone()),
            },
        )?;

        let selected = FillType::new(
            framable.clone(),
            match self.selected {
                Some(info) => info,
                None => FillTypeInfo::from(gui_handler.menu_button_selected().clone()),
            },
        )?;

        let click_executable = Executable::new();
        let select_executable = Executable::new();
        let on_select_executable = Executable::new();

        #[cfg(feature = "audio")]
        let click_sound = self
            .click_sound
            .map(|path| Audible::new(gui_handler.clone(), path))
            .transpose()?;

        #[cfg(feature = "audio")]
        let hover_sound = self
            .hover_sound
            .map(|path| Audible::new(gui_handler.clone(), path))
            .transpose()?;

        let clickable = Clickable::new(framable.clone(), click_executable.clone());

        let selectable = Selectable::new(
            gui_handler,
            click_executable.clone(),
            select_executable.clone(),
            on_select_executable.clone(),
        );

        let hoverable = Hoverable::new(
            framable.clone(),
            select_executable.clone(),
            on_select_executable.clone(),
        );

        #[cfg(feature = "audio")]
        if let Some(click_sound) = &click_sound {
            clickable.set_audible(Some(click_sound.clone()));
            selectable.set_click_audible(Some(click_sound.clone()));
        }

        #[cfg(feature = "audio")]
        if let Some(hover_sound) = &hover_sound {
            selectable.set_select_audible(Some(hover_sound.clone()));
            hoverable.set_audible(Some(hover_sound.clone()));
        }

        let textable = TextableWrapper::new(
            framable.clone(),
            self.text_color,
            self.ratio,
            self.text_alignment,
        );

        if !self.text.is_empty() {
            textable.set_text(&self.text, false)?;
        }

        let iconizable = IconizableWrapper::new(
            framable.clone(),
            self.icon.map(IconBuilderType::Image),
            None,
            self.margin,
        )?;

        let info_icon = IconizableWrapper::new(
            framable.clone(),
            None,
            Some(IconizablePositioning {
                left: 1.2,
                right: 0.0,
                top: 1.2,
                bottom: 0.0,
            }),
            self.margin,
        )?;

        let button = Arc::new(Button {
            framable,
            clickable,
            hoverable,
            selectable,
            textable,
            iconizable,
            info_icon,

            #[cfg(feature = "audio")]
            _click_sound: click_sound,
            #[cfg(feature = "audio")]
            _hover_sound: hover_sound,

            click_executable,
            select_executable,
            on_select_executable,

            normal,
            selected,

            button_state: Mutex::new(ButtonState::Normal),

            visible: AtomicBool::new(false),

            select_mode: self.button_select_mode,
        });

        // Button::create_hovered_changed_callback(button.clone())?;
        Button::create_clicked_changed_callback(button.clone());
        Button::create_selected_changed_callback(button.clone());

        Ok(button)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ButtonState {
    Normal,
    Selected,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ButtonSelectMode {
    None,
    Bigger,
}

pub struct Button {
    clickable: Arc<Clickable>,
    hoverable: Arc<Hoverable>,
    selectable: Arc<Selectable>,
    framable: Arc<Framable>,
    iconizable: IconizableWrapper,
    textable: TextableWrapper,
    info_icon: IconizableWrapper,

    #[cfg(feature = "audio")]
    _click_sound: Option<Arc<Audible>>,
    #[cfg(feature = "audio")]
    _hover_sound: Option<Arc<Audible>>,

    click_executable: Arc<Executable<()>>,
    select_executable: Arc<Executable<bool>>,
    on_select_executable: Arc<Executable<bool>>,

    normal: FillType,
    selected: FillType,

    button_state: Mutex<ButtonState>,
    select_mode: ButtonSelectMode,

    visible: AtomicBool,
}

impl Button {
    pub fn builder() -> ButtonBuilder {
        ButtonBuilder {
            icon: None,
            margin: 0,

            text: String::new(),
            text_color: Color::Black,
            ratio: 0.7,
            text_alignment: TextAlignment::Center,

            #[cfg(feature = "audio")]
            click_sound: None,
            #[cfg(feature = "audio")]
            hover_sound: None,

            button_select_mode: ButtonSelectMode::Bigger,

            normal: None,
            selected: None,
        }
    }

    pub fn select(self: &Arc<Self>) -> Result<()> {
        self.selectable.select()
    }

    pub fn set_callback<F>(&self, callback: F)
    where
        F: Fn() -> Result<()> + Send + Sync + 'static,
    {
        self.click_executable.set_callback(move |_| callback());
    }

    pub fn set_select_callback<F>(&self, callback: F)
    where
        F: Fn(bool) -> Result<()> + Send + Sync + 'static,
    {
        self.on_select_executable.set_callback(callback);
    }

    pub fn set_custom_callback<F>(&self, callback: F)
    where
        F: Fn(ControllerButton) -> Result<bool> + Send + Sync + 'static,
    {
        self.selectable.set_custom_callback(callback);
    }

    pub fn set_text(&self, text: impl ToString) -> Result<()> {
        self.textable.set_text(text, self.visible())
    }

    pub fn set_icon(&self, icon: &Arc<Image>) -> Result<()> {
        self.iconizable.set_icon(icon, self.visible())
    }

    pub fn set_icon_margon(&self, margin: u32) -> Result<()> {
        self.iconizable.set_margin(margin)
    }

    pub fn clear_icon(&self) -> Result<()> {
        self.iconizable.clear_icon(self.visible())
    }

    pub fn icon(&self) -> Result<Option<Arc<Image>>> {
        self.iconizable.icon()
    }

    pub fn set_info_icon(&self, button: &Arc<Image>) -> Result<()> {
        self.info_icon.set_icon(button, self.visible())
    }

    pub fn clear_info_icon(&self) -> Result<()> {
        self.info_icon.clear_icon(self.visible())
    }

    pub fn text(&self) -> Result<Option<String>> {
        self.textable.text()
    }

    pub fn set_text_color(&self, text_color: Color) -> Result<()> {
        self.textable.set_text_color(text_color)
    }

    pub fn try_from(button_info: &ButtonInfo, gui_handler: &Arc<GuiHandler>) -> Result<Arc<Self>> {
        let mut button_builder = Button::builder().set_select_mode(button_info.select_mode);

        if let Some(normal) = button_info.normal.clone() {
            button_builder = button_builder.set_normal(normal);
        }

        if let Some(selected) = button_info.selected.clone() {
            button_builder = button_builder.set_selected(selected);
        }

        button_builder = button_builder
            .set_text(button_info.text.read().unwrap().clone())
            .set_text_color(button_info.text_color)
            .set_text_alignment(button_info.text_alignment);

        if let Some(ratio) = button_info.text_ratio {
            button_builder = button_builder.set_text_ratio(ratio);
        }

        #[cfg(feature = "audio")]
        if !button_info.click_sound.is_empty() {
            button_builder = button_builder.set_click_sound(button_info.click_sound.clone());
        }

        #[cfg(feature = "audio")]
        if !button_info.hover_sound.is_empty() {
            button_builder = button_builder.set_hover_sound(button_info.hover_sound.clone());
        }

        button_builder = button_builder.set_icon(
            button_info
                .icon
                .clone()
                .map(|icon| {
                    Image::from_file(AssetPath::from((
                        gui_handler.resource_base_path().full_path(),
                        icon,
                    )))?
                    .attach_sampler(Sampler::nearest_sampler().build(gui_handler.device())?)
                    .build(gui_handler.device(), gui_handler.queue())
                })
                .transpose()?,
            button_info.margin,
        );

        let button = button_builder.build(gui_handler.clone())?;

        Ok(button)
    }
}

impl GuiElementTraits for Button {
    fn gridable(&self) -> Option<&dyn Gridable> {
        Some(self)
    }

    fn visibility(&self) -> Option<&dyn Visibility> {
        Some(self)
    }

    fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
        Some(GuiElement::Button(self))
    }
}

impl Visibility for Button {
    fn visible(&self) -> bool {
        self.visible.load(SeqCst)
    }

    fn set_visibility(&self, visibility: bool) -> Result<()> {
        if visibility != self.visible.load(SeqCst) {
            self.visible.store(visibility, SeqCst);

            if visibility {
                self.framable.add()?;
                self.selectable.add()?;
                self.hoverable.add()?;
                self.clickable.add()?;

                self.textable.enable()?;
                self.iconizable.enable()?;
                self.info_icon.enable()?;

                match *self.button_state.lock().unwrap() {
                    ButtonState::Normal => self.normal.enable()?,
                    ButtonState::Selected => self.selected.enable()?,
                }
            } else {
                self.disable_base()?;
            }
        }

        Ok(())
    }
}

impl Gridable for Button {
    fn set_frame(
        &self,
        x: i32,
        y: i32,
        w: u32,
        h: u32,
        vert_align: VerticalAlign,
        hori_align: HorizontalAlign,
    ) -> Result<()> {
        self.framable.set_frame(x, y, w, h, vert_align, hori_align);

        self.normal.update_frame()?;
        self.selected.update_frame()?;
        self.textable.update()?;
        self.iconizable.update_frame()?;
        self.info_icon.update_frame()?;

        if self.select_mode == ButtonSelectMode::Bigger {
            self.modify_hovered_vbo()?;
        }

        Ok(())
    }

    fn selectable(&self) -> Option<&Arc<Selectable>> {
        Some(&self.selectable)
    }

    fn type_name(&self) -> &str {
        "Button"
    }

    fn set_layer(&self, layer: i32) -> Result<()> {
        self.clickable.set_ui_layer(layer);
        self.hoverable.set_ui_layer(layer);
        self.selectable.set_ui_layer(layer);
        self.framable.set_ui_layer(layer);
        self.iconizable.set_ui_layer(layer)?;
        self.textable.set_ui_layer(layer)?;
        self.info_icon.set_ui_layer(layer)?;

        self.normal.set_ui_layer(layer);
        self.selected.set_ui_layer(layer);

        Ok(())
    }

    fn position_extent(&self) -> (i32, i32, u32, u32) {
        self.framable.position()
    }
}

impl Drop for Button {
    fn drop(&mut self) {
        if self.visible.load(SeqCst) {
            self.disable_base().unwrap();
        }
    }
}

// private
impl Button {
    // fn create_hovered_changed_callback(button: Arc<Button>) -> Result<()> {
    //     let button_weak = Arc::downgrade(&button);

    //     let hovered_changed = Box::new(move |_| {
    //         if let Some(button) = button_weak.upgrade() {
    //             if !button.clickable.clicked() {
    //                 if button.hoverable.hovered() {
    //                     button.set_button_state(ButtonState::Selected)?;
    //                 } else {
    //                     button.set_button_state(ButtonState::Normal)?;
    //                 }
    //             }
    //         }

    //         Ok(())
    //     });

    //     button
    //         .hoverable
    //         .set_hovered_changed_callback(Some(hovered_changed))
    // }

    fn create_clicked_changed_callback(button: Arc<Button>) {
        let button_weak = Arc::downgrade(&button);

        let clicked_changed = Box::new(move || {
            if let Some(button) = button_weak.upgrade() {
                if button.clickable.clicked() {
                    button.hoverable.set_hovered(false)?;
                } else {
                    // if let Some(displayable) = displayable_weak.upgrade() {
                    // displayable.update_frame()?;
                    // }
                }
            }

            Ok(())
        });

        button
            .clickable
            .set_clicked_changed_callback(Some(clicked_changed));
    }

    fn create_selected_changed_callback(button: Arc<Button>) {
        let button_weak = Arc::downgrade(&button);

        let selected_changed = move |selected| {
            if let Some(button) = button_weak.upgrade() {
                if selected {
                    button.set_button_state(ButtonState::Selected)?;
                } else {
                    button.set_button_state(ButtonState::Normal)?;
                }
            }

            Ok(())
        };

        button.select_executable.set_callback(selected_changed);
    }

    fn modify_hovered_vbo(&self) -> Result<()> {
        assert!(
            self.framable.is_framed(),
            "button frame needs to be defined before hovering can be calculated"
        );

        let offset = (self.framable.bottom() as i32 - self.framable.top()) as f32 * 0.1;

        match &self.selected.inner {
            InnerFillType::Image(displayable) => {
                let buffer = displayable.buffer();
                let mut frame = buffer.map_complete()?;

                let ortho = self.framable.ortho();
                let left = self.framable.left() as f32;
                let right = self.framable.right() as f32;
                let top = self.framable.top() as f32;
                let bottom = self.framable.bottom() as f32;

                frame[0].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0);
                frame[1].position = ortho * vec4(right + offset, bottom + offset, 0.0, 1.0);
                frame[2].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0);
                frame[3].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0);
                frame[4].position = ortho * vec4(left - offset, top - offset, 0.0, 1.0);
                frame[5].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0);

                frame[0].texture_coordinates = vec2(0.0, 1.0);
                frame[1].texture_coordinates = vec2(1.0, 1.0);
                frame[2].texture_coordinates = vec2(1.0, 0.0);
                frame[3].texture_coordinates = vec2(1.0, 0.0);
                frame[4].texture_coordinates = vec2(0.0, 0.0);
                frame[5].texture_coordinates = vec2(0.0, 1.0);
            }
            InnerFillType::Color(colorable) => {
                let buffer = colorable.buffer();
                let mut frame = buffer.map_complete()?;

                let ortho = self.framable.ortho();
                let left = self.framable.left() as f32;
                let right = self.framable.right() as f32;
                let top = self.framable.top() as f32;
                let bottom = self.framable.bottom() as f32;

                frame[0].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0);
                frame[1].position = ortho * vec4(right + offset, bottom + offset, 0.0, 1.0);
                frame[2].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0);
                frame[3].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0);
                frame[4].position = ortho * vec4(left - offset, top - offset, 0.0, 1.0);
                frame[5].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0);
            }
        }

        Ok(())
    }

    fn disable_base(&self) -> Result<()> {
        self.framable.delete()?;

        self.selectable.delete()?;
        self.hoverable.delete()?;
        self.clickable.delete()?;

        self.textable.disable()?;
        self.iconizable.disable()?;
        self.info_icon.disable()?;

        self.normal.disable()?;
        self.selected.disable()?;

        Ok(())
    }

    #[inline]
    fn set_button_state(&self, button_state: ButtonState) -> Result<()> {
        let mut current_button_state = self.button_state.lock().unwrap();

        if button_state == *current_button_state {
            return Ok(());
        }

        *current_button_state = button_state;

        match button_state {
            ButtonState::Normal => {
                if self.visible() {
                    self.normal.enable()?;
                }

                self.selected.disable()?;
            }
            ButtonState::Selected => {
                self.normal.disable()?;

                if self.visible() {
                    self.selected.enable()?;
                }
            }
        }

        Ok(())
    }
}