contract Lottery {
    using SafeMath for uint256;

    modifier withdrawRight(){
        require(msg.sender == address(bankContract), "Bank only");
        _;
    }

    modifier onlyDevTeam() {
        require(msg.sender == devTeam, "only for development team");
        _;
    }

    modifier buyable() {
        require(block.timestamp > round[curRoundId].startTime, "not ready to sell Ticket");
        require(block.timestamp < round[curRoundId].slideEndTime, "round over");
        _;
    }

    enum RewardType {
        Minor,
        Major,
        Grand,
        Bounty
    }

    // 1 buy = 1 slot = _ethAmount => (tAmount, tMul) 
    struct Slot {
        address buyer;
        uint256 rId;
        // ticket numbers in range and unique in all rounds
        uint256 tNumberFrom;
        uint256 tNumberTo;
        // weight to, used for grandPot finalize
        uint256 wTo;
        uint256 ethAmount;
        uint256 salt;
    }

    struct Round {
        // earlyIncome weight sum
        uint256 rEarlyIncomeWeight;
        // blockNumber to get hash as random seed
        uint256 keyBlockNr;
        
        mapping(address => uint256) pTicketSum;
        mapping(address => uint256) pInvestedSum;

        // early income weight by address
        mapping(address => uint256) pEarlyIncomeWeight;
        mapping(address => uint256) pEarlyIncomeCredit;
        mapping(address => uint256) pEarlyIncomeClaimed;
        // early income per weight
        uint256 ppw;
        // endTime increased every slot sold
        // endTime limited by fixedEndTime
        uint256 startTime;
        uint256 slideEndTime;
        uint256 fixedEndTime;

        // ticketSum from round 1 to this round
        uint256 ticketSum;
        // investedSum from round 1 to this round
        uint256 investedSum;
        // number of slots from round 1 to this round
        uint256 slotSum;
    }

    // round started with this grandPot amount,
    // used to calculate the rate for grandPot results
    // init in roundInit function
    uint256 initGrandPot;

    Slot[] slot;
    // slotId logs by address
    mapping( address => uint256[]) pSlot;
    mapping( address => uint256) public pSlotSum;

    // logs by address
    mapping( address => uint256) public pTicketSum;
    mapping( address => uint256) public pInvestedSum;

    CitizenInterface public citizenContract;
    F2mInterface public f2mContract;
    BankInterface public bankContract;
    RewardInterface public rewardContract;

    address public devTeam;

    uint256 constant public ZOOM = 1000;
    uint256 constant public ONE_HOUR = 60 * 60;
    uint256 constant public ONE_DAY = 24 * ONE_HOUR;
    uint256 constant public TIMEOUT0 = 3 * ONE_HOUR;
    uint256 constant public TIMEOUT1 = 12 * ONE_HOUR;
    uint256 constant public TIMEOUT2 = 7 * ONE_DAY;
    uint256 constant public FINALIZE_WAIT_DURATION = 60; // 60 Seconds
    uint256 constant public NEWROUND_WAIT_DURATION = ONE_DAY; // 24 Hours

    // 15 seconds on Ethereum, 12 seconds used instead to make sure blockHash unavaiable
    // when slideEndTime reached
    // keyBlockNumber will be estimated again after every slot buy
    uint256 constant public BLOCK_TIME = 12;
    uint256 constant public MAX_BLOCK_DISTANCE = 254;

    uint256 constant public MAJOR_RATE = 1000;
    uint256 constant public MINOR_RATE = 1000;
    uint256 constant public MAJOR_MIN = 0.1 ether ;
    uint256 constant public MINOR_MIN = 0.1 ether ;

    //uint256 public toNextPotPercent = 27;
    uint256 public grandRewardPercent = 20;
    uint256 public jRewardPercent = 60;

    uint256 public toTokenPercent = 12; // 10% dividends 2% fund
    uint256 public toBuyTokenPercent = 1;
    uint256 public earlyIncomePercent = 22;
    uint256 public toRefPercent = 15;

    // sum == 100% = toPotPercent/100 * investedSum
    // uint256 public grandPercent = 68;
    uint256 public majorPercent = 24;
    uint256 public minorPercent = 8;

    uint256 public grandPot;
    uint256 public majorPot;
    uint256 public minorPot;

    uint256 public curRoundId;
    uint256 public lastRoundId = 88888888;

    uint256 constant public startPrice = 0.002 ether;

    mapping (address => uint256) public rewardBalance;
    // used to save gas on earlyIncome calculating, curRoundId never included
    // only earlyIncome from round 1st to curRoundId-1 are fixed
    mapping (address => uint256) public lastWithdrawnRound;
    mapping (address => uint256) public earlyIncomeScannedSum;

    mapping (uint256 => Round) public round;

    // Current Round

    // first SlotId in last Block to fire jackpot
    uint256 public jSlot;
    // jackpot results of all slots in same block will be drawed at the same time,
    // by player, who buys the first slot in next block
    uint256 public lastBlockNr;
    // used to calculate grandPot results
    uint256 public curRWeight;
    // added by slot salt after every slot buy
    // does not matter with overflow
    uint256 public curRSalt;
    // ticket sum of current round
    uint256 public curRTicketSum;

    constructor (address _devTeam)
        public
    {
        // register address in network
        DevTeamInterface(_devTeam).setLotteryAddress(address(this));
        devTeam = _devTeam;
    }

    // _contract = [f2mAddress, bankAddress, citizenAddress, lotteryAddress, rewardAddress, whitelistAddress];
    function joinNetwork(address[6] _contract)
        public
    {
        require(address(citizenContract) == 0x0,"already setup");
        f2mContract = F2mInterface(_contract[0]);
        bankContract = BankInterface(_contract[1]);
        citizenContract = CitizenInterface(_contract[2]);
        //lotteryContract = LotteryInterface(lotteryAddress);
        rewardContract = RewardInterface(_contract[4]);
    }

    function activeFirstRound()
        public
        onlyDevTeam()
    {
        require(curRoundId == 0, "already activated");
        initRound();
    }

    // Core Functions

    function pushToPot() 
        public 
        payable
    {
        addPot(msg.value);
    }

    function checkpoint() 
        private
    {
        // dummy slot between every 2 rounds
        // dummy slot never win jackpot cause of min 0.1 ETH
        Slot memory _slot;
        _slot.tNumberTo = round[curRoundId].ticketSum;
        slot.push(_slot);

        Round memory _round;
        _round.startTime = NEWROUND_WAIT_DURATION.add(block.timestamp);
        // started with 3 hours timeout
        _round.slideEndTime = TIMEOUT0 + _round.startTime;
        _round.fixedEndTime = TIMEOUT2 + _round.startTime;
        _round.keyBlockNr = genEstKeyBlockNr(_round.slideEndTime);
        _round.ticketSum = round[curRoundId].ticketSum;
        _round.investedSum = round[curRoundId].investedSum;
        _round.slotSum = slot.length;

        curRoundId = curRoundId + 1;
        round[curRoundId] = _round;

        initGrandPot = grandPot;
        curRWeight = 0;
        curRTicketSum = 0;
    }

    // from round 18+ function
    function isLastRound()
        public
        view
        returns(bool)
    {
        return (curRoundId == lastRoundId);
    }

    function goNext()
        private
    {
        uint256 _totalPot = getTotalPot();
        grandPot = 0;
        majorPot = 0;
        minorPot = 0;
        f2mContract.pushDividends.value(_totalPot)();
        // never start
        round[curRoundId].startTime = block.timestamp * 10;
        round[curRoundId].slideEndTime = block.timestamp * 10 + 1;
    }

    function initRound()
        private
    {
        // update all Round Log
        checkpoint();
        if (isLastRound()) goNext();
    }

    function finalizeable() 
        public
        view
        returns(bool)
    {
        uint256 finalizeTime = FINALIZE_WAIT_DURATION.add(round[curRoundId].slideEndTime);
        if (finalizeTime > block.timestamp) return false; // too soon to finalize
        if (getEstKeyBlockNr(curRoundId) >= block.number) return false; //block hash not exist
        return curRoundId > 0;
    }

    // bounty
    function finalize()
        public
    {
        require(finalizeable(), "Not ready to draw results");
        // avoid txs blocked => curRTicket ==0 => die
        require((round[curRoundId].pTicketSum[msg.sender] > 0) || (curRTicketSum == 0), "must buy at least 1 ticket");
        endRound(msg.sender);
        initRound();
    }

    function mintReward(
        address _lucker,
        uint256 _slotId,
        uint256 _value,
        RewardType _rewardType)
        private
    {
        // add reward balance
        rewardBalance[_lucker] = rewardBalance[_lucker].add(_value);
        // reward log
        rewardContract.mintReward(
            _lucker,
            curRoundId,
            slot[_slotId].tNumberFrom,
            slot[_slotId].tNumberTo,
            _value,
            uint256(_rewardType)
        );
    }

    function jackpot()
        private
    {
        // get blocknumber to get blockhash
        uint256 keyBlockNr = getKeyBlockNr(lastBlockNr);//block.number;
        // salt not effected by jackpot, too risk
        uint256 seed = getSeed(keyBlockNr);
        // slot numberic from 1 ... totalSlot(round)
        // jackpot for all slot in last block, jSlot <= i <= lastSlotId (=slotSum - 1)
        // _to = first Slot in new block
        //uint256 _to = round[curRoundId].slotSum;

        uint256 jReward;
        uint256 toF2mAmount;
        address winner;
        // jackpot check for slots in last block
        while (jSlot + 1 < round[curRoundId].slotSum) {
            // majorPot
            if ((seed % MAJOR_RATE == 6) &&
                (slot[jSlot].ethAmount >= MAJOR_MIN)) {

                winner = slot[jSlot].buyer;
                jReward = majorPot / 100 * jRewardPercent;
                mintReward(winner, jSlot, jReward, RewardType.Major);
                toF2mAmount = majorPot / 100 * toTokenPercent;
                f2mContract.pushDividends.value(toF2mAmount)();
                majorPot = majorPot - jReward - toF2mAmount;
            }

            // minorPot
            if (((seed + jSlot) % MINOR_RATE == 8) && 
                (slot[jSlot].ethAmount >= MINOR_MIN)) {

                winner = slot[jSlot].buyer;
                jReward = minorPot / 100 * jRewardPercent;
                mintReward(winner, jSlot, jReward, RewardType.Minor);
                toF2mAmount = minorPot / 100 * toTokenPercent;
                f2mContract.pushDividends.value(toF2mAmount)();
                minorPot = minorPot - jReward - toF2mAmount;
            }
            seed = seed + 1;
            jSlot = jSlot + 1;
        }
    }

    function endRound(address _bountyHunter)
        private
    {
        uint256 _rId = curRoundId;
        uint256 keyBlockNr = getKeyBlockNr(round[_rId].keyBlockNr);
        uint256 _seed = getSeed(keyBlockNr) + curRSalt;
        uint256 onePercent = grandPot / 100;
        uint256 rGrandReward = onePercent * grandRewardPercent;

        //PUSH DIVIDENDS
        uint256 toF2mAmount = onePercent * toTokenPercent;
        //uint256 _bountyAmount = onePercent * bountyPercent;
        
        grandPot = grandPot - toF2mAmount - onePercent;
        f2mContract.pushDividends.value(toF2mAmount)();

        // base on grand-intestedSum current grandPot
        uint256 weightRange = getWeightRange();

        // roll 3 turns
        for (uint256 i = 0; i < 3; i++){
            uint256 winNr = Helper.getRandom(_seed, weightRange);
            // if winNr > curRoundWeight => no winner this turn
            // win Slot : fromWeight <= winNr <= toWeight
            // got winner this rolling turn
            if (winNr <= curRWeight) {
                grandPot -= rGrandReward;
                uint256 _winSlot = getWinSlot(winNr);
                address _winner = slot[_winSlot].buyer;
                mintReward(_winner, _winSlot, rGrandReward, RewardType.Grand);
                _seed = _seed + (_seed / 10);
            }
        }
        mintReward(_bountyHunter, 0, onePercent * 3 / 10, RewardType.Bounty);
        rewardContract.pushBounty.value(onePercent * 7 / 10)(curRoundId);
    }

    function buy(string _sSalt)
        public
        payable
    {
        buyFor(_sSalt, msg.sender);
    }

    function updateInvested(address _buyer, uint256 _ethAmount)
        private
    {
        round[curRoundId].investedSum += _ethAmount;
        round[curRoundId].pInvestedSum[_buyer] += _ethAmount;
        pInvestedSum[_buyer] += _ethAmount;
    }

    function updateTicketSum(address _buyer, uint256 _tAmount)
        private
    {
        round[curRoundId].ticketSum = round[curRoundId].ticketSum + _tAmount;
        round[curRoundId].pTicketSum[_buyer] = round[curRoundId].pTicketSum[_buyer] + _tAmount;
        curRTicketSum = curRTicketSum + _tAmount;
        pTicketSum[_buyer] = pTicketSum[_buyer] + _tAmount;
    }

    function updateEarlyIncome(address _buyer, uint256 _pWeight)
        private
    {
        round[curRoundId].rEarlyIncomeWeight = _pWeight.add(round[curRoundId].rEarlyIncomeWeight);
        round[curRoundId].pEarlyIncomeWeight[_buyer] = _pWeight.add(round[curRoundId].pEarlyIncomeWeight[_buyer]);
        round[curRoundId].pEarlyIncomeCredit[_buyer] = round[curRoundId].pEarlyIncomeCredit[_buyer].add(_pWeight.mul(round[curRoundId].ppw));
    }

    function buyFor(string _sSalt, address _sender) 
        public
        payable
        buyable()
    {
        uint256 _salt = Helper.stringToUint(_sSalt);
        uint256 _ethAmount = msg.value;
        uint256 _ticketSum = curRTicketSum;
        require(_ethAmount >= Helper.getTPrice(_ticketSum), "not enough to buy 1 ticket");

        // investedSum logs
        updateInvested(_sender, _ethAmount);
        // update salt
        curRSalt = curRSalt + _salt;
        // init new Slot, Slot Id = 1..curRSlotSum
        Slot memory _slot;
        _slot.rId = curRoundId;
        _slot.buyer = _sender;
        _slot.ethAmount = _ethAmount;
        _slot.salt = _salt;
        uint256 _tAmount = Helper.getTAmount(_ethAmount, _ticketSum);
        uint256 _tMul = Helper.getTMul(_ticketSum);
        uint256 _pMul = Helper.getEarlyIncomeMul(_ticketSum);
        uint256 _pWeight = _pMul.mul(_tAmount);
        uint256 _toAddTime = Helper.getAddedTime(_ticketSum, _tAmount);
        addTime(curRoundId, _toAddTime);

        // update weight
        uint256 _slotWeight = (_tAmount).mul(_tMul);
        curRWeight = curRWeight.add(_slotWeight);
        _slot.wTo = curRWeight;
        uint256 lastSlot = slot.length - 1;
        // update ticket params
        _slot.tNumberFrom = slot[lastSlot].tNumberTo + 1;
        _slot.tNumberTo = slot[lastSlot].tNumberTo + _tAmount;
        updateTicketSum(_sender, _tAmount);

        // EarlyIncome Weight
        // ppw and credit zoomed x1000
        // earlyIncome mul of each ticket in this slot
        updateEarlyIncome(_sender, _pWeight);
     
        // add Slot and update round data
        slot.push(_slot);
        round[curRoundId].slotSum = slot.length;
        // add slot to player logs
        pSlot[_sender].push(slot.length - 1);

        // first slot in this block draw jacpot results for 
        // all slot in last block
        if (lastBlockNr != block.number) {
            jackpot();
            lastBlockNr = block.number;
        }

        distributeSlotBuy(_sender, curRoundId, _ethAmount);

        round[curRoundId].keyBlockNr = genEstKeyBlockNr(round[curRoundId].slideEndTime);
    }

    function distributeSlotBuy(address _sender, uint256 _rId, uint256 _ethAmount)
        private
    {
        uint256 onePercent = _ethAmount / 100;
        uint256 toF2mAmount = onePercent * toTokenPercent; // 12
        uint256 toRefAmount = onePercent * toRefPercent; // 10
        uint256 toBuyTokenAmount = onePercent * toBuyTokenPercent; //1
        uint256 earlyIncomeAmount = onePercent * earlyIncomePercent; //27
        uint256 taxAmount = toF2mAmount + toRefAmount + toBuyTokenAmount + earlyIncomeAmount; // 50
        uint256 taxedEthAmount = _ethAmount.sub(taxAmount); // 50
        addPot(taxedEthAmount);
        
        // 10% Ref
        citizenContract.pushRefIncome.value(toRefAmount)(_sender);
        // 2% Fund + 10% Dividends 
        f2mContract.pushDividends.value(toF2mAmount)();
        // 1% buy Token
        f2mContract.buyFor.value(toBuyTokenAmount)(_sender);
        // 27% Early
        uint256 deltaPpw = (earlyIncomeAmount * ZOOM).div(round[_rId].rEarlyIncomeWeight);
        round[_rId].ppw = deltaPpw.add(round[_rId].ppw);
    }

    function claimEarlyIncomebyAddress(address _buyer)
        private
    {
        if (curRoundId == 0) return;
        claimEarlyIncomebyAddressRound(_buyer, curRoundId);
        uint256 _rId = curRoundId - 1;
        while ((_rId > lastWithdrawnRound[_buyer]) && (_rId + 20 > curRoundId)) {
            earlyIncomeScannedSum[_buyer] += claimEarlyIncomebyAddressRound(_buyer, _rId);
            _rId = _rId - 1;
        }
    }

    function claimEarlyIncomebyAddressRound(address _buyer, uint256 _rId)
        private
        returns(uint256)
    {
        uint256 _amount = getCurEarlyIncomeByAddressRound(_buyer, _rId);
        if (_amount == 0) return 0;
        round[_rId].pEarlyIncomeClaimed[_buyer] = _amount.add(round[_rId].pEarlyIncomeClaimed[_buyer]);
        rewardBalance[_buyer] = _amount.add(rewardBalance[_buyer]);
        return _amount;
    }

    function withdrawFor(address _sender)
        public
        withdrawRight()
        returns(uint256)
    {
        if (curRoundId == 0) return;
        claimEarlyIncomebyAddress(_sender);
        lastWithdrawnRound[_sender] = curRoundId - 1;
        uint256 _amount = rewardBalance[_sender];
        rewardBalance[_sender] = 0;
        bankContract.pushToBank.value(_amount)(_sender);
        return _amount;
    }
    
    function addTime(uint256 _rId, uint256 _toAddTime)
        private
    {
        round[_rId].slideEndTime = Helper.getNewEndTime(_toAddTime, round[_rId].slideEndTime, round[_rId].fixedEndTime);
    }

    // distribute to 3 pots Grand, Majorm Minor
    function addPot(uint256 _amount)
        private
    {
        uint256 onePercent = _amount / 100;
        uint256 toMinor = onePercent * minorPercent;
        uint256 toMajor = onePercent * majorPercent;
        uint256 toGrand = _amount - toMinor - toMajor;

        minorPot = minorPot + toMinor;
        majorPot = majorPot + toMajor;
        grandPot = grandPot + toGrand;
    }


    //////////////////////////////////////////////////////////////////
    // READ FUNCTIONS
    //////////////////////////////////////////////////////////////////

    function isWinSlot(uint256 _slotId, uint256 _keyNumber)
        public
        view
        returns(bool)
    {
        return (slot[_slotId - 1].wTo < _keyNumber) && (slot[_slotId].wTo >= _keyNumber);
    }

    function getWeightRange()
        public
        view
        returns(uint256)
    {
        return Helper.getWeightRange(grandPot, initGrandPot, curRWeight);
    }

    function getWinSlot(uint256 _keyNumber)
        public
        view
        returns(uint256)
    {
        // return 0 if not found
        uint256 _to = slot.length - 1;
        uint256 _from = round[curRoundId-1].slotSum + 1; // dummy slot ignore
        uint256 _pivot;
        //Slot memory _slot;
        uint256 _pivotWTo;
        // Binary search
        while (_from <= _to) {
            _pivot = (_from + _to) / 2;
            //_slot = round[_rId].slot[_pivot];
            _pivotWTo = slot[_pivot].wTo;
            if (isWinSlot(_pivot, _keyNumber)) return _pivot;
            if (_pivotWTo < _keyNumber) { // in right side
                _from = _pivot + 1;
            } else { // in left side
                _to = _pivot - 1;
            }
        }
        return _pivot; // never happens or smt gone wrong
    }

    // Key Block in future
    function genEstKeyBlockNr(uint256 _endTime) 
        public
        view
        returns(uint256)
    {
        if (block.timestamp >= _endTime) return block.number + 8; 
        uint256 timeDist = _endTime - block.timestamp;
        uint256 estBlockDist = timeDist / BLOCK_TIME;
        return block.number + estBlockDist + 8;
    }

    // get block hash of first block with blocktime > _endTime
    function getSeed(uint256 _keyBlockNr)
        public
        view
        returns (uint256)
    {
        // Key Block not mined atm
        if (block.number <= _keyBlockNr) return block.number;
        return uint256(blockhash(_keyBlockNr));
    }

    // current reward balance
    function getRewardBalance(address _buyer)
        public
        view
        returns(uint256)
    {
        return rewardBalance[_buyer];
    } 

    // GET endTime
    function getSlideEndTime(uint256 _rId)
        public
        view
        returns(uint256)
    {
        return(round[_rId].slideEndTime);
    }

    function getFixedEndTime(uint256 _rId)
        public
        view
        returns(uint256)
    {
        return(round[_rId].fixedEndTime);
    }

    function getTotalPot()
        public
        view
        returns(uint256)
    {
        return grandPot + majorPot + minorPot;
    }

    // EarlyIncome
    function getEarlyIncomeByAddress(address _buyer)
        public
        view
        returns(uint256)
    {
        uint256 _sum = earlyIncomeScannedSum[_buyer];
        uint256 _fromRound = lastWithdrawnRound[_buyer] + 1; // >=1
        if (_fromRound + 100 < curRoundId) _fromRound = curRoundId - 100;
        uint256 _rId = _fromRound;
        while (_rId <= curRoundId) {
            _sum = _sum + getEarlyIncomeByAddressRound(_buyer, _rId);
            _rId++;
        }
        return _sum;
    }

    // included claimed amount
    function getEarlyIncomeByAddressRound(address _buyer, uint256 _rId)
        public
        view
        returns(uint256)
    {
        uint256 _pWeight = round[_rId].pEarlyIncomeWeight[_buyer];
        uint256 _ppw = round[_rId].ppw;
        uint256 _rCredit = round[_rId].pEarlyIncomeCredit[_buyer];
        uint256 _rEarlyIncome = ((_ppw.mul(_pWeight)).sub(_rCredit)).div(ZOOM);
        return _rEarlyIncome;
    }

    function getCurEarlyIncomeByAddress(address _buyer)
        public
        view
        returns(uint256)
    {
        uint256 _sum = 0;
        uint256 _fromRound = lastWithdrawnRound[_buyer] + 1; // >=1
        if (_fromRound + 100 < curRoundId) _fromRound = curRoundId - 100;
        uint256 _rId = _fromRound;
        while (_rId <= curRoundId) {
            _sum = _sum.add(getCurEarlyIncomeByAddressRound(_buyer, _rId));
            _rId++;
        }
        return _sum;
    }

    function getCurEarlyIncomeByAddressRound(address _buyer, uint256 _rId)
        public
        view
        returns(uint256)
    {
        uint256 _rEarlyIncome = getEarlyIncomeByAddressRound(_buyer, _rId);
        return _rEarlyIncome.sub(round[_rId].pEarlyIncomeClaimed[_buyer]);
    }

    ////////////////////////////////////////////////////////////////////

    function getEstKeyBlockNr(uint256 _rId)
        public
        view
        returns(uint256)
    {
        return round[_rId].keyBlockNr;
    }

    function getKeyBlockNr(uint256 _estKeyBlockNr)
        public
        view
        returns(uint256)
    {
        require(block.number > _estKeyBlockNr, "blockHash not avaiable");
        uint256 jump = (block.number - _estKeyBlockNr) / MAX_BLOCK_DISTANCE * MAX_BLOCK_DISTANCE;
        return _estKeyBlockNr + jump;
    }

    // Logs
    function getCurRoundId()
        public
        view
        returns(uint256)
    {
        return curRoundId;
    }

    function getTPrice()
        public
        view
        returns(uint256)
    {
        return Helper.getTPrice(curRTicketSum);
    }

    function getTMul()
        public
        view
        returns(uint256)
    {
        return Helper.getTMul(curRTicketSum);
    }

    function getPMul()
        public
        view
        returns(uint256)
    {
        return Helper.getEarlyIncomeMul(curRTicketSum);
    }

    function getPTicketSumByRound(uint256 _rId, address _buyer)
        public
        view
        returns(uint256)
    {
        return round[_rId].pTicketSum[_buyer];
    }

    function getTicketSumToRound(uint256 _rId)
        public
        view
        returns(uint256)
    {
        return round[_rId].ticketSum;
    }

    function getPInvestedSumByRound(uint256 _rId, address _buyer)
        public
        view
        returns(uint256)
    {
        return round[_rId].pInvestedSum[_buyer];
    }

    function getInvestedSumToRound(uint256 _rId)
        public
        view
        returns(uint256)
    {
        return round[_rId].investedSum;
    }

    function getPSlotLength(address _sender)
        public
        view
        returns(uint256)
    {
        return pSlot[_sender].length;
    }

    function getSlotLength()
        public
        view
        returns(uint256)
    {
        return slot.length;
    }

    function getSlotId(address _sender, uint256 i)
        public
        view
        returns(uint256)
    {
        return pSlot[_sender][i];
    }

    function getSlotInfo(uint256 _slotId)
        public
        view
        returns(address, uint256[4], string)
    {
        Slot memory _slot = slot[_slotId];
        return (_slot.buyer,[_slot.rId, _slot.tNumberFrom, _slot.tNumberTo, _slot.ethAmount], Helper.uintToString(_slot.salt));
    }

    function cashoutable(address _address) 
        public
        view
        returns(bool)
    {
        // need 1 ticket or in waiting time to start new round
        return (round[curRoundId].pTicketSum[_address] > 0) || (round[curRoundId].startTime > block.timestamp);
    }

    // set endRound, prepare to upgrade new version
    function setLastRound(uint256 _lastRoundId) 
        public
        onlyDevTeam()
    {
        require(_lastRoundId >= 18 && _lastRoundId > curRoundId, "too early to end");
        require(lastRoundId == 88888888, "already set");
        lastRoundId = _lastRoundId;
    }
}
function getEarlyIncomeByAddressRound(address _buyer, uint256 _rId)
        public
        view
        returns(uint256)
    {
        uint256 _pWeight = round[_rId].pEarlyIncomeWeight[_buyer];
        uint256 _ppw = round[_rId].ppw;
        uint256 _rCredit = round[_rId].pEarlyIncomeCredit[_buyer];
        uint256 _rEarlyIncome = ((_ppw.mul(_pWeight)).sub(_rCredit)).div(ZOOM);
        return _rEarlyIncome;
    }
// included claimed amount