use gtk::prelude::*;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};

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

pub const NOTES_NUMBER: usize = 60;

pub struct Key {
    pub id: gdk::keys::Key,
    pub note: usize,
    pub widget: gtk::Box,
    pub pressed: AtomicBool,
}

impl Key {
    pub fn get_pressed(&self) -> bool {
        self.pressed.load(Ordering::SeqCst)
    }

    pub fn set_pressed(&self, pressed: bool) {
        self.pressed.store(pressed, Ordering::SeqCst);
    }
}

pub struct Keyboard {
    pub keys: HashMap<gdk::keys::Key, Key>,
    pub widget: gtk::Grid,
    pub enabled: AtomicBool,
}

impl Keyboard {
    pub fn new() -> Self {
        let init_list = [
            vec![
                ("F1", 48, gdk::keys::constants::F1),
                ("F2", 49, gdk::keys::constants::F2),
                ("F3", 50, gdk::keys::constants::F3),
                ("F4", 51, gdk::keys::constants::F4),
                ("F5", 52, gdk::keys::constants::F5),
                ("F6", 53, gdk::keys::constants::F6),
                ("F7", 54, gdk::keys::constants::F7),
                ("F8", 55, gdk::keys::constants::F8),
                ("F9", 56, gdk::keys::constants::F9),
                ("F10", 57, gdk::keys::constants::F10),
                ("F11", 58, gdk::keys::constants::F11),
                ("F12", 59, gdk::keys::constants::F12),
            ],
            vec![
                ("1", 36, gdk::keys::constants::_1),
                ("2", 37, gdk::keys::constants::_2),
                ("3", 38, gdk::keys::constants::_3),
                ("4", 39, gdk::keys::constants::_4),
                ("5", 40, gdk::keys::constants::_5),
                ("6", 41, gdk::keys::constants::_6),
                ("7", 42, gdk::keys::constants::_7),
                ("8", 43, gdk::keys::constants::_8),
                ("9", 44, gdk::keys::constants::_9),
                ("0", 45, gdk::keys::constants::_0),
                ("-", 46, gdk::keys::constants::minus),
                ("=", 47, gdk::keys::constants::equal),
            ],
            vec![
                ("Q", 24, gdk::keys::constants::q),
                ("W", 25, gdk::keys::constants::w),
                ("E", 26, gdk::keys::constants::e),
                ("R", 27, gdk::keys::constants::r),
                ("T", 28, gdk::keys::constants::t),
                ("Y", 29, gdk::keys::constants::y),
                ("U", 30, gdk::keys::constants::u),
                ("I", 31, gdk::keys::constants::i),
                ("O", 32, gdk::keys::constants::o),
                ("P", 33, gdk::keys::constants::p),
                ("[", 34, gdk::keys::constants::bracketleft),
                ("]", 35, gdk::keys::constants::bracketright),
            ],
            vec![
                ("A", 12, gdk::keys::constants::a),
                ("S", 13, gdk::keys::constants::s),
                ("D", 14, gdk::keys::constants::d),
                ("F", 15, gdk::keys::constants::f),
                ("G", 16, gdk::keys::constants::g),
                ("H", 17, gdk::keys::constants::h),
                ("J", 18, gdk::keys::constants::j),
                ("K", 19, gdk::keys::constants::k),
                ("L", 20, gdk::keys::constants::l),
                (";", 21, gdk::keys::constants::semicolon),
                ("'", 22, gdk::keys::constants::apostrophe),
                ("Enter", 23, gdk::keys::constants::Return),
            ],
            vec![
                ("Ctrl", 0, gdk::keys::constants::Control_L),
                ("Z", 1, gdk::keys::constants::z),
                ("X", 2, gdk::keys::constants::x),
                ("C", 3, gdk::keys::constants::c),
                ("V", 4, gdk::keys::constants::v),
                ("B", 5, gdk::keys::constants::b),
                ("N", 6, gdk::keys::constants::n),
                ("M", 7, gdk::keys::constants::m),
                (",", 8, gdk::keys::constants::comma),
                (".", 9, gdk::keys::constants::period),
                ("/", 10, gdk::keys::constants::slash),
                ("Ctrl", 11, gdk::keys::constants::Control_R),
            ],
        ];

        let widget = gtk::Grid::new();
        widget.set_row_spacing(1);
        widget.set_column_spacing(1);
        widget.set_row_homogeneous(true);
        widget.set_column_homogeneous(true);

        let row_max_length = init_list.iter().map(std::vec::Vec::len).max().unwrap();
        let key_width = 2;
        let key_height = 1;

        let mut keys = HashMap::new();
        for (i, row) in init_list.iter().enumerate() {
            let offset = (row_max_length - row.len()) as i32;
            for (j, (label, note, id)) in row.iter().enumerate() {
                let key_wgt = gtk::Box::new(gtk::Orientation::Vertical, 0);
                let key_label = gtk::Label::new(Some(*label));
                let note_label = gtk::Label::new(Some(synthesizer::utils::get_note_name(*note)));
                key_label.style_context().add_class("key-main-label");
                note_label.style_context().add_class("key-note-label");
                note_label.style_context().add_class("dim-label");
                key_label.set_margin(0);
                note_label.set_margin(0);
                key_wgt.pack_start(&key_label, true, true, 0);
                key_wgt.pack_start(&note_label, true, true, 0);
                key_wgt.style_context().add_class("key");

                let x = j as i32 * key_width + offset;
                let y = i as i32 * key_height;
                widget.attach(&key_wgt, x, y, key_width, key_height);
                keys.insert(
                    id.clone(),
                    Key {
                        id: id.clone(),
                        note: *note,
                        widget: key_wgt,
                        pressed: AtomicBool::new(false),
                    },
                );
            }
        }

        Self {
            keys,
            widget,
            enabled: AtomicBool::new(true),
        }
    }

    pub fn get_enabled(&self) -> bool {
        self.enabled.load(Ordering::SeqCst)
    }

    pub fn _set_enabled(&self, enabled: bool) {
        self.enabled.store(enabled, Ordering::SeqCst);
    }
}
