/**
 * This file is part of HARMONICARIUM, a web app which allows users to play
 * the Harmonic Series dynamically by changing its fundamental tone in real-time.
 * It is available in its latest version from:
 * https://github.com/IndustrieCreative/Harmonicarium
 * 
 * @license
 * Copyright (C) 2017-2020 by Walter Mantovani (http://armonici.it).
 * Written by Walter Mantovani.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* globals HUM */
/* globals QwertyHancock */

"use strict";

/** 
 * The Hancock class<br>
 *    A tool to manage and override the Qwerty Hancock lib
 * 
 * @see {@link https://github.com/stuartmemo/qwerty-hancock}
 */
HUM.Hancock = class {
    /**
    * @param {HUM.DHC} dhc - The DHC instance to which it belongs
    */
    constructor(dhc) {
        /**
        * The DHC instance
        *
        * @member {HUM.DHC}
        */
        this.dhc = dhc;
        /**
         * The state of the Hancock; if `false`, it is turned off.
         *
         * @member {boolean}
         */
        this.active = false;
        /**
         * Default Qwerty Hancock settings
         *
         * @see {@link https://stuartmemo.com/qwerty-hancock/}
         *
         * @member {Object}
         *
         * @property {string}   id               - Id of the htmlElement in which to put the keyboard
         * @property {number}   width            - Width of the keyboard in pixels
         * @property {number}   height           - Height of the keyboard in pixels
         * @property {number}   octaves          - How many octaves to show
         * @property {string}   startNote        - The note name (hancock numbering) of the first key to show
         * @property {string}   whiteNotesColour - Default released key color for white keys (bypassed by classes & css)
         * @property {string}   blackNotesColour - Default released key color for black keys (bypassed by classes & css)
         * @property {string}   hoverColour      - Default pressed-key color for all the keys (bypassed by classes & css)
         * @property {velocity} velocity         - The velocity value to be used when sending messages; an integer between 1 and 127
         * @property {midichan} channel          - The Channel to be used when sending messages; an integer between 0 and 15
         */
        // @old icHancockCurrSets
        this.settings = {
            id: 'HTMLo_hancockContainer'+dhc.id,
            width: 600,
            height: 80,
            // octaves: 4, // default 3
            // startNote: 'F3', // default 'A3'
            // whiteNotesColour: 'white', // default '#fff'
            // blackNotesColour: 'black', // default '#000'
            // hoverColour: '#f3e939', // default 'yellow'
            // borderColour: 'black', // default '#000'
            
            // Added properties (non Hancock natives)
            velocity: 120,
            channel: 1
        };
        /**
         * UI HTML elements
         *
         * @member {Object}
         * 
         * @property {Object.<string, HTMLElement>} fn  - Functional UI elements
         * @property {Object.<string, HTMLElement>} in  - Input UI elements
         * @property {Object.<string, HTMLElement>} out - Output UI elements
         */
        this.uiElements = {
            fn: {
                checkboxPiano: this.dhc.harmonicarium.html.pianoTab[dhc.id].children[0],
            },
            in: {
                velocity: document.getElementById("HTMLi_piano_velocity"+dhc.id),
                channel: document.getElementById("HTMLi_piano_channel"+dhc.id),
                keyboardOffset: document.getElementById("HTMLi_piano_offset"+dhc.id),
                keyboardRange: document.getElementById("HTMLi_piano_range"+dhc.id),
                keyboardWidth: document.getElementById("HTMLi_piano_width"+dhc.id),
                keyboardHeight: document.getElementById("HTMLi_piano_height"+dhc.id),
            },
            out: {
                pianoContainer: document.getElementById(this.settings.id),
            },
        };
        /**
         * The instance of Qwerty Hancock piano keyboard
         *
         * @member {QwertyHancock}
         */
        // @old icKeyboard
        this.keyboard = new QwertyHancock(this.settings);
        /**
         * The method invoked when a key is pressed on Hanckock
         *
         * @param {string} note - Hancock note name (e.g. G#5)
         */
        // @old icKeyboard.keyDown
        this.keyboard.keyDown = (note) => this.sendMidiNote(note, 1);
        /**
         * The method invoked when a key is released on Hanckock
         * 
         * @function
         * @param {string} note - Hancock note name (e.g. G#5)
         */
        // @old icKeyboard.keyUp
        this.keyboard.keyUp = (note) => this.sendMidiNote(note, 0);            

        this._initUI();

        // Tell to the DHC that a new app is using it
        this.dhc.registerApp(this, 'updatesFromDHC', 100);

        // =======================
    } // end class Constructor
    // ===========================

    /**
     * Manage and route an incoming message
     *
     * @param {HUM.DHCmsg} msg - The incoming message
     */
    updatesFromDHC(msg) {

        if (msg.cmd === 'panic') {
            this.allNotesOff();
        }

        if (msg.cmd === 'update') {
            if (msg.type === 'ft') {

            } else if (msg.type === 'ht') {

            } else if (msg.type === 'ctrlmap') {
                this.fitToKeymap();
            }
        // Play keys only if the accordion Piano tab is open
        } else if ( (['tone-on', 'tone-off']).includes(msg.cmd) && this.active) {
            let ctrlNum = msg.ctrlNum,
                mcXT;
            if (ctrlNum === false) {
                if (msg.type === 'ht' && msg.xtNum === 0) {

                } else {
                    mcXT = Math.round(this.dhc.tables[msg.type][msg.xtNum].mc);
                    ctrlNum = this.dhc.midi.in.tsnapFindCtrlNoteNumber(mcXT, msg.type);
                }
            }
            if (msg.cmd === 'tone-on') {
                this.keyON(ctrlNum);
            } else {
                this.keyOFF(ctrlNum);
            }
        }
    }
    /**
     * Send a fake MIDI event note ON/OFF directly to midi.in
     * in order to input Qwerty Hancock as a virtual MIDI device
     *
     * @param {string} note  - Hancock note name (e.g. G#5)
     * @param {(0|1)}  state - 0 is Note-OFF | 1 is Note-ON
     */
    // @old icFakeMidiNote
    sendMidiNote(note, state) {
        // Note ON
        let cmd = 9;
        // Channel (change offset to 0-15)
        let channel = this.settings.channel - 1;
        // Compose the Status Byte
        // Bitwise shift to the left 0x9 for 4 bits to get 0x90 (Note ON)
        // Add the channel (0-15) to complete the byte (0x90, 0x91... 0x9F)
        let statusbyte = (cmd << 4) + channel;
        // Create a fake MIDI event for midiMessageReceived
        let midievent = {
            data: [statusbyte, this.dhc.nameToMidiNumber('hancock', note), this.settings.velocity * state, "hancock", false],
            srcElement: {
                id: "952042271",
                manufacturer : "Industrie Creative",
                name: "Virtual MIDI Controller",
                type: "input"
            }
        };
        this.dhc.midi.in.midiMessageReceived(midievent);
    }    

    /*==============================================================================*
     * HANCOCK STYLE WRAPPER
     *==============================================================================*/
    /**
     * Bypass the Qwerty Hancock default key colors (released).
     *     Write the key-numbers according to the keymap.
     */
    // @old icHancockKeymap
    drawKeymap() {
        for (var i = 0; i < 128; i++) {
            let note = this.dhc.midiNumberToNames(i)[0];
            if (document.getElementById(note)) {
                let key = document.getElementById(note);
                // If the input MIDI key is in the ctrl map, proceed
                if (this.dhc.tables.ctrl[i]) {
                    // Vars for a better reading
                    var ft = this.dhc.tables.ctrl[i].ft;
                    var ht = this.dhc.tables.ctrl[i].ht;
                    // **FT**
                    // If the key is mapped to a Fundamental Tone only
                    if (ft !== 129 && ht === 129) {
                        // If is a sharp key
                        if (note.match(/#/)) {
                            // Use a darker color
                            key.classList.add("FTbKey", "releasedKey");
                            // Write the key-number
                            key.innerHTML = "<div class='FTbKeyFn unselectableText'>" + ft + "</div>";
                        // Else is a normal key
                        } else {
                            // Use a lighter color
                            key.classList.add("FTwKey", "releasedKey");
                            // Write the key-number
                            key.innerHTML = "<div class='FTwKeyFn unselectableText'>" + ft + "</div>";
                        }
                    }
                    // **HT**
                    // If the key is mapped to a Harmonic Tone only
                    else if (ht !== 129 && ht !== 0 && ft === 129) {
                        // If is a sharp key
                        if (note.match(/#/)) {
                            // Use a darker color
                            key.classList.add("HTbKey", "releasedKey");
                            // Write the key-number
                            key.innerHTML = "<div class='HTbKeyFn unselectableText'>" + ht + "</div>";
                        // Else is a normal key
                        } else {
                            // Use a lighter color
                            key.classList.add("HTwKey", "releasedKey");
                            // Write the key-number
                            key.innerHTML = "<div class='HTwKeyFn unselectableText'>" + ht + "</div>";
                        }
                    // **HT0 (Piper)**
                    // If is HT0
                    } else if (ht === 0 && ft === 129) {                    
                        // If is a sharp key
                        if (note.match(/#/)) {
                            // Use a darker color
                            key.classList.add("HT0bKey", "releasedKey");
                            // Write a "P"
                            key.innerHTML = "<div class='FTbKeyFn unselectableText'>P</div>";
                        } else {
                            // Use a lighter color
                            key.classList.add("HT0wKey", "releasedKey");
                            // Write a "P"
                            key.innerHTML = "<div class='FTwKeyFn unselectableText'>P</div>";
                        }
                    }
                // **Normal Key**
                // If the key is not mapped
                } else {
                    // If is a sharp key
                    if (note.match(/#/)) {
                        // Use a darker color
                        key.classList.add("bKey", "releasedKey");
                    // Else is a normal key
                    } else {
                        // Use a lighter color
                        key.classList.add("wKey", "releasedKey");
                    }
                }
            }
        }
    }
    /**
     * Adapt the Hancock UI parameters in order to fit the keymap to the piano width.
     */
    fitToKeymap() {
        let keysArray = Object.keys(this.dhc.tables.ctrl),
            keyMin = Math.min.apply(null, keysArray),
            keyMax = Math.max.apply(null, keysArray),
            keysNum = keyMax - keyMin,
            keyOctaves = Math.ceil(keysNum/12),
            keyRemainder = keysNum % 12;
        
        if (keyRemainder < 2) {
            keyOctaves++;
        }
        
        // Update the range
        this.changeRange(keyOctaves);
        this.uiElements.in.keyboardRange.value = keyOctaves;
        // Update the offset
        this.changeOffset(keysArray[0]);
        this.uiElements.in.keyboardOffset.value = keysArray[0];
    }
    /**
     * Bypass the Qwerty Hancock default key colors and set the right color for Note-ON message.
     *
     * @param {midinnum} ctrlNum - MIDI note number
     */
    // @old icKeyON
    keyON(ctrlNum) {
        if (ctrlNum !== false) {
            let key = this.dhc.midiNumberToNames(ctrlNum)[0];
            if (document.getElementById(key)){
                document.getElementById(key).classList.remove('releasedKey');
                document.getElementById(key).classList.add('pressedKey');
            }
        }
    }
    /**
     * Bypass the Qwerty Hancock default key colors and set the right color for Note-OFF message.
     *
     * @param {midinnum} ctrlNum - MIDI note number
     */
    // @old icKeyOFF
    keyOFF(ctrlNum) {
        if (ctrlNum !== false) {
            let key = this.dhc.midiNumberToNames(ctrlNum)[0];
            if (document.getElementById(key)){
                document.getElementById(key).classList.remove('pressedKey');
                document.getElementById(key).classList.add('releasedKey');
            }
        }
    }
    /**
     * Turns off all the keys (from `{@link midinnum}` 0 to 127)
     */
    allNotesOff() {
        for (let ctrlNum = 0; ctrlNum < 128; ctrlNum++) {
            this.keyOFF(ctrlNum);
        }
    }

    /*==============================================================================*
     * UI KEYBOARD SETTINGS TOOLS
     *==============================================================================*/
    /**
     * Update the Qwerty Hancock keyboard on UI setting changes
     */
    // @old icHancockUpdate
    update() {
        this.uiElements.out.pianoContainer.innerHTML = "";
        this.keyboard = new QwertyHancock(this.settings);
        // @todo - Why it work without reset 'this.keyboard.keyDown' 'this.keyboard.keyUp' ??
        // Start the style wrapper
        this.drawKeymap();
    }
    /**
     * Set the offset of the Qwerty Hancock and update the UI.
     *     The piano keyboard will start from the given note to the right.
     *
     * @param {midinnum} midikey - MIDI note number representing a piano key
     */
    // @old icHancockChangeOffset
    changeOffset(midikey) {
        let note =  this.dhc.midiNumberToNames(midikey)[0];
        if (!note.match(/#/)) {
            this.settings.startNote = note;
            this.update();
        }
    }
    /**
     * Set the octave-range of Qwerty Hancock and update the UI
     *
     * @param  {number} octaves - Number of octaves to show
     */
    // @old icHancockChangeRange
    changeRange(octaves) {
            this.settings.octaves = octaves;
            this.update();
    }
    /**
     * Modify the size of Qwerty Hancock and update the UI
     *
     * @param {number}    pixels - Number of pixels
     * @param {('w'|'h')} dim    - The dimension to change; 'w' is for weight, 'h' is for height 
     */
    // @old icHancockChangeSize
    changeSize(pixels, dim) {
        if (dim === "w") {
            this.settings.width = pixels;
        } else if (dim === "h") {
            this.settings.height = pixels;
        } else {
            console.log("Hancock changeSize: wrong 'dim' parameter: " + dim);
        }
        this.update();
    }

    /*==============================================================================*
     * KEYBOARD UI INITS
     *==============================================================================*/
    /**
     * Initialize the UI of the Hancock instance
     */
    // @old icKeyboardUIinit
    _initUI() {

        this.uiElements.in.velocity.value = this.settings.velocity;
        this.uiElements.in.channel.value = this.settings.channel;
        this.uiElements.in.keyboardWidth.value = this.settings.width;
        this.uiElements.in.keyboardHeight.value = this.settings.height;

        // @todo - set the "min", "max", "step" using a config file, like the user settings file...
        // this.uiElements.in.keyboardOffset.value = 
        // this.uiElements.in.keyboardRange.value = 

        // Disable the keyboard animation if the accordion Piano tab is closed
        this.uiElements.fn.checkboxPiano.addEventListener('change', (e) => {
            if (e.target.checked === true) {
                this.active = true;

            } else {
                this.active = false;
                // Turn off all the tones currently active, if there are
                this.allNotesOff();
            }
        });

        // Update the channel of the Qwerty Hancock on UI changes
        this.uiElements.in.channel.addEventListener("input", (e) => {
            this.settings.channel = Number(e.target.value);
        });
        // Update the velocity of the Qwerty Hancock on UI changes
        this.uiElements.in.velocity.addEventListener("input", (e) => {
            this.settings.velocity = e.target.value;
        });
        // Update the offset of the Qwerty Hancock on UI changes
        this.uiElements.in.keyboardOffset.addEventListener("input", (e) => {
            this.changeOffset(e.target.value);
        });

        // Update the octave range of the Qwerty Hancock on UI changes
        this.uiElements.in.keyboardRange.addEventListener("input", (e) => {
            this.changeRange(e.target.value);
        });

        // Update the width of the Qwerty Hancock on UI changes
        this.uiElements.in.keyboardWidth.addEventListener("input", (e) => {
            this.changeSize(e.target.value, "w");
        });

        // Update the height of the Qwerty Hancock on UI changes
        this.uiElements.in.keyboardHeight.addEventListener("input", (e) => {
            this.changeSize(e.target.value, "h");
        });
    }

};