use crate::{builder::validator::textfieldinfo::TextFieldInfo, prelude::*};
use anyhow::Result;
use utilities::prelude::*;

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

use super::fill_type::{FillType, InnerFillType};

pub struct TextFieldBuilder {
    background: Option<FillTypeInfo>,
    text_color: Color,
    text: Option<String>,
    text_ratio: f32,
    text_alignment: TextAlignment,
}

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

        self
    }

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

        self
    }

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

        self
    }

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

        self
    }

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

        self
    }

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

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

        let textable = Textable::new(
            framable.clone(),
            match self.text {
                Some(text) => text,
                None => "".to_string(),
            },
            self.text_ratio,
            self.text_alignment,
            self.text_color,
        )?;

        let text_changed_executable = Executable::new();

        let writeable = Writeable::new(
            gui_handler,
            textable.clone(),
            text_changed_executable.clone(),
        )?;

        let text_field = Arc::new(TextField {
            framable,
            background,
            writeable,
            textable,

            text_changed_executable,

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

        text_field.update_color_change();

        Ok(text_field)
    }
}

pub struct TextField {
    framable: Arc<Framable>,
    writeable: Arc<Writeable>,
    textable: Arc<Textable>,

    text_changed_executable: Arc<Executable<Option<String>>>,

    background: RwLock<Option<FillType>>,

    visible: AtomicBool,
}

impl TextField {
    pub fn builder() -> TextFieldBuilder {
        TextFieldBuilder {
            background: None,
            text_color: Color::Black,
            text: None,
            text_ratio: 0.7,
            text_alignment: TextAlignment::default(),
        }
    }

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

        let mut text_field_builder = Self::builder()
            .set_text_color(color)
            .set_text_alignment(info.text_alignment);

        if let Some(background_type) = &info.background_type {
            text_field_builder = text_field_builder.set_background(background_type.clone());
        }

        if let Some(ratio) = info.text_ratio {
            text_field_builder = text_field_builder.set_text_ratio(ratio);
        }

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

        text_field_builder.build(gui_handler.clone())
    }

    fn update_color_change(self: &Arc<Self>) {
        if let Some(background) = &*self.background.read().unwrap() {
            if let InnerFillType::Color(colorable) = &background.inner {
                let normal_color = colorable.color();
                let focus_color = normal_color * 1.4;

                let weak_self = Arc::downgrade(self);

                self.writeable.set_activation_changed(move |active| {
                    if let Some(me) = weak_self.upgrade() {
                        if active {
                            if let Some(background) = &*me.background.read().unwrap() {
                                if let InnerFillType::Color(colorable) = &background.inner {
                                    colorable.set_color(focus_color)?;
                                }
                            }
                        } else if let Some(background) = &*me.background.read().unwrap() {
                            if let InnerFillType::Color(colorable) = &background.inner {
                                colorable.set_color(normal_color)?;
                            }
                        }
                    }

                    Ok(())
                });
            }
        }
    }

    pub fn set_text_changed_callback<F>(&self, f: F)
    where
        F: Fn(Option<String>) -> Result<()> + Send + Sync + 'static,
    {
        self.text_changed_executable.set_callback(f);
    }

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

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

    pub fn text(&self) -> Option<String> {
        let t = self.textable.text();

        if t.is_empty() {
            None
        } else {
            Some(t)
        }
    }

    pub fn set_background(self: &Arc<Self>, background: impl Into<FillTypeInfo>) -> Result<()> {
        super::set_background(self.visible(), &self.framable, &self.background, background)?;

        self.update_color_change();

        Ok(())
    }

    pub fn background_color(&self) -> Option<Color> {
        if let Some(background) = self.background.read().unwrap().as_ref() {
            if let InnerFillType::Color(colorable) = &background.inner {
                return Some(colorable.color());
            }
        }

        None
    }

    pub fn add_letter(&self, letter: char) -> Result<()> {
        self.writeable.add_letter(letter)
    }

    pub fn remove_last(&self) -> Result<()> {
        self.writeable.remove_last()
    }

    pub fn focus_input(&self) -> Result<()> {
        self.writeable.set_active()
    }

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

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

        Ok(())
    }
}

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

impl Gridable for TextField {
    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.update_text()?;

        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 {
        "Text Field"
    }

    fn set_layer(&self, layer: i32) -> Result<()> {
        assert!(!self.visible(), "can't change ui layer while visible");

        self.framable.set_ui_layer(layer);
        self.textable.set_ui_layer(layer);
        self.writeable.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 Visibility for TextField {
    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.add()?;
                self.writeable.add()?;

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

        Ok(())
    }
}

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

        self.textable.clear_callback();
    }
}