use crate::{
    builder::validator::labelinfo::LabelInfo, gui_handler::gui::iconizable::IconizablePositioning,
    prelude::*,
};

use super::{
    fill_type::FillType,
    wrapper::{IconizableWrapper, TextableWrapper},
};

use vulkan_rs::prelude::*;

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

use anyhow::Result;

pub struct LabelBuilder {
    text_alignment: TextAlignment,
    text_ratio: f32,
    text_color: Color,
    text: Option<String>,

    background: Option<FillTypeInfo>,
}

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

        self
    }

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

        self
    }

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

        self
    }

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

        self
    }

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

        self
    }

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

        let textable_wrapper = TextableWrapper::new(
            framable.clone(),
            self.text_color,
            self.text_ratio,
            self.text_alignment,
        );

        if let Some(text) = &self.text {
            textable_wrapper.set_text(text, false)?;
        }

        let background = self
            .background
            .map(|info| FillType::new(framable.clone(), info))
            .transpose()?;

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

        Ok(Arc::new(Label {
            framable,
            background: RwLock::new(background),
            textable_wrapper,
            info_icon,

            visible: AtomicBool::new(false),
        }))
    }
}

pub struct Label {
    pub(crate) framable: Arc<Framable>,
    background: RwLock<Option<FillType>>,
    textable_wrapper: TextableWrapper,
    info_icon: IconizableWrapper,

    visible: AtomicBool,
}

impl Label {
    pub fn builder() -> LabelBuilder {
        LabelBuilder {
            text_alignment: TextAlignment::Center,
            text_ratio: 0.7,
            text_color: Color::Black,
            text: None,

            background: None,
        }
    }

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

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

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

    pub fn set_alignment(&self, alignment: TextAlignment) -> Result<()> {
        self.textable_wrapper.set_alignment(alignment)
    }

    pub fn set_text_ratio(&self, ratio: f32) -> Result<()> {
        self.textable_wrapper.set_height_ratio(ratio)
    }

    pub fn text_ratio(&self) -> f32 {
        self.textable_wrapper.height_ratio()
    }

    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 set_background(&self, background: impl Into<FillTypeInfo>) -> Result<()> {
        super::set_background(self.visible(), &self.framable, &self.background, background)
    }

    pub fn try_from(label_info: &LabelInfo, gui_handler: &Arc<GuiHandler>) -> Result<Arc<Self>> {
        let text = label_info.text.read().unwrap().clone();
        let color = label_info.text_color;

        let mut label_builder = Label::builder()
            .set_text_color(color)
            .set_text_alignment(label_info.text_alignment);

        if let Some(background_type) = &label_info.background_type {
            label_builder = label_builder.set_background(background_type.clone());
        }

        if let Some(ratio) = label_info.text_ratio {
            label_builder = label_builder.set_ratio(ratio);
        }

        if !text.is_empty() {
            label_builder = label_builder.set_text(text);
        }

        label_builder.build(gui_handler.clone())
    }

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

        self.textable_wrapper.disable()?;
        self.info_icon.disable()?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.disable()?;
        }

        Ok(())
    }
}

impl GuiElementTraits for Label {
    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::Label(self))
    }
}

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

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

            if visibility {
                self.framable.add()?;

                self.textable_wrapper.enable()?;
                self.info_icon.enable()?;

                if let Some(background) = self.background.read().unwrap().as_ref() {
                    background.enable()?;
                }
            } else {
                self.disable_base()?;
            }
        }

        Ok(())
    }
}

impl Gridable for Label {
    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.textable_wrapper.update()?;
        self.info_icon.update_frame()?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.update_frame()?;
        }

        Ok(())
    }

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

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

    fn set_layer(&self, layer: i32) -> Result<()> {
        self.framable.set_ui_layer(layer);
        self.textable_wrapper.set_ui_layer(layer)?;
        self.info_icon.set_ui_layer(layer)?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.set_ui_layer(layer);
        }

        Ok(())
    }

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

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