use crate::guihandler::gui::displayable::DisplayableFillType;
use crate::prelude::*;
use anyhow::Result;
use utilities::prelude::*;

#[cfg(feature = "audio")]
use assetpath::AssetPath;

use super::gridinfo::GridInfo;
use super::mandatory::Mandatory;

use std::convert::TryFrom;
use std::str::from_utf8;
use std::sync::{Arc, RwLock, Weak};

use super::validator::{
    cow_to_button_select_mode, cow_to_fill_type, cow_to_fill_type_info, cow_to_str,
    cow_to_text_alignment, str_into,
};

#[cfg(feature = "audio")]
use super::validator::cow_to_path;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum NeighbourDirection {
    East,
    West,
    North,
    South,
}

#[derive(Debug, Clone)]
pub struct NeighbourInfo {
    pub direction: NeighbourDirection,
    pub id: String,
}

#[derive(Debug)]
pub struct ButtonInfo {
    // global unique id, if set
    pub id: String,

    // slots inside grid
    pub x_slot: Mandatory<usize>,
    pub y_slot: Mandatory<usize>,

    pub x_dim: u32,
    pub y_dim: u32,

    // texturing
    pub normal: Option<FillTypeInfo>,
    pub selected: Option<FillTypeInfo>,

    background_fill_type: DisplayableFillType,

    #[cfg(feature = "audio")]
    pub click_sound: AssetPath,

    #[cfg(feature = "audio")]
    pub hover_sound: AssetPath,

    pub text: RwLock<String>,
    pub text_color: Color,
    pub text_ratio: Option<f32>,
    pub text_alignment: TextAlignment,

    pub icon: Option<String>,
    pub margin: u32,

    pub select_mode: ButtonSelectMode,

    pub neighbour_infos: Vec<NeighbourInfo>,

    // the chosen element for controller input
    pub select: bool,
    pub isolate: bool,

    pub parent: Weak<GridInfo>,
}

impl ButtonInfo {
    pub fn new<'a>(
        #[cfg(feature = "audio")] gui_handler: &Arc<GuiHandler>,
        attributes: quick_xml::events::attributes::Attributes<'a>,
        grid: &Arc<GridInfo>,
    ) -> Result<ButtonInfo> {
        let mut button_info = ButtonInfo {
            id: String::new(),

            x_slot: Mandatory::default(),
            y_slot: Mandatory::default(),

            x_dim: 1,
            y_dim: 1,

            normal: Self::find_menu_button(grid),
            selected: Self::find_menu_button_selected(grid),
            background_fill_type: DisplayableFillType::Expand,

            #[cfg(feature = "audio")]
            click_sound: match Self::find_click_sound(grid) {
                Some(sound) => sound,
                None => gui_handler.click_sound().clone(),
            },

            #[cfg(feature = "audio")]
            hover_sound: match Self::find_hover_sound(grid) {
                Some(sound) => sound,
                None => gui_handler.hover_sound().clone(),
            },

            text: RwLock::default(),
            text_color: Color::Black,
            text_ratio: None,
            text_alignment: TextAlignment::Center,

            icon: None,
            margin: 0,

            select_mode: ButtonSelectMode::Bigger,

            neighbour_infos: Vec::new(),

            select: false,
            isolate: false,

            parent: Arc::downgrade(grid),
        };

        for attribute_res in attributes {
            let attribute = attribute_res?;

            match attribute.key.into_inner() {
                b"id" => button_info.id = cow_to_str(attribute.value),
                b"x_slot" => button_info.x_slot.set(str_into(attribute.value)?),
                b"y_slot" => button_info.y_slot.set(str_into(attribute.value)?),
                b"x_size" => button_info.x_dim = str_into(attribute.value)?,
                b"y_size" => button_info.y_dim = str_into(attribute.value)?,
                b"normal" => button_info.normal = Some(cow_to_fill_type_info(attribute.value)),
                b"selected" => button_info.selected = Some(cow_to_fill_type_info(attribute.value)),
                b"fill_type" => {
                    button_info.background_fill_type = cow_to_fill_type(attribute.value)?;
                }

                #[cfg(feature = "audio")]
                b"click_sound" => button_info.click_sound = cow_to_path(attribute.value),

                #[cfg(feature = "audio")]
                b"hover_sound" => button_info.hover_sound = cow_to_path(attribute.value),

                b"select_mode" => {
                    button_info.select_mode = cow_to_button_select_mode(attribute.value)?
                }
                b"icon" => button_info.icon = Some(cow_to_str(attribute.value)),
                b"icon_margin" => button_info.margin = str_into(attribute.value)?,
                b"text_color" => {
                    button_info.text_color = Color::try_from(cow_to_str(attribute.value).as_str())?
                }
                b"text_ratio" => button_info.text_ratio = Some(str_into(attribute.value)?),
                b"text_alignment" => {
                    button_info.text_alignment = cow_to_text_alignment(attribute.value)?
                }
                b"select" => button_info.select = str_into(attribute.value)?,
                b"west_neighbour" => {
                    button_info.add_neighbour(NeighbourDirection::West, cow_to_str(attribute.value))
                }
                b"east_neighbour" => {
                    button_info.add_neighbour(NeighbourDirection::East, cow_to_str(attribute.value))
                }
                b"north_neighbour" => button_info
                    .add_neighbour(NeighbourDirection::North, cow_to_str(attribute.value)),
                b"south_neighbour" => button_info
                    .add_neighbour(NeighbourDirection::South, cow_to_str(attribute.value)),
                b"isolate" => button_info.isolate = str_into(attribute.value)?,
                _ => {
                    return Err(anyhow::Error::msg(format!(
                        "Unsupported attribute in Button: {}",
                        from_utf8(attribute.key.into_inner())?
                    )))
                }
            }
        }

        if let Some(background_type) = &mut button_info.normal {
            match background_type {
                FillTypeInfo::Image(_) => {}
                FillTypeInfo::Color(_, fill_type) => {
                    *fill_type = button_info.background_fill_type;
                }
                FillTypeInfo::Element(_, fill_type) => {
                    *fill_type = button_info.background_fill_type;
                }
            }
        }

        if let Some(background_type) = &mut button_info.selected {
            match background_type {
                FillTypeInfo::Image(_) => {}
                FillTypeInfo::Color(_, fill_type) => {
                    *fill_type = button_info.background_fill_type;
                }
                FillTypeInfo::Element(_, fill_type) => {
                    *fill_type = button_info.background_fill_type;
                }
            }
        }

        Ok(button_info)
    }

    fn add_neighbour(&mut self, direction: NeighbourDirection, id: String) {
        match self
            .neighbour_infos
            .iter_mut()
            .find(|info| info.direction == direction)
        {
            Some(info) => info.id = id,
            None => self.neighbour_infos.push(NeighbourInfo { direction, id }),
        }
    }

    fn find_menu_button(grid: &GridInfo) -> Option<FillTypeInfo> {
        // return immediately if present
        if let Some(menu_button) = &grid.button_normal {
            return Some(menu_button.clone());
        }

        // check for parent
        if let Some(weak_parent) = &grid.parent {
            if let Some(parent) = weak_parent.upgrade() {
                return Self::find_menu_button(&parent);
            }
        }

        None
    }

    fn find_menu_button_selected(grid: &GridInfo) -> Option<FillTypeInfo> {
        // return immediately if present
        if let Some(menu_button_selected) = &grid.button_selected {
            return Some(menu_button_selected.clone());
        }

        // check for parent
        if let Some(weak_parent) = &grid.parent {
            if let Some(parent) = weak_parent.upgrade() {
                return Self::find_menu_button_selected(&parent);
            }
        }

        None
    }

    #[cfg(feature = "audio")]
    fn find_click_sound(grid: &GridInfo) -> Option<AssetPath> {
        // return immediately if present
        if let Some(click_sound) = &grid.click_sound {
            return Some(click_sound.clone());
        }

        // check for parent
        if let Some(weak_parent) = &grid.parent {
            if let Some(parent) = weak_parent.upgrade() {
                return Self::find_click_sound(&parent);
            }
        }

        None
    }

    #[cfg(feature = "audio")]
    fn find_hover_sound(grid: &GridInfo) -> Option<AssetPath> {
        // return immediately if present
        if let Some(hover_sound) = &grid.hover_sound {
            return Some(hover_sound.clone());
        }

        // check for parent
        if let Some(weak_parent) = &grid.parent {
            if let Some(parent) = weak_parent.upgrade() {
                return Self::find_hover_sound(&parent);
            }
        }

        None
    }
}