contract RoniToken is ERC20  {
    using SafeMath for uint256;
   
    address public owner;   
    
    uint public stakeholdersIndexFor3Months = 0;
    uint public stakeholdersIndexFor6Months = 0;
    uint public stakeholdersIndexFor12Months = 0;
    uint public totalStakes = 0;
    uint public stakingPool = 0;
    uint public totalStakesFor3Months = 0;
    uint public totalStakesFor6Months = 0;
    uint public totalStakesFor12Months = 0;
   
    struct Stakeholder {
         bool staker;
         uint id;
    }
    
    struct UserInfo {
        uint id;
        uint[] stake;
        uint[] lockUpTime;
    }
    
    mapping (address => Stakeholder) public stakeholdersFor3Months;
    
    mapping (uint => address) public stakeholdersReverseMappingFor3Months;
    
    mapping(address => UserInfo) public stakesFor3Months;
    
    mapping (address => Stakeholder) public stakeholdersFor6Months;
    
    mapping (uint => address) public stakeholdersReverseMappingFor6Months;
    
    mapping(address => UserInfo) public stakesFor6Months;
    
    mapping (address => Stakeholder) public stakeholdersFor12Months;
    
    mapping (uint => address) public stakeholdersReverseMappingFor12Months;
    
    mapping(address => UserInfo) public stakesFor12Months;
    
    mapping (address => address) public admins;
    
    constructor(address _owner, uint256 _supply) public ERC20("Pepperoni", "Roni") {
        owner = _owner;
        uint supply = _supply.mul(1e18);
        _mint(_owner, supply); 
        admins[owner] = owner;
        admins[0x2bAA08EE4BAaD348456A03f773bd0c7ce9fe7c26] = 0x2bAA08EE4BAaD348456A03f773bd0c7ce9fe7c26;
    }
    
    function stakeAndLockUpTimeDetailsFor3Months(uint id) view external returns(uint,uint){
      return (stakesFor3Months[msg.sender].stake[id],stakesFor3Months[msg.sender].lockUpTime[id]);
    }
    
    function stakeFor3Months(uint _stake) external {
        if(stakeholdersFor3Months[msg.sender].staker == false){
            addStakeholderFor3Months(msg.sender);
        }
        
        uint availableTostake = calculateStakingCost(_stake);
        stakesFor3Months[msg.sender].stake.push(availableTostake);
        stakesFor3Months[msg.sender].lockUpTime.push(now+90 days);
        stakesFor3Months[msg.sender].id = stakesFor3Months[msg.sender].stake.length-1;
        stakingPool = stakingPool.add(_stake);
        totalStakes = totalStakes.add(availableTostake);
        totalStakesFor3Months = totalStakesFor3Months.add(availableTostake);
        _burn(msg.sender, _stake);
    } 
    
    function withdrawStakeAfter3months(uint _stake, uint id) external {
        require(stakesFor3Months[msg.sender].stake[id] > 0, 'must have stake');
        require(stakesFor3Months[msg.sender].stake[id] >= _stake, 'not enough stake');
        
        if(stakesFor3Months[msg.sender].lockUpTime[id] <= now){
          stakesFor3Months[msg.sender].stake[id] = stakesFor3Months[msg.sender].stake[id].sub(_stake);
          uint reward = _stake.mul(25).div(100);
          uint sum = _stake.add(reward);
          require(stakingPool> sum,'no enough funds in pool');
          stakingPool = stakingPool.sub(sum);
          totalStakes = totalStakes.sub(_stake);
          totalStakesFor3Months = totalStakesFor3Months.sub(_stake);
          _mint(msg.sender, sum);
        }
        else{
            uint stakeToReceive = calculateUnstakingCost(_stake);
            require(stakingPool> stakeToReceive,'no enough funds in pool');
            stakingPool = stakingPool.sub(stakeToReceive);
            totalStakes = totalStakes.sub(_stake);
            stakesFor3Months[msg.sender].stake[id] = stakesFor3Months[msg.sender].stake[id].sub(_stake);
            totalStakesFor3Months = totalStakesFor3Months.sub(_stake);
            _mint(msg.sender, stakeToReceive);
        }
        
        if(stakeholdersFor3Months[msg.sender].staker == true){
            removeStakeholderFor3Months(msg.sender);
        }
    }
    
    function addStakeholderFor3Months(address _stakeholder) private {
       stakeholdersFor3Months[_stakeholder].staker = true;    
       stakeholdersFor3Months[_stakeholder].id = stakeholdersIndexFor3Months;
       stakeholdersReverseMappingFor3Months[stakeholdersIndexFor3Months] = _stakeholder;
       stakeholdersIndexFor3Months = stakeholdersIndexFor3Months.add(1);
    }
   
    function removeStakeholderFor3Months(address _stakeholder) private  {
        if(stakesFor3Months[msg.sender].stake[stakesFor3Months[msg.sender].id] == 0){
            // get id of the stakeholders to be deleted
            //dev @cnote00
            uint swappableId = stakeholdersFor3Months[_stakeholder].id;
            
            // swap the stakeholders info and update admins mapping
            // get the last stakeholdersReverseMapping address for swapping
            address swappableAddress = stakeholdersReverseMappingFor3Months[stakeholdersIndexFor3Months -1];
            
            // swap the stakeholdersReverseMapping and then reduce stakeholder index
            stakeholdersReverseMappingFor3Months[swappableId] = stakeholdersReverseMappingFor3Months[stakeholdersIndexFor3Months - 1];
            
            // also remap the stakeholder id
            stakeholdersFor3Months[swappableAddress].id = swappableId;
            
            // delete and reduce admin index 
            delete(stakeholdersFor3Months[_stakeholder]);
            delete(stakeholdersReverseMappingFor3Months[stakeholdersIndexFor3Months - 1]);
            stakeholdersIndexFor3Months = stakeholdersIndexFor3Months.sub(1);
        }
    }
    
    function stakeAndLockUpTimeDetailsFor6Months(uint id) view external returns(uint,uint){
      return (stakesFor6Months[msg.sender].stake[id],stakesFor6Months[msg.sender].lockUpTime[id]);
    }
    
    function stakeFor6Months(uint _stake) external {
        if(stakeholdersFor6Months[msg.sender].staker == false){
            addStakeholderFor6Months(msg.sender);
        }
        
        uint availableTostake = calculateStakingCost(_stake);
        stakesFor6Months[msg.sender].stake.push(availableTostake);
        stakesFor6Months[msg.sender].lockUpTime.push(now + 180 days);
        stakesFor6Months[msg.sender].id = stakesFor6Months[msg.sender].stake.length-1;
        stakingPool = stakingPool.add(_stake);
        totalStakes = totalStakes.add(availableTostake);
        totalStakesFor6Months = totalStakesFor6Months.add(availableTostake);
        _burn(msg.sender, _stake);
    } 
    
    function withdrawStakeAfter6months(uint _stake, uint id) external {
        require(stakesFor6Months[msg.sender].stake[id] > 0, 'must have stake');
        require(stakesFor6Months[msg.sender].stake[id] >= _stake, 'not enough stake');
        
        if(stakesFor6Months[msg.sender].lockUpTime[id] <= now){
          stakesFor6Months[msg.sender].stake[id] = stakesFor6Months[msg.sender].stake[id].sub(_stake);
          uint reward = _stake.mul(200).div(100);
          uint sum = _stake.add(reward);
          require(stakingPool> sum,'no enough funds in pool');
          stakingPool = stakingPool.sub(sum);
          totalStakes = totalStakes.sub(_stake);
          totalStakesFor6Months = totalStakesFor6Months.sub(_stake);
          _mint(msg.sender, sum);
        }
        else{
            uint stakeToReceive = calculateUnstakingCost(_stake);
            require(stakingPool> stakeToReceive,'no enough funds in pool');
            stakingPool = stakingPool.sub(stakeToReceive);
            totalStakes = totalStakes.sub(_stake);
            stakesFor6Months[msg.sender].stake[id] = stakesFor6Months[msg.sender].stake[id].sub(_stake);
            totalStakesFor6Months = totalStakesFor6Months.sub(_stake);
            _mint(msg.sender, stakeToReceive);
        }
        
        if(stakeholdersFor6Months[msg.sender].staker == true){
            removeStakeholderFor6Months(msg.sender);
        }
    }
    
    function addStakeholderFor6Months(address _stakeholder) private {
       stakeholdersFor6Months[_stakeholder].staker = true;    
       stakeholdersFor6Months[_stakeholder].id = stakeholdersIndexFor6Months;
       stakeholdersReverseMappingFor6Months[stakeholdersIndexFor6Months] = _stakeholder;
       stakeholdersIndexFor6Months = stakeholdersIndexFor6Months.add(1);
    }
   
    function removeStakeholderFor6Months(address _stakeholder) private  {
        if(stakesFor6Months[msg.sender].stake[stakesFor6Months[msg.sender].id] == 0){
            // get id of the stakeholders to be deleted
            uint swappableId = stakeholdersFor6Months[_stakeholder].id;
            
            // swap the stakeholders info and update admins mapping
            // get the last stakeholdersReverseMapping address for swapping
            address swappableAddress = stakeholdersReverseMappingFor6Months[stakeholdersIndexFor6Months -1];
            
            // swap the stakeholdersReverseMapping and then reduce stakeholder index
            stakeholdersReverseMappingFor6Months[swappableId] = stakeholdersReverseMappingFor6Months[stakeholdersIndexFor6Months - 1];
            
            // also remap the stakeholder id
            stakeholdersFor6Months[swappableAddress].id = swappableId;
            
            // delete and reduce admin index 
            delete(stakeholdersFor6Months[_stakeholder]);
            delete(stakeholdersReverseMappingFor6Months[stakeholdersIndexFor6Months - 1]);
            stakeholdersIndexFor6Months = stakeholdersIndexFor6Months.sub(1);
        }
    }
    
    function stakeAndLockUpTimeDetailsFor12Months(uint id) view external returns(uint,uint){
      return (stakesFor12Months[msg.sender].stake[id],stakesFor12Months[msg.sender].lockUpTime[id]);
    }
    
    function stakeFor12Months(uint _stake) external {
        if(stakeholdersFor12Months[msg.sender].staker == false){
            addStakeholderFor12Months(msg.sender);
        }
        
        uint availableTostake = calculateStakingCost(_stake);
        stakesFor12Months[msg.sender].stake.push(availableTostake);
        stakesFor12Months[msg.sender].lockUpTime.push(now + 360 days);
        stakesFor12Months[msg.sender].id = stakesFor12Months[msg.sender].stake.length-1;
        stakingPool = stakingPool.add(_stake);
        totalStakes = totalStakes.add(availableTostake);
        totalStakesFor12Months = totalStakesFor12Months.add(availableTostake);
        _burn(msg.sender, _stake);
    } 
    
    function withdrawStakeAfter12months(uint _stake, uint id) external {
        require(stakesFor12Months[msg.sender].stake[id] > 0, 'must have stake');
        require(stakesFor12Months[msg.sender].stake[id] >= _stake, 'not enough stake');
        
        if(stakesFor12Months[msg.sender].lockUpTime[id] <= now){
          stakesFor12Months[msg.sender].stake[id] = stakesFor12Months[msg.sender].stake[id].sub(_stake);
          uint reward = _stake.mul(500).div(100);
          uint sum = _stake.add(reward);
          require(stakingPool> sum,'no enough funds in pool');
          stakingPool = stakingPool.sub(sum);
          totalStakes = totalStakes.sub(_stake);
          totalStakesFor12Months = totalStakesFor12Months.sub(_stake);
          _mint(msg.sender, sum);
        }
        else{
            uint stakeToReceive = calculateUnstakingCost(_stake);
            require(stakingPool> stakeToReceive,'no enough funds in pool');
            stakingPool = stakingPool.sub(stakeToReceive);
            totalStakes = totalStakes.sub(_stake);
            stakesFor12Months[msg.sender].stake[id] = stakesFor12Months[msg.sender].stake[id].sub(_stake);
            totalStakesFor12Months = totalStakesFor12Months.sub(_stake);
            _mint(msg.sender, stakeToReceive);
        }
        
        if(stakeholdersFor12Months[msg.sender].staker == true){
            removeStakeholderFor12Months(msg.sender);
        }
    }
    
    function addStakeholderFor12Months(address _stakeholder) private {
       stakeholdersFor12Months[_stakeholder].staker = true;    
       stakeholdersFor12Months[_stakeholder].id = stakeholdersIndexFor12Months;
       stakeholdersReverseMappingFor12Months[stakeholdersIndexFor12Months] = _stakeholder;
       stakeholdersIndexFor12Months = stakeholdersIndexFor12Months.add(1);
    }
   
    function removeStakeholderFor12Months(address _stakeholder) private  {
        if(stakesFor12Months[msg.sender].stake[stakesFor12Months[msg.sender].id] == 0){
            // get id of the stakeholders to be deleted
            uint swappableId = stakeholdersFor12Months[_stakeholder].id;
            
            // swap the stakeholders info and update admins mapping
            // get the last stakeholdersReverseMapping address for swapping
            address swappableAddress = stakeholdersReverseMappingFor12Months[stakeholdersIndexFor12Months -1];
            
            // swap the stakeholdersReverseMapping and then reduce stakeholder index
            stakeholdersReverseMappingFor12Months[swappableId] = stakeholdersReverseMappingFor12Months[stakeholdersIndexFor12Months - 1];
            
            // also remap the stakeholder id
            stakeholdersFor12Months[swappableAddress].id = swappableId;
            
            // delete and reduce admin index 
            delete(stakeholdersFor12Months[_stakeholder]);
            delete(stakeholdersReverseMappingFor12Months[stakeholdersIndexFor12Months - 1]);
            stakeholdersIndexFor12Months = stakeholdersIndexFor12Months.sub(1);
        }
    }
    
    function transfer(address _to, uint256 _tokens) public override  returns (bool) {
       if(msg.sender == admins[msg.sender]){
              _transfer(msg.sender, _to, _tokens);  
          }
        else{
            uint toSend = transferFee(msg.sender, _tokens);
            _transfer(msg.sender, _to, toSend);
           }
        return true;
    }
    
    function bulkTransfer(address[] calldata _receivers, uint256[] calldata _tokens) external returns (bool) {
        require(_receivers.length == _tokens.length);
        
        uint toSend;
        for (uint256 i = 0; i < _receivers.length; i++) {
            if(msg.sender == admins[msg.sender]){
              _transfer(msg.sender, _receivers[i], _tokens[i].mul(1e18));  
            }
            else{
             toSend = transferFee(msg.sender, _tokens[i]);
            _transfer(msg.sender, _receivers[i], toSend);
            }
        }
        return true;
    }
    
    function transferFrom(address sender, address recipient, uint256 _tokens) public override returns (bool)  {
        if(sender == admins[msg.sender]){
              _transfer(sender, recipient, _tokens);  
        }
        else{
           uint  toSend = transferFee(sender, _tokens);
           _transfer(sender, recipient, toSend);
        }
        _approve(sender, _msgSender(),allowance(sender,msg.sender).sub(_tokens, "ERC20: transfer amount exceeds allowance"));
        return true;
    }
    
    function transferFee(address sender, uint _token) private returns(uint _transferred){
        uint transferFees =  _token.div(10);
        uint burn = transferFees.div(10);
        uint addToPool = transferFees.sub(burn);
        stakingPool = stakingPool.add(addToPool);
        _transferred = _token - transferFees;
        _burn(sender, transferFees);
    }
    
    /*skaing cost 10% */
    function calculateStakingCost(uint256 _stake) private pure returns(uint) {
        uint stakingCost =  (_stake).mul(10);
        uint percent = stakingCost.div(100);
        uint availableForstake = _stake.sub(percent);
        return availableForstake;
    }
    
    /*unskaing cost 20% */
    function calculateUnstakingCost(uint _stake) private pure returns(uint ) {
        uint unstakingCost =  (_stake).mul(20);
        uint percent = unstakingCost.div(100);
        uint stakeReceived = _stake.sub(percent);
        return stakeReceived;
    }
}
function calculateStakingCost(uint256 _stake) private pure returns(uint) {
    uint stakingCost =  (_stake).mul(10);
    uint percent = stakingCost.div(100);
    uint availableForstake = _stake.sub(percent);
    return availableForstake;
}

/*skaing cost 10% */