mod abilities;
mod character;
mod content;
mod inventory;
mod page_content;
mod traits;

use anyhow::Result;
use downcast_rs::{Downcast, impl_downcast};
use engine::prelude::*;
use rpg_components::{
    components::{
        crafting_materials::CraftingMaterials,
        inventory::{Inventory, Storable},
    },
    items::ability_book::Ability,
};

use std::{
    any::Any,
    collections::HashMap,
    ops::{Deref, DerefMut},
    sync::{
        Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
        atomic::{AtomicUsize, Ordering::SeqCst},
    },
};

use self::{abilities::AbilityPage, character::CharacterPage, inventory::InventoryPage};

trait Page: Any + Send + Sync + Downcast {
    fn enable(&mut self) -> Result<Arc<Grid>>;
    fn disable(&mut self) -> Result<()>;
    fn select(&self) -> Result<()>;
    fn next_tab(&mut self) -> Result<()>;
    fn previous_tab(&mut self) -> Result<()>;
    fn event(&mut self, button: ControllerButton) -> Result<bool>;
}

impl_downcast!(Page);

struct Tab<'a> {
    index: usize,
    tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>,
}

impl<'a> Deref for Tab<'a> {
    type Target = Box<dyn Page>;

    fn deref(&self) -> &Self::Target {
        &self.tabs[self.index]
    }
}

struct TabMut<'a> {
    index: usize,
    tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>,
}

impl<'a> TabMut<'a> {
    pub fn downcast_mut<T: Page>(&mut self) -> &mut T {
        self.tabs[self.index].downcast_mut().unwrap()
    }
}

impl<'a> Deref for TabMut<'a> {
    type Target = Box<dyn Page>;

    fn deref(&self) -> &Self::Target {
        &self.tabs[self.index]
    }
}

impl<'a> DerefMut for TabMut<'a> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.tabs[self.index]
    }
}

pub struct Tabs<'a> {
    tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>,
}

#[allow(unused)]
impl<'a> Tabs<'a> {
    pub fn character(&mut self) -> &CharacterPage {
        self.tabs[0].downcast_ref().unwrap()
    }

    pub fn inventory<A: Ability + 'static>(&mut self) -> &InventoryPage<A> {
        self.tabs[1].downcast_ref().unwrap()
    }

    pub fn abilities<A: Ability + 'static>(&mut self) -> &AbilityPage<A> {
        self.tabs[2].downcast_ref().unwrap()
    }
}

pub struct TabsMut<'a> {
    tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>,
}

#[allow(unused)]
impl<'a> TabsMut<'a> {
    pub fn character(&mut self) -> &mut CharacterPage {
        self.tabs[0].downcast_mut().unwrap()
    }

    pub fn inventory<A: Ability + 'static>(&mut self) -> &mut InventoryPage<A> {
        self.tabs[1].downcast_mut().unwrap()
    }

    pub fn abilities<A: Ability + 'static>(&mut self) -> &mut AbilityPage<A> {
        self.tabs[2].downcast_mut().unwrap()
    }
}

pub struct CharacterWindow {
    close: Box<dyn FutureStateChange>,

    menu_gui: Arc<GuiBuilder>,
    tab_content_grid: Arc<Grid>,

    tooltips: Mutex<HashMap<String, Arc<GuiBuilder>>>,

    tabs: RwLock<[Box<dyn Page>; 3]>,
    tab: AtomicUsize,
}

impl CharacterWindow {
    pub fn new<A: Ability + 'static>(
        world: &mut World,
        hero: Entity,
        name: &str,
        close: Box<dyn FutureStateChange>,
    ) -> Result<Arc<Self>> {
        let menu_gui = GuiBuilder::from_str(
            world.resources.get_mut::<GuiHandler>(),
            include_str!("../resources/menu.xml"),
        )?;

        let content_grid = menu_gui.element("tab_content")?;
        let open_character_page: Arc<Button> = menu_gui.element("open_statistics")?;
        let open_inventory_page: Arc<Button> = menu_gui.element("open_inventory")?;
        let open_ability_page: Arc<Button> = menu_gui.element("open_abilities")?;
        let close_button: Arc<Button> = menu_gui.element("close")?;

        let character_window = Arc::new_cyclic(|me| CharacterWindow {
            close,

            menu_gui,
            tab_content_grid: content_grid,

            tooltips: Mutex::default(),

            tabs: RwLock::new([
                Box::new(CharacterPage::new(world, hero, name, me).unwrap()),
                Box::new(InventoryPage::<A>::new(world, hero, me.clone(), &close_button).unwrap()),
                Box::new(AbilityPage::<A>::new(world, hero, me.clone(), &close_button).unwrap()),
            ]),
            tab: AtomicUsize::new(0),
        });

        let open_tab = {
            let weak_me = Arc::downgrade(&character_window);

            move |gui_handler: &mut GuiHandler, index| {
                if let Some(me) = weak_me.upgrade() {
                    me.tab.store(index, SeqCst);

                    me.tab_content_grid
                        .attach(gui_handler, me.tab_mut().enable()?, 0, 0, 1, 1)?;
                    me.tab().select()?;
                }

                Ok(())
            }
        };

        open_character_page.set_callback({
            let open_tab = open_tab.clone();

            move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 0)
        });

        open_inventory_page.set_callback({
            let open_tab = open_tab.clone();

            move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 1)
        });

        open_ability_page.set_callback({
            let open_tab = open_tab.clone();

            move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 2)
        });

        Self::setup_menu(&character_window)?;

        Ok(character_window)
    }

    pub fn event(&self, button: ControllerButton) -> Result<bool> {
        self.tabs.write().unwrap()[self.tab.load(SeqCst)].event(button)
    }

    pub fn tabs<'a>(&'a self) -> Tabs<'a> {
        Tabs {
            tabs: self.tabs.read().unwrap(),
        }
    }

    pub fn tabs_mut<'a>(&'a self) -> TabsMut<'a> {
        TabsMut {
            tabs: self.tabs.write().unwrap(),
        }
    }

    fn tab(&self) -> Tab<'_> {
        Tab {
            index: self.tab.load(SeqCst),
            tabs: self.tabs.read().unwrap(),
        }
    }

    fn tab_mut(&self) -> TabMut<'_> {
        TabMut {
            index: self.tab.load(SeqCst),
            tabs: self.tabs.write().unwrap(),
        }
    }

    pub fn add_tooltip(&self, name: impl ToString, gui: impl Into<Arc<GuiBuilder>>) {
        self.tooltips
            .lock()
            .unwrap()
            .insert(name.to_string(), gui.into());
    }

    pub fn remove_tooltip(&self, name: impl ToString) {
        self.tooltips.lock().unwrap().remove(&name.to_string());
    }

    pub fn salvage_from_inventory<A, F, S>(world: &mut World, hero: Entity, f: F) -> Result<()>
    where
        A: Ability + 'static,
        F: FnOnce(&mut Inventory<A>) -> S,
        S: Storable,
    {
        let entity = world.entity_mut(hero)?;
        let mut multi_mut = entity.multi_mut();

        let crafting_materials = multi_mut.get::<CraftingMaterials>()?;
        let inventory = multi_mut.get::<Inventory<A>>()?;

        // remove callback
        let storable = f(inventory);

        crafting_materials.increment(storable.rarity());

        Ok(())
    }
}

impl TopLevelGui for CharacterWindow {
    fn gui_traits(&self) -> &dyn GuiElementTraits {
        self
    }

    fn top_gui(&self) -> Option<&dyn TopGui> {
        Some(self)
    }

    fn elements(&self) -> Option<&HashMap<String, UiElement>> {
        None
    }

    fn functionality(&self) -> Option<&dyn Functionality> {
        None
    }

    fn enable(&self, gui_handler: &mut GuiHandler) -> Result<()> {
        self.menu_gui.enable(gui_handler)?;

        self.tab_content_grid
            .attach(gui_handler, self.tab_mut().enable()?, 0, 0, 1, 1)?;
        self.tab().select()?;

        let close_button: Arc<Button> = self.menu_gui.element("close")?;
        close_button.set_info_icon(
            gui_handler,
            &self.engine.controller_icon(ControllerButton::B)?,
        )?;

        let left_info: Arc<Icon> = self.menu_gui.element("left_info")?;
        left_info.set_icon(
            gui_handler,
            &self.engine.controller_icon(ControllerButton::LeftButton)?,
        )?;

        let right_info: Arc<Icon> = self.menu_gui.element("right_info")?;
        right_info.set_icon(
            gui_handler,
            &self.engine.controller_icon(ControllerButton::RightButton)?,
        )?;

        Ok(())
    }

    fn disable(&self, gui_handler: &mut GuiHandler) -> Result<()> {
        self.menu_gui.disable(gui_handler)?;

        Ok(())
    }
}

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

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

    fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
        None
    }
}

impl TopGui for CharacterWindow {
    fn decline(&self, world: &mut World) -> Result<()> {
        (self.close)(world)
    }

    fn next_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
        match second_level {
            false => {
                // disable old tab
                self.tab_mut().disable()?;

                // add to tab index
                self.tab.store((self.tab.load(SeqCst) + 1) % 3, SeqCst);

                // update tab content
                self.tab_content_grid.attach(
                    world.resources.get_mut::<GuiHandler>(),
                    self.tab_mut().enable()?,
                    0,
                    0,
                    1,
                    1,
                )?;
                self.tab().select()?;
            }
            true => {
                self.tab_mut().next_tab()?;
            }
        }

        Ok(())
    }

    fn previous_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
        match second_level {
            false => {
                // disable old tab
                self.tab_mut().disable()?;

                // subtract from tab index
                if self.tab.load(SeqCst) == 0 {
                    self.tab.store(2, SeqCst);
                } else {
                    self.tab.store(self.tab.load(SeqCst) - 1, SeqCst);
                }

                // update tab content
                self.tab_content_grid.attach(
                    world.resources.get_mut::<GuiHandler>(),
                    self.tab_mut().enable()?,
                    0,
                    0,
                    1,
                    1,
                )?;
                self.tab().select()?;
            }
            true => {
                self.tab_mut().previous_tab()?;
            }
        }

        Ok(())
    }
}

impl CharacterWindow {
    fn setup_menu(&self) -> Result<()> {
        // let open_statistics = self.switch_tab(STATISTICS_TAB);
        // let open_abilities = self.switch_tab(ABILITY_TAB);
        // let open_inventory = self.switch_tab(INVENTORY_TAB);

        let close = self.close.clone();

        self.menu_gui.set_click_callbacks(
            ClickCallbacks::default()
                // .add("open_statistics", open_statistics)
                // .add("open_abilities", open_abilities)
                // .add("open_inventory", open_inventory)
                .add("close", |world, _gui_handler| close(world))
                .into(),
        )?;

        Ok(())
    }
}