"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeNativize = exports.safeNativizeVariables = void 0;
const bn_js_1 = require("bn.js");
const big_js_1 = require("big.js");
function safeNativizeVariables(variables) {
    return Object.assign({}, ...Object.entries(variables).map(([name, value]) => {
        try {
            return { [name]: safeNativize(value) };
        }
        catch (_) {
            return undefined; //I guess??
        }
    }));
}
exports.safeNativizeVariables = safeNativizeVariables;
//HACK! Avoid using!
/**
 * WARNING! Do NOT use this function in real code unless you absolutely have
 * to!  Using it in controlled tests is fine, but do NOT use it in real code if
 * you have any better option!
 *
 * This function is a giant hack.  It will throw exceptions on numbers that
 * don't fit in a Javascript number.  It loses various information.  It was
 * only ever written to support our hacked-together watch expression system,
 * and later repurposed to make testing easier.
 *
 * If you are not doing something as horrible as evaluating user-inputted
 * Javascript expressions meant to operate upon Solidity variables, then you
 * probably have a better option than using this in real code!
 *
 * (For instance, if you just want to nicely print individual values, without
 * attempting to first operate on them via Javascript expressions, we have the
 * [[ResultInspector]] class, which can be used with Node's
 * [util.inspect()](https://nodejs.org/api/util.html#util_util_inspect_object_options)
 * to do exactly that.)
 *
 * Remember, the decoder output format was made to be machine-readable.  It
 * shouldn't be too hard for you to process.  If it comes to it, copy-paste
 * this code and dehackify it for your use case, which hopefully is more
 * manageable than the one that caused us to write this.
 */
function safeNativize(result) {
    return safeNativizeWithTable(result, []);
}
exports.safeNativize = safeNativize;
function safeNativizeWithTable(result, seenSoFar) {
    if (result.kind === "error") {
        switch (result.error.kind) {
            case "BoolOutOfRangeError":
                return true;
            default:
                return undefined;
        }
    }
    //NOTE: for simplicity, only arrays & structs will call unsafeNativizeWithTable;
    //other containers will just call unsafeNativize because they can get away with it
    //(only things that can *be* circular need unsafeNativizeWithTable, not things that
    //can merely *contain* circularities)
    switch (result.type.typeClass) {
        case "uint":
        case "int":
            const bnVal = (result).value.asBN;
            // change bnVal's prototype to BN
            //@ts-ignore
            bnVal.__proto__ = bn_js_1.BN.prototype;
            try {
                return bnVal.toNumber(); //WARNING
            }
            catch (_) {
                return bnVal.toString();
            }
        case "bool":
            return result.value.asBoolean;
        case "bytes":
            return result.value.asHex;
        case "address":
            return result.value.asAddress;
        case "string": {
            let coercedResult = result;
            switch (coercedResult.value.kind) {
                case "valid":
                    return coercedResult.value.asString;
                case "malformed":
                    // this will turn malformed utf-8 into replacement characters (U+FFFD) (WARNING)
                    // note we need to cut off the 0x prefix
                    return Buffer.from(coercedResult.value.asHex.slice(2), "hex").toString();
            }
        }
        case "fixed":
        case "ufixed":
            //HACK: Big doesn't have a toNumber() method, so we convert to string and then parse with Number
            //NOTE: we don't bother setting the magic variables Big.NE or Big.PE first, as the choice of
            //notation shouldn't affect the result (can you believe I have to write this? @_@)
            const val = (result).value.asBig;
            // change val's prototype to Big
            //@ts-ignore
            val.__proto__ = big_js_1.Big.prototype;
            return Number(val.toString()); //WARNING
        case "array": {
            let coercedResult = result;
            if (coercedResult.reference === undefined) {
                //we need to do some pointer stuff here, so let's first create our new
                //object we'll be pointing to
                //[we don't want to alter the original accidentally so let's clone a bit]
                let output = [...coercedResult.value];
                //now, we can't use a map here, or we'll screw things up!
                //we want to *mutate* output, not replace it with a new object
                for (let index = 0; index < output.length; index++) {
                    output[index] = safeNativizeWithTable(output[index], [
                        output,
                        ...seenSoFar
                    ]);
                }
                return output;
            }
            else {
                return seenSoFar[coercedResult.reference - 1];
            }
        }
        case "userDefinedValueType": {
            return safeNativize(result.value);
        }
        case "mapping":
            return Object.assign({}, ...result.value.map(({ key, value }) => ({
                [safeNativize(key).toString()]: safeNativize(value)
            })));
        case "struct": {
            let coercedResult = result;
            if (coercedResult.reference === undefined) {
                //we need to do some pointer stuff here, so let's first create our new
                //object we'll be pointing to
                let output = Object.assign({}, ...result.value.map(({ name, value }) => ({
                    [name]: value //we *don't* nativize yet!
                })));
                //now, we can't use a map here, or we'll screw things up!
                //we want to *mutate* output, not replace it with a new object
                for (let name in output) {
                    output[name] = safeNativizeWithTable(output[name], [
                        output,
                        ...seenSoFar
                    ]);
                }
                return output;
            }
            else {
                return seenSoFar[coercedResult.reference - 1];
            }
        }
        case "type":
            switch (result.type.type.typeClass) {
                case "contract":
                    return Object.assign({}, ...result.value.map(({ name, value }) => ({
                        [name]: safeNativize(value)
                    })));
                case "enum":
                    return Object.assign({}, ...result.value.map(enumValue => ({
                        [enumValue.value.name]: safeNativize(enumValue)
                    })));
            }
        case "tuple":
            return result.value.map(({ value }) => safeNativize(value));
        case "magic":
            return Object.assign({}, ...Object.entries(result.value).map(([key, value]) => ({ [key]: safeNativize(value) })));
        case "enum":
            return enumFullName(result);
        case "contract":
            return result.value.address; //we no longer include additional info
        case "function":
            switch (result.type.visibility) {
                case "external": {
                    let coercedResult = result;
                    switch (coercedResult.value.kind) {
                        case "known":
                            return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).${coercedResult.value.abi.name}`;
                        case "invalid":
                            return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).call(${coercedResult.value.selector}...)`;
                        case "unknown":
                            return `${coercedResult.value.contract.address}.call(${coercedResult.value.selector}...)`;
                    }
                }
                case "internal": {
                    let coercedResult = result;
                    switch (coercedResult.value.kind) {
                        case "function":
                            if (coercedResult.value.definedIn) {
                                return `${coercedResult.value.definedIn.typeName}.${coercedResult.value.name}`;
                            }
                            else {
                                return coercedResult.value.name;
                            }
                        case "exception":
                            return coercedResult.value.deployedProgramCounter === 0
                                ? `<zero>`
                                : `<uninitialized>`;
                        case "unknown":
                            return `<decoding not supported>`;
                    }
                }
            }
    }
}
function enumFullName(value) {
    switch (value.type.kind) {
        case "local":
            return `${value.type.definingContractName}.${value.type.typeName}.${value.value.name}`;
        case "global":
            return `${value.type.typeName}.${value.value.name}`;
    }
}
//# sourceMappingURL=helper.js.map