Source: utils/template/convertUnits.js

class UnitPrefixConverter {
    constructor() {
        this.unitPrefixDict = {
            'f': 10 ** -15,
            'p': 10 ** -12,
            'n': 10 ** -9,
            'µ': 10 ** -6,
            'm': 10 ** -3,
            '': 10 ** 0,
            'K': 10 ** 3,
            'M': 10 ** 6,
            'G': 10 ** 9,
            'T': 10 ** 12,
            'P': 10 ** 15
        }
        this.skipTrailingZeros = false;
        this.factor = 1;
    }

    convert(value, unit, targetUnit, precision) {

        let negativeInputValue = value < 0 ? true : false;
        let valueAbsolute = Math.abs(value);

        var convertedValuePrecisely, unitPrefix;
        for (var i = 0; i < Object.keys(this.unitPrefixDict).length; i++) {
            unitPrefix = Object.keys(this.unitPrefixDict)[i];
            const nextUnitPrefix = Object.keys(this.unitPrefixDict)[i + 1];
            const unitPrefixValue = this.unitPrefixDict[unitPrefix];
            const nextUnitPrefixValue = this.unitPrefixDict[nextUnitPrefix];
            const convertedValue = Number(valueAbsolute / (unitPrefixValue * this.factor));
            convertedValuePrecisely = parseFloat(convertedValue.toPrecision(precision));
            if ((convertedValuePrecisely < nextUnitPrefixValue / unitPrefixValue) || nextUnitPrefix === undefined)
                break
        }

        const digitsBeforeDecimalPoint = String(convertedValuePrecisely).split('.')[0].length;
        const digitsAfterDecimalPoint = precision - digitsBeforeDecimalPoint;
        const skipFixedNumber = digitsAfterDecimalPoint <= 0 || this.skipTrailingZeros ? true : false;

        const sign = negativeInputValue ? -1 : 1;
        const returnNumber = skipFixedNumber ? convertedValuePrecisely : convertedValuePrecisely.toFixed(digitsAfterDecimalPoint);
        return `${sign * returnNumber} ${unitPrefix}${targetUnit}`
    }
}


class BinaryUnitPrefixConverter extends UnitPrefixConverter {
    constructor() {
        super();
        this.unitPrefixDict = {
            '': 10 ** 0,
            'k': 10 ** 3,
            'M': 10 ** 6,
            'G': 10 ** 9,
            'T': 10 ** 12,
            'P': 10 ** 15
        }
        this.skipTrailingZeros = true;
    }
}


class LengthUnitPrefixConverter extends UnitPrefixConverter {
    constructor() {
        super();
        this.unitPrefixDict = {
            'f': 10 ** -15,
            'p': 10 ** -12,
            'n': 10 ** -9,
            'µ': 10 ** -6,
            'm': 10 ** -3,
            'c': 10 ** -2,
            'd': 10 ** -1,
            '': 10 ** 0,
            'k': 10 ** 3
        }
    }
}


/**
 * Apply unit conversion. At the moment, only the conversion of unit prefixes is supported. There is no conversion of units - with the exception of bits and bytes.
 * @memberof vef.utils.template
 * @param {String|Number} value input value
 * @param {String} unit unit of input value
 * @param {String} targetUnit desired output unit. Support of bit to byte conversion and vice versa only
 * @param {String|Number} precision number of digits
 * @returns {String} converted value and unit
 * @example
 *      ['12346789', 'bit', 'B', '3'] -> 1.54 MB
 *      [0.123, 'm', 'm', '3'] -> 1.23 dm
 *      [12346789, 'bit/s', 'bit/s', '3'] -> 12.3 Mbit/s
 */
function applyUnitConversion(value, unit, targetUnit, precision) {

    const isInputUnitBit = unit.match(/bit/i) || unit.match(/^b$/) ? true : false;
    const isInputUnitByte = unit.match(/byte/i) || unit.match(/^B$/) ? true : false;
    const isTargetUnitBit = targetUnit.match(/bit/i) || targetUnit.match(/^b$/) ? true : false;
    const isTargetUnitByte = targetUnit.match(/byte/i) || targetUnit.match(/^B$/) ? true : false;
    const isBitConversion = isInputUnitBit || isInputUnitByte ? true : false;

    const isLengthConversion = unit.match(/^m$/i) ? true : false;

    let Converter;
    if (isBitConversion)
        Converter = new BinaryUnitPrefixConverter();
    else if (isLengthConversion)
        Converter = new LengthUnitPrefixConverter();
    else
        Converter = new UnitPrefixConverter();

    if (isInputUnitBit && isTargetUnitByte)
        Converter.factor = 8;
    if (isInputUnitByte && isTargetUnitBit)
        Converter.factor = 1 / 8;

    return Converter.convert(value, unit, targetUnit, precision)
}

export { applyUnitConversion };