'use strict';
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
var address_1 = require("./address");
var abi_coder_1 = require("./abi-coder");
var bignumber_1 = require("./bignumber");
var bytes_1 = require("./bytes");
var hash_1 = require("./hash");
var keccak256_1 = require("./keccak256");
var properties_1 = require("./properties");
var errors = __importStar(require("../errors"));
///////////////////////////////
var _Indexed = /** @class */ (function () {
    function _Indexed(hash) {
        properties_1.setType(this, 'Indexed');
        properties_1.defineReadOnly(this, 'hash', hash);
    }
    return _Indexed;
}());
var Description = /** @class */ (function () {
    function Description(info) {
        properties_1.setType(this, 'Description');
        for (var key in info) {
            properties_1.defineReadOnly(this, key, properties_1.deepCopy(info[key], true));
        }
        Object.freeze(this);
    }
    return Description;
}());
var _DeployDescription = /** @class */ (function (_super) {
    __extends(_DeployDescription, _super);
    function _DeployDescription() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    _DeployDescription.prototype.encode = function (bytecode, params) {
        if (!bytes_1.isHexString(bytecode)) {
            errors.throwError('invalid contract bytecode', errors.INVALID_ARGUMENT, {
                arg: 'bytecode',
                value: bytecode
            });
        }
        errors.checkArgumentCount(params.length, this.inputs.length, ' in Interface constructor');
        try {
            return (bytecode + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2));
        }
        catch (error) {
            errors.throwError('invalid constructor argument', errors.INVALID_ARGUMENT, {
                arg: error.arg,
                reason: error.reason,
                value: error.value
            });
        }
        return null;
    };
    return _DeployDescription;
}(Description));
var _FunctionDescription = /** @class */ (function (_super) {
    __extends(_FunctionDescription, _super);
    function _FunctionDescription() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    _FunctionDescription.prototype.encode = function (params) {
        errors.checkArgumentCount(params.length, this.inputs.length, ' in interface function ' + this.name);
        try {
            return this.sighash + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2);
        }
        catch (error) {
            errors.throwError('invalid input argument', errors.INVALID_ARGUMENT, {
                arg: error.arg,
                reason: error.reason,
                value: error.value
            });
        }
        return null;
    };
    _FunctionDescription.prototype.decode = function (data) {
        try {
            return abi_coder_1.defaultAbiCoder.decode(this.outputs, bytes_1.arrayify(data));
        }
        catch (error) {
            errors.throwError('invalid data for function output', errors.INVALID_ARGUMENT, {
                arg: 'data',
                errorArg: error.arg,
                errorValue: error.value,
                value: data,
                reason: error.reason
            });
        }
    };
    return _FunctionDescription;
}(Description));
var Result = /** @class */ (function (_super) {
    __extends(Result, _super);
    function Result() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return Result;
}(Description));
var _EventDescription = /** @class */ (function (_super) {
    __extends(_EventDescription, _super);
    function _EventDescription() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    _EventDescription.prototype.encodeTopics = function (params) {
        var _this = this;
        if (params.length > this.inputs.length) {
            errors.throwError('too many arguments for ' + this.name, errors.UNEXPECTED_ARGUMENT, { maxCount: params.length, expectedCount: this.inputs.length });
        }
        var topics = [];
        if (!this.anonymous) {
            topics.push(this.topic);
        }
        params.forEach(function (arg, index) {
            var param = _this.inputs[index];
            if (!param.indexed) {
                if (arg != null) {
                    errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
                }
                return;
            }
            if (arg == null) {
                topics.push(null);
            }
            else if (param.type === 'string') {
                topics.push(hash_1.id(arg));
            }
            else if (param.type === 'bytes') {
                topics.push(keccak256_1.keccak256(arg));
            }
            else if (param.type.indexOf('[') !== -1 || param.type.substring(0, 5) === 'tuple') {
                errors.throwError('filtering with tuples or arrays not implemented yet; bug us on GitHub', errors.NOT_IMPLEMENTED, { operation: 'filter(array|tuple)' });
            }
            else {
                if (param.type === 'address') {
                    address_1.getAddress(arg);
                }
                topics.push(bytes_1.hexZeroPad(bytes_1.hexlify(arg), 32).toLowerCase());
            }
        });
        // Trim off trailing nulls
        while (topics.length && topics[topics.length - 1] === null) {
            topics.pop();
        }
        return topics;
    };
    _EventDescription.prototype.decode = function (data, topics) {
        // Strip the signature off of non-anonymous topics
        if (topics != null && !this.anonymous) {
            topics = topics.slice(1);
        }
        var inputIndexed = [];
        var inputNonIndexed = [];
        var inputDynamic = [];
        this.inputs.forEach(function (param, index) {
            if (param.indexed) {
                if (param.type === 'string' || param.type === 'bytes' || param.type.indexOf('[') >= 0 || param.type.substring(0, 5) === 'tuple') {
                    inputIndexed.push({ type: 'bytes32', name: (param.name || '') });
                    inputDynamic.push(true);
                }
                else {
                    inputIndexed.push(param);
                    inputDynamic.push(false);
                }
            }
            else {
                inputNonIndexed.push(param);
                inputDynamic.push(false);
            }
        });
        if (topics != null) {
            var resultIndexed = abi_coder_1.defaultAbiCoder.decode(inputIndexed, bytes_1.concat(topics));
        }
        var resultNonIndexed = abi_coder_1.defaultAbiCoder.decode(inputNonIndexed, bytes_1.arrayify(data));
        var result = {};
        var nonIndexedIndex = 0, indexedIndex = 0;
        this.inputs.forEach(function (input, index) {
            if (input.indexed) {
                if (topics == null) {
                    result[index] = new _Indexed(null);
                }
                else if (inputDynamic[index]) {
                    result[index] = new _Indexed(resultIndexed[indexedIndex++]);
                }
                else {
                    result[index] = resultIndexed[indexedIndex++];
                }
            }
            else {
                result[index] = resultNonIndexed[nonIndexedIndex++];
            }
            if (input.name) {
                result[input.name] = result[index];
            }
        });
        result.length = this.inputs.length;
        return new Result(result);
    };
    return _EventDescription;
}(Description));
var _TransactionDescription = /** @class */ (function (_super) {
    __extends(_TransactionDescription, _super);
    function _TransactionDescription() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return _TransactionDescription;
}(Description));
var _LogDescription = /** @class */ (function (_super) {
    __extends(_LogDescription, _super);
    function _LogDescription() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return _LogDescription;
}(Description));
function addMethod(method) {
    switch (method.type) {
        case 'constructor': {
            var description = new _DeployDescription({
                inputs: method.inputs,
                payable: (method.payable == null || !!method.payable)
            });
            if (!this.deployFunction) {
                this.deployFunction = description;
            }
            break;
        }
        case 'function': {
            var signature = abi_coder_1.formatSignature(method).replace(/tuple/g, '');
            var sighash = hash_1.id(signature).substring(0, 10);
            var isConst = false;
            if (method.constant != null) {
                isConst = method.constant;
            }
            else if (method.stateMutability != null) {
                isConst = (method.stateMutability == "view" || method.stateMutability == "pure");
            }
            var description = new _FunctionDescription({
                inputs: method.inputs,
                outputs: method.outputs,
                gas: method.gas,
                payable: (method.payable == null || !!method.payable),
                type: (isConst ? 'call' : 'transaction'),
                name: method.name,
                signature: signature,
                sighash: sighash,
            });
            // Expose the first (and hopefully unique named function)
            if (method.name) {
                if (this.functions[method.name] == null) {
                    properties_1.defineReadOnly(this.functions, method.name, description);
                }
                else {
                    errors.warn('WARNING: Multiple definitions for ' + method.name);
                }
            }
            // Expose all methods by their signature, for overloaded functions
            if (this.functions[description.signature] == null) {
                properties_1.defineReadOnly(this.functions, description.signature, description);
            }
            break;
        }
        case 'event': {
            var signature = abi_coder_1.formatSignature(method).replace(/tuple/g, '');
            var description = new _EventDescription({
                name: method.name,
                signature: signature,
                inputs: method.inputs,
                topic: hash_1.id(signature),
                anonymous: (!!method.anonymous)
            });
            // Expose the first (and hopefully unique) event name
            if (method.name && this.events[method.name] == null) {
                properties_1.defineReadOnly(this.events, method.name, description);
            }
            // Expose all events by their signature, for overloaded functions
            if (this.events[description.signature] == null) {
                properties_1.defineReadOnly(this.events, description.signature, description);
            }
            break;
        }
        case "receive":
        case 'fallback':
            // Nothing to do for fallback
            break;
        default:
            errors.warn('WARNING: unsupported ABI type - ' + method.type);
            break;
    }
}
var Interface = /** @class */ (function () {
    // ParamType is not actually correct here, but for legacy reasons,
    // we need it. See #721.
    function Interface(abi) {
        errors.checkNew(this, Interface);
        if (typeof (abi) === 'string') {
            try {
                abi = JSON.parse(abi);
            }
            catch (error) {
                errors.throwError('could not parse ABI JSON', errors.INVALID_ARGUMENT, {
                    arg: 'abi',
                    errorMessage: error.message,
                    value: abi
                });
            }
            if (!Array.isArray(abi)) {
                errors.throwError('invalid abi', errors.INVALID_ARGUMENT, { arg: 'abi', value: abi });
                return null;
            }
        }
        properties_1.defineReadOnly(this, 'functions', {});
        properties_1.defineReadOnly(this, 'events', {});
        // Convert any supported ABI format into a standard ABI format
        var _abi = [];
        abi.forEach(function (fragment) {
            if (typeof (fragment) === 'string') {
                fragment = abi_coder_1.parseSignature(fragment);
            }
            // @TODO: We should probable do some validation; create abiCoder.formatSignature for checking
            _abi.push(fragment);
        });
        properties_1.defineReadOnly(this, 'abi', properties_1.deepCopy(_abi, true));
        _abi.forEach(addMethod, this);
        // If there wasn't a constructor, create the default constructor
        if (!this.deployFunction) {
            addMethod.call(this, { type: 'constructor', inputs: [] });
        }
        properties_1.setType(this, 'Interface');
    }
    Interface.prototype.parseTransaction = function (tx) {
        var sighash = tx.data.substring(0, 10).toLowerCase();
        for (var name in this.functions) {
            if (name.indexOf('(') === -1) {
                continue;
            }
            var func = this.functions[name];
            if (func.sighash === sighash) {
                var result = abi_coder_1.defaultAbiCoder.decode(func.inputs, '0x' + tx.data.substring(10));
                return new _TransactionDescription({
                    args: result,
                    decode: func.decode,
                    name: func.name,
                    signature: func.signature,
                    sighash: func.sighash,
                    value: bignumber_1.bigNumberify(tx.value || '0'),
                });
            }
        }
        return null;
    };
    Interface.prototype.parseLog = function (log) {
        for (var name in this.events) {
            if (name.indexOf('(') === -1) {
                continue;
            }
            var event = this.events[name];
            if (event.anonymous) {
                continue;
            }
            if (event.topic !== log.topics[0]) {
                continue;
            }
            // @TODO: If anonymous, and the only method, and the input count matches, should we parse and return it?
            return new _LogDescription({
                decode: event.decode,
                name: event.name,
                signature: event.signature,
                topic: event.topic,
                values: event.decode(log.data, log.topics)
            });
        }
        return null;
    };
    Interface.isInterface = function (value) {
        return properties_1.isType(value, 'Interface');
    };
    Interface.isIndexed = function (value) {
        return properties_1.isType(value, 'Indexed');
    };
    return Interface;
}());
exports.Interface = Interface;
