"use strict";
// Copyright 2017-2020 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bn_js_1 = __importDefault(require("bn.js"));
const util_1 = require("@polkadot/util");
const util_crypto_1 = require("@polkadot/util-crypto");
const Raw_1 = __importDefault(require("./Raw"));
const utils_1 = require("./utils");
/**
 * @name Set
 * @description
 * An Set is an array of string values, represented an an encoded type by
 * a bitwise representation of the values.
 */
// FIXME This is a prime candidate to extend the JavaScript built-in Set
class CodecSet extends Set {
    constructor(registry, setValues, value) {
        super(CodecSet.decodeSet(setValues, value));
        this.registry = registry;
        this._setValues = setValues;
    }
    /** @internal */
    static decodeSet(setValues, value = 0) {
        if (util_1.isString(value)) {
            return CodecSet.decodeSet(setValues, util_1.u8aToU8a(value));
        }
        else if (util_1.isU8a(value)) {
            return value.length === 0
                ? []
                : CodecSet.decodeSetNumber(setValues, value[0]);
        }
        else if (value instanceof Set) {
            return CodecSet.decodeSetArray(setValues, [...value.values()]);
        }
        else if (Array.isArray(value)) {
            return CodecSet.decodeSetArray(setValues, value);
        }
        return CodecSet.decodeSetNumber(setValues, value);
    }
    /** @internal */
    static decodeSetArray(setValues, value) {
        return value.reduce((result, key) => {
            util_1.assert(!util_1.isUndefined(setValues[key]), `Set: Invalid key '${key}' passed to Set, allowed ${Object.keys(setValues).join(', ')}`);
            result.push(key);
            return result;
        }, []);
    }
    /** @internal */
    static decodeSetNumber(setValues, _value) {
        const bn = util_1.bnToBn(_value);
        const result = Object.keys(setValues).reduce((result, key) => {
            if (bn.and(util_1.bnToBn(setValues[key])).eq(util_1.bnToBn(setValues[key]))) {
                result.push(key);
            }
            return result;
        }, []);
        const computed = CodecSet.encodeSet(setValues, result);
        util_1.assert(bn.eq(computed), `Set: Mismatch decoding '${bn}', computed as '${computed}' with ${result}`);
        return result;
    }
    static encodeSet(setValues, value) {
        return value.reduce((result, value) => {
            return result.or(util_1.bnToBn(setValues[value] || 0));
        }, new bn_js_1.default(0));
    }
    static with(values) {
        return class extends CodecSet {
            constructor(registry, value) {
                super(registry, values, value);
                Object.keys(values).forEach((_key) => {
                    const name = util_1.stringUpperFirst(util_1.stringCamelCase(_key));
                    const iskey = `is${name}`;
                    // do not clobber existing properties on the object
                    if (util_1.isUndefined(this[iskey])) {
                        Object.defineProperty(this, iskey, {
                            enumerable: true,
                            get: () => this.strings.includes(_key)
                        });
                    }
                });
            }
        };
    }
    /**
     * @description The length of the value when encoded as a Uint8Array
     */
    get encodedLength() {
        return 1;
    }
    /**
     * @description returns a hash of the contents
     */
    get hash() {
        return new Raw_1.default(this.registry, util_crypto_1.blake2AsU8a(this.toU8a(), 256));
    }
    /**
     * @description true is the Set contains no values
     */
    get isEmpty() {
        return this.size === 0;
    }
    /**
     * @description The actual set values as a string[]
     */
    get strings() {
        return [...super.values()];
    }
    /**
     * @description The encoded value for the set members
     */
    get valueEncoded() {
        return CodecSet.encodeSet(this._setValues, this.strings);
    }
    /**
     * @description adds a value to the Set (extended to allow for validity checking)
     */
    add(key) {
        // we have the isUndefined(this._setValues) in here as well, add is used internally
        // in the Set constructor (so it is undefined at this point, and should allow)
        util_1.assert(util_1.isUndefined(this._setValues) || !util_1.isUndefined(this._setValues[key]), `Set: Invalid key '${key}' on add`);
        super.add(key);
        return this;
    }
    /**
     * @description Compares the value of the input to see if there is a match
     */
    eq(other) {
        if (Array.isArray(other)) {
            // we don't actually care about the order, sort the values
            return utils_1.compareArray(this.strings.sort(), other.sort());
        }
        else if (other instanceof Set) {
            return this.eq([...other.values()]);
        }
        else if (util_1.isNumber(other) || util_1.isBn(other)) {
            return this.valueEncoded.eq(util_1.bnToBn(other));
        }
        return false;
    }
    /**
     * @description Returns a hex string representation of the value
     */
    toHex() {
        return util_1.u8aToHex(this.toU8a());
    }
    /**
     * @description Converts the Object to JSON, typically used for RPC transfers
     */
    toJSON() {
        return this.strings;
    }
    /**
     * @description The encoded value for the set members
     */
    toNumber() {
        return this.valueEncoded.toNumber();
    }
    /**
     * @description Returns the base runtime type name for this instance
     */
    toRawType() {
        // FIXME We don't cater for this in createType as of yet
        return JSON.stringify({ _set: this._setValues });
    }
    /**
     * @description Returns the string representation of the value
     */
    toString() {
        return `[${this.strings.join(', ')}]`;
    }
    /**
     * @description Encodes the value as a Uint8Array as per the SCALE specifications
     * @param isBare true when the value has none of the type-specific prefixes (internal)
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    toU8a(isBare) {
        return new Uint8Array([this.valueEncoded.toNumber()]);
    }
}
exports.default = CodecSet;
