contract IntegrationRegistry is Ownable {
  /* ============ Events ============ */

  event IntegrationAdded(
    address indexed _module,
    address indexed _adapter,
    string _integrationName
  );
  event IntegrationRemoved(
    address indexed _module,
    address indexed _adapter,
    string _integrationName
  );
  event IntegrationEdited(address indexed _module, address _newAdapter, string _integrationName);

  /* ============ State Variables ============ */

  // Address of the Controller contract
  IController public controller;

  // Mapping of module => integration identifier => adapter address
  mapping(address => mapping(bytes32 => address)) private integrations;

  /* ============ Constructor ============ */

  /**
   * Initializes the controller
   *
   * @param _controller          Instance of the controller
   */
  constructor(IController _controller) {
    controller = _controller;
  }

  /* ============ External Functions ============ */

  /**
   * GOVERNANCE FUNCTION: Add a new integration to the registry
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name         Human readable string identifying the integration
   * @param  _adapter      Address of the adapter contract to add
   */
  function addIntegration(
    address _module,
    string memory _name,
    address _adapter
  ) public onlyOwner {
    bytes32 hashedName = _nameHash(_name);
    require(controller.isModule(_module), "Must be valid module.");
    require(integrations[_module][hashedName] == address(0), "Integration exists already.");
    require(_adapter != address(0), "Adapter address must exist.");

    integrations[_module][hashedName] = _adapter;

    emit IntegrationAdded(_module, _adapter, _name);
  }

  /**
   * GOVERNANCE FUNCTION: Batch add new adapters. Reverts if exists on any module and name
   *
   * @param  _modules      Array of addresses of the modules associated with integration
   * @param  _names        Array of human readable strings identifying the integration
   * @param  _adapters     Array of addresses of the adapter contracts to add
   */
  function batchAddIntegration(
    address[] memory _modules,
    string[] memory _names,
    address[] memory _adapters
  ) external onlyOwner {
    // Storing modules count to local variable to save on invocation
    uint256 modulesCount = _modules.length;

    require(modulesCount > 0, "Modules must not be empty");
    require(modulesCount == _names.length, "Module and name lengths mismatch");
    require(modulesCount == _adapters.length, "Module and adapter lengths mismatch");

    for (uint256 i = 0; i < modulesCount; i++) {
      // Add integrations to the specified module. Will revert if module and name combination exists
      addIntegration(_modules[i], _names[i], _adapters[i]);
    }
  }

  /**
   * GOVERNANCE FUNCTION: Edit an existing integration on the registry
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name         Human readable string identifying the integration
   * @param  _adapter      Address of the adapter contract to edit
   */
  function editIntegration(
    address _module,
    string memory _name,
    address _adapter
  ) public onlyOwner {
    bytes32 hashedName = _nameHash(_name);

    require(controller.isModule(_module), "Must be valid module.");
    require(integrations[_module][hashedName] != address(0), "Integration does not exist.");
    require(_adapter != address(0), "Adapter address must exist.");

    integrations[_module][hashedName] = _adapter;

    emit IntegrationEdited(_module, _adapter, _name);
  }

  /**
   * GOVERNANCE FUNCTION: Batch edit adapters for modules. Reverts if module and
   * adapter name don't map to an adapter address
   *
   * @param  _modules      Array of addresses of the modules associated with integration
   * @param  _names        Array of human readable strings identifying the integration
   * @param  _adapters     Array of addresses of the adapter contracts to add
   */
  function batchEditIntegration(
    address[] memory _modules,
    string[] memory _names,
    address[] memory _adapters
  ) external onlyOwner {
    // Storing name count to local variable to save on invocation
    uint256 modulesCount = _modules.length;

    require(modulesCount > 0, "Modules must not be empty");
    require(modulesCount == _names.length, "Module and name lengths mismatch");
    require(modulesCount == _adapters.length, "Module and adapter lengths mismatch");

    for (uint256 i = 0; i < modulesCount; i++) {
      // Edits integrations to the specified module. Will revert if module and name combination does not exist
      editIntegration(_modules[i], _names[i], _adapters[i]);
    }
  }

  /**
   * GOVERNANCE FUNCTION: Remove an existing integration on the registry
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name         Human readable string identifying the integration
   */
  function removeIntegration(address _module, string memory _name) external onlyOwner {
    bytes32 hashedName = _nameHash(_name);
    require(integrations[_module][hashedName] != address(0), "Integration does not exist.");

    address oldAdapter = integrations[_module][hashedName];
    delete integrations[_module][hashedName];

    emit IntegrationRemoved(_module, oldAdapter, _name);
  }

  /* ============ External Getter Functions ============ */

  /**
   * Get integration adapter address associated with passed human readable name
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name         Human readable adapter name
   *
   * @return               Address of adapter
   */
  function getIntegrationAdapter(address _module, string memory _name)
    external
    view
    returns (address)
  {
    return integrations[_module][_nameHash(_name)];
  }

  /**
   * Get integration adapter address associated with passed hashed name
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name_hash     Hash of human readable adapter name
   *
   * @return               Address of adapter
   */
  function getIntegrationAdapterWithHash(address _module, bytes32 _name_hash)
    external
    view
    returns (address)
  {
    return integrations[_module][_name_hash];
  }

  /**
   * Check if adapter name is valid
   *
   * @param  _module       The address of the module associated with the integration
   * @param  _name         Human readable string identifying the integration
   *
   * @return               Boolean indicating if valid
   */
  function isValidIntegration(address _module, string memory _name) external view returns (bool) {
    return integrations[_module][_nameHash(_name)] != address(0);
  }

  /* ============ Internal Functions ============ */

  /**
   * Hashes the string and returns a bytes32 value
   */
  function _nameHash(string memory _name) internal pure returns (bytes32) {
    return keccak256(bytes(_name));
  }
}
/**
 * @title IntegrationRegistry
 * @author Set Protocol
 *
 * The IntegrationRegistry holds state relating to the Modules and the integrations they are connected with.
 * The state is combined into a single Registry to allow governance updates to be aggregated to one contract.
 */
function isValidIntegration(address _module, string memory _name) external view returns (bool) {
  return integrations[_module][_nameHash(_name)] != address(0);
}

/**
 * Check if adapter name is valid
 *
 * @param  _module       The address of the module associated with the integration
 * @param  _name         Human readable string identifying the integration
 *
 * @return               Boolean indicating if valid
 */