contract NBTC is NBTCToken,ReentrancyGuard {
    using SafeMath for uint256;

    mapping (address => User) public users;
    mapping (uint8 => Slab) public mulfact;
    mapping (uint8 => uint8) public levelComm;
    mapping (uint8 => uint256) public levelVolume;

    uint256 public totalUsers;
    uint8 public mfIndex;

    struct User {
        uint8 level;
        uint256 directVolume;
        uint256 indirectVolume;
        uint32 directCount;
        uint32 indirectCount;
        uint256 claimed;  
        address parent;
        uint256 ethBalance; 
        uint256 ethInvest;
    }

    struct Slab {
        uint256 saleVolume;
        uint16 mulFactor;
    }

    constructor(string memory cName, string memory cSymbol,uint256 expireSeconds) {
        name=cName;
        symbol=cSymbol;

        levelComm[1] = 50;
        levelComm[2] = 60;
        levelComm[3] = 65;
        levelComm[4] = 70;
        levelComm[5] = 75;
        levelComm[6] = 80;
        levelComm[7] = 85;
        levelComm[8] = 90;
        levelComm[9] = 95;
        levelComm[10] = 100;

        levelVolume[1] = 0;
        levelVolume[2] = 1000 * 10**decimals; // 1k tokens
        levelVolume[3] = 10000 * 10**decimals; // 10k lakh tokens
        levelVolume[4] = 100000 * 10**decimals; // 1 lakh tokens
        levelVolume[5] = 200000 * 10**decimals; // 2 lakh tokens
        levelVolume[6] = 400000 * 10**decimals; // 4 lakh tokens
        levelVolume[7] = 800000 * 10**decimals; // 8 lakh tokens
        levelVolume[8] = 1600000 * 10**decimals; // 16 lakh tokens
        levelVolume[9] = 3200000 * 10**decimals; // 32 lakh tokens
        levelVolume[10] = 6400000 * 10**decimals; // 64 lakh tokens
        
        mulfact[1] = Slab({ saleVolume: 500000 * 10**decimals, mulFactor: 500 }); // 5 lakh token, 0.5 mil
        mulfact[2] = Slab({ saleVolume: 2500000 * 10**decimals, mulFactor: 300 }); // 25 lakh token, 2.5 mil
        mulfact[3] = Slab({ saleVolume: 5000000 * 10**decimals, mulFactor: 250 }); // 50 lakh token, 5 mil
        mulfact[4] = Slab({ saleVolume: 10000000 * 10**decimals, mulFactor: 200 }); // 1 cr token, 10 mil
        mulfact[5] = Slab({ saleVolume: 20000000 * 10**decimals, mulFactor: 175 }); // 2 cr token, 20 mil,
        mulfact[6] = Slab({ saleVolume: 30000000 * 10**decimals, mulFactor: 150 }); // 3 cr token, 30 mil
        mulfact[7] = Slab({ saleVolume: 40000000 * 10**decimals, mulFactor: 125 }); // 4 cr token, 40 mil
        mulfact[8] = Slab({ saleVolume: 50000000 * 10**decimals, mulFactor: 100 }); // 5 cr token, 50 mil,
        mulfact[9] = Slab({ saleVolume: 100000000 * 10**decimals, mulFactor: 50 }); // 10 cr token, 100 mil

        mfIndex=1;
        totalUsers=0;

        hardCapValidTime = block.timestamp.add(expireSeconds);  // expire in one day ie in 86400 seconds

        users[OwnerAddress] = User({
        level: 10,
        directVolume: 0,
        indirectVolume: 0,
        directCount:0,
        indirectCount:0,
        claimed:0,
        parent: OwnerAddress,
        ethBalance:0,
        ethInvest:0
        });
    }

    modifier userRegistered() {
        require(users[msg.sender].level != 0, 'User does not exist');
        _;
    }

    /* Dont accept eth*/  
    receive() external payable {
        revert("The contract does not accept direct payment, please use the purchase method with a referral address.");
    }

    function mintForCommunity(address account, uint256 amount) public isOwner {
        _mintForCommunity(account,amount) ;
    }

    function burn(uint256 amount) public returns (bool) {
        require(balanceOf(msg.sender) >= amount,'Insufficient tokens to burn');
        require(hardCapAchieved == true,"You can not burn token as hardcap is not achieved.");       

        _burn(msg.sender, amount);
        return true;
    }

    function withdraw(uint256 amount) external userRegistered nonReentrant{
        require(users[msg.sender].ethBalance >=amount,'insufficient balance for withdrawl.');
        require(amount > 0,'Invalid withdrawal amount.');
    
        users[msg.sender].ethBalance = users[msg.sender].ethBalance.sub(amount);
        users[msg.sender].claimed = users[msg.sender].claimed.add(amount);
        (bool success,  ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");
        emit Claim(msg.sender,amount);
    }

    function purchase(address refAddress) external payable isContractActive {
        require(refAddress != address(0), 'Referral Address is mandatory.');
        require(users[refAddress].level != 0, 'Referral Address is not registered.');
        require(msg.value >=100000000000000000,'Minimum purchase amount is 0.1 eth');
        require(msg.value <=100000000000000000000,'Maximum purchase amount is 100 eth.');

        bool isNewUser= false;
        uint256 tokenVol;

        uint commLeft;
        uint commPer;
        uint commGiven;
        uint comm;
        uint commVal;
        address userAddr;
        address parentAddr;

        tokenVol= gettokenCount(msg.value); //msg.value.mul(100);

        if (users[msg.sender].level == 0) {
            register(refAddress);
            isNewUser = true;
        }
        else
            require(refAddress == users[msg.sender].parent, "Sender belongs to different referral address");

        _mint(msg.sender, tokenVol);

        // update eth balance and reserve 75 % 
        users[msg.sender].ethInvest = users[msg.sender].ethInvest.add(msg.value);
        commLeft=100;
        commGiven=0;
        userAddr=msg.sender;

        //distribute eth to parent heirarchy until level10
        while (commLeft > 0) {
            parentAddr=users[userAddr].parent;
            commPer=levelComm[users[parentAddr].level];  // commission percentage at parent level
            comm=commPer.sub(commGiven);              // comm to give  
            commGiven=commGiven.add(comm);
            commVal= msg.value.mul(comm).div(100);
            users[parentAddr].ethBalance=users[parentAddr].ethBalance.add(commVal);
            commLeft = commLeft.sub(comm);
            userAddr=parentAddr;
        }
        
        parentAddr=users[msg.sender].parent;

        // add Volume to immediate parent node
        users[parentAddr].directVolume=users[parentAddr].directVolume.add(tokenVol);
        upgradelevel(parentAddr);
        
        //upgrade level and volume of the heirarchy till top node
        userAddr = parentAddr;
        
        while (userAddr !=users[parentAddr].parent)
        {
            parentAddr=users[userAddr].parent;
            users[parentAddr].indirectVolume=users[parentAddr].indirectVolume.add(tokenVol);
            if (isNewUser)
                users[parentAddr].indirectCount+=1;
            upgradelevel(parentAddr);
            userAddr=parentAddr;
        }

        checkHardCap();
        emit Purchase(msg.sender,msg.value,tokenVol);
    }

    function upgradelevel(address refAddr) internal
    {
            uint256 totVolume;
            uint8 idx;
            
            totVolume = users[refAddr].directVolume.add(users[refAddr].indirectVolume);
            idx =users[refAddr].level;

            while (idx <=10)
            {
                if(totVolume >= levelVolume[idx]) 
                    users[refAddr].level = idx;  
                else    
                    break;

                idx++;
            }
    }

    function register(address refAddr) internal
    {
        users[msg.sender] = User({
            level: 1,
            directVolume: 0,
            indirectVolume: 0,
            directCount:0,
            indirectCount:0,
            claimed:0,
            parent: refAddr,
            ethBalance:0,
            ethInvest:0
        });
        
        users[refAddr].directCount+=1;
        totalUsers=totalUsers.add(1);
    }
    
    function estimateTokenCount(uint256 ethVal) public view returns (uint256) {
        require(ethVal >=100000000000000000,'Minimum estimation value is 0.1 ethereum.');
        require(ethVal <=100000000000000000000,'Maximum estimation value is 100 ethereum.');

        uint16 mf;
        uint256 tokenCnt;
        uint256 postSupply;
        uint256 diff;
        uint256 ethUsed;
        uint256 ethLeft;
        
        mf = mulfact[mfIndex].mulFactor;
        tokenCnt=ethVal.mul(mf);
        postSupply=_totalSupply.add(tokenCnt);

        if (postSupply > mulfact[mfIndex].saleVolume && mfIndex !=9)
        {
            diff = mulfact[mfIndex].saleVolume.sub(_totalSupply);
            ethUsed= diff.div(mf);
            ethLeft = ethVal.sub(ethUsed);
            mf = mulfact[mfIndex+1].mulFactor;
            tokenCnt=diff.add(ethLeft.mul(mf));
            postSupply=_totalSupply.add(tokenCnt);
        }

        if (postSupply >distSupply) {
            tokenCnt = distSupply.sub(_totalSupply);
        }
        return tokenCnt;
    }

    function gettokenCount(uint256 ethVal) internal returns (uint256) {
        uint16 mf;
        uint256 tokenCnt;
        uint256 postSupply;
        uint256 diff;
        uint256 ethUsed;
        uint256 ethLeft;
        
        mf = mulfact[mfIndex].mulFactor;
        tokenCnt=ethVal.mul(mf);
        postSupply=_totalSupply.add(tokenCnt);

        if (postSupply > mulfact[mfIndex].saleVolume && mfIndex !=9)
        {
            diff = mulfact[mfIndex].saleVolume.sub(_totalSupply);
            ethUsed= diff.div(mf);
            ethLeft = ethVal.sub(ethUsed);
            mf = mulfact[mfIndex+1].mulFactor;
            tokenCnt=diff.add(ethLeft.mul(mf));
            postSupply=_totalSupply.add(tokenCnt);
            mfIndex +=1;
        }

        if (postSupply >distSupply) {
            tokenCnt = distSupply.sub(_totalSupply);
        }
        return tokenCnt;
    }

    function calculateEthValue(uint256 tokenVal) public view returns (uint256) {
        require(tokenVal >=5000000000000000000,'Minimum estimation value is 5 nbtc tokens.');
        require(tokenVal <=50000000000000000000000,'Maximum estimation value is 50000 nbtc tokens.');

        uint16 mf;
        uint256 ethVal;
        uint256 postSupply;
        uint256 diff;
        uint256 ethUsed;
        uint256 tokenLeft;
        
        mf = mulfact[mfIndex].mulFactor;
        ethVal=tokenVal.div(mf);
        postSupply=_totalSupply.add(tokenVal);

        if (postSupply > mulfact[mfIndex].saleVolume && mfIndex !=9) {
            diff = mulfact[mfIndex].saleVolume.sub(_totalSupply);
            ethUsed= diff.div(mf);
            tokenLeft = tokenVal.sub(diff);
            mf = mulfact[mfIndex+1].mulFactor;
            ethVal= ethUsed.add(tokenLeft.div(mf));
        }
        
        return ethVal;
    }
}
receive() external payable {
        revert("The contract does not accept direct payment, please use the purchase method with a referral address.");
    }
/* Dont accept eth*/