import { DEFAULT_ADDRESS_VERSION, DEFAULT_ACCOUNT_CONTRACT, DEFAULT_SCRYPT, } from "../consts";
import logger from "../logging";
import { hash160, HexString, reverseHex } from "../u";
import { isMultisigContract } from "../sc";
import * as core from "./core";
import { constructMultiSigVerificationScript } from "./multisig";
import { decrypt, encrypt } from "./nep2";
import { isAddress, isNEP2, isPrivateKey, isPublicKey, isScriptHash, isWIF, } from "./verify";
const log = logger("wallet");
/**
 * This allows for simple utilisation and manipulating of keys without need the long access methods.
 *
 * Key formats are derived from each other lazily and stored for future access.
 * If the previous key (one level higher) is not found, it will attempt to generate it or throw an Error if insufficient information was provided (eg. trying to generate private key when only address was given.)
 *
 * ```
 * NEP2 <=> WIF <=> Private => Public => ScriptHash <=> Address
 * ```
 *
 * @param str - WIF/ Private Key / Public Key / Address or a Wallet Account object.
 *
 * @example
 * const acct = new Account("L1QqQJnpBwbsPGAuutuzPTac8piqvbR1HRjrY5qHup48TBCBFe4g");
 * acct.address; // "ALq7AWrhAueN6mJNqk6FHJjnsEoPRytLdW"
 */
export class Account {
    // tslint:enables:variable-name
    constructor(str = "", config = { addressVersion: 0 }) {
        var _a, _b, _c, _d;
        this.addressVersion = DEFAULT_ADDRESS_VERSION;
        this.label = "";
        this.isDefault = false;
        this.lock = false;
        this.contract = Object.assign({}, DEFAULT_ACCOUNT_CONTRACT);
        if (config && config.addressVersion > 0) {
            this.addressVersion = config.addressVersion;
        }
        if (!str) {
            this._privateKey = core.generatePrivateKey();
        }
        else if (typeof str === "object") {
            this._encrypted = str.key;
            this._address = str.address;
            this.label = (_a = str.label) !== null && _a !== void 0 ? _a : "";
            this.isDefault = (_b = str.isDefault) !== null && _b !== void 0 ? _b : false;
            this.lock = (_c = str.lock) !== null && _c !== void 0 ? _c : false;
            this.contract =
                (_d = str.contract) !== null && _d !== void 0 ? _d : Object.assign({}, DEFAULT_ACCOUNT_CONTRACT);
        }
        else if (isPrivateKey(str)) {
            this._privateKey = str;
        }
        else if (isPublicKey(str, false)) {
            this._publicKey = core.getPublicKeyEncoded(str);
        }
        else if (isPublicKey(str, true)) {
            this._publicKey = str;
        }
        else if (isScriptHash(str)) {
            this._scriptHash = str;
        }
        else if (isAddress(str)) {
            this._address = str;
            const addressVersionFromAddress = core.getAddressVersion(str);
            if (config.addressVersion > 0 &&
                config.addressVersion !== addressVersionFromAddress) {
                throw new Error(`Uncompatible address versions! Address ${str} uses version ${addressVersionFromAddress} but config declares version ${config.addressVersion}`);
            }
        }
        else if (isWIF(str)) {
            this._privateKey = core.getPrivateKeyFromWIF(str);
            this._WIF = str;
        }
        else if (isNEP2(str)) {
            this._encrypted = str;
        }
        else {
            throw new ReferenceError(`Invalid input: ${str}`);
        }
        this._updateContractScript();
        // Attempts to make address the default label of the Account.
        if (!this.label) {
            try {
                this.label = this.address;
            }
            catch (err) {
                this.label = "";
            }
        }
    }
    /**
     * Create a multi-sig account from a list of public keys
     * @param signingThreshold - Minimum number of signatures required for verification. Must be larger than 0 and less than number of keys provided.
     * @param publicKeys - list of public keys to form the account. 2-16 keys allowed. Order is important.
     * @example
     * const threshold = 2;
     * const publicKeys = [
     * "02028a99826edc0c97d18e22b6932373d908d323aa7f92656a77ec26e8861699ef",
     * "031d8e1630ce640966967bc6d95223d21f44304133003140c3b52004dc981349c9",
     * "02232ce8d2e2063dce0451131851d47421bfc4fc1da4db116fca5302c0756462fa"
     * ];
     * const acct = Account.createMultiSig(threshold, publicKeys);
     */
    static createMultiSig(signingThreshold, publicKeys) {
        const verificationScript = constructMultiSigVerificationScript(signingThreshold, publicKeys);
        return new Account({
            contract: {
                script: HexString.fromHex(verificationScript).toBase64(),
                parameters: Array(signingThreshold).map((_, i) => ({
                    name: `signature${i}`,
                    type: "Signature",
                })),
                deployed: false,
            },
        });
    }
    get [Symbol.toStringTag]() {
        return "Account";
    }
    get isMultiSig() {
        var _a;
        return isMultisigContract(HexString.fromBase64((_a = this.contract) === null || _a === void 0 ? void 0 : _a.script));
    }
    /**
     * Key encrypted according to NEP2 standard.
     *
     * @example 6PYLHmDf6AjF4AsVtosmxHuPYeuyJL3SLuw7J1U8i7HxKAnYNsp61HYRfF
     */
    get encrypted() {
        if (this._encrypted) {
            return this._encrypted;
        }
        else {
            throw new Error("No encrypted key found");
        }
    }
    /**
     * Case sensitive key of 52 characters long.
     *
     * @example L1QqQJnpBwbsPGAuutuzPTac8piqvbR1HRjrY5qHup48TBCBFe4g
     */
    get WIF() {
        if (this._WIF) {
            return this._WIF;
        }
        else {
            this._WIF = core.getWIFFromPrivateKey(this.privateKey);
            return this._WIF;
        }
    }
    /**
     * Key of 64 hex characters.
     *
     * @example 7d128a6d096f0c14c3a25a2b0c41cf79661bfcb4a8cc95aaaea28bde4d732344
     */
    get privateKey() {
        if (this._privateKey) {
            return this._privateKey;
        }
        else if (this._WIF) {
            this._privateKey = core.getPrivateKeyFromWIF(this._WIF);
            return this._privateKey;
        }
        else if (this._encrypted) {
            throw new ReferenceError("Private Key encrypted!");
        }
        else {
            throw new ReferenceError("No Private Key provided!");
        }
    }
    /**
     * Returns the public key in encoded form. This is the form that is the short version (starts with 02 or 03).
     * If you require the unencoded form, do use the publicKey method instead of this getter.
     *
     * @remarks
     * There are 2 sources of data: The verification script or the private key.
     * We attempt to rely on the verification script first if possible as that does not require decrypting the private key.
     * If it fails, then we rely on the private key and only throw if the private key is not available for conversion.
     *
     * @example 02028a99826edc0c97d18e22b6932373d908d323aa7f92656a77ec26e8861699ef
     */
    get publicKey() {
        var _a;
        if (this._publicKey) {
            return this._publicKey;
        }
        if ((_a = this.contract) === null || _a === void 0 ? void 0 : _a.script) {
            // Disassemble and attempt to pull the public key
            try {
                const verificationScript = HexString.fromBase64(this.contract.script).toBigEndian();
                this._publicKey =
                    core.getPublicKeyFromVerificationScript(verificationScript);
                return this._publicKey;
            }
            catch (_b) {
                // Failed to get public key from contract. Account might be malformed.
            }
        }
        this._publicKey = core.getPublicKeyFromPrivateKey(this.privateKey);
        return this._publicKey;
    }
    /** Retrieves the Public Key in encoded / unencoded form.
     *
     * @param encoded - Encoded or unencoded.
     */
    getPublicKey(encoded = true) {
        return encoded
            ? this.publicKey
            : core.getPublicKeyUnencoded(this.publicKey);
    }
    /**
     * Script hash of the key. This format is usually used in the code instead of address as this is a hexstring.
     */
    get scriptHash() {
        if (this._scriptHash) {
            return this._scriptHash;
        }
        else {
            if (this._address) {
                this._scriptHash = core.getScriptHashFromAddress(this.address);
                return this._scriptHash;
            }
            else if (this.contract.script) {
                this._scriptHash = this._getScriptHashFromVerificationScript();
                return this._scriptHash;
            }
            else {
                this._scriptHash = core.getScriptHashFromPublicKey(this.publicKey);
                return this._scriptHash;
            }
        }
    }
    /**
     * Public address used to receive transactions. Case sensitive.
     * @example ALq7AWrhAueN6mJNqk6FHJjnsEoPRytLdW
     */
    get address() {
        if (this._address) {
            return this._address;
        }
        else {
            this._address = core.getAddressFromScriptHash(this.scriptHash, this.addressVersion);
            return this._address;
        }
    }
    /**
     * This is the safe way to get a key without it throwing an error.
     */
    tryGet(keyType) {
        var _a, _b, _c, _d, _e, _f;
        switch (keyType) {
            case "encrypted":
                return (_a = this._encrypted) !== null && _a !== void 0 ? _a : "";
            case "WIF":
                return (_b = this._WIF) !== null && _b !== void 0 ? _b : "";
            case "privateKey":
                return (_c = this._privateKey) !== null && _c !== void 0 ? _c : "";
            case "publicKey":
                return (_d = this._publicKey) !== null && _d !== void 0 ? _d : "";
            case "scriptHash":
                return (_e = this._scriptHash) !== null && _e !== void 0 ? _e : "";
            case "address":
                return (_f = this._address) !== null && _f !== void 0 ? _f : "";
        }
    }
    /**
     * Encrypts the current privateKey and return the Account object.
     */
    async encrypt(keyphrase, scryptParams = DEFAULT_SCRYPT) {
        this._encrypted = await encrypt(this.privateKey, keyphrase, scryptParams);
        return this;
    }
    /**
     * Decrypts the encrypted key and return the Account object.
     */
    async decrypt(keyphrase, scryptParams = DEFAULT_SCRYPT) {
        this._WIF = await decrypt(this.encrypted, keyphrase, scryptParams);
        this._updateContractScript();
        return this;
    }
    /**
     * Export Account as a WalletAccount object.
     */
    export() {
        var _a;
        if (this._privateKey && !this._encrypted) {
            throw new Error("Encrypt private key first!");
        }
        const key = (_a = this._encrypted) !== null && _a !== void 0 ? _a : "";
        return {
            address: this.address,
            label: this.label,
            isDefault: this.isDefault,
            lock: this.lock,
            key,
            contract: this.contract,
        };
    }
    equals(other) {
        return this.address === other.address;
    }
    /**
     * Attempts to update the contract.script field if public key is available.
     */
    _updateContractScript() {
        try {
            if (this.contract.script === "") {
                const publicKey = this.publicKey;
                this.contract.script = HexString.fromHex(core.getVerificationScriptFromPublicKey(publicKey)).toBase64();
                this._scriptHash = this._getScriptHashFromVerificationScript();
                log.debug(`Updated ContractScript for Account: ${this.address}`);
            }
        }
        catch (e) {
            return;
        }
    }
    _getScriptHashFromVerificationScript() {
        const hexScript = HexString.fromBase64(this.contract.script).toBigEndian();
        return reverseHex(hash160(hexScript));
    }
}
export default Account;
//# sourceMappingURL=Account.js.map