/**
 * 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 */

"use strict";

window.AudioContext = window.AudioContext || window.webkitAudioContext;

/** 
 * The H-Stack class<br>
 *    A tool for visualize the Harmonic Series in the UI
 */
// @old icSYNTH
HUM.Hstack = class {
    /**
    * @param {HUM.DHC} dhc - The DHC instance to which it belongs
    */
    constructor(dhc) {
        /**
        * The DHC instance
        *
        * @member {HUM.DHC}
        */
        this.dhc = dhc;
        /**
        * H-Stack table font size
        *
        * @member {integer}
        */
        this.fontSize = 20;
        /**
         * An array containing all the used Harmonic/Subharmonic in the controller Keymap
         *
         * @member {Array.<xtnum>}
         */
        this.usedHT = [];
        /**
         * The state of the H-Stack; if `false`, it is turned off.
         *
         * @member {boolean}
         */
        this.active = false;
        /**
         * UI HTML elements
         *
         * @member {Object}
         * 
         * @property {Object.<string, HTMLElement>} fn  - Functional UI elements
         * @property {Object.<string, HTMLElement>} out - Output UI elements
         */
        this.uiElements = {
            fn: {
                checkboxHstack: this.dhc.harmonicarium.html.hstackTab[dhc.id].children[0],
                hstackFTrow: document.getElementById("HTMLf_hstackFTrow"+dhc.id),
                hstack_zoom: document.getElementById("HTMLf_hstack_zoom"+dhc.id),
            },
            out: {
                hstack_fontsize: document.getElementById("HTMLo_hstack_fontsize"+dhc.id),
                hstackHT: document.getElementById("HTMLo_hstackHT"+dhc.id),
                rowsHT: {}
            }
        };
        this._initUI();

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

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

    /**
     * Initialize the UI elements
     */
    _initUI() {
        let zoomVal = this.fontSize;
        // Init H Stack zoom with default value
        this.uiElements.fn.hstack_zoom.value = zoomVal;
        this.uiElements.out.hstack_fontsize.style.fontSize = zoomVal + "px";
        this.uiElements.fn.hstack_zoom.setAttribute("data-tooltip", zoomVal + "px" );

        // Add an EventListener to the zoom slider
        this.uiElements.fn.hstack_zoom.addEventListener("input", (e) => {
            window.requestAnimationFrame( () => {
                this.fontSize = e.target.value;
                this.uiElements.out.hstack_fontsize.style.fontSize = e.target.value + "px";
                this.uiElements.fn.hstack_zoom.setAttribute("data-tooltip", e.target.value + "px");
            });
        });

        // Disable the hstack fill & animation if the accordion Synth tab is closed
        this.uiElements.fn.checkboxHstack.addEventListener('change', (e) => {
            if (e.target.checked === true) {
                this.active = true;
                this.fillin();
                this.ftMonitor(this.dhc.settings.ht.curr_ft);
            } else {
                this.active = false;
                // Turn off all the tones currently active, if there are
                for (let htNum of this.usedHT) {
                    this.playFx("ht", 0, htNum);
                }
                this.playFx("ft", 0, this.dhc.settings.ht.curr_ft);
            }
        });

    }
    /**
     * Manage and route an incoming message
     *
     * @param {HUM.DHCmsg} msg - The incoming message
     */
    updatesFromDHC(msg) {
        if (msg.cmd === 'init') {
            this.fillin();
            this.ftMonitor(this.dhc.settings.ht.curr_ft);
        }

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

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

            } else if (msg.type === 'ht' && this.active) {
                
                this.fillin();

            } else if (msg.type === 'ctrlmap') {
                
                this.create();
            }

        } else if (msg.cmd === 'tone-on' && this.active) {
            if (msg.type === 'ft') {
                
                // this.fillin();
                this.playFx('ft', 1, msg.xtNum);
            
            } else if (msg.type === 'ht') {
                
                if (msg.xtNum !== 0) {
                    this.playFx("ht", 1, msg.xtNum);
                }

            }

        } else if (msg.cmd === 'tone-off' && this.active) {
            if (msg.type === 'ft') {
                
                this.playFx("ft", 0, msg.xtNum);
            
            } else if (msg.type === 'ht') {

                if (msg.xtNum !== 0) {
                    this.playFx("ht", 0, msg.xtNum);
                }
            }
        }
    }

    /*==============================================================================*
     * UI HSTACK
     *==============================================================================*/
    /**
     * Create the H-Stack HTML table 
     */
    // @old icHSTACKcreate
    create() {
        let dhcID = this.dhc.id,
            hstackContainer = this.uiElements.out.hstackHT,
            hstackTable = document.createElement("table");
        
        hstackTable.className = "dataTable";
        hstackTable.innerHTML = `
                    <tr>
                        <th colspan="4">Harmonics</th>
                    </tr>
                    <tr class="hstackHeader">
                        <th width="13%" class="hstackHT_h">HT</th>
                        <th width="15%" class="hstackHT_note">note</th>
                        <th width="30%" class="hstackHT_cents">cents</th>
                        <th width="42%" class="hstackHT_hz">Hz</th>
                    </tr>
                    <!-- Here the HT rows -->`;
        this.updateUsedHT();
        
        this.uiElements.out.rowsHT = {};
        for (let htNum of this.usedHT) {
            let newRow = new this.HstackRow(htNum, dhcID);
            this.uiElements.out.rowsHT[htNum] = newRow;
            hstackTable.append(newRow.elemRow);
        }
        if (hstackContainer.firstChild) {
            hstackContainer.removeChild(hstackContainer.firstChild);
        }
        hstackContainer.appendChild(hstackTable);
        
        this.fillin();
    }
    /**
     * Update the {@link HUM.Hstack#usedHT} property
     */
    updateUsedHT() {
        let usedHT = [];
        for (let key of Object.keys(this.dhc.tables.ctrl)) {
            let ht = this.dhc.tables.ctrl[key].ht;
            if (ht !== 129 && ht !== 0) {
                usedHT.push(ht);
            }
        }
        // Sort the array from max to min
        usedHT.sort( (a, b) => { return b - a; } );
        // Store in the global var the uniquified version of the array useful to this.fillin
        this.usedHT = this.dhc.constructor.uniqArray(usedHT);
    }
    /**
     * Turns off all the rows
     */
    allNotesOff() {
        for (let htNum of this.usedHT) {
            this.playFx("ht", 0, htNum);
        }
        this.playFx("ft", 0, this.dhc.settings.ht.curr_ft);
    }
    /**
     * Fill-in the H-Stack table data 
     */
    // @old icHSTACKfillin
    fillin() {
        // Empty object to store the HTn data
        let htObj = {};
        // For every HT used in the Controller Keymap (this.dhc.tables.ctrl)
        for (let htNum of this.usedHT) {
            // If it's not 0 (piper)
            if (htNum !== 0) {
                // Read 'mc' and 'hz' data of the HTn from 'ht table'
                htObj = this.dhc.tables.ht[htNum];
                // Apply the controller pitchbend (if present) to the array 
                htObj = this.dhc.bendXtone(htObj);
                // Get the array containing the standard note name info and +/- cents
                let notename = this.dhc.mcToName(htObj.mc),
                    name = notename[0],
                    sign = notename[1],
                    cent = notename[2];
                // Print the infos to the UI HStack
                this.uiElements.out.rowsHT[htNum].elemHtNum.innerText = htNum;
                this.uiElements.out.rowsHT[htNum].elemNote.innerText = name;
                this.uiElements.out.rowsHT[htNum].elemCents.innerText = sign + cent;
                this.uiElements.out.rowsHT[htNum].elemHz.innerText = htObj.hz.toFixed(this.dhc.settings.global.hz_accuracy);
            }
        }
    }
    /**
     * Print the data about the FT at the bottom of the H-Stack
     * 
     * @param {xtnum} ftNum - The FT number
     */
    ftMonitor(ftNum) {
        let dhcID = this.dhc.id;
        let ftObj = this.dhc.tables.ft[ftNum];
        // Apply the controller pitchbend (if present) to the array 
        ftObj = this.dhc.bendXtone(ftObj);
        let notename = this.dhc.mcToName(ftObj.mc),
            name = notename[0],
            sign = notename[1],
            cent = notename[2],
            hzAccuracy = this.dhc.settings.global.hz_accuracy;
        // Update the log on HSTACK FT info on the UI
        document.getElementById("HTMLo_hstackFT_tone"+dhcID).innerText = ftNum;
        document.getElementById("HTMLo_hstackFT_note"+dhcID).innerText = name;
        document.getElementById("HTMLo_hstackFT_cents"+dhcID).innerText = sign + cent;
        document.getElementById("HTMLo_hstackFT_hz"+dhcID).innerText = ftObj.hz.toFixed(hzAccuracy);
    }
    /**
     * Turn ON or OFF the rows of the H-Stack
     *
     * @param {tonetype} type  - If the note to turn ON/OFF is a FT or HT
     * @param {0|1}      state - Note ON/OFF; 0 is OFF, 1 is ON 
     * @param {xtnum}    xtNum - FT or HT number
     */
    // @old icHSTACKmonitor
    playFx(type, state, xtNum) {
        let dhcID = this.dhc.id;
        if (type === "ft") {
            // Note ON
            if (state === 1) {

                // Recreate the element to force the css animation
                let old = this.uiElements.fn.hstackFTrow;
                let parent = old.parentNode;
                let clone = old.cloneNode(true);
                parent.insertBefore(clone, old);
                old.remove();
                this.uiElements.fn.hstackFTrow = clone;
                this.uiElements.fn.hstackFTrow.classList.add("FTon");
                this.uiElements.fn.hstackFTrow.classList.remove("FToff");

                // Write the FM at the bottom of the hstack
                this.ftMonitor(xtNum);

            // Note OFF
            } else if (state === 0) {
                if (this.dhc.settings.ht.curr_ft === xtNum) {
                    this.uiElements.fn.hstackFTrow.classList.add("FToff");
                    this.uiElements.fn.hstackFTrow.classList.remove("FTon");
                }
            }
        } else if (type === "ht") {
            // If is a normal HT (it's not HT0)
            if (xtNum !== 0) {
                // Only if the HT is mapped in the keymap
                if (this.usedHT.includes(xtNum)) {
                    let htmlElem = this.uiElements.out.rowsHT[xtNum].elemRow;
                    // let htmlElem = document.getElementById("HTMLf_hstackHTrow_h"+xtNum+"_"+dhcID);
                    // Note ON
                    if (state === 1) {
                        htmlElem.classList.add("HTon");
                        htmlElem.classList.remove("HToff");
                    // Note OFF
                    } else if (state === 0) {
                        htmlElem.classList.add("HToff");
                        htmlElem.classList.remove("HTon");
                    }
                }
            }
        }
    }
};


/**
 * A HTML table's row for the H-Stack
 */
HUM.Hstack.prototype.HstackRow = class {
    /**
     * @property {xtnum}  htNum - The number of HT
     * @property {string} dhcID - The DHC instance id
     */
    constructor(htNum, dhcID) {
        this.htNum = htNum;
        this.dhcID = dhcID;
        /**
        * The HTML row element
        *
        * @member {HTMLElement}
        */
        this.elemRow = document.createElement("tr");
        this.elemHtNum = document.createElement("td");
        this.elemNote = document.createElement("td");
        this.elemCents = document.createElement("td");
        this.elemHz = document.createElement("td");

        this.elemRow.className = "HToff";
        this.elemHtNum.className = "hstackHT_h";
        this.elemNote.className = "hstackHT_note";
        this.elemCents.className = "hstackHT_cents";
        this.elemHz.className = "hstackHT_hz";
        
        this.elemRow.id = `HTMLf_hstackHTrow_h${htNum}_${dhcID}`;
        this.elemHtNum.id = `HTMLo_hstackHT_h${htNum}_${dhcID}`;
        this.elemNote.id = `HTMLo_hstackHT_note${htNum}_${dhcID}`;
        this.elemCents.id = `HTMLo_hstackHT_cents${htNum}_${dhcID}`;
        this.elemHz.id = `HTMLo_hstackHT_hz${htNum}_${dhcID}`;
        this.elemHtNum.innerText = "htNum";

        this.elemRow.append(this.elemHtNum, this.elemNote, this.elemCents, this.elemHz);
    }
};