pub mod editables;
pub mod ui;

use crate::context::GameContext;
use crate::editor::EditorSubsystem;
use crate::interactible::Interactible;
use raui_core::widget::unit::text::TextBoxSizeValue;
use raui_immediate_widgets::material::text_paper;
use raui_material::component::text_paper::TextPaperProps;
use spitfire_draw::utils::ShaderRef;
use spitfire_input::{InputActionRef, InputAxisRef};
use std::ops::{BitAnd, BitOr, BitXor, Not};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
    any::{Any, TypeId},
    borrow::Cow,
    cell::RefCell,
    collections::{BTreeMap, BTreeSet},
};

thread_local! {
    static REGISTRY: RefCell<BTreeMap<EditableEntry, EditableItem>> = Default::default();
    static ACTIVELY_EDITING: RefCell<BTreeSet<EditableEntry>> = Default::default();
    static STACK: RefCell<Vec<Vec<EditableEntry>>> = Default::default();
    static INSTANCE: AtomicUsize = Default::default();
}

const SHADER_NAME: &str = "~~editable-renderables-shader~~";
const DEBUG_VERTEX: &str = r#"#version 300 es
    layout(location = 0) in vec2 a_position;
    out vec4 v_color;
    uniform mat4 u_projection_view;

    void main() {
        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
    }
    "#;

const DEBUG_FRAGMENT: &str = r#"#version 300 es
    precision highp float;
    precision highp int;
    out vec4 o_color;
    uniform float u_time;

    vec3 hsv2rgb(vec3 c) {
        vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
        vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
        return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
    }

    void main() {
        vec2 pixel = floor(gl_FragCoord.xy);
        float hue = fract((floor(pixel.x) + floor(pixel.y)) * 0.01 + u_time);
        o_color = vec4(hsv2rgb(vec3(hue, 1.0, 1.0)), 1.0);
    }
    "#;

pub(crate) struct EditableItem {
    pub widget_location: EditableWidgetLocation,
    pub entry: EditableEntry,
    pub data: Box<dyn Any>,
    #[allow(clippy::type_complexity)]
    pub update: Box<dyn FnMut(&mut dyn Any, &mut GameContext, f32)>,
    #[allow(clippy::type_complexity)]
    pub draw: Box<dyn FnMut(&mut dyn Any, &mut GameContext)>,
    #[allow(clippy::type_complexity)]
    pub draw_widget: Box<dyn FnMut(&mut dyn Any, &mut GameContext)>,
    #[allow(clippy::type_complexity)]
    pub description: Box<dyn Fn(&dyn Any) -> Cow<'static, str>>,
    pub is_alive: bool,
}

impl EditableItem {
    pub(crate) fn update(&mut self, context: &mut GameContext, delta_time: f32) {
        (self.update)(&mut *self.data, context, delta_time);
    }

    pub(crate) fn draw(&mut self, context: &mut GameContext) {
        (self.draw)(&mut *self.data, context);
    }

    pub(crate) fn draw_widget(&mut self, context: &mut GameContext) {
        text_paper(TextPaperProps {
            text: format!(
                "#{} | {}",
                self.entry.instance(),
                (self.description)(&*self.data)
            ),
            variant: "S".to_owned(),
            height: TextBoxSizeValue::Content,
            ..Default::default()
        });

        (self.draw_widget)(&mut *self.data, context);
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EditableEntry {
    instance: usize,
    location: u64,
    wrapper_type_id: TypeId,
    data_type_id: TypeId,
}

impl EditableEntry {
    pub fn maintain() {
        REGISTRY.with_borrow_mut(|registry| {
            registry.retain(|_, record| record.is_alive);
            for record in registry.values_mut() {
                record.is_alive = false;
            }
        });
        STACK.with_borrow_mut(|stack| stack.clear());
        INSTANCE.with(|v| v.store(0, Ordering::SeqCst))
    }

    fn item<'a, T: EditableType>(
        location: u64,
        default_value: impl FnOnce() -> T,
        description: Option<Cow<'static, str>>,
        registry: &'a mut BTreeMap<EditableEntry, EditableItem>,
        stack: &'a mut [Vec<EditableEntry>],
    ) -> &'a mut EditableItem {
        let entry = Self {
            instance: INSTANCE.with(|v| v.fetch_add(1, Ordering::SeqCst)),
            location,
            wrapper_type_id: TypeId::of::<T>(),
            data_type_id: T::data_type_id(),
        };
        if let Some(region) = stack.last_mut() {
            region.push(entry)
        }
        registry.entry(entry).or_insert_with(|| EditableItem {
            widget_location: T::WIDGET_LOCATION,
            entry,
            data: Box::new(default_value()),
            update: Box::new(|data, context, delta_time| {
                data.downcast_mut::<T>()
                    .unwrap()
                    .update(context, delta_time)
            }),
            draw: Box::new(|data, context| data.downcast_mut::<T>().unwrap().draw(context)),
            draw_widget: Box::new(|data, context| {
                data.downcast_mut::<T>().unwrap().widget(context)
            }),
            description: Box::new(move |data| {
                if let Some(description) = description.as_ref() {
                    description.clone()
                } else {
                    data.downcast_ref::<T>().unwrap().description()
                }
            }),
            is_alive: false,
        })
    }

    fn with_registry<R>(f: impl FnOnce(&mut BTreeMap<EditableEntry, EditableItem>) -> R) -> R {
        REGISTRY.with_borrow_mut(|registry| f(registry))
    }

    fn with_registry_and_stack<R>(
        f: impl FnOnce(&mut BTreeMap<EditableEntry, EditableItem>, &mut Vec<Vec<EditableEntry>>) -> R,
    ) -> R {
        REGISTRY.with_borrow_mut(|registry| STACK.with_borrow_mut(|stack| f(registry, stack)))
    }

    pub(crate) fn with_registry_and_actively_editing<R>(
        f: impl FnOnce(&mut BTreeMap<EditableEntry, EditableItem>, &BTreeSet<EditableEntry>) -> R,
    ) -> R {
        REGISTRY.with_borrow_mut(|registry| {
            ACTIVELY_EDITING.with_borrow(|actively_editing| f(registry, actively_editing))
        })
    }

    pub fn entry<T: EditableType>(
        location: u64,
        default_value: impl FnOnce() -> T,
        description: Option<Cow<'static, str>>,
    ) -> Self {
        Self::with_registry_and_stack(|registry, stack| {
            let item = Self::item(location, default_value, description, registry, stack);
            item.is_alive = true;
            item.entry
        })
    }

    pub fn value<T: EditableType>(
        location: u64,
        default_value: impl FnOnce() -> T,
        description: Option<Cow<'static, str>>,
        edit: bool,
    ) -> T::Value {
        Self::with_registry_and_stack(|registry, stack| {
            let item = Self::item(location, default_value, description, registry, stack);
            if edit {
                item.entry.start_editing();
            }
            item.is_alive = true;
            item.data
                .downcast_ref::<T>()
                .expect("EditableEntry type mismatch")
                .unpack()
        })
    }

    pub fn entry_value<T: EditableType>(
        location: u64,
        default_value: impl FnOnce() -> T,
        description: Option<Cow<'static, str>>,
    ) -> (Self, T::Value) {
        Self::with_registry_and_stack(|registry, stack| {
            let item = Self::item(location, default_value, description, registry, stack);
            item.is_alive = true;
            let value = item
                .data
                .downcast_ref::<T>()
                .expect("EditableEntry type mismatch")
                .unpack();
            (item.entry, value)
        })
    }

    pub fn get<T: EditableType>(self) -> Option<T::Value> {
        Self::with_registry(|registry| {
            let item = registry.get_mut(&self)?;
            item.data
                .downcast_ref::<T>()
                .map(|wrapped| wrapped.unpack())
        })
    }

    pub fn with<T: 'static, R>(self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
        Self::with_registry(|registry| {
            let item = registry.get_mut(&self)?;
            item.data.downcast_mut::<T>().map(f)
        })
    }

    pub fn location(self) -> u64 {
        self.location
    }

    pub fn instance(self) -> usize {
        self.instance
    }

    pub fn wrapper_type_id(self) -> TypeId {
        self.wrapper_type_id
    }

    pub fn data_type_id(self) -> TypeId {
        self.data_type_id
    }

    pub fn start_editing(self) {
        ACTIVELY_EDITING.with_borrow_mut(|actively_editing| {
            if !actively_editing.contains(&self) {
                actively_editing.insert(self);
            }
        });
    }

    pub fn start_editing_many(entries: impl IntoIterator<Item = Self>) {
        ACTIVELY_EDITING.with_borrow_mut(|actively_editing| {
            actively_editing.extend(entries);
        });
    }

    pub fn stop_editing(self) {
        ACTIVELY_EDITING.with_borrow_mut(|actively_editing| {
            actively_editing.remove(&self);
        });
    }

    pub fn stop_editing_many(entries: impl IntoIterator<Item = Self>) {
        ACTIVELY_EDITING.with_borrow_mut(|actively_editing| {
            for entry in entries {
                actively_editing.remove(&entry);
            }
        });
    }

    pub fn stop_editing_all() {
        ACTIVELY_EDITING.with_borrow_mut(|actively_editing| {
            actively_editing.clear();
        });
    }

    pub fn begin_region() {
        STACK.with_borrow_mut(|stack| {
            stack.push(Default::default());
        });
    }

    pub fn end_region() -> Vec<Self> {
        STACK.with_borrow_mut(|stack| {
            let entries = stack.pop().unwrap_or_default();
            if let Some(region) = stack.last_mut() {
                region.extend(entries.iter().copied());
            }
            entries
        })
    }

    pub fn push_to_region(self) {
        STACK.with_borrow_mut(|stack| {
            if let Some(region) = stack.last_mut() {
                region.push(self);
            }
        });
    }
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct EditableWidgetLocation(u8);

impl EditableWidgetLocation {
    pub const NOWHERE: Self = Self(0);
    pub const WORLD_SPACE: Self = Self(1 << 0);
    pub const EDITING_PANEL: Self = Self(1 << 1);
}

impl EditableWidgetLocation {
    pub const fn with(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }

    pub const fn contains(self, other: Self) -> bool {
        (self.0 & other.0) == other.0
    }
}

impl BitOr for EditableWidgetLocation {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl BitAnd for EditableWidgetLocation {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

impl BitXor for EditableWidgetLocation {
    type Output = Self;

    fn bitxor(self, rhs: Self) -> Self::Output {
        Self(self.0 ^ rhs.0)
    }
}

impl Not for EditableWidgetLocation {
    type Output = Self;

    fn not(self) -> Self::Output {
        Self(!self.0)
    }
}

pub trait EditableType: 'static {
    type Value;
    const WIDGET_LOCATION: EditableWidgetLocation;

    fn data_type_id() -> TypeId {
        TypeId::of::<Self::Value>()
    }

    fn unpack(&self) -> Self::Value;

    #[allow(unused_variables)]
    fn update(&mut self, context: &mut GameContext, delta_time: f32) {}

    #[allow(unused_variables)]
    fn draw(&mut self, context: &mut GameContext) {}

    #[allow(unused_variables)]
    fn widget(&mut self, context: &mut GameContext) {}

    fn description(&self) -> Cow<'static, str> {
        std::any::type_name::<Self::Value>().into()
    }
}

#[derive(Default)]
pub struct Editable {
    pub pointer_x: InputAxisRef,
    pub pointer_y: InputAxisRef,
    pub pointer_trigger: InputActionRef,
    pub highlight: Option<(Vec<EditableEntry>, Interactible)>,
}

#[derive(Default)]
pub struct EditableSubsystem;

impl Drop for EditableSubsystem {
    fn drop(&mut self) {
        EditableEntry::stop_editing_all();
    }
}

impl EditorSubsystem for EditableSubsystem {
    fn update(&mut self, mut context: GameContext, delta_time: f32) {
        EditableEntry::with_registry_and_actively_editing(|registry, entries| {
            for entry in entries {
                if let Some(editable) = registry.get_mut(entry)
                    && editable
                        .widget_location
                        .contains(EditableWidgetLocation::WORLD_SPACE)
                {
                    editable.update(&mut context, delta_time);
                }
            }
        });
    }

    fn draw(&mut self, mut context: GameContext) {
        let shader = Self::shader(&mut context);

        {
            let mut editable = context.globals.ensure::<Editable>();
            let mut editable = editable.write();
            if let Some((entries, interactible)) = editable.highlight.take() {
                interactible.draw_wireframe(
                    &shader,
                    [1.0, 1.0, 0.0, 1.0].into(),
                    context.draw,
                    context.graphics,
                    context.time,
                );
                if editable.pointer_trigger.get().is_pressed() {
                    EditableEntry::stop_editing_all();
                    EditableEntry::start_editing_many(entries);
                }
            }
        }

        EditableEntry::with_registry_and_actively_editing(|registry, entries| {
            for entry in entries {
                if let Some(editable) = registry.get_mut(entry)
                    && editable
                        .widget_location
                        .contains(EditableWidgetLocation::WORLD_SPACE)
                {
                    editable.draw(&mut context);
                }
            }
        });
    }

    fn draw_gui(&mut self, _: GameContext) {
        EditableEntry::maintain();
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

impl EditableSubsystem {
    pub fn shader(context: &mut GameContext) -> ShaderRef {
        let shader = context
            .draw
            .shaders
            .entry(SHADER_NAME.into())
            .or_insert_with(|| {
                context
                    .graphics
                    .shader(DEBUG_VERTEX, DEBUG_FRAGMENT)
                    .unwrap()
            });
        ShaderRef::object(shader.clone())
    }
}

#[macro_export]
macro_rules! editable {
    (@location) => {{
        use std::{
            collections::hash_map::DefaultHasher,
            hash::{Hash, Hasher},
        };
        let file = file!();
        let line = line!();
        let column = column!();
        let mut hasher = DefaultHasher::new();
        (file, line, column).hash(&mut hasher);
        hasher.finish()
    }};
    (@entry $description:literal => $value:expr) => {{
        $crate::editor::features::editable::EditableEntry::entry(
            editable!(@location),
            move || $value,
            Some($description.into()),
        )
    }};
    (@entry $value:expr) => {{
        $crate::editor::features::editable::EditableEntry::entry(
            editable!(@location),
            move || $value,
            None,
        )
    }};
    (@edit $description:literal => $value:expr) => {{
        $crate::editor::features::editable::EditableEntry::value(
            editable!(@location),
            move || $value,
            Some($description.into()),
            true,
        )
    }};
    (@edit $value:expr) => {{
        $crate::editor::features::editable::EditableEntry::value(
            editable!(@location),
            move || $value,
            None,
            true,
        )
    }};
    ($description:literal => $value:expr) => {{
        $crate::editor::features::editable::EditableEntry::value(
            editable!(@location),
            move || $value,
            Some($description.into()),
            false,
        )
    }};
    ($value:expr) => {{
        $crate::editor::features::editable::EditableEntry::value(
            editable!(@location),
            move || $value,
            None,
            false,
        )
    }};
}

#[macro_export]
macro_rules! editable_renderables {
    ($context:expr, $body:block) => {{
        if !$context.globals.editor.is_editing() {
            { $body }
        } else {
            $crate::editor::features::editable::EditableEntry::begin_region();
            let interactible = $crate::interactible::RenderableInteractible::new($context.graphics);
            {
                $body
            }
            let entries = $crate::editor::features::editable::EditableEntry::end_region();
            let mut editable = $context
                .globals
                .ensure::<$crate::editor::features::editable::Editable>();
            let x = editable.read().pointer_x.get().0;
            let y = editable.read().pointer_y.get().0;
            let screen = $context
                .globals
                .editor
                .window_screen_to_viewport_screen($context.graphics, Vec2::new(x, y));
            let world = $context
                .graphics
                .state
                .main_camera
                .screen_to_world_point(screen);
            let interactible = interactible.interactible($context.graphics);
            if interactible.contains_point(world) {
                editable.write().highlight = Some((entries, interactible));
            }
        }
    }};
}

#[cfg(test)]
mod tests {
    use super::*;

    impl EditableType for i32 {
        type Value = Self;
        const WIDGET_LOCATION: EditableWidgetLocation = EditableWidgetLocation::NOWHERE;

        fn data_type_id() -> TypeId {
            TypeId::of::<Self>()
        }

        fn unpack(&self) -> Self::Value {
            *self
        }
    }

    #[test]
    fn test_editable_hash_uniqueness() {
        let hash1 = editable!(@location);
        let hash2 = editable!(@location);
        assert_ne!(hash1, hash2, "Hashes should be unique for different calls");
    }

    #[test]
    fn test_editable_value_storage() {
        let initial_value = editable!(10_i32);
        assert_eq!(initial_value, 10);

        let updated_value = editable!(20_i32);
        assert_eq!(updated_value, 20);
    }

    #[test]
    fn test_editable_entry_modification() {
        for index in 0..3 {
            let entry = editable!(@entry 5_i32);

            match index {
                0 => {
                    let value = entry.get::<i32>().unwrap();
                    assert_eq!(value, 5);
                    entry.with::<i32, ()>(|value| {
                        *value = 42;
                    });
                }
                2 => {
                    let value = entry.get::<i32>().unwrap();
                    assert_eq!(value, 42);
                }
                _ => break,
            }
        }
    }
}
