contract NTA3D is NTA3DEvents {
    using SafeMath for *;
    using NameFilter for string;
    using NTA3DKeysCalc for uint256;

    string constant public name = "No Tricks Allow 3D";
    string constant public symbol = "NTA3D";
    bool activated_;
    address admin;
    uint256 constant private rndStarts = 12 hours; // ??need to be continue
    uint256 constant private rndPerKey = 15 seconds; // every key increase seconds
    uint256 constant private rndMax = 12 hours;  //max count down time;
    uint256 constant private cardValidity = 1 hours; //stock cards validity
    uint256 constant private cardPrice = 0.05 ether; //stock cards validity
    uint256 constant private DIVIDE = 1000; // common divide tool

    uint256 constant private smallDropTrigger = 50 ether;
    uint256 constant private bigDropTrigger = 300000 * 1e18;
    uint256 constant private keyPriceTrigger = 50000 * 1e18;
    uint256 constant private keyPriceFirst = 0.0005 ether;
    uint256 constant private oneOffInvest1 = 0.1 ether;//VIP 1
    uint256 constant private oneOffInvest2 = 1 ether;// VIP 2

    //uint256 public airDropTracker_ = 0;
    uint256 public rID;    // round id number / total rounds that have happened
    uint256 public pID;    // total players

    //player map data
    mapping (address => uint256) public pIDxAddr; // get pid by address
    mapping (bytes32 => uint256) public pIDxName; // get name by pid
    mapping (uint256 => NTAdatasets.Player) public pIDPlayer; // get player struct by pid\
    mapping (uint256 => mapping (uint256 => NTAdatasets.PlayerRound)) public pIDPlayerRound; // pid => rid => playeround

    //stock cards
    mapping (uint256 => NTAdatasets.Card) cIDCard; //get card by cID
    address cardSeller;

    //team map data
    //address gameFundsAddr = 0xFD7A82437F7134a34654D7Cb8F79985Df72D7076;
    address[11] partner;
    address to06;
    address to04;
    address to20A;
    address to20B;
    mapping (address => uint256) private gameFunds; // game develeopment get 5% funds
    //uint256 private teamFee;    // team Fee 5%

    //round data
    mapping (uint256 => NTAdatasets.Round) public rIDRound;   // round data

    // team dividens
    mapping (uint256 => NTAdatasets.Deposit) public deposit;
    mapping (uint256 => NTAdatasets.PotSplit) public potSplit;

    constructor() public {

        //constructor
        activated_ = false;
        admin = msg.sender;
        // Team allocation structures
        // 0 = BISHOP
        // 1 = ROOK
                // BISHOP team: ==> |46% to all, 17% to winnerPot, 5% to develop funds, 5% to teamfee, 10% to cards,
        //                  |7% to fisrt degree invatation
        //                  |3% to second degree invatation, 2% to big airdrop, 5% to small airdrop
        deposit[0] = NTAdatasets.Deposit(460, 170, 50, 50, 100, 100, 20, 50);
        // ROOK team:   ==> |20% to all, 43% to winnerPot, 5% to develop funds, 5% to teamfee, 10% to cards,
        //                  |7% to fisrt degree invatation
        //                  |3% to second degree invatation, 2% to big airdrop, 5% to small airdrop
        deposit[1] = NTAdatasets.Deposit(200, 430, 50, 50, 100, 100, 20, 50);

        // potSplit:    ==> |20% to all, 45% to lastwinner, 5% to inviter 1st, 3% to inviter 2nd, 2% to inviter 3rd,
        //                  |8% to key buyer 1st, 5% to key buyer 2nd, 2% to key buyer 3rd, 10% to next round
        potSplit[0] = NTAdatasets.PotSplit(200, 450, 50, 30, 20, 80, 50, 20, 100);
        potSplit[1] = NTAdatasets.PotSplit(200, 450, 50, 30, 20, 80, 50, 20, 100);
    }

//==============================================================================
//
//    safety checks
//==============================================================================
    //tested
    modifier isActivated() {
        require(activated_ == true, "its not ready yet");
        _;
    }

    /**
     * @dev prevents contracts from interacting with fomo3d
     */
    //tested
    modifier isHuman() {
        address _addr = msg.sender;
        uint256 _codeLength;

        assembly {_codeLength := extcodesize(_addr)}
        require(_codeLength == 0, "sorry humans only");
        _;
    }
    //tested
    modifier isAdmin() {require(msg.sender == admin, "its can only be call by admin");_;}

    /**
     * @dev sets boundaries for incoming tx
     */
    //tested
    modifier isWithinLimits(uint256 _eth) {
        require(_eth >= 1000000000, "pocket lint: not a valid currency");
        require(_eth <= 100000000000000000000000, "no vitalik, no");
        _;
    }

//==============================================================================
//
//    admin functions
//==============================================================================
    //tested
    function activeNTA() isAdmin() public {
        activated_ = true;
        partner[0] = 0xE27Aa5E7D8906779586CfB9DbA2935BDdd7c8210;
        partner[1] = 0xD4638827Dc486cb59B5E5e47955059A160BaAE13;
        partner[2] = 0xa088c667591e04cC78D6dfA3A392A132dc5A7f9d;
        partner[3] = 0xed38deE26c751ff575d68D9Bf93C312e763f8F87;
        partner[4] = 0x42A7B62f71b7778279DA2639ceb5DD6ee884f905;
        partner[5] = 0xd471409F3eFE9Ca24b63855005B08D4548742a5b;
        partner[6] = 0x41c9F005eD19C2B620152f5562D26029b32078B6;
        partner[7] = 0x11b85bc860A6C38fa7fe6f54f18d350eF5f2787b;
        partner[8] = 0x11a7c5E7887F2C34356925275882D4321a6B69A8;
        partner[9] = 0xB5754c7bD005b6F25e1FDAA5f94b2b71e6eA260f;
        partner[10] = 0x6fbC15cF6d0B05280E99f753E45B631815715E99;
        to06 = 0x9B53CC857cD9DD5EbE6bc07Bde67D8CE4076345f;
        to04 = 0x5835a72118c0C784203B8d39936A0875497B6eCa;
        to20A = 0xEc2441D3113fC2376cd127344331c0F1b959Ce1C;
        to20B = 0xd1Dac908c97c0a885e9B413a84ACcC0010C002d2;
        //card
        cardSeller = 0xeE4f032bdB0f9B51D6c7035d3DEFfc217D91225C;
    }
    //tested
    function startFirstRound() isAdmin() isActivated() public {
        //must not open before
        require(rID == 0);
        newRound(0);
    }

    function teamWithdraw() public
    isHuman()
    isActivated()
    {
        uint256 _temp;
        address to = msg.sender;
        require(gameFunds[to] != 0, "you dont have funds");
        _temp = gameFunds[to];
        gameFunds[to] = 0;
        to.transfer(_temp);
    }

    function getTeamFee(address _addr)
    public
    view
    returns(uint256) {
        return gameFunds[_addr];
    }

    function getScore(address _addr)
    public
    view
    isAdmin()
    returns(uint256) {
        uint256 _pID = pIDxAddr[_addr];
        if(_pID == 0 ) return 0;
        else return pIDPlayerRound[_pID][rID].score;
    }

    function setInviterCode(address _addr, uint256 _inv, uint256 _vip, string _nameString)
    public
    isAdmin() {
        //this is for popularzing channel
        bytes32 _name = _nameString.nameFilter();
        uint256 temp = pIDxName[_name];
        require(temp == 0, "name already regist");
        uint256 _pID = pIDxAddr[_addr];
        if(_pID == 0) {
            pID++;
            _pID = pID;
            pIDxAddr[_addr] = _pID;
            pIDPlayer[_pID].addr = _addr;
            pIDPlayer[_pID].name = _name;
            pIDxName[_name] = _pID;
            pIDPlayer[_pID].inviter1 = _inv;
            pIDPlayer[_pID].vip = _vip;
        } else {
            if(_inv != 0)
                pIDPlayer[_pID].inviter1 = _inv;
            pIDPlayer[_pID].name = _name;
            pIDxName[_name] = _pID;
            pIDPlayer[_pID].vip = _vip;
        }
    }

//==============================================================================
//
//    player functions
//==============================================================================
    //emergency buy uses BISHOP team to buy keys
    function()
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        //fetch player
        require(rID != 0, "No round existed yet");
        uint256 _pID = managePID(0);
        //buy key
        buyCore(_pID, 0);
    }

    // buy with ID: inviter use pID to invate player to buy like "www.NTA3D.io/?id=101"
    function buyXid(uint256 _team,uint256 _inviter)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        require(rID != 0, "No round existed yet");
        uint256 _pID = managePID(_inviter);
        if (_team < 0 || _team > 1 ) {
            _team = 0;
        }
        buyCore(_pID, _team);
    }

    // buy with ID: inviter use pID to invate player to buy like "www.NTA3D.io/?n=obama"
    function buyXname(uint256 _team,string _invName)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        require(rID != 0, "No round existed yet");
        bytes32 _name = _invName.nameFilter();
        uint256 _invPID = pIDxName[_name];
        uint256 _pID = managePID(_invPID);
        if (_team < 0 || _team > 1 ) {
            _team = 0;
        }
        buyCore(_pID, _team);
    }

    function buyCardXname(uint256 _cID, string _invName)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable {
        uint256 _value = msg.value;
        uint256 _now = now;
        require(_cID < 20, "only has 20 cards");
        require(_value == cardPrice, "the card cost 0.05 ether");
        require(cIDCard[_cID].owner == 0 || (cIDCard[_cID].buyTime + cardValidity) < _now, "card is in used");
        bytes32 _name = _invName.nameFilter();
        uint256 _invPID = pIDxName[_name];
        uint256 _pID = managePID(_invPID);
        for (uint i = 0; i < 20; i++) {
            require(_pID != cIDCard[i].owner, "you already registed a card");
        }
        gameFunds[cardSeller] = gameFunds[cardSeller].add(_value);
        cIDCard[_cID].addr = msg.sender;
        cIDCard[_cID].owner = _pID;
        cIDCard[_cID].buyTime = _now;
        cIDCard[_cID].earnings = 0;
        emit onBuyCard(_pID, pIDPlayer[_pID].addr, pIDPlayer[_pID].name, _cID, _value, now);
    }

    function buyCardXid(uint256 _cID, uint256 _inviter)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable {
        uint256 _value = msg.value;
        uint256 _now = now;
        require(_cID < 20, "only has 20 cards");
        require(_value == cardPrice, "the card cost 0.05 ether");
        require(cIDCard[_cID].owner == 0 || (cIDCard[_cID].buyTime + cardValidity) < _now, "card is in used");
        uint256 _pID = managePID(_inviter);
        for (uint i = 0; i < 20; i++) {
            require(_pID != cIDCard[i].owner, "you already registed a card");
        }
        gameFunds[cardSeller] = gameFunds[cardSeller].add(_value);
        cIDCard[_cID].addr = msg.sender;
        cIDCard[_cID].owner = _pID;
        cIDCard[_cID].buyTime = _now;
        cIDCard[_cID].earnings = 0;
        emit onBuyCard(_pID, pIDPlayer[_pID].addr, pIDPlayer[_pID].name, _cID, _value, now);
    }


    // regist a name
    function registNameXid(string _nameString, uint256 _inviter)
    isActivated()
    isHuman()
    public {
        bytes32 _name = _nameString.nameFilter();
        uint256 temp = pIDxName[_name];
        require(temp == 0, "name already regist");
        uint256 _pID = managePID(_inviter);
        pIDxName[_name] = _pID;
        pIDPlayer[_pID].name = _name;
    }

    function registNameXname(string _nameString, string _inviterName)
    isActivated()
    isHuman()
    public {
        bytes32 _name = _nameString.nameFilter();
        uint256 temp = pIDxName[_name];
        require(temp == 0, "name already regist");
        bytes32 _invName = _inviterName.nameFilter();
        uint256 _invPID = pIDxName[_invName];
        uint256 _pID = managePID(_invPID);
        pIDxName[_name] = _pID;
        pIDPlayer[_pID].name = _name;
    }

    function withdraw()
    isActivated()
    isHuman()
    public {
        // setup local rID
        uint256 _rID = rID;
        // grab time
        uint256 _now = now;
        uint256 _pID = pIDxAddr[msg.sender];
        require(_pID != 0, "cant find user");
        uint256 _eth = 0;
        if (rIDRound[_rID].end < _now && rIDRound[_rID].ended == false) {
            rIDRound[_rID].ended = true;
            endRound();
        }
        // get their earnings
        _eth = withdrawEarnings(_pID);
        // gib moni
        if (_eth > 0)
            pIDPlayer[_pID].addr.transfer(_eth);
        //emit
        emit onWithdraw(_pID, pIDPlayer[_pID].addr, pIDPlayer[_pID].name, _eth, now);
    }
//==============================================================================
//
//    view functions
//==============================================================================
    /**
     * return the price buyer will pay for next 1 individual key.
     * @return price for next key bought (in wei format)
     */
     //tested
    function getBuyPrice(uint256 _key)
    public
    view
    returns(uint256) {
        // setup local rID
        uint256 _rID = rID;
        // grab time
        uint256 _now = now;
        uint256 _keys = rIDRound[_rID].team1Keys + rIDRound[_rID].team2Keys;
        // round is active
        if (rIDRound[_rID].end >= _now || (rIDRound[_rID].end < _now && rIDRound[_rID].leadPID == 0))
            return _keys.ethRec(_key * 1e18);
        else
            return keyPriceFirst;
    }

    /**
     * @dev returns time left.  dont spam this, you'll ddos yourself from your node
     * @return time left in seconds
     */
     //tested
    function getTimeLeft()
    public
    view
    returns(uint256) {
        // setup local rID
        uint256 _rID = rID;
        // grab time
        uint256 _now = now;

        if (rIDRound[_rID].end >= _now)
            return (rIDRound[_rID].end.sub(_now));
        else
            return 0;
    }

    //tested
    function getPlayerVaults()
    public
    view
    returns(uint256, uint256, uint256, uint256, uint256) {
        // setup local rID
        uint256 _rID = rID;
        uint256 _now = now;
        uint256 _pID = pIDxAddr[msg.sender];
        if (_pID == 0)
            return (0, 0, 0, 0, 0);
        uint256 _last = pIDPlayer[_pID].lrnd;
        uint256 _inv = pIDPlayerRound[_pID][_last].inv;
        uint256 _invMask = pIDPlayerRound[_pID][_last].invMask;
        // if round has ended.  but round end has not been run (so contract has not distributed winnings)
        if (rIDRound[_rID].end < _now && rIDRound[_rID].ended == false && rIDRound[_rID].leadPID != 0) {
            // if player is winner
            if (rIDRound[_rID].leadPID == _pID)
                return (
                    (pIDPlayer[_pID].win).add((rIDRound[_rID].pot).mul(45) / 100),
                    pIDPlayer[_pID].gen.add(calcUnMaskedEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].inv.add(_inv).sub0(_invMask),
                    pIDPlayer[_pID].tim.add(calcTeamEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].crd
                );
            else
                return (
                    pIDPlayer[_pID].win,
                    pIDPlayer[_pID].gen.add(calcUnMaskedEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].inv.add(_inv).sub0(_invMask),
                    pIDPlayer[_pID].tim.add(calcTeamEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].crd
                );
        } else {
             return (
                    pIDPlayer[_pID].win,
                    pIDPlayer[_pID].gen.add(calcUnMaskedEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].inv.add(_inv).sub0(_invMask),
                    pIDPlayer[_pID].tim.add(calcTeamEarnings(_pID, pIDPlayer[_pID].lrnd)),
                    pIDPlayer[_pID].crd
                );
        }
    }

    function getCurrentRoundInfo()
    public
    view
    returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, address, bytes32, uint256, uint256, uint256) {
        // setup local rID
        uint256 _rID = rID;
        return(_rID,
            rIDRound[_rID].team1Keys + rIDRound[_rID].team2Keys,    //total key
            rIDRound[_rID].eth,      //total eth
            rIDRound[_rID].strt,     //start time
            rIDRound[_rID].end,      //end time
            rIDRound[_rID].pot,      //last winer pot
            rIDRound[_rID].leadPID,  //current last player
            pIDPlayer[rIDRound[_rID].leadPID].addr, //cureest last player address
            pIDPlayer[rIDRound[_rID].leadPID].name, //cureest last player name
            rIDRound[_rID].smallDrop,
            rIDRound[_rID].bigDrop,
            rIDRound[_rID].teamPot   //teampot
            );
    }

    function getRankList()
    public
    view
            //invitetop3   amout      keyTop3      key
    returns (address[3], uint256[3], bytes32[3], address[3], uint256[3], bytes32[3]) {
        uint256 _rID = rID;
        address[3] memory inv;
        address[3] memory key;
        bytes32[3] memory invname;
        uint256[3] memory invRef;
        uint256[3] memory keyamt;
        bytes32[3] memory keyname;
        inv[0] = pIDPlayer[rIDRound[_rID].invTop3[0]].addr;
        inv[1] = pIDPlayer[rIDRound[_rID].invTop3[1]].addr;
        inv[2] = pIDPlayer[rIDRound[_rID].invTop3[2]].addr;
        invRef[0] = pIDPlayerRound[rIDRound[_rID].invTop3[0]][_rID].inv;
        invRef[1] = pIDPlayerRound[rIDRound[_rID].invTop3[1]][_rID].inv;
        invRef[2] = pIDPlayerRound[rIDRound[_rID].invTop3[2]][_rID].inv;
        invname[0] = pIDPlayer[rIDRound[_rID].invTop3[0]].name;
        invname[1] = pIDPlayer[rIDRound[_rID].invTop3[1]].name;
        invname[2] = pIDPlayer[rIDRound[_rID].invTop3[2]].name;

        key[0] = pIDPlayer[rIDRound[_rID].keyTop3[0]].addr;
        key[1] = pIDPlayer[rIDRound[_rID].keyTop3[1]].addr;
        key[2] = pIDPlayer[rIDRound[_rID].keyTop3[2]].addr;
        keyamt[0] = pIDPlayerRound[rIDRound[_rID].keyTop3[0]][_rID].team1Keys + pIDPlayerRound[rIDRound[_rID].keyTop3[0]][_rID].team2Keys;
        keyamt[1] = pIDPlayerRound[rIDRound[_rID].keyTop3[1]][_rID].team1Keys + pIDPlayerRound[rIDRound[_rID].keyTop3[1]][_rID].team2Keys;
        keyamt[2] = pIDPlayerRound[rIDRound[_rID].keyTop3[2]][_rID].team1Keys + pIDPlayerRound[rIDRound[_rID].keyTop3[2]][_rID].team2Keys;
        keyname[0] = pIDPlayer[rIDRound[_rID].keyTop3[0]].name;
        keyname[1] = pIDPlayer[rIDRound[_rID].keyTop3[1]].name;
        keyname[2] = pIDPlayer[rIDRound[_rID].keyTop3[2]].name;

        return (inv, invRef, invname, key, keyamt, keyname);
    }

    /**
     * @dev returns player info based on address.  if no address is given, it will
     * use msg.sender
     * -functionhash- 0xee0b5d8b
     * @param _addr address of the player you want to lookup
     * @return player ID
     * @return player name
     * @return keys owned (current round)
     * @return winnings vault
     * @return general vault
     * @return affiliate vault
	 * @return player round eth
     */
    //tested
    function getPlayerInfoByAddress(address _addr)
        public
        view
        returns(uint256, bytes32, uint256, uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID;

        if (_addr == address(0))
        {
            _addr == msg.sender;
        }
        uint256 _pID = pIDxAddr[_addr];
        if (_pID == 0)
            return (0, 0x0, 0, 0, 0);
        else
            return
            (
            _pID,                               //0
            pIDPlayer[_pID].name,                   //1
            pIDPlayerRound[_pID][_rID].team1Keys + pIDPlayerRound[_pID][_rID].team2Keys,
            pIDPlayerRound[_pID][_rID].eth,           //6
            pIDPlayer[_pID].vip
            );
    }

    function getCards(uint256 _id)
    public
    view
    returns(uint256, address, bytes32, uint256, uint256) {
        bytes32 _name = pIDPlayer[cIDCard[_id].owner].name;
        return (
            cIDCard[_id].owner,
            cIDCard[_id].addr,
            _name,
            cIDCard[_id].buyTime,
            cIDCard[_id].earnings
        );
    }
//==============================================================================
//
//    private functions
//==============================================================================

    //tested
    function managePID(uint256 _inviter) private returns(uint256) {
        uint256 _pID = pIDxAddr[msg.sender];
        if (_pID == 0) {
            pID++;
            pIDxAddr[msg.sender] = pID;
            pIDPlayer[pID].addr = msg.sender;
            pIDPlayer[pID].name = 0x0;
            _pID = pID;
        }
            // handle direct and second hand inviter
        if (pIDPlayer[_pID].inviter1 == 0 && pIDPlayer[_inviter].addr != address(0) && _pID != _inviter) {
            pIDPlayer[_pID].inviter1 = _inviter;
            uint256 _in = pIDPlayer[_inviter].inviter1;
            if (_in != 0) {
                    pIDPlayer[_pID].inviter2 = _in;
                }
            }
        // oneoff invite get invitation link
        if (msg.value >= oneOffInvest2) {
            pIDPlayer[_pID].vip = 2;
        } else if (msg.value >= oneOffInvest1) {
            if (pIDPlayer[_pID].vip != 2)
                pIDPlayer[_pID].vip = 1;
        }
        return _pID;
    }

    function buyCore(uint256 _pID, uint256 _team) private {
        // setup local rID
        uint256 _rID = rID;
        // grab time
        uint256 _now = now;

        //update last round;
        if (pIDPlayer[_pID].lrnd != _rID)
            updateVault(_pID);
        pIDPlayer[_pID].lrnd = _rID;
        uint256 _inv1 = pIDPlayer[_pID].inviter1;
        uint256 _inv2 = pIDPlayer[_pID].inviter2;

        // round is active
        if (rIDRound[_rID].end >= _now || (rIDRound[_rID].end < _now && rIDRound[_rID].leadPID == 0)) {
            core(_rID, _pID, msg.value, _team);
            if (_inv1 != 0)
                doRankInv(_rID, _inv1, rIDRound[_rID].invTop3, pIDPlayerRound[_inv1][_rID].inv);
            if (_inv2 != 0)
                doRankInv(_rID, _inv2, rIDRound[_rID].invTop3, pIDPlayerRound[_inv2][_rID].inv);
            doRankKey(_rID, _pID, rIDRound[_rID].keyTop3, pIDPlayerRound[_pID][_rID].team1Keys + pIDPlayerRound[_pID][_rID].team2Keys);
            emit onBuyKey(
                _pID,
                pIDPlayer[_pID].addr,
                pIDPlayer[_pID].name, _rID,
                msg.value,
                pIDPlayerRound[_pID][_rID].team1Keys + pIDPlayerRound[_pID][_rID].team2Keys,
                now);
        } else {
            if (rIDRound[_rID].end < _now && rIDRound[_rID].ended == false) {
                rIDRound[_rID].ended = true;
                endRound();
                //if you trigger the endround. whatever how much you pay ,you will fail to buykey
                //and the eth will return to your gen.
                pIDPlayer[_pID].gen = pIDPlayer[_pID].gen.add(msg.value);
            }
        }
    }

    function core(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _team) private {

        NTAdatasets.Round storage _roundID = rIDRound[_rID];
        NTAdatasets.Deposit storage _deposit = deposit[_team];
        //NTAdatasets.PlayerRound storage _playerRound = pIDPlayerRound[_pID][_rID];
        // calculate how many keys they can get
        uint256 _keysAll = _roundID.team1Keys + _roundID.team2Keys;//(rIDRound[_rID].eth).keysRec(_eth);
        uint256 _keys = _keysAll.keysRec(rIDRound[_rID].eth + _eth);
        if (_keys >= 1000000000000000000) {
            updateTimer(_keys, _rID);
        }

        uint256 _left = _eth;
        //2% to bigDrop
        uint256 _temp = _eth.mul(_deposit.bigDrop) / DIVIDE;
        doBigDrop(_rID, _pID, _keys, _temp);
        _left = _left.sub0(_temp);

        //5% to smallDrop
        _temp = _eth.mul(_deposit.smallDrop) / DIVIDE;
        doSmallDrop(_rID, _pID, _eth, _temp);
        _left = _left.sub0(_temp);

        _roundID.eth = _roundID.eth.add(_eth);
        pIDPlayerRound[_pID][_rID].eth = pIDPlayerRound[_pID][_rID].eth.add(_eth);
        if (_team == 0) {
            _roundID.team1Keys = _roundID.team1Keys.add(_keys);
            pIDPlayerRound[_pID][_rID].team1Keys = pIDPlayerRound[_pID][_rID].team1Keys.add(_keys);
        } else {
            _roundID.team2Keys = _roundID.team2Keys.add(_keys);
            pIDPlayerRound[_pID][_rID].team2Keys = pIDPlayerRound[_pID][_rID].team2Keys.add(_keys);
        }


        //X% to all
        uint256 _all = _eth.mul(_deposit.allPlayer) / DIVIDE;
        _roundID.playerPot = _roundID.playerPot.add(_all);
        uint256 _dust = updateMasks(_rID, _pID, _all, _keys);
        _roundID.pot = _roundID.pot.add(_dust);
        _left = _left.sub0(_all);

        //X% to winnerPot
        _temp = _eth.mul(_deposit.pot) / DIVIDE;
        _roundID.pot = _roundID.pot.add(_temp);
        _left = _left.sub0(_temp);

        //5% to develop funds
        _temp = _eth.mul(_deposit.devfunds) / DIVIDE;
        doDevelopFunds(_temp);
        //gameFunds[gameFundsAddr] = gameFunds[gameFundsAddr].add(_temp);
        _left = _left.sub0(_temp);

        //5% to team fee
        _temp = _eth.mul(_deposit.teamFee) / DIVIDE;
        //gameFunds[partner1] = gameFunds[partner1].add(_temp.mul(50) / DIVIDE);
        _dust = doPartnerShares(_temp);
        _left = _left.sub0(_temp).add(_dust);

        //10% to cards
        _temp = _eth.mul(_deposit.cards) / DIVIDE;
        _left = _left.sub0(_temp).add(distributeCards(_temp));
        // if no cards ,the money will add into left

        // 10% to invatation
        _temp = _eth.mul(_deposit.inviter) / DIVIDE;
        _dust = doInvite(_rID, _pID, _temp);
        _left = _left.sub0(_temp).add(_dust);

        //update round;
        if (_keys >= 1000000000000000000) {
            _roundID.leadPID = _pID;
            _roundID.team = _team;
        }


        _roundID.smallDrop = _roundID.smallDrop.add(_left);
    }

    //tested
    function doInvite(uint256 _rID, uint256 _pID, uint256 _value) private returns(uint256){
        uint256 _score = msg.value;
        uint256 _left = _value;
        uint256 _inviter1 = pIDPlayer[_pID].inviter1;
        uint256 _fee;
        uint256 _inviter2 = pIDPlayer[_pID].inviter2;
        if (_inviter1 != 0)
            pIDPlayerRound[_inviter1][_rID].score = pIDPlayerRound[_inviter1][_rID].score.add(_score);
        if (_inviter2 != 0)
            pIDPlayerRound[_inviter2][_rID].score = pIDPlayerRound[_inviter2][_rID].score.add(_score);
        //invitor
        if (_inviter1 == 0 || pIDPlayer[_inviter1].vip == 0)
            return _left;
        if (pIDPlayer[_inviter1].vip == 1) {
            _fee = _value.mul(70) / 100;
            _inviter2 = pIDPlayer[_pID].inviter2;
            _left = _left.sub0(_fee);
            pIDPlayerRound[_inviter1][_rID].inv = pIDPlayerRound[_inviter1][_rID].inv.add(_fee);
            if (_inviter2 == 0 || pIDPlayer[_inviter2].vip != 2)
                return _left;
            else {
                _fee = _value.mul(30) / 100;
                _left = _left.sub0(_fee);
                pIDPlayerRound[_inviter2][_rID].inv = pIDPlayerRound[_inviter2][_rID].inv.add(_fee);
                return _left;
            }
        } else if (pIDPlayer[_inviter1].vip == 2) {
            _left = _left.sub0(_value);
            pIDPlayerRound[_inviter1][_rID].inv = pIDPlayerRound[_inviter1][_rID].inv.add(_value);
            return _left;
        } else {
            return _left;
        }
    }

    function doRankInv(uint256 _rID, uint256 _pID, uint256[3] storage rank, uint256 _value) private {

        if (_value >= pIDPlayerRound[rank[0]][_rID].inv && _value != 0) {
            if (_pID != rank[0]) {
            uint256 temp = rank[0];
            rank[0] = _pID;
            if (rank[1] == _pID) {
                rank[1] = temp;
            } else {
                rank[2] = rank[1];
                rank[1] = temp;
            }
            }
        } else if (_value >= pIDPlayerRound[rank[1]][_rID].inv && _value != 0) {
            if (_pID != rank[1]) {
            rank[2] = rank[1];
            rank[1] = _pID;
            }
        } else if (_value >= pIDPlayerRound[rank[2]][_rID].inv && _value != 0) {
            rank[2] = _pID;
        }
    }

    function doRankKey(uint256 _rID, uint256 _pID, uint256[3] storage rank, uint256 _value) private {

        if (_value >= (pIDPlayerRound[rank[0]][_rID].team1Keys + pIDPlayerRound[rank[0]][_rID].team2Keys)) {
            if (_pID != rank[0]) {
            uint256 temp = rank[0];
            rank[0] = _pID;
            if (rank[1] == _pID) {
                rank[1] = temp;
            } else {
                rank[2] = rank[1];
                rank[1] = temp;
            }
            }
        } else if (_value >= (pIDPlayerRound[rank[1]][_rID].team1Keys + pIDPlayerRound[rank[1]][_rID].team2Keys)) {
            if (_pID != rank[1]){
            rank[2] = rank[1];
            rank[1] = _pID;
            }
        } else if (_value >= (pIDPlayerRound[rank[2]][_rID].team1Keys + pIDPlayerRound[rank[2]][_rID].team2Keys)) {
            rank[2] = _pID;
        }
    }

    //tested
    function doSmallDrop(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _small) private {
        // modulo current round eth, and add player eth to see if it can trigger the trigger;
        uint256 _remain = rIDRound[_rID].eth % smallDropTrigger;
        if ((_remain + _eth) >= smallDropTrigger) {
            uint256 _reward = rIDRound[_rID].smallDrop;
            rIDRound[_rID].smallDrop = 0;
            pIDPlayer[_pID].win = pIDPlayer[_pID].win.add(_reward);
            rIDRound[_rID].smallDrop = rIDRound[_rID].smallDrop.add(_small);
            emit NTA3DEvents.onDrop(pIDPlayer[_pID].addr, pIDPlayer[_pID].name, _rID, 0, _reward, now);
            //emit
        } else {
            rIDRound[_rID].smallDrop = rIDRound[_rID].smallDrop.add(_small);
        }
    }

    //tested
    function doBigDrop(uint256 _rID, uint256 _pID, uint256 _key, uint256 _big) private {
        uint256 _keys = rIDRound[_rID].team1Keys + rIDRound[_rID].team2Keys;
        uint256 _remain = _keys % bigDropTrigger;
        if ((_remain + _key) >= bigDropTrigger) {
            uint256 _reward = rIDRound[_rID].bigDrop;
            rIDRound[_rID].bigDrop = 0;
            pIDPlayer[_pID].win = pIDPlayer[_pID].win.add(_reward);
            rIDRound[_rID].bigDrop = rIDRound[_rID].bigDrop.add(_big);
            emit NTA3DEvents.onDrop(pIDPlayer[_pID].addr, pIDPlayer[_pID].name, _rID, 1, _reward, now);
            //emit
        } else {
            rIDRound[_rID].bigDrop = rIDRound[_rID].bigDrop.add(_big);
        }
    }

    function distributeCards(uint256 _eth) private returns(uint256){
        uint256 _each = _eth / 20;
        uint256 _remain = _eth;
        for (uint i = 0; i < 20; i++) {
            uint256 _pID = cIDCard[i].owner;
            if (_pID != 0) {
                pIDPlayer[_pID].crd = pIDPlayer[_pID].crd.add(_each);
                cIDCard[i].earnings = cIDCard[i].earnings.add(_each);
                _remain = _remain.sub0(_each);
            }
        }
        return _remain;
    }

    function doPartnerShares(uint256 _eth) private returns(uint256) {
        uint i;
        uint256 _temp;
        uint256 _left = _eth;
        //first 10%
        _temp = _eth.mul(10) / 100;
        gameFunds[partner[0]] = gameFunds[partner[0]].add(_temp);
        for(i = 1; i < 11; i++) {
            _temp = _eth.mul(9) / 100;
            gameFunds[partner[i]] = gameFunds[partner[i]].add(_temp);
            _left = _left.sub0(_temp);
        }
        return _left;
    }

    function doDevelopFunds(uint256 _eth) private{
        uint256 _temp;
        _temp = _eth.mul(12) / 100;
        gameFunds[to06] = gameFunds[to06].add(_temp);
        _temp = _eth.mul(8) / 100;
        gameFunds[to04] = gameFunds[to04].add(_temp);
        _temp = _eth.mul(40) / 100;
        gameFunds[to20A] = gameFunds[to20A].add(_temp);
        _temp = _eth.mul(40) / 100;
        gameFunds[to20B] = gameFunds[to20B].add(_temp);
    }

    function endRound() private {
        NTAdatasets.Round storage _roundID = rIDRound[rID];
        NTAdatasets.PotSplit storage _potSplit = potSplit[0];
        uint256 _winPID = _roundID.leadPID;
        uint256 _pot = _roundID.pot;
        uint256 _left = _pot;

        //the pot is too small endround will ignore the dividens
        //new round will start at 0 eth
        if(_pot < 10000000000000) {
            emit onRoundEnd(pIDPlayer[_winPID].addr, pIDPlayer[_winPID].name, rID, _roundID.pot,0, now);
            newRound(0);
            return;
        }

        // potSplit:    ==> |20% to all, 45% to lastwinner, 5% to inviter 1st, 3% to inviter 2nd, 2% to inviter 3rd,
        //                  |8% to key buyer 1st, 5% to key buyer 2nd, 2% to key buyer 3rd, 10% to next round

        //20% to all
        uint256 _all = _pot.mul(_potSplit.allPlayer) / DIVIDE;
        _roundID.teamPot = _roundID.teamPot.add(_all);
        _left = _left.sub0(_all);

        //45% to lastwinner
        uint256 _temp = _pot.mul(_potSplit.lastWinner) / DIVIDE;
        pIDPlayer[_winPID].win = pIDPlayer[_winPID].win.add(_temp);
        _left = _left.sub0(_temp);

        //5% to inviter 1st, 3% to inviter 2nd, 2% to inviter 3rd
        uint256 _inv1st = _pot.mul(_potSplit.inviter1st) / DIVIDE;
        if (_roundID.invTop3[0] != 0) {
            pIDPlayer[_roundID.invTop3[0]].win = pIDPlayer[_roundID.invTop3[0]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

        _inv1st = _pot.mul(_potSplit.inviter2nd) / DIVIDE;
        if (_roundID.invTop3[1] != 0) {
            pIDPlayer[_roundID.invTop3[1]].win = pIDPlayer[_roundID.invTop3[1]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

        _inv1st = _pot.mul(_potSplit.inviter3rd) / DIVIDE;
        if (_roundID.invTop3[2] != 0) {
            pIDPlayer[_roundID.invTop3[2]].win = pIDPlayer[_roundID.invTop3[2]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

         //8% to key buyer 1st, 5% to key buyer 2nd, 2% to key buyer 3rd
        _inv1st = _pot.mul(_potSplit.key1st) / DIVIDE;
        if (_roundID.keyTop3[0] != 0) {
            pIDPlayer[_roundID.keyTop3[0]].win = pIDPlayer[_roundID.keyTop3[0]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

        _inv1st = _pot.mul(_potSplit.key2nd) / DIVIDE;
        if (_roundID.keyTop3[1] != 0) {
            pIDPlayer[_roundID.keyTop3[1]].win = pIDPlayer[_roundID.keyTop3[1]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

        _inv1st = _pot.mul(_potSplit.key3rd) / DIVIDE;
        if (_roundID.keyTop3[2] != 0) {
            pIDPlayer[_roundID.keyTop3[2]].win = pIDPlayer[_roundID.keyTop3[2]].win.add(_inv1st);
            _left = _left.sub0(_inv1st);
        }

        //10% to next round
        uint256 _newPot = _pot.mul(potSplit[0].next) / DIVIDE;
        _left = _left.sub0(_newPot);
        emit onRoundEnd(pIDPlayer[_winPID].addr, pIDPlayer[_winPID].name, rID, _roundID.pot, _newPot + _left, now);
        //start new round
        newRound(_newPot + _left);
    }

    //tested
    function newRound(uint256 _eth) private {
        if (rIDRound[rID].ended == true || rID == 0) {
            rID++;
            rIDRound[rID].strt = now;
            rIDRound[rID].end = now.add(rndMax);
            rIDRound[rID].pot = rIDRound[rID].pot.add(_eth);
        }
    }

    function updateMasks(uint256 _rID, uint256 _pID, uint256 _all, uint256 _keys) private
    returns(uint256) {
        //calculate average share of each new eth in
        uint256 _allKeys = rIDRound[_rID].team1Keys + rIDRound[_rID].team2Keys;
        uint256 _unit = _all.mul(1000000000000000000) / _allKeys;
        rIDRound[_rID].mask = rIDRound[_rID].mask.add(_unit);
        //calculate this round player can get
        uint256 _share = (_unit.mul(_keys)) / (1000000000000000000);
        pIDPlayerRound[_pID][_rID].mask = pIDPlayerRound[_pID][_rID].mask.add((rIDRound[_rID].mask.mul(_keys) / (1000000000000000000)).sub(_share));
        return(_all.sub(_unit.mul(_allKeys) / (1000000000000000000)));
    }

    function withdrawEarnings(uint256 _pID) private returns(uint256) {
        updateVault(_pID);
        uint256 earnings = (pIDPlayer[_pID].win).add(pIDPlayer[_pID].gen).add(pIDPlayer[_pID].inv).add(pIDPlayer[_pID].tim).add(pIDPlayer[_pID].crd);
        if (earnings > 0) {
            pIDPlayer[_pID].win = 0;
            pIDPlayer[_pID].gen = 0;
            pIDPlayer[_pID].inv = 0;
            pIDPlayer[_pID].tim = 0;
            pIDPlayer[_pID].crd = 0;
        }
        return earnings;
    }

    function updateVault(uint256 _pID) private {
        uint256 _rID = pIDPlayer[_pID].lrnd;
        updateGenVault(_pID, _rID);
        updateInvVault(_pID, _rID);
        uint256 _team = calcTeamEarnings(_pID, _rID);
        //already calculate team reward,ended round key and mask dont needed
        if(rIDRound[_rID].ended == true) {
            pIDPlayerRound[_pID][_rID].team1Keys = 0;
            pIDPlayerRound[_pID][_rID].team2Keys = 0;
            pIDPlayerRound[_pID][_rID].mask = 0;
        }
        pIDPlayer[_pID].tim = pIDPlayer[_pID].tim.add(_team);
    }

    function updateGenVault(uint256 _pID, uint256 _rID) private {
        uint256 _earnings = calcUnMaskedEarnings(_pID, _rID);
        //put invitation reward to gen
        if (_earnings > 0) {
            // put in gen vault
            pIDPlayer[_pID].gen = _earnings.add(pIDPlayer[_pID].gen);
            // zero out their earnings by updating mask
            pIDPlayerRound[_pID][_rID].mask = _earnings.add(pIDPlayerRound[_pID][_rID].mask);
        }

    }

    function updateInvVault(uint256 _pID, uint256 _rID) private {
        uint256 _inv = pIDPlayerRound[_pID][_rID].inv;
        uint256 _invMask = pIDPlayerRound[_pID][_rID].invMask;
        if (_inv > 0) {
            pIDPlayer[_pID].inv = pIDPlayer[_pID].inv.add(_inv).sub0(_invMask);
            pIDPlayerRound[_pID][_rID].invMask = pIDPlayerRound[_pID][_rID].invMask.add(_inv).sub0(_invMask);
        }
    }

    //calculate valut not update
    function calcUnMaskedEarnings(uint256 _pID, uint256 _rID) private view
    returns (uint256)
    {
        uint256 _all = pIDPlayerRound[_pID][_rID].team1Keys + pIDPlayerRound[_pID][_rID].team2Keys;
        return ((rIDRound[_rID].mask.mul(_all)) / (1000000000000000000)).sub(pIDPlayerRound[_pID][_rID].mask);
    }

    function calcTeamEarnings(uint256 _pID, uint256 _rID) private view
    returns (uint256)
    {
        uint256 _key1 = pIDPlayerRound[_pID][_rID].team1Keys;
        uint256 _key2 = pIDPlayerRound[_pID][_rID].team2Keys;
        if (rIDRound[_rID].ended == false)
            return 0;
        else {
            if (rIDRound[_rID].team == 0)
                return rIDRound[_rID].teamPot.mul(_key1 / rIDRound[_rID].team1Keys);
            else
                return rIDRound[_rID].teamPot.mul(_key2 / rIDRound[_rID].team2Keys);
        }
    }
    //tested
    function updateTimer(uint256 _keys, uint256 _rID) private {
        // grab time
        uint256 _now = now;
        // calculate new time
        uint256 _newTime;
        if (_now > rIDRound[_rID].end && rIDRound[_rID].leadPID == 0)
            _newTime = (((_keys) / (1000000000000000000)).mul(rndPerKey)).add(_now);
        else
            _newTime = (((_keys) / (1000000000000000000)).mul(rndPerKey)).add(rIDRound[_rID].end);

        // compare to max and set new end time
        if (_newTime < (rndMax).add(_now))
            rIDRound[_rID].end = _newTime;
        else
            rIDRound[_rID].end = rndMax.add(_now);
    }


}
function managePID(uint256 _inviter) private returns(uint256) {
        uint256 _pID = pIDxAddr[msg.sender];
        if (_pID == 0) {
            pID++;
            pIDxAddr[msg.sender] = pID;
            pIDPlayer[pID].addr = msg.sender;
            pIDPlayer[pID].name = 0x0;
            _pID = pID;
        }
            // handle direct and second hand inviter
        if (pIDPlayer[_pID].inviter1 == 0 && pIDPlayer[_inviter].addr != address(0) && _pID != _inviter) {
            pIDPlayer[_pID].inviter1 = _inviter;
            uint256 _in = pIDPlayer[_inviter].inviter1;
            if (_in != 0) {
                    pIDPlayer[_pID].inviter2 = _in;
                }
            }
        // oneoff invite get invitation link
        if (msg.value >= oneOffInvest2) {
            pIDPlayer[_pID].vip = 2;
        } else if (msg.value >= oneOffInvest1) {
            if (pIDPlayer[_pID].vip != 2)
                pIDPlayer[_pID].vip = 1;
        }
        return _pID;
    }
//==============================================================================
//
//    private functions
//==============================================================================
//tested