contract FeeSplitAdapter is BaseAdapter, TimeLockUpgrade {
    using Address for address;
    using PreciseUnitMath for uint256;
    using SafeMath for uint256;

    /* ============ Events ============ */

    event FeesAccrued(address indexed _operator, address indexed _methodologist, uint256 _operatorTake, uint256 _methodologistTake);
    
    /* ============ State Variables ============ */

    ISetToken public setToken;
    IStreamingFeeModule public streamingFeeModule;
    IIssuanceModule public issuanceModule;

    // Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
    uint256 public operatorFeeSplit;

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

    constructor(
        IBaseManager _manager,
        IStreamingFeeModule _streamingFeeModule,
        IIssuanceModule _issuanceModule,
        uint256 _operatorFeeSplit
    )
        public
        BaseAdapter(_manager)
    {
        streamingFeeModule = _streamingFeeModule;
        issuanceModule = _issuanceModule;
        operatorFeeSplit = _operatorFeeSplit;
        setToken = manager.setToken();
    }

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

    /**
     * ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
     * operator and methodologist, and sends to each. NOTE: mint/redeem fees will automatically be sent to this address so reading
     * the balance of the SetToken in the contract after accrual is sufficient for accounting for all collected fees.
     */
    function accrueFeesAndDistribute() public {
        streamingFeeModule.accrueFee(setToken);
        
        uint256 totalFees = setToken.balanceOf(address(manager));
        
        address operator = manager.operator();
        address methodologist = manager.methodologist();

        uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
        uint256 methodologistTake = totalFees.sub(operatorTake);

        if (operatorTake > 0) {
            invokeManagerTransfer(address(setToken), operator, operatorTake);
        }

        if (methodologistTake > 0) {
            invokeManagerTransfer(address(setToken), methodologist, methodologistTake);
        }

        emit FeesAccrued(operator, methodologist, operatorTake, methodologistTake);
    }

    /**
     * ONLY OPERATOR: Updates streaming fee on StreamingFeeModule. NOTE: This will accrue streaming fees though not send to operator
     * and methodologist.
     */
    function updateStreamingFee(uint256 _newFee) external onlyOperator timeLockUpgrade {
        bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", manager.setToken(), _newFee);
        invokeManager(address(streamingFeeModule), callData);
    }

    /**
     * ONLY OPERATOR: Updates issue fee on IssuanceModule. Only is executed once time lock has passed.
     */
    function updateIssueFee(uint256 _newFee) external onlyOperator timeLockUpgrade {
        bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee);
        invokeManager(address(issuanceModule), callData);
    }

    /**
     * ONLY OPERATOR: Updates redeem fee on IssuanceModule. Only is executed once time lock has passed.
     */
    function updateRedeemFee(uint256 _newFee) external onlyOperator timeLockUpgrade {
        bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", manager.setToken(), _newFee);
        invokeManager(address(issuanceModule), callData);
    }

    /**
     * ONLY OPERATOR: Updates fee recipient on both streaming fee and issuance modules.
     */
    function updateFeeRecipient(address _newFeeRecipient) external onlyOperator {
        bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", manager.setToken(), _newFeeRecipient);
        invokeManager(address(streamingFeeModule), callData);
        invokeManager(address(issuanceModule), callData);
    }

    /**
     * ONLY OPERATOR: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
     */
    function updateFeeSplit(uint256 _newFeeSplit) external onlyOperator {
        require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
        accrueFeesAndDistribute();
        operatorFeeSplit = _newFeeSplit;
    }
}
/**
 * @title FeeSplitAdapter
 * @author Set Protocol
 *
 * Smart contract adapter that allows for splitting and setting streaming and mint/redeem fees.
 */
function updateIssueFee(uint256 _newFee) external onlyOperator timeLockUpgrade {
    bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee);
    invokeManager(address(issuanceModule), callData);
}

/**
 * ONLY OPERATOR: Updates issue fee on IssuanceModule. Only is executed once time lock has passed.
 */