contract GovernanceModule is ModuleBase, ReentrancyGuard {
    using Invoke for ISetToken;

    /* ============ Events ============ */
    event ProposalVoted(
        ISetToken indexed _setToken,
        IGovernanceAdapter indexed _governanceAdapter,
        uint256 indexed _proposalId,
        bool _support
    );

    event VoteDelegated(
        ISetToken indexed _setToken,
        IGovernanceAdapter indexed _governanceAdapter,
        address _delegatee
    );

    event ProposalCreated(
        ISetToken indexed _setToken,
        IGovernanceAdapter indexed _governanceAdapter,
        bytes _proposalData
    );

    event RegistrationSubmitted(
        ISetToken indexed _setToken,
        IGovernanceAdapter indexed _governanceAdapter
    );

    event RegistrationRevoked(
        ISetToken indexed _setToken,
        IGovernanceAdapter indexed _governanceAdapter
    );

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

    constructor(IController _controller) public ModuleBase(_controller) {}

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

    /**
     * SET MANAGER ONLY. Delegate voting power to an Ethereum address. Note: for some governance adapters, delegating to self is
     * equivalent to registering and delegating to zero address is revoking right to vote.
     *
     * @param _setToken                 Address of SetToken
     * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
     * @param _delegatee                Address of delegatee
     */
    function delegate(
        ISetToken _setToken,
        string memory _governanceName,
        address _delegatee
    )
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
    {
        IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

        (
            address targetExchange,
            uint256 callValue,
            bytes memory methodData
        ) = governanceAdapter.getDelegateCalldata(_delegatee);

        _setToken.invoke(targetExchange, callValue, methodData);

        emit VoteDelegated(_setToken, governanceAdapter, _delegatee);
    }

    /**
     * SET MANAGER ONLY. Create a new proposal for a specified governance protocol.
     *
     * @param _setToken                 Address of SetToken
     * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
     * @param _proposalData             Byte data of proposal to pass into governance adapter
     */
    function propose(
        ISetToken _setToken,
        string memory _governanceName,
        bytes memory _proposalData
    )
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
    {
        IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

        (
            address targetExchange,
            uint256 callValue,
            bytes memory methodData
        ) = governanceAdapter.getProposeCalldata(_proposalData);

        _setToken.invoke(targetExchange, callValue, methodData);

        emit ProposalCreated(_setToken, governanceAdapter, _proposalData);
    }

    /**
     * SET MANAGER ONLY. Register for voting for the SetToken
     *
     * @param _setToken                 Address of SetToken
     * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
     */
    function register(
        ISetToken _setToken,
        string memory _governanceName
    )
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
    {
        IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

        (
            address targetExchange,
            uint256 callValue,
            bytes memory methodData
        ) = governanceAdapter.getRegisterCalldata(address(_setToken));

        _setToken.invoke(targetExchange, callValue, methodData);

        emit RegistrationSubmitted(_setToken, governanceAdapter);
    }

    /**
     * SET MANAGER ONLY. Revoke voting for the SetToken
     *
     * @param _setToken                 Address of SetToken
     * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
     */
    function revoke(
        ISetToken _setToken,
        string memory _governanceName
    )
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
    {
        IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

        (
            address targetExchange,
            uint256 callValue,
            bytes memory methodData
        ) = governanceAdapter.getRevokeCalldata();

        _setToken.invoke(targetExchange, callValue, methodData);

        emit RegistrationRevoked(_setToken, governanceAdapter);
    }

    /**
     * SET MANAGER ONLY. Cast vote for a specific governance token held in the SetToken. Manager specifies whether to vote for or against
     * a given proposal
     *
     * @param _setToken                 Address of SetToken
     * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
     * @param _proposalId               ID of the proposal to vote on
     * @param _support                  Boolean indicating whether to support proposal
     * @param _data                     Arbitrary bytes to be used to construct vote call data
     */
    function vote(
        ISetToken _setToken,
        string memory _governanceName,
        uint256 _proposalId,
        bool _support,
        bytes memory _data
    )
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
    {
        IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

        (
            address targetExchange,
            uint256 callValue,
            bytes memory methodData
        ) = governanceAdapter.getVoteCalldata(
            _proposalId,
            _support,
            _data
        );

        _setToken.invoke(targetExchange, callValue, methodData);

        emit ProposalVoted(_setToken, governanceAdapter, _proposalId, _support);
    }

    /**
     * Initializes this module to the SetToken. Only callable by the SetToken's manager.
     *
     * @param _setToken             Instance of the SetToken to issue
     */
    function initialize(ISetToken _setToken) external onlySetManager(_setToken, msg.sender) onlyValidAndPendingSet(_setToken) {
        _setToken.initializeModule();
    }

    /**
     * Removes this module from the SetToken, via call by the SetToken.
     */
    function removeModule() external override {}
}
/**
 * @title GovernanceModule
 * @author Set Protocol
 *
 * A smart contract module that enables participating in governance of component tokens held in the SetToken.
 * Examples of intended protocols include Compound, Uniswap, and Maker governance. 
 */
function vote(
    ISetToken _setToken,
    string memory _governanceName,
    uint256 _proposalId,
    bool _support,
    bytes memory _data
)
    external
    nonReentrant
    onlyManagerAndValidSet(_setToken)
{
    IGovernanceAdapter governanceAdapter = IGovernanceAdapter(getAndValidateAdapter(_governanceName));

    (
        address targetExchange,
        uint256 callValue,
        bytes memory methodData
    ) = governanceAdapter.getVoteCalldata(
        _proposalId,
        _support,
        _data
    );

    _setToken.invoke(targetExchange, callValue, methodData);

    emit ProposalVoted(_setToken, governanceAdapter, _proposalId, _support);
}

/**
 * SET MANAGER ONLY. Cast vote for a specific governance token held in the SetToken. Manager specifies whether to vote for or against
 * a given proposal
 *
 * @param _setToken                 Address of SetToken
 * @param _governanceName           Human readable name of integration (e.g. COMPOUND) stored in the IntegrationRegistry
 * @param _proposalId               ID of the proposal to vote on
 * @param _support                  Boolean indicating whether to support proposal
 * @param _data                     Arbitrary bytes to be used to construct vote call data
 */