/**

        __    ___  ___  ____  ____  ___  
        /__\  / __)/ __)( ___)(_  _)/ __)
        /(__)\ \__ \\__ \ )__)   )(  \__ \ 
        (__)(__)(___/(___/(____) (__) (___/     for SPARC
                                                By Mike & Nike

    This file is part of Sparc by Mike & Nike.
    Sparc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License,
    as published by the Free Software Foundation, 
    either version 3 of the License, or (at your option) any later version.
    Sparc 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 General Public License for more details.
    Get your copy of the GNU General Public License at <https://www.gnu.org/licenses/>. 


 * @category Core
 * @subcategory Libraries
 * @hideconstructor
*/

class Assets {

    static defaults = {};
    /**
     * @type {object}
     * @property {Array<object>} images
     * @property {Array<object>} css
     * @property {Array<object>} html
     * @property {Array<object>} json
     * @property {Array<object>} fonts
     * @property {Array<object>} sfx
     */
    static Store = { 'images':{}, 'css':{}, 'html':{}, 'json':{}, 'fonts':{}, 'sfx':{} };

    /**
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     */
    static getImage(args){       
        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'images/'+args.name;
        else var fpath = (args.path+'/'+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');
        let aid = args.id || fpath;
        if(this.Store.images.hasOwnProperty(aid) && (!args.refresh))  { 
            return(new Promise((resolve) => { 
                    resolve(this.Store.images[aid]);
                })
            );              
        }

        let node = document.createElement('img');
        return(new Promise( (resolve, fail) => {
                node.onload = (e) => { 
                    this.Store.images[aid] = node;
                    resolve(node);
                }
                node.onerror= (e) => { 
                    console.warn('Could not load IMAGE asset ',aid, fpath);
                }
                node.src = fpath+'?'+crypto.randomUUID();
                // for img nodes, setting the src is sufficient to start loading, then trigger the onload
            })

        );
    }
   
    /**
     * 
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     */
    static loadJson(args){
        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'json/'+args.name;
        else var fpath = (args.path+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');
        let aid = args.id || fpath;
        if( (this.Store.json.hasOwnProperty(aid)) && (!args.refresh) ){
            return(new Promise((resolve) => { 
                    resolve(this.Store.json[aid]);
                })
            );                  
        } 

        return( fetch(fpath+'?'+crypto.randomUUID())
                .then(response => response.json())
                .then(data => {
                    this.Store.json[aid] = data;
                    return(data);
                })
                .catch(error => {
                    console.warn('Could not load json asset:', aid, fpath, error);
                })
        );
    }

    /**
     * 
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     */
    static loadCss(args){
        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'styles/'+args.name;
        else var fpath = (args.path+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');
        let aid = args.id || fpath;
        
        let _notInIndex = (aid) => { 
            if( (!app.config.squeeze) || (!app.config.squeeze.packages) ) return(true)
            for(let dragee of app.config.squeeze.packages) {
                let ext = dragee.target.toLowerCase().substring(dragee.target.lastIndexOf('.'))
                if((ext!='.css') || (!dragee.setIndexPage)) continue
                for(let file of dragee.sources) {
                    if(file == aid) return(false)
                }
            }
            return(true)
        }

        if( this.Store.css.hasOwnProperty(aid)  && (!args.refresh) ) {
            return(new Promise((resolve) => { 
                    resolve(this.Store.css[aid]);
                })
            );            
        }

        let node;
        if( (this.Store.css.hasOwnProperty(aid)) && (args.refresh) ) node = this.Store.css[aid];
        else { 
            node = document.createElement('link');
            node.setAttribute('rel','stylesheet');
            node.setAttribute('type','text/css');
        }
        return( new Promise( (resolve, fail) => {
                node.onload = (e) => {                 
                    this.Store.css[aid] = node;
                    resolve(node);
                }
                node.onerror= (e) => {
                    console.warn('Could not load CSS asset ', aid, fpath); 
                    fail(node);
                }
                node.setAttribute('href',fpath+'?'+crypto.randomUUID());
                // for link nodes, setting the href is NOT sufficient to start loading
                // , then trigger the onload. Must add it to the dom !
                document.head.appendChild(node)
            })
        );
    }

    /**
     * Loads an audio file.
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     */
    static loadSound(args){
        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'sfx/'+args.name;
        else var fpath = (args.path+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');
        let aid = args.id || fpath;
        if( (this.Store.sfx.hasOwnProperty(aid)) && (!args.refresh) ) {
            return(new Promise((resolve) => { 
                    resolve(this.Store.sfx[aid]);
                })
            );
        }

        return(new Promise((resolve, fail) => { 
                this.Store.sfx[aid] = new Audio();
                this.Store.sfx[aid].setAttribute('aid', aid);
                this.Store.sfx[aid].addEventListener('canplaythrough', (e) => resolve(e.target), { 'once':true });
                this.Store.sfx[aid].addEventListener('error', (e) => fail(e.target), { 'once':true });
                this.Store.sfx[aid].src = fpath+'?'+crypto.randomUUID();
                return(this.Store.sfx[aid]);
            })
        );
    }

    /**
     * Plays a loaded audio, loads it first if needed.
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     */
    static playSound(args){
        // Nike: Promise when fulfilled when sfx loaded.
        // If we want when sfx finished to play then 
        // stick a 'ended' event listener as resolver in a new promise 
        return(
            this.loadSound(args).then( (snd) => { snd.play(); })
        ); 
    }

    /**
     * 
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {Promise}
     * @todo check issue with undeclared aid
     */
    static loadFont(args){
        // something fishy here => aid not declared
        let acronym = aid || args.name.substr(0,args.name.lastIndexOf('.'));

        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'fonts/'+args.name;
        else var fpath = (args.path+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');
        let aid = args.id || fpath;
        if( (this.Store.fonts.hasOwnProperty(aid)) && (!args.refresh) ) {
            return(new Promise((resolve) => { 
                    resolve([loaded_face, acronym]);
                })
            );
        }

        let ff = new FontFace(acronym,'url('+fpath+'?'+crypto.randomUUID()+')');
        return(ff.load()
            .then(loaded_face => {
                document.fonts.add(loaded_face);
                this.Store.fonts[aid] = loaded_face;
                return([loaded_face, acronym]);
            })
            .catch(error => {
                console.warn('Could not load FONT asset:' + fpath);
                [null, acronym]
            })
        );
    }

    /**
     * Loads an HTML file 
     * @async
     * @param {*} args 
     * @param {string} args.path 
     * @param {string} args.name 
     * @param {string} [args.id] 
     * @param {boolean} [args.refresh] 
     * @returns {string}
     */
    static loadHtml(args) {  
        if( (!args.hasOwnProperty('path')) || (args.path=='') ) var fpath = this.defaults.basePath+'html/'+args.name;
        else var fpath = (args.path+args.name).replace(/\/+/g,'/').replace(/http(s)?:\//g, 'http$1://');        
        let aid = args.id || fpath;
        if( (this.Store.html.hasOwnProperty(aid)) && (!args.refresh) ) {
            let html = this.Store.html[aid];
            return(new Promise((resolve) => { 
                    resolve(html);
                })
            );
        }

        return(fetch(fpath + '?' + crypto.randomUUID())
            .then(response => response.text())
            .then(html => {             
                this.Store.html[aid] = html;
                return html;
            })
            .catch(error => {
                console.warn('Could not load HTML asset:', aid, fpath);
                return(null);
            })
        );
    }
}

app.LoadedClasses.Assets = Assets;