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");
        require(block.number <= round[curRoundId].keyBlockNr, "round over");
        _;
    }

    modifier notStarted() {
        require(block.timestamp < round[curRoundId].startTime, "round started");
        _;
    }

    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;
        uint256 ethAmount;
        uint256 salt;
    }

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

        mapping(address => uint256) pBoughtTicketSum;
        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 public initGrandPot;

    Slot[] public 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 = 18 * ONE_HOUR; // 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 = 250;

    uint256 constant public TBONUS_RATE = 100;
    uint256 public CASHOUT_REQ = 1;

    uint256 public GRAND_RATE;
    uint256 public MAJOR_RATE = 1001;
    uint256 public MINOR_RATE = 501;
    uint256 constant public MAJOR_MIN = 0.1 ether;
    uint256 constant public MINOR_MIN = 0.1 ether;

    //Bonus Tickets : Bounty + 7 sBounty
    uint256 public bountyPercent = 30;
    uint256 public sBountyPercent = 10;

    //uint256 public toNextPotPercent = 27;
    uint256 public grandRewardPercent = 15;
    uint256 public sGrandRewardPercent = 5;
    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 = 80; //68;
    uint256 public majorPercent = 10; // 24;
    uint256 public minorPercent = 10; // 8;

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

    uint256 public curRoundId;
    uint256 public lastRoundId = 88888888;

    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;
    // added by slot salt after every slot buy
    // does not matter with overflow
    uint256 public curRSalt;
    // ticket sum of current round
    uint256 public curRTicketSum;

    uint256 public lastBuyTime;
    uint256 public lastEndTime;
    uint256 constant multiDelayTime = 60;

    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();
        GRAND_RATE = getWeightRange();
    }

    // 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;
        curRTicketSum = 0;
    }

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

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

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

    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");
        uint256 _pRoundTicketSum = round[curRoundId].pBoughtTicketSum[msg.sender];
        uint256 _bountyTicketSum = _pRoundTicketSum * bountyPercent / 100;
        endRound(msg.sender, _bountyTicketSum);
        initRound();
        mintSlot(msg.sender, _bountyTicketSum, 0, 0);
    }

    function mintReward(
        address _lucker,
        uint256 _winNr,
        uint256 _slotId,
        uint256 _value,
        RewardType _rewardType)
        private
    {
        // add reward balance if its not Bounty Type and winner != 0x0
        if ((_rewardType != RewardType.Bounty) && (_lucker != 0x0))
            rewardBalance[_lucker] = rewardBalance[_lucker].add(_value);
        // reward log
        rewardContract.mintReward(
            _lucker,
            curRoundId,
            _winNr,
            slot[_slotId].tNumberFrom,
            slot[_slotId].tNumberTo,
            _value,
            uint256(_rewardType)
        );
    }

    function mintSlot(
        address _buyer,
        // uint256 _rId,
        // ticket numbers in range and unique in all rounds
        // uint256 _tNumberFrom,
        // uint256 _tNumberTo,
        uint256 _tAmount,
        uint256 _ethAmount,
        uint256 _salt
        )
        private
    {
        uint256 _tNumberFrom = curRTicketSum + 1;
        uint256 _tNumberTo = _tNumberFrom + _tAmount - 1;
        Slot memory _slot;
        _slot.buyer = _buyer;
        _slot.rId = curRoundId;
        _slot.tNumberFrom = _tNumberFrom;
        _slot.tNumberTo = _tNumberTo;
        _slot.ethAmount = _ethAmount;
        _slot.salt = _salt;
        slot.push(_slot);
        updateTicketSum(_buyer, _tAmount);
        round[curRoundId].slotSum = slot.length;
        pSlot[_buyer].push(slot.length - 1);
    }

    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 (MAJOR_RATE > 2) MAJOR_RATE--;
            if (Helper.isJackpot(seed, MAJOR_RATE, MAJOR_MIN, slot[jSlot].ethAmount)){

                winner = slot[jSlot].buyer;
                jReward = majorPot / 100 * jRewardPercent;
                mintReward(winner, 0, jSlot, jReward, RewardType.Major);
                majorPot = majorPot - jReward;
                MAJOR_RATE = 1001;
            }
            seed = seed + jSlot;
            // minorPot
            if (MINOR_RATE > 2) MINOR_RATE--;
            if (Helper.isJackpot(seed, MINOR_RATE, MINOR_MIN, slot[jSlot].ethAmount)){

                winner = slot[jSlot].buyer;
                jReward = minorPot / 100 * jRewardPercent;
                mintReward(winner, 0, jSlot, jReward, RewardType.Minor);
                minorPot = minorPot - jReward;
                MINOR_RATE = 501;
            }
            seed = seed + jSlot;
            jSlot++;
        }
    }

    function endRound(address _bountyHunter, uint256 _bountyTicketSum)
        private
    {
        // GRAND_RATE = GRAND_RATE * 9 / 10; // REMOVED
        uint256 _rId = curRoundId;
        uint256 keyBlockNr = getKeyBlockNr(round[_rId].keyBlockNr);
        // curRSalt SAFE, CHECKED
        uint256 _seed = getSeed(keyBlockNr) + curRSalt;
        uint256 onePercent = grandPot / 100;

        // 0 : 5% grandPot, 100% winRate
        // 1 : 15% grandPot, dynamic winRate
        uint256[2] memory rGrandReward = [
            onePercent * sGrandRewardPercent, 
            onePercent * grandRewardPercent
        ];
        uint256[2] memory weightRange = [
            curRTicketSum, 
            GRAND_RATE > curRTicketSum ? GRAND_RATE : curRTicketSum
        ];
        // REMOVED
        // uint256[2] memory toF2mAmount = [0, onePercent * toTokenPercent];

        // 1st turn for small grandPot (val = 5% rate = 100%)
        // 2nd turn for big grandPot (val = 15%, rate = max(GRAND_RATE, curRTicketSum), 12% to F2M contract if got winner)

        for (uint256 i = 0; i < 2; i++){
            address _winner = 0x0;
            uint256 _winSlot = 0;
            uint256 _winNr = Helper.getRandom(_seed, weightRange[i]);
            // if winNr > curRTicketSum => no winner this turn
            // win Slot : fromWeight <= winNr <= toWeight
            // got winner this rolling turn
            if (_winNr <= curRTicketSum) {
                // grandPot -= rGrandReward[i] + toF2mAmount[i];
                grandPot -= rGrandReward[i];
                // big grandPot 15%
                if (i == 1) {
                    GRAND_RATE = GRAND_RATE * 2;
                    // f2mContract.pushDividends.value(toF2mAmount[i])();
                }
                _winSlot = getWinSlot(_winNr);
                _winner = slot[_winSlot].buyer;
                _seed = _seed + (_seed / 10);
            }
            mintReward(_winner, _winNr, _winSlot, rGrandReward[i], RewardType.Grand);
        }

        mintReward(_bountyHunter, 0, 0, _bountyTicketSum, RewardType.Bounty);
        rewardContract.resetCounter(curRoundId);
        GRAND_RATE = (GRAND_RATE / 100) * 99 + 1;
    }

    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 getBonusTickets(address _buyer)
        private
        returns(uint256)
    {
        if (round[curRoundId].pBonusReceived[_buyer]) return 0;
        round[curRoundId].pBonusReceived[_buyer] = true;
        return round[curRoundId - 1].pBoughtTicketSum[_buyer] / TBONUS_RATE;
    }

    function updateMulti()
        private
    {
        if (lastBuyTime + multiDelayTime < block.timestamp) {
            lastEndTime = round[curRoundId].slideEndTime;
        }
        lastBuyTime = block.timestamp;
    }

    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);
        updateMulti();
        // update salt
        curRSalt = curRSalt + _salt;
        uint256 _tAmount = Helper.getTAmount(_ethAmount, _ticketSum);
        uint256 _tMul = getTMul(); // 100x Zoomed
        uint256 _pMul = Helper.getEarlyIncomeMul(_ticketSum);
        uint256 _pWeight = _pMul.mul(_tAmount);
        uint256 _toAddTime = Helper.getAddedTime(_ticketSum, _tAmount);
        addTime(curRoundId, _toAddTime);
        _tAmount = _tAmount.mul(_tMul) / 100;
        round[curRoundId].pBoughtTicketSum[_sender] += _tAmount;
        mintSlot(_sender, _tAmount + getBonusTickets(_sender), _ethAmount, _salt);

        // EarlyIncome Weight
        // ppw and credit zoomed x1000
        // earlyIncome mul of each ticket in this slot
        updateEarlyIncome(_sender, _pWeight);

        // 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].tNumberTo < _keyNumber) && (slot[_slotId].tNumberTo >= _keyNumber);
    }

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

    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 _pivotTo;
        // Binary search
        while (_from <= _to) {
            _pivot = (_from + _to) / 2;
            _pivotTo = slot[_pivot].tNumberTo;
            if (isWinSlot(_pivot, _keyNumber)) return _pivot;
            if (_pivotTo < _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(
                initGrandPot, 
                grandPot, 
                lastBuyTime + multiDelayTime < block.timestamp ? round[curRoundId].slideEndTime : lastEndTime, 
                round[curRoundId].fixedEndTime
            );
    }

    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 of curRound or lastRound in waiting time to start new round
        if (round[curRoundId].pTicketSum[_address] >= CASHOUT_REQ) return true;
        if (round[curRoundId].startTime > block.timestamp) {
            // underflow return false
            uint256 _lastRoundTickets = getPTicketSumByRound(curRoundId - 1, _address);
            if (_lastRoundTickets >= CASHOUT_REQ) return true;
        }
        return false;
    }

    // set endRound, prepare to upgrade new version
    function setLastRound(uint256 _lastRoundId) 
        public
        onlyDevTeam()
    {
        require(_lastRoundId >= 8 && _lastRoundId > curRoundId, "too early to end");
        require(lastRoundId == 88888888, "already set");
        lastRoundId = _lastRoundId;

    }

    function sBountyClaim(address _sBountyHunter)
        public
        notStarted()
        returns(uint256)
    {
        require(msg.sender == address(rewardContract), "only Reward contract can manage sBountys");
        uint256 _lastRoundTickets = round[curRoundId - 1].pBoughtTicketSum[_sBountyHunter];
        uint256 _sBountyTickets = _lastRoundTickets * sBountyPercent / 100;
        mintSlot(_sBountyHunter, _sBountyTickets, 0, 0);
        return _sBountyTickets;
    }

    /*
        TEST
    */

    // function forceEndRound() 
    //     public
    // {
    //     round[curRoundId].keyBlockNr = block.number;
    //     round[curRoundId].slideEndTime = block.timestamp;
    //     round[curRoundId].fixedEndTime = block.timestamp;
    // }

    // function setTimer1(uint256 _hours)
    //     public
    // {
    //     round[curRoundId].slideEndTime = block.timestamp + _hours * 1 hours + 60;
    //     round[curRoundId].keyBlockNr = genEstKeyBlockNr(round[curRoundId].slideEndTime);
    // }

    // function setTimer2(uint256 _days)
    //     public
    // {
    //     round[curRoundId].fixedEndTime = block.timestamp + _days * 1 days + 60;
    //     require(round[curRoundId].fixedEndTime >= round[curRoundId].slideEndTime, "invalid test data");
    // }
}
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;
}

// distribute to 3 pots Grand, Majorm Minor