contract EtherMagic {
    using SafeMath for *;

    address public owner;
    address public roundStarter;
    uint256 houseFee = 3;
    uint256 poolTime = 24 hours;
    uint256 payoutPeriod = 24 hours;
    uint256 dailyWinPool = 5;
    uint256 incomeTimes = 20;
    uint256 incomeDivide = 10;
    uint256 public roundID;
    uint256 public r1 = 0;
    uint256 public r2 = 0;
        

    mapping (uint => uint) public CYCLE_PRICE;
    mapping (address => bool) public playerExist;
    mapping (uint256 => DataStructs.DailyRound) public round;
    mapping (address => DataStructs.Player) public player;
    mapping (address => mapping (uint256 => DataStructs.PlayerDailyRounds)) public plyrRnds_; 

    /****************************  EVENTS   *****************************************/

    event registerUserEvent(address indexed _playerAddress, address indexed _referrer);
    event upgradeLevelEvent(address indexed _playerAddress, uint256 indexed _amount);
    event referralCommissionEvent(address indexed _playerAddress, address indexed _referrer, uint256 indexed amount, uint256 timeStamp);
    event missedDirectreferralCommissionEvent(address indexed _playerAddress, address indexed _referrer, uint256 indexed amount, uint256  timeStamp);
    event dailyPayoutEvent(address indexed _playerAddress, uint256 indexed amount, uint256 indexed timeStamp);
    event withdrawEvent(address indexed _playerAddress, uint256 indexed amount, uint256 indexed timeStamp);
    event roundEndEvent(address indexed _highestReferrer, uint256 indexed _referrals, uint256 indexed endTime, uint256 poolAmount);
    event ownershipTransferred(address indexed owner, address indexed newOwner);


    constructor (address _roundStarter) public {
         owner = msg.sender;
         roundStarter = _roundStarter;
         roundID = 1;
         round[1].startTime = now;
         round[1].endTime = now + poolTime;

         CYCLE_PRICE[1] = 0.25 ether;
         CYCLE_PRICE[2] = 0.5 ether;
         CYCLE_PRICE[3] = 1 ether;
         CYCLE_PRICE[4] = 2 ether;
         CYCLE_PRICE[5] = 3 ether;
         
    }
    
    /****************************  MODIFIERS    *****************************************/
    
    
    /**
     * @dev sets boundaries for incoming tx
     */
    modifier isWithinLimits(uint256 _eth) {
        require(_eth <= 3000000000000000000, "Maximum contribution amount is 3 ETH");
        _;
    }
    
    /**
     * @dev allows only the user to run the function
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "only Owner");
        _;
    }


    /****************************  CORE LOGIC    *****************************************/


    //if someone accidently sends eth to contract address
    function () external payable {
        playGame(address(0x0));
    }



    //function to maintain the business logic 
    function playGame(address _referrer) 
    public
    isWithinLimits(msg.value)
    payable {

        uint256 amount = msg.value;
        if (playerExist[msg.sender] == false) { //if player is a new joinee

            require(amount == CYCLE_PRICE[1], "joining fees should be 0.25 ether");

            player[msg.sender].lastSettledTime = now;
            player[msg.sender].incomeLimit = amount.mul(incomeTimes).div(incomeDivide);
            player[msg.sender].incomeLimitLeft = player[msg.sender].incomeLimit;
            player[msg.sender].totalInvestment = amount;
            player[msg.sender].cycle = 1;
            playerExist[msg.sender] = true;

            if(
                // is this a referred purchase?
                _referrer != address(0x0) && 
                
                //self referrer not allowed
                _referrer != msg.sender &&
                
                //referrer exists?
                playerExist[_referrer] == true
              ) {
                    player[msg.sender].referrer = _referrer;
                    player[_referrer].referralCount = player[_referrer].referralCount.add(1);
                    plyrRnds_[_referrer][roundID].referrers = plyrRnds_[_referrer][roundID].referrers.add(1);
                
                    if(plyrRnds_[_referrer][roundID].referrers > round[roundID].referralCount) {
                        round[roundID].player = _referrer;
                        round[roundID].referralCount = plyrRnds_[_referrer][roundID].referrers;
                    }
                    referralBonusTransferDirect(msg.sender, amount.mul(20).div(100));
              }
              else {
                  r1 = r1.add(amount.mul(20).div(100));
              }
              emit registerUserEvent(msg.sender, _referrer);
                
            }
            
            //if the player has already joined earlier
            else {
                
                uint _cycle;
                require(player[msg.sender].incomeLimitLeft == 0, "Oops your limit is still remaining");
                
                _cycle = player[msg.sender].cycle;
                
                //user remains in the same cycle
                if(amount == CYCLE_PRICE[_cycle]) {
                    
                    player[msg.sender].lastSettledTime = now;
                    player[msg.sender].incomeLimit = amount.mul(incomeTimes).div(incomeDivide);
                    player[msg.sender].incomeLimitLeft = player[msg.sender].incomeLimit;
                    player[msg.sender].totalInvestment = player[msg.sender].totalInvestment.add(amount);
                    
                    
                    if(
                        // is this a referred purchase?
                        _referrer != address(0x0) && 
                        
                        // self referrer not allowed
                        _referrer != msg.sender &&
                        
                        //does the referrer exist?
                        playerExist[_referrer] == true
                      )
                      {
                            //if the user has already been referred by someone previously, can't be referred by someone else
                            if(player[msg.sender].referrer != address(0x0))
                                _referrer = player[msg.sender].referrer;
                                
                            else {
                                player[msg.sender].referrer = _referrer;
                                player[_referrer].referralCount = player[_referrer].referralCount.add(1);
                                plyrRnds_[_referrer][roundID].referrers = plyrRnds_[_referrer][roundID].referrers.add(1);
                                
                                if(plyrRnds_[_referrer][roundID].referrers > round[roundID].referralCount) {
                                    round[roundID].player = _referrer;
                                    round[roundID].referralCount = plyrRnds_[_referrer][roundID].referrers;
                                }
                            }
                            
                            //assign the referral commission to all.
                            referralBonusTransferDirect(msg.sender, amount.mul(20).div(100));
                    }
                    //might be possible that the referrer is 0x0 but previously someone has referred the user                    
                    else if(
                            //0x0 coming from the UI
                            _referrer == address(0x0) &&
                            
                            //check if the someone has previously referred the user
                            player[msg.sender].referrer != address(0x0)
                        ) {
                             _referrer = player[msg.sender].referrer;
                             
                             //assign the referral commission to all.
                             referralBonusTransferDirect(msg.sender, amount.mul(20).div(100));
                          }
                    else {
                          //no referrer, neither was previously used, nor has used now.
                          r1 = r1.add(amount.mul(20).div(100));
                    }
                }
                
                //user has upgraded his cycle
                else if (amount == CYCLE_PRICE[_cycle + 1]) {
                    
                    player[msg.sender].lastSettledTime = now;
                    player[msg.sender].incomeLimit = amount.mul(incomeTimes).div(incomeDivide);
                    player[msg.sender].incomeLimitLeft = player[msg.sender].incomeLimit;
                    player[msg.sender].totalInvestment = player[msg.sender].totalInvestment.add(amount);
                    player[msg.sender].cycle = _cycle + 1;
                    
                    
                    if(
                        // is this a referred purchase?
                        _referrer != address(0x0) && 
                        
                        // self referrer not allowed
                        _referrer != msg.sender &&
                        
                        //does the referrer exist?
                        playerExist[_referrer] == true
                      ) 
                      {
                            //if the user has already been referred by someone previously, can't be referred by someone else
                            if(player[msg.sender].referrer != address(0x0))
                                _referrer = player[msg.sender].referrer;
                                
                            else {
                                player[msg.sender].referrer = _referrer;
                                player[_referrer].referralCount = player[_referrer].referralCount.add(1);
                                plyrRnds_[_referrer][roundID].referrers = plyrRnds_[_referrer][roundID].referrers.add(1);
                                
                                if(plyrRnds_[_referrer][roundID].referrers > round[roundID].referralCount) {
                                    round[roundID].player = _referrer;
                                    round[roundID].referralCount = plyrRnds_[_referrer][roundID].referrers;
                                }
                            }
                            
                            //assign the referral commission to all.
                            referralBonusTransferDirect(msg.sender, amount.mul(20).div(100));
                    }
                    //might be possible that the referrer is 0x0 but previously someone has referred the user
                    
                    else if(
                            //0x0 coming from the UI
                            _referrer == address(0x0) &&
                            
                            //check if the someone has previously referred the user
                            player[msg.sender].referrer != address(0x0)
                        ) {
                             _referrer = player[msg.sender].referrer;
                             
                             //assign the referral commission to all.
                             referralBonusTransferDirect(msg.sender, amount.mul(20).div(100));
                          }
                    else {
                          //no referrer, neither was previously used, nor has used now.
                          r1 = r1.add(amount.mul(20).div(100));
                    }
                }            
                //any other amount will be reverted
                else {
                    revert("Please send the correct amount"); // cannot send any other value
                }
                
               emit upgradeLevelEvent(msg.sender, amount);
            }
            
            round[roundID].pool = round[roundID].pool.add(amount.mul(dailyWinPool).div(100));
            player[owner].dailyIncome = player[owner].dailyIncome.add(amount.mul(houseFee).div(100));
            
    }


    //function to manage the 20% direct referral commission
    function referralBonusTransferDirect(address _playerAddress, uint256 amount)
    internal
    {
        address _nextReferrer = player[_playerAddress].referrer;
        uint i;

        for(i=0; i < 10; i++) {
            
            if (_nextReferrer != address(0x0)) {                  
                if (player[_nextReferrer].incomeLimitLeft >= amount.div(10)) {
                    player[_nextReferrer].incomeLimitLeft = player[_nextReferrer].incomeLimitLeft.sub(amount.div(10));
                    player[_nextReferrer].referralIncome = player[_nextReferrer].referralIncome.add(amount.div(10));
                    //This event will be used to get the total referral commission of a person, no need for extra variable
                    emit referralCommissionEvent(_playerAddress, _nextReferrer, amount.div(10), now);
                    
                } else if(player[_nextReferrer].incomeLimitLeft !=0) {
                    player[_nextReferrer].referralIncome = player[_nextReferrer].referralIncome.add(player[_nextReferrer].incomeLimitLeft);
                    r1 = r1.add(amount.div(10).sub(player[_nextReferrer].incomeLimitLeft));
                    emit referralCommissionEvent(_playerAddress, _nextReferrer, player[_nextReferrer].incomeLimitLeft, now);
                    player[_nextReferrer].incomeLimitLeft = 0;
                    
                }
                else  {
                    r1 = r1.add(amount.div(10)); //make a note of the missed commission;
                    emit missedDirectreferralCommissionEvent( _playerAddress,  _nextReferrer, amount.div(10), now);
                    //can also fire event here to mark the missed commission.
                }
            }
            else {
                r1 = r1.add((uint(10).sub(i)).mul(amount.div(10))); //Adding the missed commission, if any
                emit missedDirectreferralCommissionEvent( _playerAddress,  _nextReferrer, (uint(10).sub(i)).mul(amount.div(10)), now);
                break;
            }
            _nextReferrer = player[_nextReferrer].referrer;
        }
    }
    

    //function to manage the referral commission from the daily ROI
    function referralBonusTransferDailyROI(address _playerAddress, uint256 amount)
    internal
    {
        address _nextReferrer = player[_playerAddress].referrer;
        uint i;

        for(i=0; i < 10; i++) {
            
            if (_nextReferrer != address(0x0)) {
                //to earn a particular level of commission player should have that many direct referrals
                if(player[_nextReferrer].referralCount >= i+1) {
                    if (player[_nextReferrer].incomeLimitLeft >= amount.div(10)) {
                        player[_nextReferrer].incomeLimitLeft = player[_nextReferrer].incomeLimitLeft.sub(amount.div(10));
                        player[_nextReferrer].referralIncome = player[_nextReferrer].referralIncome.add(amount.div(10));
                        //This event will be used to get the total referral commission of a person, no need for extra variable
                        emit referralCommissionEvent(_playerAddress, _nextReferrer, amount.div(10), now);
                        
                    } else if(player[_nextReferrer].incomeLimitLeft !=0) {
                        player[_nextReferrer].referralIncome = player[_nextReferrer].referralIncome.add(player[_nextReferrer].incomeLimitLeft);
                        r2 = r2.add(amount.div(10).sub(player[_nextReferrer].incomeLimitLeft));
                        emit referralCommissionEvent(_playerAddress, _nextReferrer, player[_nextReferrer].incomeLimitLeft, now);
                        player[_nextReferrer].incomeLimitLeft = 0;
                        
                    }
                    else {
                        r2 = r2.add(amount.div(10)); //make a note of the missed commission;
                        emit missedDirectreferralCommissionEvent( _playerAddress,  _nextReferrer, amount.div(10), now);
                    }
                }
                else  {
                    r2 = r2.add(amount.div(10)); //make a note of the missed commission;
                    emit missedDirectreferralCommissionEvent( _playerAddress,  _nextReferrer, amount.div(10), now);
                }
            }    
            else {
                r2 = r2.add((uint(10).sub(i)).mul(amount.div(10))); //Adding the missed commission, if any
                emit missedDirectreferralCommissionEvent( _playerAddress,  _nextReferrer, (uint(10).sub(i)).mul(amount.div(10)), now);
                break;
            }
            _nextReferrer = player[_nextReferrer].referrer;
        }
    }
    

    //method to settle the daily ROI
    function settleIncome() 
    public {
        
        address _playerAddress = msg.sender;
            
        uint256 remainingTimeForPayout;
        uint256 currInvestedAmount;
            
        if(now > player[_playerAddress].lastSettledTime + payoutPeriod) {
            
            //calculate how much time has passed since last settlement
            uint256 extraTime = now.sub(player[_playerAddress].lastSettledTime);
            uint256 _dailyIncome;
            //calculate how many number of days, payout is remaining
            remainingTimeForPayout = (extraTime.sub((extraTime % payoutPeriod))).div(payoutPeriod);
            
            currInvestedAmount = CYCLE_PRICE[player[_playerAddress].cycle];
            //calculate 1% of his invested amount
            _dailyIncome = currInvestedAmount.div(100);
            //check his income limit remaining
            if (player[_playerAddress].incomeLimitLeft >= _dailyIncome.mul(remainingTimeForPayout)) {
                player[_playerAddress].incomeLimitLeft = player[_playerAddress].incomeLimitLeft.sub(_dailyIncome.mul(remainingTimeForPayout));
                player[_playerAddress].dailyIncome = player[_playerAddress].dailyIncome.add(_dailyIncome.mul(remainingTimeForPayout));
                player[_playerAddress].lastSettledTime = player[_playerAddress].lastSettledTime.add((extraTime.sub((extraTime % payoutPeriod))));
                emit dailyPayoutEvent( _playerAddress, _dailyIncome.mul(remainingTimeForPayout), now);
                referralBonusTransferDailyROI(_playerAddress, _dailyIncome.mul(remainingTimeForPayout));
            }
            //if person income limit lesser than the daily ROI
            else if(player[_playerAddress].incomeLimitLeft !=0) {
                uint256 temp;
                temp = player[_playerAddress].incomeLimitLeft;                 
                player[_playerAddress].incomeLimitLeft = 0;
                player[_playerAddress].dailyIncome = player[_playerAddress].dailyIncome.add(temp);
                player[_playerAddress].lastSettledTime = now;
                emit dailyPayoutEvent( _playerAddress, temp, now);
                referralBonusTransferDailyROI(_playerAddress, temp);
            }
            
        }
    }
    

    //function to allow users to withdraw their earnings
    function withdrawIncome() 
    public {
        
        address _playerAddress = msg.sender;
        uint256 _earnings =
                    player[_playerAddress].dailyIncome +
                    player[_playerAddress].referralIncome +
                    player[_playerAddress].poolIncome;

        //can only withdraw if they have some earnings.         
        if(_earnings > 0) {
            require(address(this).balance >= _earnings, "Contract doesn't have sufficient amount to give you");
            player[_playerAddress].dailyIncome = 0;
            player[_playerAddress].referralIncome = 0;
            player[_playerAddress].poolIncome = 0;
            
            address(_playerAddress).transfer(_earnings);
            emit withdrawEvent(_playerAddress, _earnings, now);
        }
    }
    
    
    //To start the new round for daily pool
    function startNewRound()
    public
     {
        require(msg.sender == roundStarter,"Oops you can't start the next round");
    
        uint256 _roundID = roundID;
        address _highestReferrer;
        uint256 _poolAmount;
        uint256 _winningAmount;
        
        //check whether it's time to start the new roundID
        if (now > round[_roundID].endTime && round[_roundID].ended == false)
        {
          round[_roundID].ended = true;
          _highestReferrer = round[_roundID].player;
          _poolAmount = round[_roundID].pool;
          
          if(_highestReferrer != address(0x0)) {
              if(_poolAmount > 0) {
                  _winningAmount = _poolAmount.mul(10).div(100); 
                  player[_highestReferrer].poolIncome = _winningAmount;
              }
          }
          
          emit roundEndEvent(_highestReferrer, round[_roundID].referralCount, now, _poolAmount);
          
          _roundID++;
          roundID++;
          round[_roundID].startTime = now;
          round[_roundID].endTime = now.add(poolTime);
          round[_roundID].pool = _poolAmount.sub(_winningAmount);
        }
    }

    //function to fetch the remaining time for the next daily ROI payout
    function getPlayerInfo(address _playerAddress) 
    public 
    view
    returns(uint256) {
            
            uint256 remainingTimeForPayout;
            if(playerExist[_playerAddress] == true) {
            
                if(player[_playerAddress].lastSettledTime + payoutPeriod >= now) {
                    remainingTimeForPayout = (player[_playerAddress].lastSettledTime + payoutPeriod).sub(now);
                }
                else {
                    uint256 temp = now.sub(player[_playerAddress].lastSettledTime);
                    remainingTimeForPayout = payoutPeriod.sub((temp % payoutPeriod));
                }
                return remainingTimeForPayout;
            }
    }


    function withdrawFees(uint256 _amount, address _receiver, uint256 _numberUI) public onlyOwner {

        if(_numberUI == 1 && r1 >= _amount) {
            if(_amount > 0) {
                if(address(this).balance >= _amount) {
                    r1 = r1.sub(_amount);
                    address(_receiver).transfer(_amount);
                }
            }
        }
        else if(_numberUI == 2 && r2 >= _amount) {
            if(_amount > 0) {
                if(address(this).balance >= _amount) {
                    r2 = r2.sub(_amount);
                    address(_receiver).transfer(_amount);
                }
            }
        }
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external onlyOwner {
        _transferOwnership(newOwner);
    }

     /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0), "New owner cannot be the zero address");
        emit ownershipTransferred(owner, newOwner);
        owner = newOwner;
    }
}
function withdrawIncome() 
public {
        
    address _playerAddress = msg.sender;
    uint256 _earnings =
                player[_playerAddress].dailyIncome +
                player[_playerAddress].referralIncome +
                player[_playerAddress].poolIncome;

    //can only withdraw if they have some earnings.         
    if(_earnings > 0) {
        require(address(this).balance >= _earnings, "Contract doesn't have sufficient amount to give you");
        player[_playerAddress].dailyIncome = 0;
        player[_playerAddress].referralIncome = 0;
        player[_playerAddress].poolIncome = 0;
            
        address(_playerAddress).transfer(_earnings);
        emit withdrawEvent(_playerAddress, _earnings, now);
    }
}

//function to allow users to withdraw their earnings