pub mod keyboard;
pub mod timeline;
pub mod menu;

mod model;

use std::sync::Arc;
use gtk::prelude::*;

use crate::globals;
use crate::audio::sources::synthesizer;

pub fn init(app: &gtk::Application, audio_context: Arc<crate::AudioContext>) {
    // -- Window and header ---------------------------------------------------
    let header = gtk::HeaderBarBuilder::new()
        .title(globals::APP_NAME)
        .has_subtitle(false)
        .show_close_button(true)
        .build();

    let content = gtk::GridBuilder::new()
        .border_width(5)
        .row_spacing(5)
        .column_homogeneous(true)
        .row_homogeneous(true)
        .build();

    let win = gtk::ApplicationWindowBuilder::new()
        .application(app)
        .title(globals::APP_NAME)
        .default_width(800)
        .default_height(500)
        .build();

    win.set_titlebar(Some(&header));
    win.set_position(gtk::WindowPosition::Center);
    win.add(&content);

    // -- Menu ----------------------------------------------------------------
    let menu = menu::Menu::new(model::MenuModel::new(audio_context.clone()));
    header.pack_start(&menu.widget);

    // -- Real-time notes chart -----------------------------------------------
    let timeline = timeline::Timeline::new(
        keyboard::NOTES_NUMBER,
        audio_context.metronome.clone());
    content.attach(&timeline.widget, 0, 0, 1, 2);

    // -- Screen keyboard -----------------------------------------------------
    let keyboard = Arc::new(keyboard::Keyboard::new());
    content.attach(&keyboard.widget, 0, 2, 1, 1);


    // -- CSS loading ---------------------------------------------------------
    let css = include_str!("../../res/style.css");
    let screen = win.get_screen().unwrap();
    let style_provider = gtk::CssProvider::new();
    style_provider.load_from_data(css.as_bytes()).unwrap();
    gtk::StyleContext::add_provider_for_screen(
        &screen,
        &style_provider,
        gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);


    // -- Global event handling -----------------------------------------------

    // Key press
    let kbd = keyboard.clone();
    let audio = audio_context.clone();
    let tmln = timeline.clone();
    win.connect_key_press_event(move |_, event| {
        if kbd.get_enabled() {
            if let Some(key) = kbd.keys.get(&event.get_keyval()) {
                if !key.get_pressed() {
                    match audio.synthesizer.send(synthesizer::Message::NoteHold(key.note)) {
                        Ok(()) => {
                            key.widget.get_style_context().add_class("key-pressed");
                            key.set_pressed(true);
                            tmln.log_note_start(key.note);
                        },
                        Err(err) => {
                            eprintln!("Failed to send NoteHold message:\n{}", err);
                        }
                    }
                }
            } else {
                if event.get_keyval() == gdk::enums::key::space {
                    if let Err(err) = audio.synthesizer.send(synthesizer::Message::MuteAll) {
                        eprintln!("Failed to send MuteAll message:\n{}", err);
                    }
                }
            }
        }
        Inhibit(false)
    });

    // Key release
    let kbd = keyboard.clone();
    let audio = audio_context.clone();
    let tmln = timeline.clone();
    win.connect_key_release_event(move |_, event| {
        if kbd.get_enabled() {
            if let Some(key) = kbd.keys.get(&event.get_keyval()) {
                match audio.synthesizer.send(synthesizer::Message::NoteRelease(key.note)) {
                    Ok(()) => {
                        key.widget.get_style_context().remove_class("key-pressed");
                        key.set_pressed(false);
                        tmln.log_note_end(key.note);
                    },
                    Err(err) => {
                        eprintln!("Failed to send NoteRelease message:\n{}", err);
                    }
                }
            }
        }
        Inhibit(false)
    });

    win.show_all();
}