contract Staking is Ownable, ReentrancyGuard {
    
    using SafeMath for uint;
    using SafeERC20 for IERC20;
    using SafeERC20 for ERC20Burnable;

	ERC20Burnable public duckToken;

	uint public stakingStartTime;
	uint public stakingFinishTime;

	uint public harvestStartTime;

	address public rewardAddress;
	uint public rewardAmount;

	address public duckReceiverAddress;
	uint public burnThreshold;

	mapping(address => uint) public balances;
	uint public totalStaked;
	
	uint public stakingLimit;
	uint public userStakingLimit;
	bool public canWithdraw;

	bool finished;

	struct Reward {
		address addr;
		uint amount;
	}

	Reward[] public rewards;
	mapping(address => uint) public userLastUnclaimedReward;

	event NewReward(address indexed tokenAddress, uint amount);

	constructor(address _duckAddress, address _duckReceiverAddress) public Ownable() {
		duckToken = ERC20Burnable(_duckAddress);
		duckReceiverAddress = _duckReceiverAddress;
	}

	function initialSetup(uint _stakingStartTime, uint _stakingFinishTime, uint _stakingLimit) external onlyOwner {
		require(stakingStartTime == 0, "already set");
        
		stakingStartTime = _stakingStartTime;
		stakingFinishTime = _stakingFinishTime;
		
		stakingLimit = _stakingLimit;
	}

	function secondarySetup(uint _burnThreshold, uint _harvestStartTime, uint _userStakingLimit) external onlyOwner {
        require(_harvestStartTime >= stakingFinishTime, "harvestStartTime < stakingFinishTime");
		burnThreshold = _burnThreshold;
		harvestStartTime = _harvestStartTime;
		userStakingLimit = _userStakingLimit;
	}

	function addReward(address _rewardAddress, uint _rewardAmount) external onlyOwner {
		rewards.push(Reward({addr: _rewardAddress, amount: _rewardAmount}));
		emit NewReward(_rewardAddress, _rewardAmount);
	}
	
	function stake(uint amount) external nonReentrant() {
		require(block.timestamp > stakingStartTime && block.timestamp < stakingFinishTime, "not a staking time");

        if(totalStaked.add(amount) > stakingLimit) {
            amount = stakingLimit.sub(totalStaked);
        }
        
        if(balances[msg.sender].add(amount) > userStakingLimit) {
            amount = userStakingLimit.sub(balances[msg.sender]);
        }
        
        require(amount > 0, "nothing to stake");
		require(duckToken.transferFrom(msg.sender, address(this), amount));
	    
		balances[msg.sender] = balances[msg.sender].add(amount);
		totalStaked = totalStaked.add(amount);
	}

	function claimReward() external nonReentrant() {
		require(block.timestamp >= harvestStartTime, "not a harvest time");
		require(balances[msg.sender] > 0, "nothing to claim");

		uint buf = userLastUnclaimedReward[msg.sender];
		userLastUnclaimedReward[msg.sender] = rewards.length;

		for(uint i = buf; i < rewards.length; i++) {
			uint reward = rewards[i].amount.mul(balances[msg.sender]).div(totalStaked);
			IERC20(rewards[i].addr).safeTransfer(msg.sender, reward);
		}
	}

	function finish() external {
		require(!finished, "already finished");
		require(block.timestamp > stakingFinishTime, "not a finish time");

		finished = true;

		if(totalStaked <= burnThreshold) {
			duckToken.safeTransfer(duckReceiverAddress, totalStaked);
			return;
		}

		duckToken.safeTransfer(duckReceiverAddress, burnThreshold);
		duckToken.burn(totalStaked.sub(burnThreshold));
	}

	function withdrawLostTokens(address tokenAddress) external onlyOwner {
		require(finished, "call finish first");
		
		require(tokenAddress != rewardAddress, "can't withdraw reward tokens");
		IERC20(tokenAddress).safeTransfer(owner(), IERC20(tokenAddress).balanceOf(address(this)));
	}
	
	function setWithdrawPermission(bool perm) external onlyOwner {
	    canWithdraw = perm;
	}
    
    function withdraw() public {
        require(canWithdraw, "withdraw disabled");
        require(balances[msg.sender] > 0, "nothing to claim");
        
        uint toSend = balances[msg.sender];
        balances[msg.sender] = 0;
        
        duckToken.safeTransfer(msg.sender, toSend);
    }
    
    //frontend
	function calculateReward(address user) public view returns(uint) {
		uint res;
	    for(uint i = userLastUnclaimedReward[user]; i < rewards.length; i++) {
			uint reward = rewards[i].amount.mul(balances[user]).div(totalStaked);
			res += reward;
		}

		return res;
	}
}
function calculateReward(address user) public view returns(uint) {
	uint res;
    for(uint i = userLastUnclaimedReward[user]; i < rewards.length; i++) {
		uint reward = rewards[i].amount.mul(balances[user]).div(totalStaked);
		res += reward;
	}

	return res;
}

//frontend