contract ReferContract is ReferConstants, Ownable {
    ReferContractInterface referContractInterface;
    uint public baseRate;
    
    mapping (address => uint) public etherBalance;
    mapping (address => address) public userReferrer;
    mapping (address => uint8) public userLevel;
    mapping (address => uint) public tokensBought;
    
    constructor(address _tokenAddress) public {
        referContractInterface = ReferContractInterface(_tokenAddress);
        baseRate = 3000000000000000;
        // to be consistent with game
        userReferrer[owner] = owner;
        userLevel[owner] = 4;
    }
    
     // Update only if contract is not getting traction or got more
     // traction that initially thought.
     // increase the price if there is huge traffic to compensate more people
     // decrease the price if there is less traffic to attract more users.
    function updateRate(uint _newRate) onlyOwner public {
        require(baseRate != 0);
        // rate shouldn't be less than half or more than twice.
        require(_newRate.mul(2) > baseRate && baseRate.mul(2) > _newRate);
        baseRate = _newRate;
    }
    
    function getRate(uint level) public view returns (uint) {
        if (level == 4) {
            return baseRate.mul(6);
        } else if (level == 3) {
            return baseRate.mul(5);
        } else if (level == 2) {
            return baseRate.mul(3);
        } else if (level == 1) {
            return baseRate.mul(2);
        } else {
            return baseRate.mul(6);
        } 
    }
    
    function fundAccount(address ref, uint eth, uint level) internal {
        if (ref != address(0x0) && userLevel[ref] >= level) {
            etherBalance[ref] += eth;
        } else {
            etherBalance[owner] += eth;
        }
    }
    
    function distributeMoney(address ref, address parent1, uint money) internal {
        // since we are calculating percentage which will be 
        // (money * x)/100
        money = money.div(100);
        
        fundAccount(ref, money.mul(FIRST_USER_CUT), 1);
        fundAccount(parent1, money.mul(SECOND_USER_CUT), 2);
        fundAccount(userReferrer[parent1], money.mul(THIRD_USER_CUT), 3);
        fundAccount(userReferrer[userReferrer[parent1]], money.mul(FOURTH_USER_CUT), 4);
        fundAccount(owner, money.mul(OWNER_CUT), 0);
    }
    
    function buyReferTokens(address ref, uint8 level) payable public {
        require(level > 0 && level < 5);
        
        if (userLevel[msg.sender] == 0) { // new user
            userLevel[msg.sender] = level;
            if (getTokenBalance(ref) < 1) {  // The referee doesn't have a token 
                ref = owner; // change referee
            }
            userReferrer[msg.sender] = ref; // permanently set owner as the referrer
            referContractInterface.decrement(userReferrer[msg.sender]);
        } else { // old user
            require(userLevel[msg.sender] == level);
            if (getTokenBalance(userReferrer[msg.sender]) < 1) { // The referee doesn't have a token
                ref = owner; // only change the parent but don't change gradparents
            } else {
                ref = userReferrer[msg.sender];
            }
            referContractInterface.decrement(ref);
        }
        
        uint tokens = msg.value.div(getRate(level));
        require(tokens >= 5);
        referContractInterface.mint(msg.sender, tokens);
        distributeMoney(ref, userReferrer[userReferrer[msg.sender]] , msg.value);
        tokensBought[msg.sender] += tokens;
        emit Bought(msg.sender, ref, userReferrer[userReferrer[msg.sender]], msg.value, tokens, level);
    }
    
    function upgradeLevel(uint8 level) payable public {
        require(level <= 4);
        require(userLevel[msg.sender] != 0 && userLevel[msg.sender] < level);
        uint rateDiff = getRate(level).sub(getRate(userLevel[msg.sender]));
        uint toBePaid = rateDiff.mul(tokensBought[msg.sender]);
        require(msg.value >= toBePaid);
        userLevel[msg.sender] = level;
        distributeMoney(userReferrer[msg.sender], userReferrer[userReferrer[msg.sender]] , msg.value);
        emit LevelUpdated(msg.sender, msg.value, level);
    }
    
    function getAmountToUpdate(uint8 level) view public returns (uint) {
        uint rate = getRate(level).mul(tokensBought[msg.sender]);
        uint ratePaid = getRate(userLevel[msg.sender]).mul(tokensBought[msg.sender]);
        return rate.sub(ratePaid);
    }
    
    function withdraw() public {
        uint amount = etherBalance[msg.sender];
        etherBalance[msg.sender] = 0;
        msg.sender.transfer(amount);
    }
    
    function getTokenBalance(address _who) public view returns(uint) {
        return referContractInterface.getBalance(_who);
    }
}
function updateRate(uint _newRate) onlyOwner public {
    require(baseRate != 0);
    // rate shouldn't be less than half or more than twice.
    require(_newRate.mul(2) > baseRate && baseRate.mul(2) > _newRate);
    baseRate = _newRate;
}

// Update only if contract is not getting traction or got more
// traction that initially thought.
// increase the price if there is huge traffic to compensate more people
// decrease the price if there is less traffic to attract more users.