/**
 * Hexadecimal functions.
 *
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2016
 * @license Apache-2.0
 */

import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";


/**
 * Convert a byte array into a hex string.
 *
 * @param {byteArray|Uint8Array|ArrayBuffer} data
 * @param {string} [delim=" "]
 * @param {number} [padding=2]
 * @returns {string}
 *
 * @example
 * // returns "0a 14 1e"
 * toHex([10,20,30]);
 *
 * // returns "0a:14:1e"
 * toHex([10,20,30], ":");
 *
 * // returns "0x0a,0x14,0x1e"
 * toHex([10,20,30], "0x", 2, ",")
 */
export function toHex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
    if (!data) return "";
    if (data instanceof ArrayBuffer) data = new Uint8Array(data);

    let output = "";
    const prepend = (delim === "0x" || delim === "\\x");

    for (let i = 0; i < data.length; i++) {
        const hex = data[i].toString(16).padStart(padding, "0");
        output += prepend ? delim + hex : hex + delim;

        if (extraDelim) {
            output += extraDelim;
        }
        // Add LF after each lineSize amount of bytes but not at the end
        if ((i !== data.length - 1) && ((i + 1) % lineSize === 0)) {
            output += "\n";
        }
    }

    // Remove the extraDelim at the end (if there is one)
    // and remove the delim at the end, but if it's prepended there's nothing to remove
    const rTruncLen = extraDelim.length + (prepend ? 0 : delim.length);
    if (rTruncLen) {
        // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing
        return output.slice(0, -rTruncLen);
    } else {
        return output;
    }
}


/**
 * Convert a byte array into a hex string as efficiently as possible with no options.
 *
 * @param {byteArray|Uint8Array|ArrayBuffer} data
 * @returns {string}
 *
 * @example
 * // returns "0a141e"
 * toHex([10,20,30]);
 */
export function toHexFast(data) {
    if (!data) return "";
    if (data instanceof ArrayBuffer) data = new Uint8Array(data);

    const output = [];

    for (let i = 0; i < data.length; i++) {
        output.push((data[i] >>> 4).toString(16));
        output.push((data[i] & 0x0f).toString(16));
    }

    return output.join("");
}


/**
 * Convert a hex string into a byte array.
 *
 * @param {string} data
 * @param {string} [delim]
 * @param {number} [byteLen=2]
 * @returns {byteArray}
 *
 * @example
 * // returns [10,20,30]
 * fromHex("0a 14 1e");
 *
 * // returns [10,20,30]
 * fromHex("0a:14:1e", "Colon");
 */
export function fromHex(data, delim="Auto", byteLen=2) {
    if (byteLen < 1 || Math.round(byteLen) !== byteLen)
        throw new OperationError("Byte length must be a positive integer");

    if (delim !== "None") {
        const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim);
        data = data.split(delimRegex);
    } else {
        data = [data];
    }

    const output = [];
    for (let i = 0; i < data.length; i++) {
        for (let j = 0; j < data[i].length; j += byteLen) {
            output.push(parseInt(data[i].substr(j, byteLen), 16));
        }
    }
    return output;
}


/**
 * To Hexadecimal delimiters.
 */
export const TO_HEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "0x with comma", "\\x", "None"];


/**
 * From Hexadecimal delimiters.
 */
export const FROM_HEX_DELIM_OPTIONS = ["Auto"].concat(TO_HEX_DELIM_OPTIONS);
