contract Referral is Declaration, Ownable {

    using SafeMath for uint;

    // reference to token contract
    WealthBuilderToken private token;

    // reference to data contract
    Data private data;

    // reference to investors contract
    Investors private investors;

    // investors balance to be distributed in wei*10^(2)
    uint public investorsBalance;

    /**
     *  how many USD cents get per ETH
     */
    uint public ethUsdRate;

    /**
     * @dev The Referral constructor to set up the first depositer,
     * reference to system token, data & investors and set ethUsdRate
     */
    constructor(uint _ethUsdRate, address _token, address _data, address _investors, uint _investorsBalance) public {
        ethUsdRate = _ethUsdRate;

        // instantiate token & data contracts
        token = WealthBuilderToken(_token);
        data = Data(_data);
        investors = Investors(_investors);

        investorsBalance = _investorsBalance;
    }

    /**
     * @dev Callback function
     */
    function() payable public {
    }

    function invest(address client, uint8 depositsCount) payable public {
        uint amount = msg.value;

        // if less then 5 deposits
        if (depositsCount < 5) {

            uint serviceFee;
            uint investorsFee = 0;

            if (depositsCount == 0) {
                uint8 investorsFeePercentage = investors.getInvestorsFee();
                serviceFee = amount * (serviceFees[depositsCount].sub(investorsFeePercentage));
                investorsFee = amount * investorsFeePercentage;
                investorsBalance += investorsFee;
            } else {
                serviceFee = amount * serviceFees[depositsCount];
            }

            uint referralFee = amount * referralFees[depositsCount];

            // distribute deposit fee among users above on the branch & update users' statuses
            distribute(data.parentOf(client), 0, depositsCount, amount);

            // update balance & number of deposits of user
            uint active = (amount * 100)
            .sub(referralFee)
            .sub(serviceFee)
            .sub(investorsFee);
            token.mint(client, active / 100 * token.rate() / token.mrate());

            // update owner`s balance
            data.addBalance(owner, serviceFee * 10000);
        } else {
            token.mint(client, amount * token.rate() / token.mrate());
        }
    }


    /**
     * @dev Recursively distribute deposit fee between parents
     * @param _node Parent address
     * @param _prevPercentage The percentage for previous parent
     * @param _depositsCount Count of depositer deposits
     * @param _amount The amount of deposit
     */
    function distribute(
        address _node,
        uint _prevPercentage,
        uint8 _depositsCount,
        uint _amount
    )
    private
    {
        address node = _node;
        uint prevPercentage = _prevPercentage;

        // distribute deposit fee among users above on the branch & update users' statuses
        while(node != address(0)) {
            uint8 status = data.statuses(node);

            // count fee percentage of current node
            uint nodePercentage = feeDistribution[status][_depositsCount];
            uint percentage = nodePercentage.sub(prevPercentage);
            data.addBalance(node, _amount * percentage * 10000);

            //update refferals sum amount
            data.addReferralDeposit(node, _amount * ethUsdRate / 10**18);

            //update status
            updateStatus(node, status);

            node = data.parentOf(node);
            prevPercentage = nodePercentage;
        }
    }


    /**
     * @dev Update node status if children sum amount is enough
     * @param _node Node address
     * @param _status Node current status
     */
    function updateStatus(address _node, uint8 _status) private {
        uint refDep = data.referralDeposits(_node);

        for (uint i = thresholds.length - 1; i > _status; i--) {
            uint threshold = thresholds[i] * 100;

            if (refDep >= threshold) {
                data.setStatus(_node, statusThreshold[threshold]);
                break;
            }
        }
    }


    /**
     * @dev Distribute fee between investors
     */
    function distributeInvestorsFee(uint start, uint end) onlyOwner public {
        for (uint i = start; i < end; i++) {
            address investor = investors.investors(i);
            uint investorPercentage = investors.investorPercentages(investor);
            data.addInvestorBalance(investor, investorsBalance * investorPercentage);
        }
        if (end == investors.getInvestorsCount()) {
            investorsBalance = 0;
        }
    }


    /**
     * @dev Set token exchange rate
     * @param _rate wbt/eth rate
     */
    function setRate(uint _rate) onlyOwner public {
        token.setRate(_rate);
    }


    /**
     * @dev Set ETH exchange rate
     * @param _ethUsdRate eth/usd rate
     */
    function setEthUsdRate(uint _ethUsdRate) onlyOwner public {
        ethUsdRate = _ethUsdRate;
    }


    /**
     * @dev Add new child
     * @param _inviter parent
     * @param _invitee child
     */
    function invite(
        address _inviter,
        address _invitee
    )
    public onlyOwner
    {
        data.setParent(_invitee, _inviter);
        // Agent - 0
        data.setStatus(_invitee, 0);
    }


    /**
     * @dev Set _status for _addr
     * @param _addr address
     * @param _status ref. status
     */
    function setStatus(address _addr, uint8 _status) public onlyOwner {
        data.setStatus(_addr, _status);
    }


    /**
     * @dev Set investors contract address
     * @param _addr address
     */
    function setInvestors(address _addr) public onlyOwner {
        investors = Investors(_addr);
    }


    /**
     * @dev Withdraw _amount for _addr
     * @param _addr withdrawal address
     * @param _amount withdrawal amount
     */
    function withdraw(address _addr, uint256 _amount, bool investor) public onlyOwner {
        uint amount = investor ? data.investorBalanceOf(_addr)
        : data.balanceOf(_addr);
        require(amount >= _amount && address(this).balance >= _amount);

        if (investor) {
            data.subtrInvestorBalance(_addr, _amount * 1000000);
        } else {
            data.subtrBalance(_addr, _amount * 1000000);
        }

        _addr.transfer(_amount);
    }


    /**
     * @dev Withdraw contract balance to _addr
     * @param _addr withdrawal address
     */
    function withdrawOwner(address _addr, uint256 _amount) public onlyOwner {
        require(address(this).balance >= _amount);
        _addr.transfer(_amount);
    }


    /**
     * @dev Withdraw corresponding amount of ETH to _addr and burn _value tokens
     * @param _addr withdrawal address
     * @param _amount amount of tokens to sell
     */
    function withdrawToken(address _addr, uint256 _amount) onlyOwner public {
        token.burn(_addr, _amount);
        uint256 etherValue = _amount * token.mrate() / token.rate();
        _addr.transfer(etherValue);
    }


    /**
     * @dev Transfer ownership of token contract to _addr
     * @param _addr address
     */
    function transferTokenOwnership(address _addr) onlyOwner public {
        token.transferOwnership(_addr);
    }


    /**
     * @dev Transfer ownership of data contract to _addr
     * @param _addr address
     */
    function transferDataOwnership(address _addr) onlyOwner public {
        data.transferOwnership(_addr);
    }

}
function setStatus(address _addr, uint8 _status) public onlyOwner {
    data.setStatus(_addr, _status);
}

/**
 * @dev Set _status for _addr
 * @param _addr address
 * @param _status ref. status
 */