"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 Null_1 = __importDefault(require("../primitive/Null"));
const utils_1 = require("./utils");
const Base_1 = __importDefault(require("./Base"));
/**
 * @name Option
 * @description
 * An Option is an optional field. Basically the first byte indicates that there is
 * is value to follow. If the byte is `1` there is an actual value. So the Option
 * implements that - decodes, checks for optionality and wraps the required structure
 * with a value if/as required/found.
 */
class Option extends Base_1.default {
    constructor(registry, Type, value) {
        const Clazz = utils_1.typeToConstructor(registry, Type);
        super(registry, Option.decodeOption(registry, Clazz, value));
        this._Type = Clazz;
    }
    /** @internal */
    static decodeOption(registry, Type, value) {
        if (util_1.isNull(value) || util_1.isUndefined(value) || value instanceof Null_1.default) {
            return new Null_1.default(registry);
        }
        else if (value instanceof Option) {
            return Option.decodeOption(registry, Type, value.value);
        }
        else if (value instanceof Type) {
            // don't re-create, use as it (which also caters for derived types)
            return value;
        }
        else if (util_1.isU8a(value)) {
            // the isU8a check happens last in the if-tree - since the wrapped value
            // may be an instance of it, so Type and Option checks go in first
            return Option.decodeOptionU8a(registry, Type, value);
        }
        return new Type(registry, value);
    }
    /** @internal */
    static decodeOptionU8a(registry, Type, value) {
        return !value.length || value[0] === 0
            ? new Null_1.default(registry)
            : new Type(registry, value.subarray(1));
    }
    static with(Type) {
        return class extends Option {
            constructor(registry, value) {
                super(registry, Type, value);
            }
        };
    }
    /**
     * @description The length of the value when encoded as a Uint8Array
     */
    get encodedLength() {
        // boolean byte (has value, doesn't have) along with wrapped length
        return 1 + this.raw.encodedLength;
    }
    /**
     * @description Checks if the Option has no value
     */
    get isEmpty() {
        return this.isNone;
    }
    /**
     * @description Checks if the Option has no value
     */
    get isNone() {
        return this.raw instanceof Null_1.default;
    }
    /**
     * @description Checks if the Option has a value
     */
    get isSome() {
        return !this.isNone;
    }
    /**
     * @description The actual value for the Option
     */
    get value() {
        return this.raw;
    }
    /**
     * @description Compares the value of the input to see if there is a match
     */
    eq(other) {
        if (other instanceof Option) {
            return (this.isSome === other.isSome) && this.value.eq(other.value);
        }
        return this.value.eq(other);
    }
    /**
     * @description Returns a hex string representation of the value
     */
    toHex() {
        // This attempts to align with the JSON encoding - actually in this case
        // the isSome value is correct, however the `isNone` may be problematic
        return this.isNone
            ? '0x'
            : util_1.u8aToHex(this.toU8a().subarray(1));
    }
    /**
     * @description Returns the base runtime type name for this instance
     */
    toRawType(isBare) {
        const wrapped = new this._Type(this.registry).toRawType();
        return isBare
            ? wrapped
            : `Option<${wrapped}>`;
    }
    /**
     * @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)
     */
    toU8a(isBare) {
        if (isBare) {
            return this.raw.toU8a(true);
        }
        const u8a = new Uint8Array(this.encodedLength);
        if (this.isSome) {
            u8a.set([1]);
            u8a.set(this.raw.toU8a(), 1);
        }
        return u8a;
    }
    /**
     * @description Returns the value that the Option represents (if available), throws if null
     */
    unwrap() {
        if (this.isNone) {
            throw new Error('Option: unwrapping a None value');
        }
        return this.raw;
    }
    /**
     * @description Returns the value that the Option represents (if available) or defaultValue if none
     * @param defaultValue The value to return if the option isNone
     */
    unwrapOr(defaultValue) {
        return this.isSome
            ? this.unwrap()
            : defaultValue;
    }
}
exports.default = Option;
