/**
    base class for data models of the EIC platform

    instanciation requires 2 parameters, businessObject and privileges.
    - businessObject: a string reference to a set of endpoints provided by the API discovery service (API descriptr can be found in app.config.api)
    - privileges: an array of endpoints references (aka actions) allowed specifically for a certain resource. Those privileges are provided by the middle tier prior accessing the resource.    * @category MyEic
 * @category MyEic
 * @subcategory Libraries
 * @extends Model
*/
class EICModel extends Model {

    // data container for the item
    itemData = {};
    // list of available api services for this model
    api = {};
    // privileges granted on this model
    privileges = [];
    
    /**
     * 
     * @param {string} resource A string reference to the resource (aka business obkect) to be handled (ex: "users" or "project")
     * @param {array} privileges An array of allowed privileges (as string reference. ex: "save", "delete", etc...). Privileges should match a corresponding method in the controller
     */
    constructor(resource, privileges) {
        super();
        
        // if((app.config.hasOwnProperty('api')) && (app.config.api.hasOwnProperty(resource))) {
        //     this.api = app.config.api[resource];
        // } else this.config.api = {}

        this.setPrivileges(resource, privileges);
    }

    /**
     * 
     * @param {*} action 
     * @param {*} resource 
     * @returns {object}
     */
    getApiEndpoint(action, resource=null) {
        resource = resource || this.resource; 
        for(let ApiBizObj of Object.keys(app.config.api)) {
            
            //If called without real ids
            if(ApiBizObj==resource) return(JSON.parse(JSON.stringify(app.config.api[ApiBizObj][action]))); 

            //If called with real ids
            let re = new RegExp('^'+ApiBizObj.replace(/\//g,'\\/').replace(/\{\w+\}/gi,'([0-9a-zA-Z]+)')+'$', 'm')
            if(resource.match(re)) {
                if(app.config.api[ApiBizObj].hasOwnProperty(action)) return(JSON.parse(JSON.stringify(app.config.api[ApiBizObj][action])));
                else console.error(`No API endpoint for action "${action}" on resource "${resource}"`)
            } 
        }
        console.error(`No API service for resource:${resource}`)
        return({ "method":null, "uri": null });
    }

    setPrivileges(resource, privileges) {
        this.resource = resource;
        this.privileges = privileges || [];
    }

    /**
     * Checks if a privilege is available for this profile
     * @param {string} privilege ref 
     * @returns {boolean}
     */
    hasPrivilege(privilege) {
        if(this.privileges.indexOf(privilege) != -1) return(true);
        else {
            //console.warn('You do not have the privilege:',privilege);
            return(false);
        }
    }

    /**
     * Fills the model item properties with provided object
     * @param {object} data 
     * @returns {object}
     */
    async loadData(data) {
        // itemData should be cleared first
        this.itemData = {}
        Object.assign(this.itemData, data); // this way getters are natural      
        return this.itemData;
    }

    /**
     * Generates a stringified JSON output of the itemData object
     * Practical for cloning the model data
     * @returns {string}
     */
    toString() { return(JSON.stringify(this)); }
    
    /**
     * Deep copies updates object into target 
     * @param {object} target 
     * @param {object} updates 
     * @returns {object}
     */
    merge(current, updates) {
        for (let key of Object.keys(updates)) {
            if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
            else this.merge(current[key], updates[key]);
        }
        return current;
    }

    /**
     * Returns the current item id. Shortcut for itemData.id
     * @returns {string}
     */
    get id() { return(this.itemData.id); }

    /** 
    * Tries to match the instance with given data
    * 
    * @param {Object} matchValues - { {string} key: {string} valueToMatch }
    * @param {Object} matchExact - { strin{key}: {Boolean} } Defaults to exact match if parameter absent or if key absent.
    * Non-exact match uses valueToMatch as a REGEX. valueToMatch is then a string like "/regex/" or "/regex/flags".
    * @return {boolean} true if match
    */    
    match(matchValues, matchExact = {}){
        for(let key in matchValues){
            let val = matchValues[key];
            if((key in matchExact) && (matchExact[key])){ // equality
                if(this.itemData[key]!=val) return(false);
            } else { //Regex
                let re = new RegExp(val.substring(1, val.lastIndexOf('/')), val.substring(val.lastIndexOf('/')+1));
                if(!re.test(this.itemData[key])) return(false);
            }
        }
        return(true);
    }

    /**
     * 
     * @param {*} error 
     * @param {number} error.code 
     * @param {string} error.displayMessage 
     */
    onRequestError(error) {
        if([400,404,405,409].indexOf(error.code) > -1) {
            console.warn('Ajax request error:', error);
            ui.growl.append(`${error.displayMessage}`, 'warning', 10000);
        } else if(error.code > 499) {
            console.error('Ajax request error:', error);
            ui.growl.append(
                `Unfortunately, there was a server error.<br>
                Please contact the helpdesk if it persists
                `, 'danger', 5000);
        } else if(error.code==403){
            console.warn(`Ajax request unauthorized in ${this.constructor.name}`, error);
            ui.growl.append(`${error.displayMessage}`, 'danger', 10000);
        } else if((error.code==401) && (app.User.isAuthenticated)){
            console.warn(`Unauthenticated in ${this.constructor.name}`, error);
            app.User.isAuthenticated = false
            app.User.stopKeepAlive()
            app.Router.route('/401', {'triggerUrl' :app.Router.currentRoute.realUrl } );
        }

    }
}

app.registerClass('EICModel',EICModel);

