/**
 * @author Karsten Silkenbäumer [github.com/kassi]
 * @copyright Karsten Silkenbäumer 2019
 * @license Apache-2.0
 */

import Operation from "../Operation.mjs";
import {
    BACON_ALPHABETS,
    BACON_TRANSLATION_CASE, BACON_TRANSLATION_AMNZ, BACON_TRANSLATIONS, BACON_CLEARER_MAP, BACON_NORMALIZE_MAP,
    swapZeroAndOne
} from "../lib/Bacon.mjs";

/**
 * Bacon Cipher Decode operation
 */
class BaconCipherDecode extends Operation {
    /**
     * BaconCipherDecode constructor
     */
    constructor() {
        super();

        this.name = "Bacon Cipher Decode";
        this.module = "Default";
        this.description = "Bacon's cipher or the Baconian cipher is a method of steganography devised by Francis Bacon in 1605. A message is concealed in the presentation of text, rather than its content.";
        this.infoURL = "https://wikipedia.org/wiki/Bacon%27s_cipher";
        this.inputType = "string";
        this.outputType = "string";
        this.args = [
            {
                "name": "Alphabet",
                "type": "option",
                "value": Object.keys(BACON_ALPHABETS)
            },
            {
                "name": "Translation",
                "type": "option",
                "value": BACON_TRANSLATIONS
            },
            {
                "name": "Invert Translation",
                "type": "boolean",
                "value": false
            }
        ];
        this.checks = [
            {
                pattern:  "^\\s*([01]{5}\\s?)+$",
                flags:  "",
                args:   ["Standard (I=J and U=V)", "0/1", false]
            },
            {
                pattern:  "^\\s*([01]{5}\\s?)+$",
                flags:  "",
                args:   ["Standard (I=J and U=V)", "0/1", true]
            },
            {
                pattern:  "^\\s*([AB]{5}\\s?)+$",
                flags:  "",
                args:   ["Standard (I=J and U=V)", "A/B", false]
            },
            {
                pattern:  "^\\s*([AB]{5}\\s?)+$",
                flags:  "",
                args:   ["Standard (I=J and U=V)", "A/B", true]
            },
            {
                pattern:  "^\\s*([01]{5}\\s?)+$",
                flags:  "",
                args:   ["Complete", "0/1", false]
            },
            {
                pattern:  "^\\s*([01]{5}\\s?)+$",
                flags:  "",
                args:   ["Complete", "0/1", true]
            },
            {
                pattern:  "^\\s*([AB]{5}\\s?)+$",
                flags:  "",
                args:   ["Complete", "A/B", false]
            },
            {
                pattern:  "^\\s*([AB]{5}\\s?)+$",
                flags:  "",
                args:   ["Complete", "A/B", true]
            }
        ];
    }

    /**
     * @param {string} input
     * @param {Object[]} args
     * @returns {string}
     */
    run(input, args) {
        const [alphabet, translation, invert] = args;
        const alphabetObject = BACON_ALPHABETS[alphabet];

        // remove invalid characters
        input = input.replace(BACON_CLEARER_MAP[translation], "");

        // normalize to unique alphabet
        if (BACON_NORMALIZE_MAP[translation] !== undefined) {
            input = input.replace(/./g, function (c) {
                return BACON_NORMALIZE_MAP[translation][c];
            });
        } else if (translation === BACON_TRANSLATION_CASE) {
            const codeA = "A".charCodeAt(0);
            const codeZ = "Z".charCodeAt(0);
            input = input.replace(/./g, function (c) {
                const code = c.charCodeAt(0);
                if (code >= codeA && code <= codeZ) {
                    return "1";
                } else {
                    return "0";
                }
            });
        } else if (translation === BACON_TRANSLATION_AMNZ) {
            const words = input.split(/\s+/);
            const letters = words.map(function (e) {
                if (e) {
                    const code = e[0].toUpperCase().charCodeAt(0);
                    return code >= "N".charCodeAt(0) ? "1" : "0";
                } else {
                    return "";
                }
            });
            input = letters.join("");
        }

        if (invert) {
            input = swapZeroAndOne(input);
        }

        // group into 5
        const inputArray = input.match(/(.{5})/g) || [];

        let output = "";
        for (let i = 0; i < inputArray.length; i++) {
            const code = inputArray[i];
            const number = parseInt(code, 2);
            output += number < alphabetObject.alphabet.length ? alphabetObject.alphabet[number] : "?";
        }
        return output;
    }
}

export default BaconCipherDecode;
