/* eslint-disable no-bitwise */

// indexes
const R = 0;
const G = 1;
const B = 2;
const A = 3;

/**
 Get a CSS string from an array of channels
 @param {array} channels [0-255, 0-255, 0-255, 0-1]
 @returns {string} The new color's rgba css string
*/
function channelsToCssString(channels) {
    return `rgba(${channels[R] || '0'}, ${channels[G] || '0'}, ${channels[B] || '0'}, ${channels[A] || '1'})`;
}

/**
 Get the channel components of a properly formatted css color (rgb, rgba or hex)
 Is assumed that color format is valid, and won't work with hsl format
 @param {string} valid css color
 @returns {array} [0-255, 0-255, 0-255, 0-1]
*/
export function getChannelsFromCssString(cssString) {
    const parts = [];
    let m;
    // if color is hex, extract
    if (cssString[0] === '#') {
        // if string is not long enough to contain long numbers, we search single digit parts
        const isShort = cssString.length <= 6;
        const reg = isShort ? /[0-9A-F]/gi : /[0-9A-F]{2}/gi;
        // eslint-disable-next-line no-cond-assign
        while (m = reg.exec(cssString)) {
            const n = isShort ? `${m[0]}${m[0]}` : m[0];
            parts.push(parseInt(n, 16));
        }
        // if alpha component exists, convert it to a float between 0 and 1
        if (parts[A]) {
            parts[A] /= 255;
        }
    } else {
        // color is rgb or rgba format, so we have a series of decimal numbers
        const reg = /[0-9.]+/g;
        // eslint-disable-next-line no-cond-assign
        while (m = reg.exec(cssString)) {
            parts.push(Number(m[0]));
        }
    }
    // alpha defaults to 1
    if (!parts[A] && parts[A] !== 0) {
        parts[A] = 1;
    }
    return parts;
}
/**
 Get a variant of color
 @param {string} colorString valid css color (rgba, rgb or hexadecimal)
 @param {number} shadeRatio expressed as a float value between -1 (darken) and 1 (lighten)
 @returns {string} The new color's rgba css string
*/
function shadeColor(colorString, shadeRatio) {
    const t = shadeRatio < 0 ? 0 : 255;
    const p = shadeRatio < 0 ? shadeRatio * -1 : shadeRatio;
    const channels = [R, G, B].reduce((accumulator, channel) => {
        const transformed = [...accumulator];
        const val = transformed[channel];
        transformed[channel] = Math.round((t - val) * p) + val;
        return transformed;
    }, getChannelsFromCssString(colorString));
    return channelsToCssString(channels);
}

/**
 Determines if the passed color is dark or light
 @param {string} colorString valid css color string
 @returns {boolean} True if the color is dark
*/
export const isDark = colorString => {
    const [r, g, b] = getChannelsFromCssString(colorString);
    const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    return luma < 150;
};

/**
 Convert the current color to darker one, based on specific percentage
 @param {string} color valid css color string
 @param {number} darkenRatio expressed as a float value between 0 and 1
 @returns {string} The darkened color in css rgba format
*/
export const darken = (color, darkenRatio) => shadeColor(color, -darkenRatio);

/**
 Convert the current color to lighter one, based on specific percentage
 @param {string} color valid css color string
 @param {number} lightenRatio expressed as a float value between 0 and 1
 @returns {string} The lightened color in css rgba format
*/
export const lighten = (color, lightenRatio) => shadeColor(color, lightenRatio);
