use gio::prelude::*;
use gtk::prelude::*;
use std::collections::HashMap;

const LAPTOP_DARK: &[u8] = include_bytes!("../assets/laptop-dark.svg");
const DISPLAY_DARK: &[u8] = include_bytes!("../assets/display-dark.svg");

const CSS: &[u8] = br#"
"#;

const COLUMNS: &[&[Section]] = &[
    &[
        Section::new(
            "Direction keys",
            &[
                Shortcut::new(
                    "Arrow keys",
                    Event::ArrowKeys,
                    Schema::Hardcoded(&["←", "↓", "↑", "→"]),
                ),
                Shortcut::new(
                    "Or Vim shortcuts",
                    Event::ArrowKeysAlt,
                    Schema::Hardcoded(&["H", "J", "K", "L"]),
                ),
            ],
        ),
        Section::new(
            "Navigate applications and windows",
            &[
                Shortcut::new(
                    "Launch and switch applications",
                    Event::Search,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "activate-launcher" },
                ),
                Shortcut::new(
                    "Switch focus between windows",
                    Event::SwitchFocus,
                    Schema::Hardcoded(&["Super", "←", "↓", "↑", "→"]),
                ),
            ],
        ),
        Section::new(
            "Move, resize and swap windows in adjustment mode",
            &[
                Shortcut::new(
                    "Enter adjustment mode",
                    Event::EnterAdjustment,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "tile-enter" },
                ),
                Shortcut::new(
                    "Move window",
                    Event::MoveWindow,
                    Schema::Hardcoded(&["←", "↓", "↑", "→"]),
                ),
                Shortcut::new(
                    "Increase Window Size",
                    Event::ResizeWindowIncrease,
                    Schema::Hardcoded(&["Shift", "→", "↓"]),
                ),
                Shortcut::new(
                    "Decrease Window Size",
                    Event::ResizeWindowDecrease,
                    Schema::Hardcoded(&["Shift", "←", "↑"]),
                ),
                Shortcut::new(
                    "Swap windows",
                    Event::SwapWindows,
                    Schema::Hardcoded(&["Ctrl", "←", "↓", "↑", "→"]),
                ),
                Shortcut::new(
                    "Apply changes",
                    Event::ApplyChanges,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "tile-accept" },
                ),
                Shortcut::new("Cancel",
                    Event::Cancel,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "tile-reject" },
                ),
            ],
        ),
        Section::new(
            "Workspaces",
            &[
                Shortcut::new(
                    "Move current window up one workspace",
                    Event::MoveWorkspaceAbove,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "move-to-workspace-up" },
                ),
                Shortcut::new(
                    "Move current window down one workspace",
                    Event::MoveWorkspaceBelow,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "move-to-workspace-down" },
                ),
                Shortcut::new(
                    "Switch focus to the workspace above",
                    Event::MoveWorkspaceAbove,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "switch-to-workspace-up" },
                ),
                Shortcut::new(
                    "Switch focus to the workspace below",
                    Event::MoveWorkspaceBelow,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "switch-to-workspace-down" },
                ),
            ],
        ),
        Section::new(
            "Window Shortcuts",
            &[
                Shortcut::new(
                    "Change window orientation",
                    Event::OrientationToggle,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "tile-orientation" },
                ),
                Shortcut::new(
                    "Toggle floating mode",
                    Event::FloatingToggle,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "toggle-floating" },
                ),
                Shortcut::new(
                    "Toggle auto-tiling",
                    Event::AutoTileToggle,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "toggle-tiling" },
                ),
                Shortcut::new(
                    "Close window",
                    Event::CloseWindow,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "close" },
                ),
                Shortcut::new(
                    "Toggle maximize",
                    Event::MaximizeToggle,
                    Schema::GSettings { schema: "org.gnome.desktop.wm.keybindings", key: "toggle-maximized" },
                ),
            ],
        ),
        Section::new(
            "Launcher Shortcuts",
            &[
                Shortcut::new(
                    "Activate Launcher",
                    Event::Search,
                    Schema::GSettings { schema: "org.gnome.shell.extensions.pop-shell", key: "activate-launcher" },
                ),
                Shortcut::new(
                    "Scroll through the Launcher list (or use Arrow keys)",
                    Event::ScrollLauncher,
                    Schema::Hardcoded(&["Ctrl", "J", "K"]),
                ),
                Shortcut::new(
                    "Execute a command in a terminal",
                    Event::ExecuteCommandTerminal,
                    Schema::Hardcoded(&["t:"]),
                ),
                Shortcut::new(
                    "Execute a command in sh",
                    Event::ExecuteCommandSh,
                    Schema::Hardcoded(&[":"]),
                ),
                Shortcut::new(
                    "Calculate an equation",
                    Event::Calculate,
                    Schema::Hardcoded(&["="]),
                ),
            ],
        ),
    ],
];

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Event {
    ArrowKeys,
    ArrowKeysAlt,
    MoveMonitorLeft,
    MoveMonitorRight,
    Search,
    ScrollLauncher,
    ExecuteCommandTerminal,
    ExecuteCommandSh,
    Calculate,
    SwitchFocus,
    SwitchFocusMonitorLeft,
    SwitchFocusMonitorRight,
    SwitchFocusWorkspaceAbove,
    SwitchFocusWorkspaceBelow,
    MoveWorkspaceAbove,
    MoveWorkspaceBelow,
    EnterAdjustment,
    MoveWindow,
    ResizeWindowIncrease,
    ResizeWindowDecrease,
    SwapWindows,
    ApplyChanges,
    Cancel,
    FloatingToggle,
    AutoTileToggle,
    OrientationToggle,
    MaximizeToggle,
    CloseWindow,
}

pub struct State {
    // Holds the schema settings that are to be connected later
    pub registration: HashMap<&'static str, (gio::Settings, HashMap<&'static str, Vec<gtk::Box>>)>,
}

impl Default for State {
    fn default() -> Self {
        Self {
            registration: HashMap::default(),
        }
    }
}

impl State {
    /// Register an intention to watch a key on a schema for changes
    pub fn register(&mut self, schema: &'static str, settings: gio::Settings, key: &'static str, shortcuts: gtk::Box) {
        self.registration.entry(schema)
            .or_insert_with(|| (settings, HashMap::new()))
            .1
            .entry(key)
            .or_insert_with(Vec::new)
            .push(shortcuts)
    }

    /// Watch for changes to keys on schemas, with one signal per `gio::Settings`
    pub fn connect_schemas(&mut self) {
        for (_, (settings, keys)) in self.registration.drain() {
            settings.connect_changed(enclose!((settings) move |_, changed| {
                if let Some(widgets) = keys.get(changed) {
                    for widget in widgets {
                        widget.foreach(|w| widget.remove(w));
                        for shortcut in settings.get_strv(changed) {
                            widget.add(&gtk::ShortcutLabel::new(&shortcut));
                        }
                        widget.show_all();
                    }
                }
            }));
        }
    }
}


pub struct Section {
    pub header: &'static str,
    pub shortcuts: &'static [Shortcut],
}

impl Section {
    pub const fn new(header: &'static str, shortcuts: &'static [Shortcut]) -> Self {
        Self { header, shortcuts }
    }
}

pub struct Shortcut {
    pub description: &'static str,
    pub event: Event,
    pub schema: Schema,
}

impl Shortcut {
    pub const fn new(description: &'static str, event: Event, schema: Schema) -> Self {
        Self {
            description,
            event,
            schema,
        }
    }
}

pub enum Schema {
    GSettings { schema: &'static str, key: &'static str },
    Hardcoded(&'static [&'static str]),
}

pub fn main(app: &gtk::Application) {
    cascade! {
        provider: gtk::CssProvider::new();
        ..load_from_data(CSS).unwrap();
        | gtk::StyleContext::add_provider_for_screen(
            gdk::Screen::get_default().as_ref().unwrap(),
            &provider,
            gtk::STYLE_PROVIDER_PRIORITY_APPLICATION
        );
    }

    let mut state = State::default();
    let shortcuts_section = shortcuts_section(&mut state);
    state.connect_schemas();
    // let laptop = &svg_draw_area(LAPTOP_DARK, 300, 230);
    // let display = &svg_draw_area(DISPLAY_DARK, 300, 300);

    let shortcuts = cascade! {
        gtk::Box::new(gtk::Orientation::Vertical, 24);
        ..set_border_width(8);
        ..add(&shortcuts_section);
    };

    let scroller = cascade! {
        gtk::ScrolledWindowBuilder::new()
            .hscrollbar_policy(gtk::PolicyType::Never)
            .build();
        ..set_vexpand(true);
        ..add(&shortcuts);
    };

    let content = cascade! {
        gtk::Box::new(gtk::Orientation::Vertical, 24);
        //..add(&demo_section(&laptop, display));
        ..add(&scroller);
    };

    let window = cascade! {
        gtk::ApplicationWindow::new(app);
        ..set_size_request(600, 350);
        ..set_default_size(600, 500);
        ..set_icon_name("input-keyboard".into());
        ..add(&content);
        ..show_all();
        ..connect_delete_event(move |window, _| {
            window.close();
            gtk::Inhibit(false)
        });
    };

    window.connect_size_allocate(move |_, allocation| {
        let width = (allocation.width - allocation.width.min(1000)) / 2;

        shortcuts.set_margin_start(width);
        shortcuts.set_margin_end(width);
        shortcuts.set_halign(if width == 0 {
            gtk::Align::Center
        } else {
            gtk::Align::Fill
        });
    });
}

fn legend() -> gtk::Box {
    let arrow_keys = cascade! {
        gtk::Box::new(gtk::Orientation::Horizontal, 24);
        ..add(&cascade! {
            gtk::Box::new(gtk::Orientation::Horizontal, 4);
            ..add(&crate::misc::keycap("←"));
            ..add(&crate::misc::keycap("↓"));
            ..add(&crate::misc::keycap("↑"));
            ..add(&crate::misc::keycap("→"));
        });
        ..add(&gtk::Label::new("- arrow keys".into()));
    };

    let alt_arrow_keys = cascade! {
        gtk::Box::new(gtk::Orientation::Horizontal, 24);
        ..add(&cascade! {
            gtk::Box::new(gtk::Orientation::Horizontal, 4);
            ..add(&crate::misc::keycap("H"));
            ..add(&crate::misc::keycap("J"));
            ..add(&crate::misc::keycap("K"));
            ..add(&crate::misc::keycap("L"));
        });
        ..add(&gtk::Label::new("- use in place of arrow keys".into()));
    };

    let container = cascade! {
        gtk::Box::new(gtk::Orientation::Vertical, 8);
        ..add(&arrow_keys);
        ..add(&alt_arrow_keys);
    };

    container
}

fn demo_section(laptop: &gtk::DrawingArea, display: &gtk::DrawingArea) -> gtk::Box {
    let container = cascade! {
        gtk::Box::new(gtk::Orientation::Horizontal, 32);
        ..set_halign(gtk::Align::Center);
        ..add(laptop);
        ..add(display);
    };

    laptop.set_valign(gtk::Align::End);

    container
}

fn settings_reference() -> gtk::Box {
    use std::process::Command;

    let hyperlink = cascade! {
        gtk::LinkButton::new("Keyboard Settings");
        ..connect_clicked(move |_| {
            let _ = Command::new("gnome-control-center")
                .arg("keyboard")
                .spawn();
        });
    };

    let container = cascade! {
        gtk::Box::new(gtk::Orientation::Horizontal, 0);
        ..set_halign(gtk::Align::Start);
        ..add(&cascade! {
            gtk::Label::new("These keyboard shortcuts can be changed in".into());
        });
        ..add(&hyperlink);
    };

    container
}

fn shortcuts_section(state: &mut State) -> gtk::FlowBox {
    let key_sg = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);

    let container = cascade! {
        gtk::FlowBox::new();
        ..set_selection_mode(gtk::SelectionMode::None);
        ..set_max_children_per_line(2);
        ..set_row_spacing(12);
        ..set_column_spacing(12);
    };

    let mut settings_map = HashMap::new();
    let iter = COLUMNS.iter().flat_map(|i| i.iter()).map(|section| {
        crate::widgets::Section::new(&key_sg, section, state, &mut settings_map)
    });

    for widget in iter {
        container.add(widget.as_ref());
    }

    container
}

pub fn open_schema(schema: &str) -> gio::Settings {
    if schema == "org.gnome.shell.extensions.pop-shell" {
        let settings_schema = gio::SettingsSchemaSource::from_directory(
        "/usr/share/gnome-shell/extensions/pop-shell@system76.com/schemas",
        None,
        false
    ).unwrap().lookup(schema, false).unwrap();
        gio::Settings::new_full::<gio::SettingsBackend>(
            &settings_schema,
            None,
            None,
        )
    } else {
        gio::Settings::new(schema)
    }
}

/*
fn svg_draw_area(svg: &[u8], width: i32, height: i32) -> gtk::DrawingArea {
    let drawing_area = gtk::DrawingArea::new();

    let opt = resvg::Options::default();
    let tree = resvg::usvg::Tree::from_data(svg, &opt.usvg).unwrap();

    drawing_area.connect_draw(move |w, cr| {
        let screen = resvg::ScreenSize::new(
            w.get_allocated_width() as u32,
            w.get_allocated_height() as u32,
        )
        .unwrap();

        resvg::backend_cairo::render_to_canvas(&tree, &opt, screen, cr);

        gtk::Inhibit(false)
    });

    drawing_area.set_size_request(width, height);

    drawing_area
}
*/
