import env from './env.js';

/**
 * Parses a link header
 *
 * The node module parse-link-header didn't work, so https://gist.github.com/niallo/3109252 became handy
 *
 * @param header
 */
export const parseLinkHeader = (header) => {
    if (header.length === 0) {
        throw new Error("input must not be of zero length");
    }

    // Split parts by comma
    const parts = header.split(',');
    const links = {};

    // Parse each part into a named link
    for(let i=0; i<parts.length; i++) {
        const section = parts[i].split(';');
        if (section.length !== 2) {
            throw new Error("section could not be split on ';'");
        }
        const url = section[0].replace(/<(.*)>/, '$1').trim();
        const name = section[1].replace(/rel="(.*)"/, '$1').trim();
        links[name] = url;
    }

    return links;
};

/**
 * Reads a setting
 *
 * @param key
 * @returns {*}
 */
export const setting = (key) => {
    return env[key];
};

export const getAPiUrl = (path = "", withPrefix = true) => {
    return env.apiBaseUrl + (withPrefix ? env.apiUrlPrefix : "") + path;
};

/**
 * Parses the base url from an url
 *
 * @param url
 * @returns {string}
 */
export const parseBaseUrl = (url) => {
    const pathArray = url.split('/');
    const protocol = pathArray[0];
    const host = pathArray[2];
    return protocol + '//' + host;
};

/**
 * Converts a string list to a data array for Select2
 *
 * @param list
 * @returns {Array}
 */
export const stringListToSelect2DataArray = (list) => {
    let data = [];
    list.forEach((item) => {data.push({id: item, text: item});});
    return data;
};

/**
 * Does generic Base64 Encoding with support for 16-bit encoded strings
 *
 * @see https://www.base64encoder.io/javascript/
 *
 * @param str
 * @returns {string}
 */
export const base64EncodeUnicode = (str) => {
    // First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters,
    // then we convert the percent encodings into raw bytes, and finally feed it to btoa() function.
    const utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode('0x' + p1);
    });

    return btoa(utf8Bytes);
};

/**
 * Like customElements.define() but tries to display an error in case the browser doesn't
 * support everything we need.
 *
 * Returns false in case define failed, true otherwise.
 *
 * @returns {boolean}
 */

/**
 * 
 * @param {string} name 
 * @param {Function} constructor 
 * @param {object} options 
 */
export const defineCustomElement = (name, constructor, options) => {
    // Checks taken from https://github.com/webcomponents/webcomponentsjs/blob/master/webcomponents-loader.js
    if (!('attachShadow' in Element.prototype && 'getRootNode' in Element.prototype && window.customElements)) {
        var elements = document.getElementsByTagName(name);
        for(var i=0; i < elements.length; i++) {
            elements[i].innerHTML = "<span style='border: 1px solid red; font-size: 0.8em; "
                + "opacity: 0.5; padding: 0.2em;'>☹ Your browser is not supported ☹</span>";
        }
       return false;
    }
    customElements.define(name, constructor, options);
    return true;
};

/**
 * Creates a random id
 *
 * taken from: https://stackoverflow.com/a/1349426/1581487
 *
 * @param length
 * @returns {string}
 */
export const makeId = (length) => {
    let result           = '';
    const characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const charactersLength = characters.length;
    for ( let i = 0; i < length; i++ ) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }

    return result;
};

/**
 * Pads a number with a 0 so it has two digits
 *
 * @param n
 * @returns {string}
 */
export const pad10 = (n) => { return n < 10 ? '0' + n : n; };

/**
 * Converts a date object or string to a local iso datetime with stripped seconds and timezone for the datetime-local input
 *
 * @param date
 * @returns {string}
 */
export const dateToStrippedIsoDT = (date) => {
    if (!(date instanceof Date)) {
        date = new Date(date);
    }

    return `${date.getFullYear()}-${pad10(date.getMonth()+1)}-${pad10(date.getDate())}T${pad10(date.getHours())}:${pad10(date.getMinutes())}`;
};

/**
 * Converts a date object or string to a local date string the date input
 *
 * @param date
 * @returns {string}
 */
export const dateToInputDateString = (date) => {
    if (!(date instanceof Date)) {
        date = new Date(date);
    }

    return `${date.getFullYear()}-${pad10(date.getMonth()+1)}-${pad10(date.getDate())}`;
};

/**
 * Converts a date object or string to a local time string the time input
 *
 * @param date
 * @returns {string}
 */
export const dateToInputTimeString = (date) => {
    if (!(date instanceof Date)) {
        date = new Date(date);
    }

    return `${pad10(date.getHours())}:${pad10(date.getMinutes())}`;
};

let _assetBaseURL = null;

/**
 * Set the base url for future calls to getAssetURL()
 *
 * @param {string} [id] An optional id of the script tag to figure out the
 *  base bundle URL.
 */
export const initAssetBaseURL = (id) => {
    // We don't want future calls to change things
    if (_assetBaseURL)
        return;

    if (id) {
        // Find the id added to the script tag
        const elm = document.getElementById(id);
        if (elm && elm.src) {
            _assetBaseURL = elm.src;
        }
    }

    // In the (unlikely) event that we are bundled as a non-module
    // we can use the old currentScript API
    if (document.currentScript && document.currentScript.src) {
        _assetBaseURL = document.currentScript.src;
    }

    // XXX: Can't be parsed in (old) edge, but makes everything so much easier...
    if (!_assetBaseURL)
        _assetBaseURL = new URL('..', import.meta.url).href;
};

/**
 * Get an absolute path for assets given a relative path to the js bundle.
 * Call initAssetBaseURL() before this.
 *
 * @param {string} path The relative path based on the js bundle path
 */
export const getAssetURL = (path) => {
    // Maybe initScriptURL() can do something without the id
    if (!_assetBaseURL) {
        initAssetBaseURL();
    }

    // We already found the path before, just go with it
    if (_assetBaseURL) {
        return new URL(path, _assetBaseURL).href;
    } else {
        console.error('Using getAssetURL(), but initAssetBaseURL() wasn\'t called yet');
    }

    // If all fails we just fall back to relative paths and hope the
    // html is on the same path as the bundle
    return path;
};

/**
 * Poll <fn> every <interval> ms until <timeout> ms
 *
 * @param fn
 * @param timeout
 * @param interval
 */
export const pollFunc =  (fn, timeout, interval) => {
    var startTime = (new Date()).getTime();
    interval = interval || 1000;

    (function p() {
        // don't retry if we took longer than timeout ms
        if (((new Date).getTime() - startTime ) > timeout) {
            return;
        }

        // retry until fn() returns true
        if (!fn())  {
            setTimeout(p, interval);
        }
    })();
};