text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  const ethereumjs_util_1 = require("ethereumjs-util");
  const library_utils_1 = require("./library-utils");
  const message_trace_1 = require("./message-trace");
  /**
   * This class represent a somewhat special Trie of bytecodes.
   *
   * What makes it special is that every node has a set of all of its descendants and its depth.
   */
  class BytecodeTrie {
      constructor(depth) {
          this.depth = depth;
          this.childNodes = new Map();
          this.descendants = new Set();
      }
      static isBytecodeTrie(o) {
          if (o === undefined || o === null) {
              return false;
          }
          return "childNodes" in o;
      }
      add(bytecode) {
          // tslint:disable-next-line no-this-assignment
          let trieNode = this;
          for (let currentCodeByte = 0; currentCodeByte <= bytecode.normalizedCode.length; currentCodeByte += 1) {
              if (currentCodeByte === bytecode.normalizedCode.length) {
                  // If multiple contracts with the exact same bytecode are added we keep the last of them,
                  // which is probably correct, especially if we are going to support multiple compilations
                  trieNode.match = bytecode;
                  return;
              }
              const byte = bytecode.normalizedCode[currentCodeByte];
              trieNode.descendants.add(bytecode);
              let childNode = trieNode.childNodes.get(byte);
              if (childNode === undefined) {
                  childNode = new BytecodeTrie(currentCodeByte);
                  trieNode.childNodes.set(byte, childNode);
              }
              trieNode = childNode;
          }
      }
      /**
       * Searches for a bytecode. If it's an exact match, it is returned. If there's no match, but a
       * prefix of the code is found in the trie, the node of the longest prefix is returned. If the
       * entire code is covered by the trie, and there's no match, we return undefined.
       */
      search(code, currentCodeByte = 0) {
          if (currentCodeByte > code.length) {
              return undefined;
          }
          // tslint:disable-next-line no-this-assignment
          let trieNode = this;
          for (; currentCodeByte <= code.length; currentCodeByte += 1) {
              if (currentCodeByte === code.length) {
                  return trieNode.match;
              }
              const childNode = trieNode.childNodes.get(code[currentCodeByte]);
              if (childNode === undefined) {
                  return trieNode;
              }
              trieNode = childNode;
          }
      }
  }
  class ContractsIdentifier {
      constructor(_enableCache = true) {
          this._enableCache = _enableCache;
          this._trie = new BytecodeTrie(-1);
          this._cache = new Map();
      }
      addBytecode(bytecode) {
          this._trie.add(bytecode);
          this._cache.clear();
      }
      getBytecodeFromMessageTrace(trace) {
          const normalizedCode = library_utils_1.normalizeLibraryRuntimeBytecodeIfNecessary(trace.code);
          let normalizedCodeHex;
          if (this._enableCache) {
              normalizedCodeHex = ethereumjs_util_1.bufferToHex(normalizedCode);
              const cached = this._cache.get(normalizedCodeHex);
              if (cached !== undefined) {
                  return cached;
              }
          }
          const result = this._searchBytecode(trace, normalizedCode);
          if (this._enableCache) {
              if (result !== undefined) {
                  this._cache.set(normalizedCodeHex, result);
              }
          }
          return result;
      }
      _searchBytecode(trace, code, normalizeLibraries = true, trie = this._trie, firstByteToSearch = 0) {
          const searchResult = trie.search(code, firstByteToSearch);
          if (searchResult === undefined) {
              return undefined;
          }
          if (!BytecodeTrie.isBytecodeTrie(searchResult)) {
              return searchResult;
          }
          // Create traces are followed by metadata that we don't index
          if (message_trace_1.isCreateTrace(trace) &&
              searchResult.match !== undefined &&
              searchResult.match.isDeployment) {
              return searchResult.match;
          }
          if (normalizeLibraries) {
              for (const bytecodeWithLibraries of searchResult.descendants) {
                  if (bytecodeWithLibraries.libraryAddressPositions.length === 0 &&
                      bytecodeWithLibraries.immutableReferences.length === 0) {
                      continue;
                  }
                  const normalizedLibrariesCode = library_utils_1.zeroOutAddresses(code, bytecodeWithLibraries.libraryAddressPositions);
                  const normalizedCode = library_utils_1.zeroOutSlices(normalizedLibrariesCode, bytecodeWithLibraries.immutableReferences);
                  const normalizedResult = this._searchBytecode(trace, normalizedCode, false, searchResult, searchResult.depth + 1);
                  if (normalizedResult !== undefined) {
                      return normalizedResult;
                  }
              }
          }
          return undefined;
      }
  }
  exports.ContractsIdentifier = ContractsIdentifier;
  //# sourceMappingURL=contracts-identifier.js.map