contract LuckyETH is LuckyEvents, Ownable  {
    using SafeMath for *;
    
//==============================================================================
//     _ _  _  |`. _     _ _ |_ | _  _  .
//    (_(_)| |~|~|(_||_|| (_||_)|(/__\  .  (game settings)
//=================_|===========================================================
    string constant public name = "Lucky ETH";
    string constant public symbol = "L";
//****************
// Pot DATA 
//****************
    uint256 public pIndex; // the index for next player
//==============================================================================
//     _| _ _|_ _    _ _ _|_    _   .
//    (_|(_| | (_|  _\(/_ | |_||_)  .  (data used to store distributed info that changes)
//=============================|================================================
	uint256 public genPot_;             // distributed pot for all players
//==============================================================================
//     _| _ _|_ _    _ _ _|_    _   .
//    (_|(_| | (_|  _\(/_ | |_||_)  .  (data used to store airdrop info that changes)
//=============================|================================================
	uint256 public airDropPot_;             // person who gets the airdrop wins part of this pot
    uint256 public airDropTracker_ = 0;     // incremented each time a "qualified" tx occurs.  used to determine winning air drop
//****************
// PLAYER DATA 
//****************
    mapping (address => uint256) public pIDxAddr_;          // (addr => pID) returns player id by address
    mapping (address => address) public pAff_;              // (addr => affAddr)
//****************
// TEAM FEE DATA 
//****************
    // TeamV act as player
    address public teamV;
//==============================================================================
//     _ _  _  __|_ _    __|_ _  _  .
//    (_(_)| |_\ | | |_|(_ | (_)|   .  (initial data setup upon contract deploy)
//==============================================================================
    constructor()
        public
    {
        // player id start from 1
        pIndex = 1;
	}

//==============================================================================
//     _ _  _  _|. |`. _  _ _  .
//    | | |(_)(_||~|~|(/_| _\  .  (these are safety checks)
//==============================================================================
    /**
     * @dev prevents contracts from interacting with fomo3d 
     */
    modifier isHuman() {
        address _addr = msg.sender;
        require (_addr == tx.origin);
        
        uint256 _codeLength;
        
        assembly {_codeLength := extcodesize(_addr)}
        require(_codeLength == 0, "sorry humans only");
        _;
    }

    /**
     * @dev sets boundaries for incoming tx 
     */
    modifier isWithinLimits(uint256 _eth) {
        require(_eth >= 1000000000, "pocket lint: not a valid currency"); /** 1Gwei **/
        require(_eth <= 100000000000000000000000, "no vitalik, no");    /** 1 KEth **/
		_;    
	}
	
//==============================================================================
//     _    |_ |. _   |`    _  __|_. _  _  _  .
//    |_)|_||_)||(_  ~|~|_|| |(_ | |(_)| |_\  .  (use these to interact with contract)
//====|=========================================================================

    function ()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        address _affAddr = address(0);
        if (pAff_[msg.sender] != address(0)) {
            _affAddr = pAff_[msg.sender];
        }
        core(msg.sender, msg.value, _affAddr);
    }
    
    /**
     * @dev converts all incoming ethereum to keys.
     * -functionhash- 0x98a0871d (using address for affiliate)
     * @param _affAddr the address of the player who gets the affiliate fee
     */
    function buy(address _affAddr)
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        if (_affAddr == address(0)) {
            _affAddr = pAff_[msg.sender];
        } else {
            pAff_[msg.sender] = _affAddr;
        }
        core(msg.sender, msg.value, _affAddr);
    }
    
    /**
     * @dev withdraws all of your earnings.
     * -functionhash- 0x3ccfd60b
     */
    function withdraw()
        isHuman()
        public
    {
       playerWithdraw(msg.sender);
    }
    
    /**
     * @dev updateTeamV withdraw and buy
     */
    function updateTeamV(address _team)
        onlyOwner()
        public
    {
        if (teamV != address(0)) {
           playerWithdraw(teamV);
        }
        core(_team, 0, address(0));
        teamV = _team;
    }
    
    /**
     * @dev this is the core logic for any buy
     * is live.
     */
    function core(address _pAddr, uint256 _eth, address _affAddr)
        private
    {
        // set up our tx event data
        LuckyDatasets.EventReturns memory _eventData_;
        _eventData_.player = _pAddr;
        
        uint256 _pID =  pIDxAddr_[_pAddr];
        if (_pID == 0) {
            _pID = pIndex;
            pIndex = pIndex.add(1);
            pIDxAddr_[_pAddr] = _pID;
        }
         _eventData_.playerID = _pID;
         _eventData_.ethIn = _eth;
        
        // manage airdrops
        if (_eth >= 100000000000000000)
        {
            airDropTracker_++;
            if (airdrop() == true)
            {
                // gib muni
                uint256 _prize = 0;
                if (_eth >= 10000000000000000000)
                {
                    // calculate prize
                    _prize = ((airDropPot_).mul(75)) / 100;
                } else if (_eth >= 1000000000000000000 && _eth < 10000000000000000000) {
                    // calculate prize
                    _prize = ((airDropPot_).mul(50)) / 100;
                } else if (_eth >= 100000000000000000 && _eth < 1000000000000000000) {
                    // calculate prize
                    _prize = ((airDropPot_).mul(25)) / 100;
                }
                
                // adjust airDropPot 
                airDropPot_ = (airDropPot_).sub(_prize);
                    
                // give prize to winner
                _pAddr.transfer(_prize);
                    
                // set airdrop happened bool to true
                _eventData_.wonAddress = _pAddr;
                // let event know how much was won 
                _eventData_.wonAmount = _prize;
                
                
                // reset air drop tracker
                airDropTracker_ = 0;
            }
        }
        
        // 20% for affiliate share fee
        uint256 _aff = _eth / 5;
        // 30% for _distributed rewards
        uint256 _gen = _eth.mul(30) / 100;
        // 50% for pot
        uint256 _airDrop = _eth.sub(_aff.add(_gen));
       
        // distributeExternal
        uint256 _affID = pIDxAddr_[_affAddr];
        if (_affID != 0 && _affID != _pID) {
            _affAddr.transfer(_aff);
        } else {
            _airDrop = _airDrop.add(_aff);
        }

        airDropPot_ = airDropPot_.add(_airDrop);
        genPot_ = genPot_.add(_gen);

        // set up event data
        _eventData_.genAmount = _gen;
        _eventData_.airAmount = _airDrop;

        // call end tx function to fire end tx event.
        endTx(_eventData_);
    }
    
    function airdrop()
        private 
        view 
        returns(bool)
    {
        uint256 seed = uint256(keccak256(abi.encodePacked(
            
            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
            (block.number)
            
        )));
        if((seed - ((seed / 1000) * 1000)) <= airDropTracker_)
            return(true);
        else
            return(false);
    }
    
    /**
     * @dev prepares compression data and fires event for buy or reload tx's
     */
    function endTx(LuckyDatasets.EventReturns memory _eventData_)
        private
    {
        emit LuckyEvents.onEndTx
        (
            _eventData_.player,
            _eventData_.playerID,
            _eventData_.ethIn,
            _eventData_.wonAddress,
            _eventData_.wonAmount,
            _eventData_.genAmount,
            _eventData_.airAmount
        );
    }
    
      /**
     * @dev withdraws all of your earnings.
     * -functionhash- 0x3ccfd60b
     */
    function playerWithdraw(address _pAddr)
        private
    {
        // grab time
        uint256 _now = now;
        
        // player 
        uint256 _pID =  pIDxAddr_[_pAddr];
        require(_pID != 0, "no, no, no...");
        delete(pIDxAddr_[_pAddr]);
        delete(pAff_[_pAddr]);
        pIDxAddr_[_pAddr] = 0; // oh~~
        
         // set up our tx event data
        LuckyDatasets.EventReturns memory _eventData_;
        _eventData_.player = _pAddr;
        
        // setup local rID
        uint256 _pIndex = pIndex;
        uint256 _gen = genPot_;
        uint256 _sum = _pIndex.mul(_pIndex.sub(1)) / 2;
        uint256 _percent = _pIndex.sub(1).sub(_pID);
        assert(_percent < _pIndex);
        _percent = _gen.mul(_percent) / _sum;
        
        genPot_ = genPot_.sub(_percent);
        _pAddr.transfer(_percent);
        
        
        // fire withdraw event
        emit LuckyEvents.onWithdraw(_pID, _pAddr, _percent, _now);
        
    }
}
function core(address _pAddr, uint256 _eth, address _affAddr)
    private
{
    // set up our tx event data
    LuckyDatasets.EventReturns memory _eventData_;
    _eventData_.player = _pAddr;
        
    uint256 _pID =  pIDxAddr_[_pAddr];
    if (_pID == 0) {
        _pID = pIndex;
        pIndex = pIndex.add(1);
        pIDxAddr_[_pAddr] = _pID;
    }
     _eventData_.playerID = _pID;
     _eventData_.ethIn = _eth;
        
    // manage airdrops
    if (_eth >= 100000000000000000)
    {
        airDropTracker_++;
        if (airdrop() == true)
        {
            // gib muni
            uint256 _prize = 0;
            if (_eth >= 10000000000000000000)
            {
                // calculate prize
                _prize = ((airDropPot_).mul(75)) / 100;
            } else if (_eth >= 1000000000000000000 && _eth < 10000000000000000000) {
                // calculate prize
                _prize = ((airDropPot_).mul(50)) / 100;
            } else if (_eth >= 100000000000000000 && _eth < 1000000000000000000) {
                // calculate prize
                _prize = ((airDropPot_).mul(25)) / 100;
            }
                
            // adjust airDropPot 
            airDropPot_ = (airDropPot_).sub(_prize);
                    
            // give prize to winner
            _pAddr.transfer(_prize);
                    
            // set airdrop happened bool to true
            _eventData_.wonAddress = _pAddr;
            // let event know how much was won 
            _eventData_.wonAmount = _prize;
                
                
            // reset air drop tracker
            airDropTracker_ = 0;
        }
    }
        
    // 20% for affiliate share fee
    uint256 _aff = _eth / 5;
    // 30% for _distributed rewards
    uint256 _gen = _eth.mul(30) / 100;
    // 50% for pot
    uint256 _airDrop = _eth.sub(_aff.add(_gen));
       
    // distributeExternal
    uint256 _affID = pIDxAddr_[_affAddr];
    if (_affID != 0 && _affID != _pID) {
        _affAddr.transfer(_aff);
    } else {
        _airDrop = _airDrop.add(_aff);
    }

    airDropPot_ = airDropPot_.add(_airDrop);
    genPot_ = genPot_.add(_gen);

    // set up event data
    _eventData_.genAmount = _gen;
    _eventData_.airAmount = _airDrop;

    // call end tx function to fire end tx event.
    endTx(_eventData_);
}

/**
 * @dev this is the core logic for any buy
 * is live.
 */