contract StreamingFeeModule is ModuleBase, ReentrancyGuard {
    using SafeMath for uint256;
    using PreciseUnitMath for uint256;
    using SafeCast for uint256;

    using SignedSafeMath for int256;
    using PreciseUnitMath for int256;
    using SafeCast for int256;


    /* ============ Structs ============ */

    struct FeeState {
        address feeRecipient;                   // Address to accrue fees to
        uint256 maxStreamingFeePercentage;      // Max streaming fee maanager commits to using (1% = 1e16, 100% = 1e18)
        uint256 streamingFeePercentage;         // Percent of Set accruing to manager annually (1% = 1e16, 100% = 1e18)
        uint256 lastStreamingFeeTimestamp;      // Timestamp last streaming fee was accrued
    }

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

    event FeeActualized(address indexed _setToken, uint256 _managerFee, uint256 _protocolFee);
    event StreamingFeeUpdated(address indexed _setToken, uint256 _newStreamingFee);
    event FeeRecipientUpdated(address indexed _setToken, address _newFeeRecipient);

    /* ============ Constants ============ */

    uint256 private constant ONE_YEAR_IN_SECONDS = 365.25 days;
    uint256 private constant PROTOCOL_STREAMING_FEE_INDEX = 0;

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

    mapping(ISetToken => FeeState) public feeStates;

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

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

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

    /*
     * Calculates total inflation percentage then mints new Sets to the fee recipient. Position units are
     * then adjusted down (in magnitude) in order to ensure full collateralization. Callable by anyone.
     *
     * @param _setToken       Address of SetToken
     */
    function accrueFee(ISetToken _setToken) public nonReentrant onlyValidAndInitializedSet(_setToken) {
        uint256 managerFee;
        uint256 protocolFee;

        if (_streamingFeePercentage(_setToken) > 0) {
            uint256 inflationFeePercentage = _calculateStreamingFee(_setToken);

            // Calculate incentiveFee inflation
            uint256 feeQuantity = _calculateStreamingFeeInflation(_setToken, inflationFeePercentage);

            // Mint new Sets to manager and protocol
            (
                managerFee,
                protocolFee
            ) = _mintManagerAndProtocolFee(_setToken, feeQuantity);

            _editPositionMultiplier(_setToken, inflationFeePercentage);
        }

        feeStates[_setToken].lastStreamingFeeTimestamp = block.timestamp;

        emit FeeActualized(address(_setToken), managerFee, protocolFee);
    }

    /**
     * SET MANAGER ONLY. Initialize module with SetToken and set the fee state for the SetToken. Passed
     * _settings will have lastStreamingFeeTimestamp over-written.
     *
     * @param _setToken                 Address of SetToken
     * @param _settings                 FeeState struct defining fee parameters
     */
    function initialize(
        ISetToken _setToken,
        FeeState memory _settings
    )
        external
        onlySetManager(_setToken, msg.sender)
        onlyValidAndPendingSet(_setToken)
    {
        require(_settings.feeRecipient != address(0), "Fee Recipient must be non-zero address.");
        require(_settings.maxStreamingFeePercentage < PreciseUnitMath.preciseUnit(), "Max fee must be < 100%.");
        require(_settings.streamingFeePercentage <= _settings.maxStreamingFeePercentage, "Fee must be <= max.");

        _settings.lastStreamingFeeTimestamp = block.timestamp;

        feeStates[_setToken] = _settings;
        _setToken.initializeModule();
    }

    /**
     * Removes this module from the SetToken, via call by the SetToken. Manager's feeState is deleted. Fees
     * are not accrued in case reason for removing module is related to fee accrual.
     */
    function removeModule() external override {
        delete feeStates[ISetToken(msg.sender)];
    }

    /*
     * Set new streaming fee. Fees accrue at current rate then new rate is set.
     * Fees are accrued to prevent the manager from unfairly accruing a larger percentage.
     *
     * @param _setToken       Address of SetToken
     * @param _newFee         New streaming fee 18 decimal precision
     */
    function updateStreamingFee(
        ISetToken _setToken,
        uint256 _newFee
    )
        external
        onlySetManager(_setToken, msg.sender)
        onlyValidAndInitializedSet(_setToken)
    {
        require(_newFee < _maxStreamingFeePercentage(_setToken), "Fee must be less than max");
        accrueFee(_setToken);

        feeStates[_setToken].streamingFeePercentage = _newFee;

        emit StreamingFeeUpdated(address(_setToken), _newFee);
    }

    /*
     * Set new fee recipient.
     *
     * @param _setToken             Address of SetToken
     * @param _newFeeRecipient      New fee recipient
     */
    function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient)
        external
        onlySetManager(_setToken, msg.sender)
        onlyValidAndInitializedSet(_setToken)
    {
        require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address.");

        feeStates[_setToken].feeRecipient = _newFeeRecipient;

        emit FeeRecipientUpdated(address(_setToken), _newFeeRecipient);
    }

    /*
     * Calculates total inflation percentage in order to accrue fees to manager.
     *
     * @param _setToken       Address of SetToken
     * @return  uint256       Percent inflation of supply
     */
    function getFee(ISetToken _setToken) external view returns (uint256) {
        return _calculateStreamingFee(_setToken);
    }

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

    /**
     * Calculates streaming fee by multiplying streamingFeePercentage by the elapsed amount of time since the last fee
     * was collected divided by one year in seconds, since the fee is a yearly fee.
     *
     * @param  _setToken          Address of Set to have feeState updated
     * @return uint256            Streaming fee denominated in percentage of totalSupply
     */
    function _calculateStreamingFee(ISetToken _setToken) internal view returns(uint256) {
        uint256 timeSinceLastFee = block.timestamp.sub(_lastStreamingFeeTimestamp(_setToken));

        // Streaming fee is streaming fee times years since last fee
        return timeSinceLastFee.mul(_streamingFeePercentage(_setToken)).div(ONE_YEAR_IN_SECONDS);
    }

    /**
     * Returns the new incentive fee denominated in the number of SetTokens to mint. The calculation for the fee involves
     * implying mint quantity so that the feeRecipient owns the fee percentage of the entire supply of the Set.
     *
     * The formula to solve for fee is:
     * (feeQuantity / feeQuantity) + totalSupply = fee / scaleFactor
     *
     * The simplified formula utilized below is:
     * feeQuantity = fee * totalSupply / (scaleFactor - fee)
     *
     * @param   _setToken               SetToken instance
     * @param   _feePercentage          Fee levied to feeRecipient
     * @return  uint256                 New RebalancingSet issue quantity
     */
    function _calculateStreamingFeeInflation(
        ISetToken _setToken,
        uint256 _feePercentage
    )
        internal
        view
        returns (uint256)
    {
        uint256 totalSupply = _setToken.totalSupply();

        // fee * totalSupply
        uint256 a = _feePercentage.mul(totalSupply);

        // ScaleFactor (10e18) - fee
        uint256 b = PreciseUnitMath.preciseUnit().sub(_feePercentage);

        return a.div(b);
    }

    /**
     * Mints sets to both the manager and the protocol. Protocol takes a percentage fee of the total amount of Sets
     * minted to manager.
     *
     * @param   _setToken               SetToken instance
     * @param   _feeQuantity            Amount of Sets to be minted as fees
     * @return  uint256                 Amount of Sets accrued to manager as fee
     * @return  uint256                 Amount of Sets accrued to protocol as fee
     */
    function _mintManagerAndProtocolFee(ISetToken _setToken, uint256 _feeQuantity) internal returns (uint256, uint256) {
        address protocolFeeRecipient = controller.feeRecipient();
        uint256 protocolFee = controller.getModuleFee(address(this), PROTOCOL_STREAMING_FEE_INDEX);

        uint256 protocolFeeAmount = _feeQuantity.preciseMul(protocolFee);
        uint256 managerFeeAmount = _feeQuantity.sub(protocolFeeAmount);

        _setToken.mint(_feeRecipient(_setToken), managerFeeAmount);

        if (protocolFeeAmount > 0) {
            _setToken.mint(protocolFeeRecipient, protocolFeeAmount);
        }

        return (managerFeeAmount, protocolFeeAmount);
    }

    /**
     * Calculates new position multiplier according to following formula:
     *
     * newMultiplier = oldMultiplier * (1-inflationFee)
     *
     * This reduces position sizes to offset increase in supply due to fee collection.
     *
     * @param   _setToken               SetToken instance
     * @param   _inflationFee           Fee inflation rate
     */
    function _editPositionMultiplier(ISetToken _setToken, uint256 _inflationFee) internal {
        int256 currentMultipler = _setToken.positionMultiplier();
        int256 newMultiplier = currentMultipler.preciseMul(PreciseUnitMath.preciseUnit().sub(_inflationFee).toInt256());

        _setToken.editPositionMultiplier(newMultiplier);
    }

    function _feeRecipient(ISetToken _set) internal view returns (address) {
        return feeStates[_set].feeRecipient;
    }

    function _lastStreamingFeeTimestamp(ISetToken _set) internal view returns (uint256) {
        return feeStates[_set].lastStreamingFeeTimestamp;
    }

    function _maxStreamingFeePercentage(ISetToken _set) internal view returns (uint256) {
        return feeStates[_set].maxStreamingFeePercentage;
    }

    function _streamingFeePercentage(ISetToken _set) internal view returns (uint256) {
        return feeStates[_set].streamingFeePercentage;
    }
}
/**
 * @title StreamingFeeModule
 * @author Set Protocol
 *
 * Smart contract that accrues streaming fees for Set managers. Streaming fees are denominated as percent
 * per year and realized as Set inflation rewarded to the manager.
 */
function _editPositionMultiplier(ISetToken _setToken, uint256 _inflationFee) internal {
        int256 currentMultipler = _setToken.positionMultiplier();
        int256 newMultiplier = currentMultipler.preciseMul(PreciseUnitMath.preciseUnit().sub(_inflationFee).toInt256());

        _setToken.editPositionMultiplier(newMultiplier);
    }
/**
 * Calculates new position multiplier according to following formula:
 *
 * newMultiplier = oldMultiplier * (1-inflationFee)
 *
 * This reduces position sizes to offset increase in supply due to fee collection.
 *
 * @param   _setToken               SetToken instance
 * @param   _inflationFee           Fee inflation rate
 */