contract DFDComptroller is RewardDistributionRecipient, IDFDComptroller {
    using SafeERC20 for IERC20;
    using SafeMath for uint;

    uint public constant DURATION = 7 days;

    address public constant bal = address(0xD8E9690eFf99E21a2de25E0b148ffaf47F47C972);
    IERC20 public constant dusd = IERC20(0x5BC25f649fc4e26069dDF4cF4010F9f706c23831);
    IERC20 public constant dfd = IERC20(0x20c36f062a31865bED8a5B1e512D9a1A20AA333A);
    IComptroller public constant comptroller = IComptroller(0xf79b548ff56E661ee19a59303178E444E9e81FCc);

    address public beneficiary;
    uint public periodFinish;
    uint public rewardRate;
    uint public lastUpdateTime;
    uint public rewardStored;
    uint public rewardPaid;
    mapping(address => bool) public isHarvester;

    event RewardAdded(uint reward);
    event RewardPaid(address indexed user, uint256 reward);
    event Harvested(uint indexed dusd, uint indexed dfd);

    modifier onlyHarvester() {
        require(isHarvester[_msgSender()], "Caller is not authorized harvester");
        _;
    }

    function getReward()
        external
    {
        _updateReward();
        require(msg.sender == beneficiary, "GET_REWARD_NO_AUTH");
        uint reward = rewardStored.sub(rewardPaid);
        if (reward > 0) {
            rewardPaid = rewardStored;
            dfd.safeTransfer(beneficiary, reward);
            emit RewardPaid(beneficiary, reward);
        }
    }

    function notifyRewardAmount(uint256 reward)
        external
        onlyRewardDistribution
    {
        _updateReward();
        dfd.safeTransferFrom(msg.sender, address(this), reward);
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(DURATION);
        emit RewardAdded(reward);
    }

    function harvest(uint minAmountOut)
        onlyHarvester
        external
    {
        // This contract will receive dusd because it should be a registered beneficiary
        comptroller.harvest();

        uint256 _dusd = dusd.balanceOf(address(this));
        if (_dusd > 0) {
            dusd.approve(bal, _dusd);
            (uint tokenAmountOut,) = Uni(bal).swapExactAmountIn(address(dusd), _dusd, address(dfd), minAmountOut, uint(-1) /* max */);
            if (tokenAmountOut > 0) {
                dfd.safeTransfer(beneficiary, tokenAmountOut);
            }
            emit Harvested(_dusd, tokenAmountOut);
        }
    }

    function setHarvester(address _harvester, bool _status)
        external
        onlyOwner
    {
        isHarvester[_harvester] = _status;
    }

    function setBeneficiary(address _beneficiary)
        external
        onlyOwner
    {
        beneficiary = _beneficiary;
    }

    /* ##### View ##### */

    function lastTimeRewardApplicable() public view returns (uint256) {
        return Math.min(_timestamp(), periodFinish);
    }

    function availableReward() public view returns(uint) {
        return lastTimeRewardApplicable()
            .sub(lastUpdateTime)
            .mul(rewardRate)
            .add(rewardStored)
            .sub(rewardPaid);
    }

    /* ##### Internal ##### */

    function _updateReward() internal {
        uint _lastTimeRewardApplicable = lastTimeRewardApplicable();
        rewardStored = rewardStored.add(
            _lastTimeRewardApplicable
                .sub(lastUpdateTime)
                .mul(rewardRate)
        );
        lastUpdateTime = _lastTimeRewardApplicable;
    }

    function _timestamp() internal view returns (uint) {
        return block.timestamp;
    }
}
function _updateReward() internal {
        uint _lastTimeRewardApplicable = lastTimeRewardApplicable();
        rewardStored = rewardStored.add(
            _lastTimeRewardApplicable
                .sub(lastUpdateTime)
                .mul(rewardRate)
        );
        lastUpdateTime = _lastTimeRewardApplicable;
    }
/* ##### Internal ##### */