text: "use strict";
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
      return new (P || (P = Promise))(function (resolve, reject) {
          function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
          function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
          function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
          step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
  };
  var __importDefault = (this && this.__importDefault) || function (mod) {
      return (mod && mod.__esModule) ? mod : { "default": mod };
  };
  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 });
  const debug_1 = __importDefault(require("debug"));
  const debug = debug_1.default("decoder:wire");
  const CodecUtils = __importStar(require("truffle-codec-utils"));
  const truffle_codec_utils_1 = require("truffle-codec-utils");
  const async_eventemitter_1 = __importDefault(require("async-eventemitter"));
  const web3_1 = __importDefault(require("web3"));
  const truffle_codec_utils_2 = require("truffle-codec-utils");
  const Codec = __importStar(require("truffle-codec"));
  const Utils = __importStar(require("./utils"));
  class TruffleWireDecoder extends async_eventemitter_1.default {
      constructor(contracts, provider) {
          super();
          this.contracts = {};
          this.contractNodes = {};
          this.contexts = {};
          this.contextsById = {}; //deployed contexts only
          this.constructorContextsById = {};
          this.codeCache = {};
          this.web3 = new web3_1.default(provider);
          for (let contract of contracts) {
              let node = Utils.getContractNode(contract);
              if (node !== undefined) {
                  this.contracts[node.id] = contract;
                  this.contractNodes[node.id] = node;
                  if (contract.deployedBytecode && contract.deployedBytecode !== "0x") {
                      const context = Utils.makeContext(contract, node);
                      const hash = CodecUtils.Conversion.toHexString(CodecUtils.EVM.keccak256({ type: "string",
                          value: context.binary
                      }));
                      this.contexts[hash] = context;
                  }
                  if (contract.bytecode && contract.bytecode !== "0x") {
                      const constructorContext = Utils.makeContext(contract, node, true);
                      const hash = CodecUtils.Conversion.toHexString(CodecUtils.EVM.keccak256({ type: "string",
                          value: constructorContext.binary
                      }));
                      this.contexts[hash] = constructorContext;
                  }
              }
          }
          this.contexts = truffle_codec_utils_1.Contexts.normalizeContexts(this.contexts);
          this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter(({ isConstructor }) => !isConstructor).map(context => ({ [context.contractId]: context })));
          this.constructorContextsById = Object.assign({}, ...Object.values(this.contexts).filter(({ isConstructor }) => isConstructor).map(context => ({ [context.contractId]: context })));
          ({ definitions: this.referenceDeclarations, types: this.userDefinedTypes } = this.collectUserDefinedTypes());
          let allocationInfo = Object.entries(this.contracts).map(([id, { abi }]) => ({
              abi: truffle_codec_utils_2.AbiUtils.schemaAbiToAbi(abi),
              id: parseInt(id),
              constructorContext: this.constructorContextsById[parseInt(id)]
          }));
          debug("allocationInfo: %O", allocationInfo);
          this.allocations = {};
          this.allocations.storage = Codec.getStorageAllocations(this.referenceDeclarations, this.contractNodes);
          this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations);
          this.allocations.calldata = Codec.getCalldataAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi);
          this.allocations.event = Codec.getEventAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi);
          debug("done with allocation");
      }
      collectUserDefinedTypes() {
          let references = {};
          let types = {};
          for (const id in this.contracts) {
              const compiler = this.contracts[id].compiler;
              //first, add the contract itself
              const contractNode = this.contractNodes[id];
              references[id] = contractNode;
              types[id] = truffle_codec_utils_1.Types.definitionToStoredType(contractNode, compiler);
              //now, add its struct and enum definitions
              for (const node of contractNode.nodes) {
                  if (node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") {
                      references[node.id] = node;
                      //HACK even though we don't have all the references, we only need one:
                      //the reference to the contract itself, which we just added, so we're good
                      types[node.id] = truffle_codec_utils_1.Types.definitionToStoredType(node, compiler, references);
                  }
              }
          }
          return { definitions: references, types };
      }
      //for internal use
      getCode(address, block) {
          return __awaiter(this, void 0, void 0, function* () {
              //first, set up any preliminary layers as needed
              if (this.codeCache[block] === undefined) {
                  this.codeCache[block] = {};
              }
              //now, if we have it cached, just return it
              if (this.codeCache[block][address] !== undefined) {
                  return this.codeCache[block][address];
              }
              //otherwise, get it, cache it, and return it
              let code = CodecUtils.Conversion.toBytes(yield this.web3.eth.getCode(address, block));
              this.codeCache[block][address] = code;
              return code;
          });
      }
      //NOTE: additionalContexts parameter is for internal use only.
      decodeTransaction(transaction, additionalContexts = {}) {
          return __awaiter(this, void 0, void 0, function* () {
              debug("transaction: %O", transaction);
              const block = transaction.blockNumber;
              const context = yield this.getContextByAddress(transaction.to, block, transaction.input, additionalContexts);
              const data = CodecUtils.Conversion.toBytes(transaction.input);
              const info = {
                  state: {
                      storage: {},
                      calldata: data,
                  },
                  userDefinedTypes: this.userDefinedTypes,
                  allocations: this.allocations,
                  contexts: Object.assign({}, this.contextsById, additionalContexts),
                  currentContext: context
              };
              const decoder = Codec.decodeCalldata(info);
              let result = decoder.next();
              while (!result.done) {
                  let request = (result.value);
                  let response;
                  //only code requests should occur here
                  if (Codec.isCodeRequest(request)) {
                      response = yield this.getCode(request.address, block);
                  }
                  result = decoder.next(response);
              }
              //at this point, result.value holds the final value
              const decoding = result.value;
              return Object.assign({}, transaction, { decoding });
          });
      }
      //NOTE: options is meant for internal use; do not rely on it
      //NOTE: additionalContexts parameter is for internal use only.
      decodeLog(log, options = {}, additionalContexts = {}) {
          return __awaiter(this, void 0, void 0, function* () {
              const block = log.blockNumber;
              const data = CodecUtils.Conversion.toBytes(log.data);
              const topics = log.topics.map(CodecUtils.Conversion.toBytes);
              const info = {
                  state: {
                      storage: {},
                      eventdata: data,
                      eventtopics: topics
                  },
                  userDefinedTypes: this.userDefinedTypes,
                  allocations: this.allocations,
                  contexts: Object.assign({}, this.contextsById, additionalContexts)
              };
              const decoder = Codec.decodeEvent(info, log.address, options.name);
              let result = decoder.next();
              while (!result.done) {
                  let request = (result.value);
                  let response;
                  //only code requests should occur here
                  if (Codec.isCodeRequest(request)) {
                      response = yield this.getCode(request.address, block);
                  }
                  result = decoder.next(response);
              }
              //at this point, result.value holds the final value
              const decodings = result.value;
              return Object.assign({}, log, { decodings });
          });
      }
      //NOTE: options is meant for internal use; do not rely on it
      //NOTE: additionalContexts parameter is for internal use only.
      decodeLogs(logs, options = {}, additionalContexts = {}) {
          return __awaiter(this, void 0, void 0, function* () {
              return yield Promise.all(logs.map(log => this.decodeLog(log, options, additionalContexts)));
          });
      }
      //NOTE: additionalContexts parameter is for internal use only.
      events(options = {}, additionalContexts = {}) {
          return __awaiter(this, void 0, void 0, function* () {
              let { address, name, fromBlock, toBlock } = options;
              const logs = yield this.web3.eth.getPastLogs({
                  address,
                  fromBlock,
                  toBlock,
              });
              let events = yield this.decodeLogs(logs, options, additionalContexts);
              debug("events: %o", events);
              //if a target name was specified, we'll restrict to events that decoded
              //to something with that name.  (note that only decodings with that name
              //will have been returned from decodeLogs in the first place)
              if (name !== undefined) {
                  events = events.filter(event => event.decodings.length > 0);
              }
              return events;
          });
      }
      onEvent(name, callback) {
          //this.web3.eth.subscribe(name);
      }
      removeEventListener(name) {
      }
      //normally, this function gets the code of the given address at the given block,
      //and checks this against the known contexts to determine the contract type
      //however, if this fails and constructorBinary is passed in, it will then also
      //attempt to determine it from that
      getContextByAddress(address, block, constructorBinary, additionalContexts = {}) {
          return __awaiter(this, void 0, void 0, function* () {
              let code;
              if (address !== null) {
                  code = CodecUtils.Conversion.toHexString(yield this.getCode(address, block));
              }
              else if (constructorBinary) {
                  code = constructorBinary;
              }
              //if neither of these hold... we have a problem
              let contexts = Object.assign({}, this.contexts, additionalContexts);
              return truffle_codec_utils_1.Contexts.findDecoderContext(contexts, code);
          });
      }
      //the following functions are intended for internal use only
      getReferenceDeclarations() {
          return this.referenceDeclarations;
      }
      getUserDefinedTypes() {
          return this.userDefinedTypes;
      }
      getAbiAllocations() {
          return this.allocations.abi;
      }
      getWeb3() {
          return this.web3;
      }
      getContexts() {
          return { byHash: this.contexts, byId: this.contextsById, constructorsById: this.constructorContextsById };
      }
  }
  exports.default = TruffleWireDecoder;
  //# sourceMappingURL=wire.js.map