/**
 * @typedef {import("../../ecma/AnyankaKeys.ecma.js") AnyankaKeys} AnyankaKeys
 */

/**
 * @constructor
 * @returns {void}
 * @access public
 */
const AnyankaKeysTestsEncode = function(){

    /** @type {AnyankaKeysTestsEncode} */
    const self = this;

    /**
     * @returns {void}
     * @access private
     */
    const constructor = () => {};

    constructor();

};

/**
 * @param {!string} mode 
 * @param {!Array.<number>} array 
 * @param {!Array.<number>} encoded 
 * @param {!Array.<number>} decoded 
 * @param {boolean} ok 
 * @returns {void}
 * @access public
 * @static
 */
AnyankaKeysTestsEncode.prototype.show_results = (mode, array, encoded, decoded, ok) => {
    switch(mode){

        case "array_named":
            console.log(["array", ...array]);
            console.log(["encoded", ...encoded]);
            console.log(["decoded", ...decoded]);
        break;
 
        case "array":
            console.log(array);
            console.log(encoded);
            console.log(decoded);
        break;
 
        case "only_errors":
            console.log(ok);
            if(!ok){
                console.log(array);
                console.log(encoded);
                console.log(decoded);
            };
        break;
 
    };
};

/**
 * @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} inputs 
 * @returns {Array.<Array.<number, number>, Array.<number>, boolean>}
 * @access public
 * @static
 */
AnyankaKeysTestsEncode.prototype.get_parameters = inputs => {

    /** @type {number} */
    const items = AnyankaKeys.prototype.get_value("items", inputs || (inputs = {}), 10) - 1, 
          /** @type {Array.<number, number>} */
          base = AnyankaKeys.prototype.get_value("base", inputs, [256, 58]), 
          /** @type {Array.<number>} */
          array = inputs.array || (new Array((Math.random() * items >> 0) + 1)).fill(0).map((value, i) => {

            while(!(value = Math.random() * base[0] >> 0) && !i);

            return value;
        }), 
        /** @type {boolean} */
        show_mode = AnyankaKeys.prototype.get_value(["show_mode", "show"], inputs, "only_errors");

    return [base, array, show_mode];
};

/**
* @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} [inputs] 
* @returns {boolean}
* @access public
*/
AnyankaKeysTestsEncode.prototype.check = inputs => {

   /** @type {Array.<Array.<number, number>, Array.<number>, boolean>} */
   const [base, array, show_mode] = AnyankaKeysTestsEncode.prototype.get_parameters(inputs), 
         /** @type {Array.<number>} */
         encoded = AnyankaKeys.prototype.encode(array, {base : base})[0], 
         /** @type {Array.<number>} */
         decoded = AnyankaKeys.prototype.encode(encoded, {base : base.reverse()})[0], 
         /** @type {boolean} */
         ok = array.every((value, i) => decoded[i] == value);

    AnyankaKeysTestsEncode.prototype.show_results(show_mode, array, encoded, decoded, ok);

   return ok;
};

/**
 * @callback anyanka_keys_test_encode_asynchronous_callback
 * @param {boolean} ok 
 * @returns {void}
 */

/**
 * @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} inputs 
 * @param {!anyanka_keys_test_encode_asynchronous_callback} callback 
 * @returns {void}
 * @access public
 * @static
 */
AnyankaKeysTestsEncode.prototype.check_asynchronous = (inputs, callback) => {

    /** @type {Array.<Array.<number, number>, Array.<number>, boolean>} */
    const [base, array, show_mode] = AnyankaKeysTestsEncode.prototype.get_parameters(inputs);
    
    AnyankaKeys.prototype.encode(array, {
        base : base, 
        synchronous : false, 
        set : (_, data, ended) => {
            if(!ended)
                return;

            /** @type {Array.<number>} */
            const encoded = data.results;

            AnyankaKeys.prototype.encode(encoded, {
                base : base.reverse(), 
                synchronous : false, 
                set : (_, data, ended) => {
                    if(!ended)
                        return;

                    /** @type {Array.<number>} */
                    const decoded = data.results, 
                          /** @type {boolean} */
                          ok = array.every((value, i) => decoded[i] == value);

                    AnyankaKeysTestsEncode.prototype.show_results(show_mode, array, encoded, decoded, ok);

                    AnyankaKeys.prototype.execute(callback, ok);

                }
            });

        }
    });

 };

/**
 * @param {!(number|Object.<string, any|null>|Array.<Object.<string, any|null>>)} [inputs = 100] 
 * @returns {void}
 * @access public
 */
AnyankaKeysTestsEncode.prototype.check_bucle = (inputs = 100) => {

    if(typeof inputs == "number")
        inputs = {iterations : inputs};
    else if(!AnyankaKeys.prototype.is_object(inputs))
        inputs = {};

    /** @type {number} */
    let i = 0, 
        /** @type {boolean} */
        has_error = false;
    /** @type {number} */
    const iterations = AnyankaKeys.prototype.get_value("iterations", inputs, 100), 
          /** @type {boolean} */
          retry_ok = AnyankaKeys.prototype.get_value("retry_ok", inputs, true), 
          /** @type {number} */
          timer = AnyankaKeys.prototype.get_value(["timer", "timeout"], inputs, 500);
    
    /** @type {number} */
    const interval = setInterval(() => {
        if(++ i == iterations){
            if(has_error || !retry_ok){
                clearInterval(interval);
                return;
            }else 
                i = 0;
        };
        !AnyankaKeysTestsEncode.prototype.check(inputs) && !has_error && (has_error = true);
    }, timer);

};

/**
 * @constructor
 * @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} inputs 
 * @param {!(Object.<string, any|null>)} [extra = {}] 
 * @returns {void}
 * @access public
 */
const AnyankaKeysTestsEncodeBucleData = function(inputs, extra = {}){

    /** @type {AnyankaKeysTestsEncodeBucleData} */
    const self = this;

    /** @type {number} */
    this.iterations = 100;
    /** @type {boolean} */
    this.retry_ok = true;
    /** @type {number} */
    this.timer = 100;

    /** @type {number} */
    this.i = 0;
    /** @type {boolean} */
    this.has_error = false;

    /** @type {Object.<string, any|null>} */
    this.inputs = {};

    /**
     * @returns {void}
     * @access private
     */
    const constructor = () => {

        self.inputs = {...AnyankaKeys.prototype.get_dictionary(
            typeof inputs == "number" ? {iterations : inputs} : 
        inputs), ...extra};

        self.iterations = AnyankaKeys.prototype.get_value("iterations", self.inputs, self.iterations);
        self.retry_ok = AnyankaKeys.prototype.get_value("retry_ok", self.inputs, self.retry_ok);
        self.timer = AnyankaKeys.prototype.get_value(["timer", "timeout"], self.inputs, self.timer);

    };

    constructor();

};

/**
 * @param {!(AnyankaKeysTestsEncodeBucleData|number|Object.<string, any|null>|Array.<Object.<string, any|null>>)} [inputs = 100] 
 * @returns {void}
 * @access public
 */
AnyankaKeysTestsEncode.prototype.check_asynchronous_bucle = (data = 100) => {

    (!data || !(data instanceof AnyankaKeysTestsEncodeBucleData)) && (data = new AnyankaKeysTestsEncodeBucleData(data, {synchronous : false}));

    if(++ data.i < data.iterations){
        AnyankaKeysTestsEncode.prototype.check_asynchronous(data.inputs, ok => {
            ok || data.has_error || (data.has_error = true);
            AnyankaKeysTestsEncode.prototype.check_asynchronous_bucle(data);
        });
    }else if(!data.has_error && data.retry_ok){
        data.i = 0;
        AnyankaKeysTestsEncode.prototype.check_asynchronous_bucle(data);
    };

};