contract Staking is Ownable {

    using SafeMath for uint256;
    using SafeERC20 for OrexToken;
    using Address for address;

    OrexToken public token;
    
    // annual yield period
    uint256 private constant DURATION = 365 days;
    uint256 private constant MINIMUM_AMOUNT = 1e18;
    //uint256 public _poolAmount = 0;
    bool public haveStarted = false;
    //uint256 public _totalTokenBalance;
    
    uint256 public _plan1RewardLimit = 70000e18;
    uint256 public _plan2RewardLimit = 55000e18;
    uint256 public _plan3RewardLimit = 45000e18;
    uint256 public _plan4RewardLimit = 60000e18;
    uint256 public _plan5RewardLimit = 20000e18;
    uint256 public _plan1RewardAmount = 0;
    uint256 public _plan2RewardAmount = 0;
    uint256 public _plan3RewardAmount = 0;
    uint256 public _plan4RewardAmount = 0;
    uint256 public _plan5RewardAmount = 0;

    mapping(address => uint256) private _stakerTokenBalance;
    mapping(address => uint256) public _stakerRewardRate;
    mapping(address => uint256) public _stakerStakingPlan;
    mapping(address => uint256) public _stakerWithdrawFeeRate;
    mapping(address => uint256) public _stakerStakingTime;
    mapping(address => uint256) public _stakerTokenRewardsClaimed;

    event Stake(address indexed from, uint amount);
    event Withdraw(address indexed to, uint amount);
    event Claim(address indexed to, uint amount);
    event StartStaking(bool haveStarted);

    constructor(address _token) public{
        token = OrexToken(_token);
    }


    function stake(uint plan, uint amount) public shouldStarted {
        
        require(_stakerStakingPlan[msg.sender] == 0, "Withdraw your current plan.");
        require(!address(msg.sender).isContract(), "Please use your individual account.");
        require(amount >= MINIMUM_AMOUNT, "Should stake at least 1 Token.");
        
        _stakerStakingTime[msg.sender] = block.timestamp;
        _stakerStakingPlan[msg.sender] = plan;

        updateRewards(msg.sender);
        
        token.safeTransferFrom(msg.sender, address(this), amount);
       // _totalTokenBalance = _totalTokenBalance.add(amount);
        _stakerTokenBalance[msg.sender] = _stakerTokenBalance[msg.sender].add(amount);
        
        emit Stake(msg.sender, amount);
    }

    function withdraw(uint amount) public shouldStarted {
        
        require(amount <= _stakerTokenBalance[msg.sender] && _stakerTokenBalance[msg.sender] > 0, "Bad withdraw.");
        
        if(_stakerStakingPlan[msg.sender] == 4 || _stakerStakingPlan[msg.sender] == 5){
            require(block.timestamp >= _stakerStakingTime[msg.sender] + 30 days, "Early withdrawal available after 30 days");
        }
        
        claim();
        
        _stakerTokenBalance[msg.sender] = _stakerTokenBalance[msg.sender].sub(amount);
        _stakerTokenRewardsClaimed[msg.sender] = 0;
        _stakerStakingPlan[msg.sender] = 0;


        if(_stakerWithdrawFeeRate[msg.sender] > 0){
            uint256 _burnedAmount = amount.mul(_stakerWithdrawFeeRate[msg.sender]).div(100);
            amount = amount.sub(_burnedAmount);
            token.burn(_burnedAmount);
        }
        
        token.safeTransfer(msg.sender, amount);
        
        emit Withdraw(msg.sender, amount);
    }
    
    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint amount) public shouldStarted {
        require(amount <= _stakerTokenBalance[msg.sender] && _stakerTokenBalance[msg.sender] > 0, "Bad withdraw.");
        
        if(_stakerStakingPlan[msg.sender] == 4 || _stakerStakingPlan[msg.sender] == 5){
            require(block.timestamp >= _stakerStakingTime[msg.sender] + 30 days, "Early withdrawal available after 30 days");
        }
        
        updateRewards(msg.sender);
        
        _stakerTokenBalance[msg.sender] = _stakerTokenBalance[msg.sender].sub(amount);
        _stakerTokenRewardsClaimed[msg.sender] = 0;
        _stakerStakingPlan[msg.sender] = 0;


        if(_stakerWithdrawFeeRate[msg.sender] > 0){
            uint256 _burnedAmount = amount.mul(_stakerWithdrawFeeRate[msg.sender]).div(100);
            amount = amount.sub(_burnedAmount);
            token.burn(_burnedAmount);
        }
        
        token.safeTransfer(msg.sender, amount);
        
        emit Withdraw(msg.sender, amount);
    }

    function claim() public shouldStarted {
        
        uint pending = getTotalRewardsAmount(msg.sender);
        
        updateRewards(msg.sender);

        if(pending > 0 && token.balanceOf(address(this)) > 0){

            if(_stakerStakingPlan[msg.sender] == 1){
                
            require(_plan1RewardLimit >= _plan1RewardAmount, "Reward Limit 70,000 Tokens");
             _plan1RewardAmount = _plan1RewardAmount.add(pending);
          
        } else if(_stakerStakingPlan[msg.sender] == 2){

            require(_plan2RewardLimit >= _plan2RewardAmount, "Reward Limit 55,000 Tokens");
             _plan2RewardAmount = _plan2RewardAmount.add(pending);
            
        } else if(_stakerStakingPlan[msg.sender] == 3){
    
           require(_plan3RewardLimit >= _plan3RewardAmount, "Reward Limit 45,000 Tokens");
            _plan3RewardAmount = _plan3RewardAmount.add(pending);
            
        } else if(_stakerStakingPlan[msg.sender] == 4){

           require(_plan4RewardLimit >= _plan4RewardAmount, "Reward Limit 60,000 Tokens");
            _plan4RewardAmount = _plan4RewardAmount.add(pending);
           
            
        } else if(_stakerStakingPlan[msg.sender] == 5){

           require(_plan5RewardLimit >= _plan5RewardAmount, "Reward Limit 20,000 Tokens");
           _plan5RewardAmount = _plan5RewardAmount.add(pending);
            
        }
        
        token.safeTransfer(msg.sender, getTotalRewardsAmount(msg.sender));
        
       
        _stakerTokenRewardsClaimed[msg.sender] = _stakerTokenRewardsClaimed[msg.sender].add(pending);
        
        
        emit Claim(msg.sender, pending);

        }
        
        
    }

    modifier shouldStarted() {
        require(haveStarted == true, "Have not started.");
        _;
    }

    function getRewardsAmountPerSeconds(address account) public view returns(uint256) {
        return balanceOf(account).mul(_stakerRewardRate[msg.sender]).div(100)
                .div(DURATION);
    }
    
    function balanceOf(address account) public view returns(uint256) {
        return _stakerTokenBalance[account];
    }
    
    function getTotalRewardsAmount(address account) public view returns (uint256) {
   
        return block.timestamp.sub(_stakerStakingTime[account]).mul(getRewardsAmountPerSeconds(account)).sub(_stakerTokenRewardsClaimed[account]);
    }


    function updateRewards(address account) internal {
        
        if(_stakerStakingPlan[msg.sender] == 1){

            _stakerRewardRate[msg.sender] = 40;

            if(block.timestamp < _stakerStakingTime[msg.sender] + 60 days){
                 _stakerWithdrawFeeRate[msg.sender] = 6;
            } else {
                _stakerWithdrawFeeRate[msg.sender] = 0;
            }

        } else if(_stakerStakingPlan[msg.sender] == 2){

            _stakerRewardRate[msg.sender] = 69;

            if(block.timestamp < _stakerStakingTime[msg.sender] + 120 days){
                 _stakerWithdrawFeeRate[msg.sender] = 12;
            } else {
                _stakerWithdrawFeeRate[msg.sender] = 0;
            }
            
        } else if(_stakerStakingPlan[msg.sender] == 3){

            _stakerRewardRate[msg.sender] = 81;

            if(block.timestamp < _stakerStakingTime[msg.sender] + 240 days){
                 _stakerWithdrawFeeRate[msg.sender] = 24;
            } else {
                _stakerWithdrawFeeRate[msg.sender] = 0;
            }
            
        } else if(_stakerStakingPlan[msg.sender] == 4 &&  token.isInPlan4Whitelist(msg.sender)){

            _stakerRewardRate[msg.sender] = 75;
            _stakerWithdrawFeeRate[msg.sender] = 0;
            
            
        } else if(_stakerStakingPlan[msg.sender] == 5 &&  token.isInPlan5Whitelist(msg.sender) ){

            _stakerRewardRate[msg.sender] = 40;
            _stakerWithdrawFeeRate[msg.sender] = 0;
            
        }

        
    }

    function startStaking() external onlyOwner {
        updateRewards(address(0));
        haveStarted = true;
        emit StartStaking(haveStarted);
    }


}
function emergencyWithdraw(uint amount) public shouldStarted {
    require(amount <= _stakerTokenBalance[msg.sender] && _stakerTokenBalance[msg.sender] > 0, "Bad withdraw.");
        
    if(_stakerStakingPlan[msg.sender] == 4 || _stakerStakingPlan[msg.sender] == 5){
        require(block.timestamp >= _stakerStakingTime[msg.sender] + 30 days, "Early withdrawal available after 30 days");
    }
        
    updateRewards(msg.sender);
        
    _stakerTokenBalance[msg.sender] = _stakerTokenBalance[msg.sender].sub(amount);
    _stakerTokenRewardsClaimed[msg.sender] = 0;
    _stakerStakingPlan[msg.sender] = 0;


    if(_stakerWithdrawFeeRate[msg.sender] > 0){
        uint256 _burnedAmount = amount.mul(_stakerWithdrawFeeRate[msg.sender]).div(100);
        amount = amount.sub(_burnedAmount);
        token.burn(_burnedAmount);
    }
        
    token.safeTransfer(msg.sender, amount);
        
    emit Withdraw(msg.sender, amount);
}

// Withdraw without caring about rewards. EMERGENCY ONLY.