/**
 * @author mikecat
 * @copyright Crown Copyright 2023
 * @license Apache-2.0
 */

import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import CryptoApi from "crypto-api/src/crypto-api.mjs";

/**
 * Derive HKDF Key operation
 */
class DeriveHKDFKey extends Operation {

    /**
     * DeriveHKDFKey constructor
     */
    constructor() {
        super();

        this.name = "Derive HKDF key";
        this.module = "Crypto";
        this.description = "A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869.";
        this.infoURL = "https://wikipedia.org/wiki/HKDF";
        this.inputType = "ArrayBuffer";
        this.outputType = "string";
        this.args = [
            {
                "name": "Salt",
                "type": "toggleString",
                "value": "",
                "toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
            },
            {
                "name": "Info",
                "type": "toggleString",
                "value": "",
                "toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
            },
            {
                "name": "Hashing function",
                "type": "option",
                "value": [
                    "MD2",
                    "MD4",
                    "MD5",
                    "SHA0",
                    "SHA1",
                    "SHA224",
                    "SHA256",
                    "SHA384",
                    "SHA512",
                    "SHA512/224",
                    "SHA512/256",
                    "RIPEMD128",
                    "RIPEMD160",
                    "RIPEMD256",
                    "RIPEMD320",
                    "HAS160",
                    "Whirlpool",
                    "Whirlpool-0",
                    "Whirlpool-T",
                    "Snefru"
                ],
                "defaultIndex": 6
            },
            {
                "name": "Extract mode",
                "type": "argSelector",
                "value": [
                    {
                        "name": "with salt",
                        "on": [0]
                    },
                    {
                        "name": "no salt",
                        "off": [0]
                    },
                    {
                        "name": "skip",
                        "off": [0]
                    }
                ]
            },
            {
                "name": "L (number of output octets)",
                "type": "number",
                "value": 16,
                "min": 0
            },
        ];
    }

    /**
     * @param {ArrayBuffer} input
     * @param {Object[]} args
     * @returns {ArrayBuffer}
     */
    run(input, args) {
        const argSalt = Utils.convertToByteString(args[0].string || "", args[0].option),
            info = Utils.convertToByteString(args[1].string || "", args[1].option),
            hashFunc = args[2].toLowerCase(),
            extractMode = args[3],
            L = args[4],
            IKM = Utils.arrayBufferToStr(input, false),
            hasher = CryptoApi.getHasher(hashFunc),
            HashLen = hasher.finalize().length;

        if (L < 0) {
            throw new OperationError("L must be non-negative");
        }
        if (L > 255 * HashLen) {
            throw new OperationError("L too large (maximum length for " + args[2] + " is " + (255 * HashLen) + ")");
        }

        const hmacHash = function(key, data) {
            hasher.reset();
            const mac = CryptoApi.getHmac(key, hasher);
            mac.update(data);
            return mac.finalize();
        };
        const salt = extractMode === "with salt" ? argSalt : "\0".repeat(HashLen);
        const PRK = extractMode === "skip" ? IKM : hmacHash(salt, IKM);
        let T = "";
        let result = "";
        for (let i = 1; i <= 255 && result.length < L; i++) {
            const TNext = hmacHash(PRK, T + info + String.fromCharCode(i));
            result += TNext;
            T = TNext;
        }
        return CryptoApi.encoder.toHex(result.substring(0, L));
    }

}

export default DeriveHKDFKey;
