"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 util_1 = require("@polkadot/util");
const Enum_1 = __importDefault(require("../../codec/Enum"));
const Tuple_1 = __importDefault(require("../../codec/Tuple"));
const Raw_1 = __importDefault(require("../../codec/Raw"));
const U64_1 = __importDefault(require("../U64"));
const constants_1 = require("./constants");
/**
 * @name ImmortalEra
 * @description
 * The ImmortalEra for an extrinsic
 */
class ImmortalEra extends Raw_1.default {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    constructor(registry, value) {
        // For immortals, we always provide the known value (i.e. treated as a
        // constant no matter how it is constructed - it is a fixed structure)
        super(registry, constants_1.IMMORTAL_ERA);
    }
}
exports.ImmortalEra = ImmortalEra;
/**
 * @name MortalEra
 * @description
 * The MortalEra for an extrinsic, indicating period and phase
 */
class MortalEra extends Tuple_1.default {
    constructor(registry, value) {
        super(registry, {
            period: U64_1.default,
            phase: U64_1.default
        }, MortalEra.decodeMortalEra(registry, value));
    }
    /** @internal */
    static decodeMortalEra(registry, value) {
        if (util_1.isHex(value)) {
            return MortalEra.decodeMortalU8a(registry, util_1.hexToU8a(value));
        }
        else if (Array.isArray(value)) {
            return MortalEra.decodeMortalU8a(registry, new Uint8Array(value));
        }
        else if (util_1.isU8a(value)) {
            return MortalEra.decodeMortalU8a(registry, value);
        }
        else if (util_1.isObject(value)) {
            return MortalEra.decodeMortalObject(registry, value);
        }
        else if (!value) {
            return [new U64_1.default(registry), new U64_1.default(registry)];
        }
        throw new Error('Invalid data passed to Mortal era');
    }
    /** @internal */
    static decodeMortalObject(registry, value) {
        const { current, period } = value;
        let calPeriod = Math.pow(2, Math.ceil(Math.log2(period)));
        calPeriod = Math.min(Math.max(calPeriod, 4), 1 << 16);
        const phase = current % calPeriod;
        const quantizeFactor = Math.max(calPeriod >> 12, 1);
        const quantizedPhase = phase / quantizeFactor * quantizeFactor;
        return [new U64_1.default(registry, calPeriod), new U64_1.default(registry, quantizedPhase)];
    }
    /** @internal */
    static decodeMortalU8a(registry, value) {
        if (value.length === 0) {
            return [new U64_1.default(registry), new U64_1.default(registry)];
        }
        const first = util_1.u8aToBn(value.subarray(0, 1)).toNumber();
        const second = util_1.u8aToBn(value.subarray(1, 2)).toNumber();
        const encoded = first + (second << 8);
        const period = 2 << (encoded % (1 << 4));
        const quantizeFactor = Math.max(period >> 12, 1);
        const phase = (encoded >> 4) * quantizeFactor;
        util_1.assert(period >= 4 && phase < period, 'Invalid data passed to Mortal era');
        return [new U64_1.default(registry, period), new U64_1.default(registry, phase)];
    }
    /**
     * @description Encoded length for mortals occupy 2 bytes, different from the actual Tuple since it is encoded. This is a shortcut fro `toU8a().length`
     */
    get encodedLength() {
        return 2;
    }
    /**
     * @description The period of this Mortal wraps as a [[U64]]
     */
    get period() {
        return this[0];
    }
    /**
     * @description The phase of this Mortal wraps as a [[U64]]
     */
    get phase() {
        return this[1];
    }
    /**
     * @description Returns a JSON representation of the actual value
     */
    toJSON() {
        return this.toHex();
    }
    /**
     * @description Encodes the value as a Uint8Array as per the parity-codec specifications
     * @param isBare true when the value has none of the type-specific prefixes (internal)
     * Period and phase are encoded:
     *   - The period of validity from the block hash found in the signing material.
     *   - The phase in the period that this transaction's lifetime begins (and, importantly,
     *     implies which block hash is included in the signature material). If the `period` is
     *     greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
     *     `period` is.
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    toU8a(isBare) {
        const period = this.period.toNumber();
        const phase = this.phase.toNumber();
        const quantizeFactor = Math.max(period >> 12, 1);
        const trailingZeros = this.getTrailingZeros(period);
        const encoded = Math.min(15, Math.max(1, trailingZeros - 1)) + (((phase / quantizeFactor) << 4));
        const first = encoded >> 8;
        const second = encoded & 0xff;
        return new Uint8Array([second, first]);
    }
    /**
     * @description Get the block number of the start of the era whose properties this object describes that `current` belongs to.
     */
    birth(current) {
        // FIXME No toNumber() here
        return Math.floor((Math.max(util_1.bnToBn(current).toNumber(), this.phase.toNumber()) - this.phase.toNumber()) / this.period.toNumber()) * this.period.toNumber() + this.phase.toNumber();
    }
    /**
     * @description Get the block number of the first block at which the era has ended.
     */
    death(current) {
        // FIXME No toNumber() here
        return this.birth(current) + this.period.toNumber();
    }
    /**
     * @description convert the number to binary and get the trailing zero's.
     */
    getTrailingZeros(period) {
        const binary = period.toString(2);
        let index = 0;
        while (binary[binary.length - 1 - index] === '0') {
            index++;
        }
        return index;
    }
}
exports.MortalEra = MortalEra;
/**
 * @name GenericExtrinsicEra
 * @description
 * The era for an extrinsic, indicating either a mortal or immortal extrinsic
 */
class ExtrinsicEra extends Enum_1.default {
    constructor(registry, value) {
        super(registry, {
            ImmortalEra,
            MortalEra
        }, ExtrinsicEra.decodeExtrinsicEra(value));
    }
    /** @internal */
    // eslint-disable-next-line @typescript-eslint/ban-types
    static decodeExtrinsicEra(value = new Uint8Array()) {
        if (!value) {
            return new Uint8Array([0]);
        }
        else if (value instanceof ExtrinsicEra) {
            return ExtrinsicEra.decodeExtrinsicEra(value.toU8a());
        }
        else if (util_1.isHex(value)) {
            return ExtrinsicEra.decodeExtrinsicEra(util_1.hexToU8a(value));
        }
        else if (util_1.isU8a(value)) {
            return (!value.length || value[0] === 0)
                ? new Uint8Array([0])
                : new Uint8Array([1, value[0], value[1]]);
        }
        else if (util_1.isObject(value)) {
            // this is to de-serialize from JSON
            if (value.MortalEra) {
                return { MortalEra: value.MortalEra };
            }
            else if (value.ImmortalEra) {
                return { ImmortalEra: value.ImmortalEra };
            }
            return { MortalEra: value };
        }
        throw new Error('Invalid data passed to Era');
    }
    /**
     * @description Override the encoded length method
     */
    get encodedLength() {
        if (this.index === 0) {
            return this.asImmortalEra.encodedLength;
        }
        else {
            return this.asMortalEra.encodedLength;
        }
    }
    /**
     * @description Returns the item as a [[ImmortalEra]]
     */
    get asImmortalEra() {
        util_1.assert(this.isImmortalEra, `Cannot convert '${this.type}' via asImmortalEra`);
        return this.value;
    }
    /**
     * @description Returns the item as a [[MortalEra]]
     */
    get asMortalEra() {
        util_1.assert(this.isMortalEra, `Cannot convert '${this.type}' via asMortalEra`);
        return this.value;
    }
    /**
     * @description `true` if Immortal
     */
    get isImmortalEra() {
        return this.index === 0;
    }
    /**
     * @description `true` if Mortal
     */
    get isMortalEra() {
        return this.index > 0;
    }
    /**
     * @description Encodes the value as a Uint8Array as per the parity-codec specifications
     * @param isBare true when the value has none of the type-specific prefixes (internal)
     */
    toU8a(isBare) {
        return this.isMortalEra
            ? this.asMortalEra.toU8a(isBare)
            : this.asImmortalEra.toU8a(isBare);
    }
}
exports.default = ExtrinsicEra;
