use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseEvent};
use mlua::{Lua, Table};

use crate::app::plugins::ui_location::ui_location::UiLocation;

use super::key_settings::KeySettings;

#[macro_export]
macro_rules! RegisterKeySettings {(
    $(#[$attr:meta])*
    $pub:vis struct $key_settings:ident {
        $(
            $(#[$field_attr:meta])*
            $field_pub:vis $field_name:ident: $field_type:ty,
        )*
    }) => {
        impl $key_settings
        {
            pub fn register_userdata(data: &mut mlua::UserDataRegistry<$crate::app::settings::Settings>)
            {
                $(
                    mlua::UserDataFields::add_field_method_get(data, concat!("key_",stringify!($field_name)), |lua, settings| {
                        $crate::app::settings::register_key_settings_macro::
                            get_key(lua, &settings.key.$field_name)
                    });
                    mlua::UserDataFields::add_field_method_set(data, concat!("key_",stringify!($field_name)), |lua, settings, value| {
                        $crate::app::settings::register_key_settings_macro::
                            set_key(lua, &mut settings.key.$field_name, value)
                    });
                )*
            }
        }
    };
}

fn key_modifiers_to_table(lua: &Lua, modifiers: KeyModifiers) -> mlua::Result<Table> {
    let ret = lua.create_table()?;
    ret.set("alt", modifiers.contains(KeyModifiers::ALT))?;
    ret.set("control", modifiers.contains(KeyModifiers::CONTROL))?;
    ret.set("hyper", modifiers.contains(KeyModifiers::HYPER))?;
    ret.set("meta", modifiers.contains(KeyModifiers::META))?;
    ret.set("shift", modifiers.contains(KeyModifiers::SHIFT))?;
    ret.set("super", modifiers.contains(KeyModifiers::SUPER))?;
    Ok(ret)
}

fn key_state_to_table(lua: &Lua, state: KeyEventState) -> mlua::Result<Table> {
    let ret = lua.create_table()?;
    ret.set("caps_lock", state.contains(KeyEventState::CAPS_LOCK))?;
    ret.set("keypad", state.contains(KeyEventState::KEYPAD))?;
    ret.set("num_lock", state.contains(KeyEventState::NUM_LOCK))?;
    Ok(ret)
}

pub fn mouse_event_to_lua(
    lua: &Lua,
    mouse: &MouseEvent,
    location: Option<UiLocation>,
) -> mlua::Result<Table> {
    let ret = lua.create_table()?;
    ret.set("kind", format!("{:?}", mouse.kind))?;
    ret.set("column", mouse.column)?;
    ret.set("row", mouse.row)?;

    let modifiers = key_modifiers_to_table(lua, mouse.modifiers)?;
    ret.set("modifiers", modifiers)?;
    ret.set("location", location)?;
    Ok(ret)
}

pub fn key_event_to_lua(lua: &Lua, key: &KeyEvent) -> mlua::Result<Table> {
    let ret = lua.create_table()?;
    ret.set("code", KeySettings::key_code_to_string(key.code))?;

    let modifiers = key_modifiers_to_table(lua, key.modifiers)?;
    ret.set("modifiers", modifiers)?;

    ret.set("kind", format!("{:?}", key.kind))?;

    let state = key_state_to_table(lua, key.state)?;
    ret.set("state", state)?;

    Ok(ret)
}

pub fn lua_to_key_event(_lua: &Lua, table: &mlua::Table) -> mlua::Result<KeyEvent> {
    let code = match table.get::<String>("code") {
        Ok(value) => KeySettings::string_to_key_code(&value).map_err(mlua::Error::RuntimeError)?,
        Err(e) => match e {
            mlua::Error::FromLuaConversionError {
                from: "nil",
                to,
                message: _,
            } if to == "String" => KeyCode::Null,
            _ => return Err(e),
        },
    };

    let mut modifiers = KeyModifiers::NONE;
    if let Ok(modifiers_table) = table.get::<Table>("modifiers") {
        if modifiers_table.get::<bool>("alt").unwrap_or(false) {
            modifiers |= KeyModifiers::ALT;
        }
        if modifiers_table.get::<bool>("control").unwrap_or(false) {
            modifiers |= KeyModifiers::CONTROL;
        }
        if modifiers_table.get::<bool>("hyper").unwrap_or(false) {
            modifiers |= KeyModifiers::HYPER;
        }
        if modifiers_table.get::<bool>("meta").unwrap_or(false) {
            modifiers |= KeyModifiers::META;
        }
        if modifiers_table.get::<bool>("shift").unwrap_or(false) {
            modifiers |= KeyModifiers::SHIFT;
        }
        if modifiers_table.get::<bool>("super").unwrap_or(false) {
            modifiers |= KeyModifiers::SUPER;
        }
    }

    let kind = match table.get::<String>("kind") {
        Ok(value) => {
            KeySettings::string_to_key_event_kind(&value).map_err(mlua::Error::RuntimeError)?
        }
        Err(e) => match e {
            mlua::Error::FromLuaConversionError {
                from: "nil",
                to,
                message: _,
            } if to == "String" => KeyEventKind::Press,
            _ => return Err(e),
        },
    };

    let mut state = KeyEventState::NONE;
    if let Ok(state_table) = table.get::<Table>("state") {
        if state_table.get::<bool>("caps_lock").unwrap_or(false) {
            state |= KeyEventState::CAPS_LOCK;
        }
        if state_table.get::<bool>("keypad").unwrap_or(false) {
            state |= KeyEventState::KEYPAD;
        }
        if state_table.get::<bool>("num_lock").unwrap_or(false) {
            state |= KeyEventState::NUM_LOCK;
        }
    }

    Ok(KeyEvent {
        code,
        modifiers,
        kind,
        state,
    })
}

pub(super) fn get_key(lua: &Lua, key: &KeyEvent) -> mlua::Result<mlua::Value> {
    key_event_to_lua(lua, key).map(mlua::Value::Table)
}

pub(super) fn set_key(lua: &Lua, color: &mut KeyEvent, value: mlua::Value) -> mlua::Result<()> {
    if let Some(table) = value.as_table() {
        *color = lua_to_key_event(lua, table)?;
        Ok(())
    } else {
        Err(mlua::Error::RuntimeError("Expected table".to_string()))
    }
}
