this.setGenericLocalstorage = function(key, data) {
    console.log("setGenericLocalstorage");
    console.log(key);
    console.log(data);
    if(!data) {
        localStorage.removeItem(key);
        return;
    }
    try {
        localStorage.setItem(key, JSON.stringify(data));
    }
    catch (error) {
        console.error("Error on setGenericLocalstorage")
        console.error(error)
    }

    // console.log(`Local storage size: ${this.getLocalStorageSize()}kb`)
}


this.getLocalStorageSize = function() {
    let total = 0;

    for (let key in localStorage) {
        let value = localStorage.getItem(key);
        total += (new TextEncoder().encode(value)).length;
    }
    let inKB = (total / 1024);
    return inKB.toFixed(2); //return value in kb
}

this.getGenericLocalstorage = function(key) {
    const localstoragevalue = localStorage.getItem(key);
    if(localstoragevalue === "undefined") {
        this.removeGenericLocalstorageItem(key);
        return undefined;
    };
    if(localstoragevalue) {
        return JSON.parse(localstoragevalue);
    };
    return undefined;
}
this.removeGenericLocalstorageItem = function(key) {
    const isPresent = localStorage.getItem(key);
    if(isPresent) {
        localStorage.removeItem(key);
    }
};

this.getQueryString = function(querystring) {
    var results = new RegExp('[\?&]' + querystring + '=([^&#]*)').exec(window.location.search);
    return (results !== null) ? results[1] || 0 : false;
};
this.getRandomnumber = function(max) {
    return Math.floor(Math.random() * max);;
};
/**
 * 
 * @param {*} selectors array di selettori per cercare gli script di dipendenza
 * @param  {...any} callbacks funzioni da chiamare una volta che le dipendenze sono state soddisfatte
 */
this.checkScriptDependencies = function(selectors, ...callbacks) {
    document.addEventListener("DOMContentLoaded", () => {
        let notFound = false;
        const notFoundScripts = [];
        const scriptsStatus = {};
        const scripts = [];
        for(let selind = 0;selind < selectors.length;selind++) {
            let sel = selectors[selind];
            const script = document.querySelector(sel);
            if(!script) {
                console.warn("No script found with selector " + sel);
                notFound = true;
                notFoundScripts.push(sel);
            } else {
                if(!script.src && !script.dataset.src) {
                    scriptsStatus[selind] = {src: undefined, loaded: true, error: false};
                } else {
                    scripts.push(script);
                    scriptsStatus[selind] = {src: script.src ? script.src : script.dataset.src, loaded: false, error: false};
                };
            };
        };
        if(notFound) {
            console.warn("Scripts not found checking dependencies");
            console.warn(notFoundScripts);
            console.warn("Not called callbacks");
            console.warn(callbacks);
            return;
        };
        let withErrors = false;
        const notLoadedScripts = [];
        const checkResolution = function() {
            let status = true;
            for(let i in scriptsStatus) {
                status = status && scriptsStatus[i].loaded;
            };
            return status;
        }
        if(checkResolution()) {
            for(const c of callbacks) {
                c();
            };
        } else {
            const manageDependenciesResult = () => {
                if(checkResolution()) {
                    if(!withErrors) {
                        for(const c of callbacks) {
                            c();
                        };
                    } else {
                        console.warn("Some dependencies have not been loaded");
                        console.warn(notLoadedScripts);
                        console.warn("Not called callbacks");
                        console.warn(callbacks);
                    };
                };
            };
            const handlerLoadedScript = (ev) => {
                let target = ev.target;
                /* console.log("Caricato script con src: " + target.src + " e dataset.src: " + target.dataset.src); */
                if(target.src || target.dataset.src) {
                    let finalSrc = target.src ? target.src : target.dataset.src;
                    for(let i = 0; i < scripts.length; i++) {
                        let substrindex = finalSrc.indexOf(scriptsStatus[i].src);
                        if(substrindex >= 0) {
                            scriptsStatus[i].loaded = true;
                            scriptsStatus[i].error = false;
                            break;
                        };
                    };
                    manageDependenciesResult();
                };
            };
            const handlerErrorLoadedScript = (ev) => {
                withErrors = true;
                notLoadedScripts.push(scr);
                for(let i = 0; i < scripts.length; i++) {
                    let substrindex = finalSrc.indexOf(scriptsStatus[i].src);
                    if(substrindex >= 0) {
                        scriptsStatus[i].loaded = true;
                        scriptsStatus[i].error = true;
                        break;
                    };
                };
                manageDependenciesResult();
            };
            for(const script of scripts) {
                if(script.src || script.dataset?.src) {
                    script.addEventListener("load", handlerLoadedScript);
                    script.addEventListener("error", handlerErrorLoadedScript);
                };
            };
        };
    });
};
/**
* 
* @param {*} length lunghezza dell' id, default 25
* @returns 
*/
this.generateRandomId = function(length) {
   const res = {
       0: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
       1: "1234567890",
   };
   if(!length) {
       length = 25;
   };
   let newId = "";
   for(let i = 0; i < length; i++) {
       let resChoice = getRandomnumber(2);
       let resource = res[resChoice];
       let char = resource[getRandomnumber(resource.length)];
       newId += char;
   };
   return newId;
};

this.htmlToElement = function(html) {
    let temp = document.createElement('template');
    html = html.trim(); // Never return a space text node as a result
    temp.innerHTML = html;
    return temp.content.firstChild;
};
/**
    * 
    * @param {*} html html string (N.B. this does NOT execute scripts, remember to sanitize)
    * @param {*} datas un oggetto con variabili sostituibili nell' html (al momento limitato);
    * @returns i nuovi elementi
    */
this.htmlToElements = function(html) {
    let temp = document.createElement('template');
    html = html.trim(); // Never return a space text node as a result
    temp.innerHTML = html;
    return temp.content.childNodes;
};

/**
 * 
 * @param {*} titleId stringa no spazi o caratteri particolari, finisce per il labelledby
 * @param {*} id id del modal, se non serve specificarlo inserire false
 * @param {*} contentFn opzionale, funzione che recupera il contenuto del modal, DEVE TORNARE UNA PROMISE
 * @returns il nuovo modal come DOMElement
 */
this.getModal = function(titleId, id, contentFn, style) {
    if(!id) {
        id = "genmodal_" + this.generateRandomId();
    };
    return new Promise((resolve, reject) => {
        if(typeof contentFn === "function") {
            contentFn().then((content) => {
                const headerContent = content.headerContent;
                const bodyContent = content.bodyContent;
                const footerContent = content.footerContent;
                const titleId = content.titleId
                const GenericModal = this.htmlToElement(
`<div class="modal fade" id="${id}" tabindex="-1" role="dialog" aria-labelledby="${titleId}" aria-hidden="true">\
    <div class="modal-dialog" role="document">\
        <div class="modal-content">\
            <div class="modal-header">\
                ${headerContent ? headerContent : ""}\
            </div>\
            <div class="modal-body">\
                ${bodyContent ? bodyContent : ""}\
            </div>\
            <div class="modal-footer">\
                ${footerContent ? footerContent : ""}\
            </div>\
        </div>\
    </div>\
</div>`);
                resolve(GenericModal);
            }, (err) => {
                console.warn("Error in function " + contentFn.name +  " to get modal content");
                console.warn(err);
                const GenericModal = this.htmlToElement(
`<div class="modal fade" id="${id}" tabindex="-1" role="dialog" aria-labelledby="${titleId}" aria-hidden="true">\
    <div class="modal-dialog" role="document">\
        <div class="modal-content">\
            <div class="modal-header">\
                <h5 class="modal-title" id="${titleId}"></h5>\
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"></button>\
            </div>\
            <div class="modal-body">\
            </div>\
            <div class="modal-footer">\
            </div>\
        </div>\
    </div>\
</div>`);
                resolve(GenericModal);
            })
        } else {
            const GenericModal = this.htmlToElement(
`<div class="modal fade" id="${id}" tabindex="-1" role="dialog" aria-labelledby="${titleId}" aria-hidden="true">\
    <div class="modal-dialog" role="document">\
        <div class="modal-content">\
            <div class="modal-header">\
                <h5 class="modal-title" id="${titleId}"></h5>\
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"></button>\
            </div>\
            <div class="modal-body">\
            </div>\
            <div class="modal-footer">\
            </div>\
        </div>\
    </div>\
</div>`);
            resolve(GenericModal);
        };
    });
};

this.getWarningModal = function(title, id, warning_list, warning_type="error") {
    if(!id) {
        id = "genmodal_" + this.generateRandomId();
    };

    let modalMody = ""

    for (let single_warning of warning_list) {
        modalMody += `
            <p style="font-size: 1rem">${single_warning}</p>
            <br/>
        `
    }

    let icon = ""
    if (warning_type == "warning") {
        icon = '<span class="fa fa-exclamation-circle text-center text-info w-100 pb-3" style="font-size: 3.5rem;"></span>'
    } else {
        icon = '<span class="fa fa-times-circle text-center text-warning w-100 pb-3" style="font-size: 3.5rem;"></span>'
    }

    return new Promise((resolve, reject) => {

        const ModalContent = this.htmlToElement(
            `<div class="modal fade" id="${id}" tabindex="-1" role="dialog" aria-labelledby="${title}" style="z-index: 100000" aria-hidden="true">\
                <div class="modal-dialog" role="document">\
                    <div class="modal-content reduced">\
                        <div class="modal-header">\
                            <h3 class="modal-title font-weight-bold">${title}</h3>\
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close"></button>\
                        </div>\
                        <div class="modal-body p-3">\
                            ${icon}
                            ${modalMody}
                        </div>\
                        <div class="modal-footer reduced">\
                        </div>\
                    </div>\
                </div>\
            </div>`
        )
        resolve(ModalContent);
    })
}

this.splitQuerystrings = function(querystrings) {
    if(!querystrings) {
        return {};
    };
    let result = {};
    let splittedQss = querystrings.split("&");
    splittedQss.forEach((qs) => {
        let resplitted = qs.split("=");
        result[resplitted[0]] = resplitted[1];
    });
    return result;
};

/**
 * 
 * @param {*} form la form di cui fare i submit
 * @param {*} externalData dati aggiuntivi da passargli
 */
this.submitFormdata = function(form, externalData) {
    if(form) {
        let formData = new FormData(form);
        if(externalData) {
            for(let ed in externalData) {
                let value = externalData[ed];
                formData.append(ed, value);
            };
        };
        let action = form.getAttribute("action");
        let method = form.getAttribute("method");
        if(action === "" || !action) {
            action = window.location.href;
        };
        if(method === "" || !method) {
            method = "POST";
        };
        /** the submit request */
        let opts = {
            method: method,
            mode: 'same-origin',
            body: formData,
        }
        return fetch(action, opts);
    };
    return Promise.resolve({error: "No form element received"});
};

/**
 * selector deve essere una stringa che identifica !!UNIVOCAMENTE!! la form 
 * (se vogliamo usarla come funzione nell' attributo onsubmit in quanto in quel caso 
 * 1) il metodo viene chiamato PRIMA del submit 
 * 2) il this è la window e non ci passa nulla di default il bastardo).
 * Siccome viene usato queryselector si prende sempre il primo che trova nel DOM. SAPPIATELO!
 * In alternativa questa funzione può essere chiamata da un elemento form tramite uno script javascript
 * es. formElement.submit_response_handler().
 * In quel caso il this sarà l'elemento e non sarà necessario passare selector (anche se nessuno ve lo vieta). 
 * se la validationFunc ritorna un oggetto con "error" allora la form non submitta
*/
this.submit_response_handler = function(selector, validationFunc, externalData) {
    try {
        let realThis = this;
        if(selector) {
            let form = document.body.querySelector(selector);
            realThis = form ? form : realThis;
        }
        let canSubmit = true;
        let messageField = realThis.querySelector("div.submit-messages");
        let submitBtn = realThis.querySelector("button[type='submit']");
        if(validationFunc && typeof validationFunc === "string") {
            validationFunc = validationFunc ? this[validationFunc] : false;
        } else if(!validationFunc) {
            let validationFunctionName = realThis && realThis.dataset ? realThis.dataset.validationfunction : false;
            validationFunc = validationFunctionName ? this[validationFunctionName] : false;
        };
        if(validationFunc && typeof validationFunc === "function") {
            canSubmit = validationFunc(realThis);
        }
        if(canSubmit && canSubmit === true) {
            if(submitBtn) {
                submitBtn.classList.add("submitting");
                submitBtn.setAttribute("disabled", "disabled");
            };
            let target = realThis.getAttribute("target");
            if(target === "" || !target) {
                target = "_self";
            };
            if(messageField) {
                messageField.innerHTML = "<i class='fa fa-hourglass' style='font-size: 17px; color: blue'/>";
            };
            this.submitFormdata(realThis, externalData).then((r) => {
                if(r.error) {
                    return Promise.resolve({success: false, error: r.error});
                } else {
                    if(r.ok) {
                        return r.json();
                    } else {
                        return Promise.resolve({success:false, error: r.text()});
                    };
                };
            }).then((respRes) => {
                if(messageField) {
                    messageField.innerHTML = "";
                };
                if(!respRes.success) {
                    if(submitBtn) {
                        submitBtn.classList.remove("submitting");
                        submitBtn.removeAttribute("disabled");
                    };
                    if(respRes.error) {
                        console.error("Error on submit request: " + realThis.action);
                        console.error(respRes.error);
                        if(messageField) {
                            messageField.innerHTML = "<p style='color: red; font-size: 14px;'>" + respRes.error + "</p>";
                        };
                    } else {
                        if(messageField) {
                            messageField.innerHTML = "";
                        };
                    };
                } else {
                    if(respRes.message) {
                        if(messageField) {
                            messageField.innerHTML = "<p style='color: darkgreen; font-size: 14px;'>" + respRes.message + "</p>";
                        };
                    };
                    if(respRes.redirect) {
                        let timeout = respRes.redirectTimeout ? respRes.redirectTimeout : 3500;
                        setTimeout(() => {
                            window.open(respRes.redirect, target);
                        }, timeout);
                    };
                };
            });
        } else if(canSubmit && canSubmit.error) {
            if(messageField) {
                messageField.innerHTML = "<p style='color: red; font-size: 14px;'>" + canSubmit.error + "</p>";
            };
        };
    } catch (error) {
        console.error("Errore non gestito in funzioen di submit");
        console.error(error);
    };
    return false;
};

this.CoreCustomClass = class CoreCustomClass {
    constructor() {
        this.boundFunctions = {};
        this.backDrop = document.getElementById("default_backdrop")
    };
    renderTemplate(regularString, valuesDict) {
        let self = this;
        let finalValuesdict = {}
        if(!valuesDict) {
            finalValuesdict = {}
        } else if(Array.isArray(valuesDict)) {
            for(let v of valuesDict) {
                finalValuesdict[v] = v;
            };
        } else {
            finalValuesdict = valuesDict;
        };
        /**
         * tentativi regex
         * /[\$]\{\s*[A-Za-z0-9_][^;\s\{\}][A-Za-z0-9_]+\s*\}/g <- questa pare non matchare nulla
         */
        let replaceFn = function(match, matchIndex) {
            let cleanedTag = match.replace("${", "").replace("}", "");
            cleanedTag = cleanedTag.trim();
            if(cleanedTag.length === 0 || !cleanedTag) {
                return "";
            };
            let finalString = "";
            if(typeof finalValuesdict[cleanedTag] === "function") {
                finalString = finalValuesdict[cleanedTag]();
            } else {
                /**
                 * solo > 0 perchè non ha senso che inizi da
                 */
                if(cleanedTag.indexOf(".") > 0) {
                    let tokens = cleanedTag.split(".");
                    let finalValue = finalValuesdict[tokens.splice(0, 1)[0]];
                    for(let t of tokens) {
                        finalValue = finalValue[t];
                    };
                    finalString = finalValue;
                } else {
                    finalString = finalValuesdict[cleanedTag];
                };
            };
            return finalString;
        };
        try {
            replaceFn = replaceFn.bind(self);
            return regularString.replace(/\$\{\s*[A-Za-z0-9_\.]+\s*\}/g, replaceFn);
        } catch(err) {
            console.error("error in render template replace function.");
            console.error(err);
            return "";
        };
    };
    bindFunctions(...fns) {
        let self = this;
        fns.forEach((fn) => {
            if(typeof fn === "function") {
                if(!self.boundFunctions[fn.name]) {
                    self.boundFunctions[fn.name] = fn.bind(self);
                };
            };
        });
    };
    getBoundFunction(fName) {
        let self = this;
        return self.boundFunctions[fName];
    };

    addBd() {
        const self = this;
        if (self.backDrop) {
            self.backDrop.classList.add("active")
        }
    }
    removeBd() {
        const self = this;
        if (self.backDrop) {
            self.backDrop.classList.remove("active")
        }
    }
};