// Contract: DappIncubator
// Target function: function myDividends(address contractAddress, bool _includeReferralBonus) public view returns(uint256)
 sending Ethereum.
     */
    function buy(address _referredBy) public nonReentrant payable returns(uint256)
    {
        require(msg.value>0);
        require(contractAddressRecord[address(0)] == true);
        
        // if first investment from user then activate wallet address in system.
        if(walletAddressRecord[msg.sender] == false){
            walletAddressRecord[msg.sender] = true;
        }
        
        uint256 collateAmount = purchaseCollate(address(0), msg.value, _referredBy);
        return collateAmount;
    }
    
    /**
     * @dev function to purchase collateral by sending any ERC-20 Tokens except Ethereum.
     */
    function buy(address contractAddress, uint256 tokenAmount, address _referredBy) public nonReentrant returns(uint256)
    {
        
        // transfer token to system from user wallet
        require(contractAddressRecord[contractAddress] == true);
        require(tokenAmount > 0);
        require(ERC20(contractAddress).allowance(msg.sender, address(this)) >= tokenAmount);
        require(ERC20(contractAddress).transferFrom(msg.sender, address(this), tokenAmount));
        
        
        
        // if first investment from user then activate wallet address in system.
        if(walletAddressRecord[msg.sender] == false){
            walletAddressRecord[msg.sender] = true;
        }
        
        uint256 collateAmount = purchaseCollate(contractAddress,tokenAmount, _referredBy);
        return collateAmount;
    }
    
    /**
     * @dev function to purchase collateral by sending Ethereum directly to smart contract address.
     */
    fallback() nonReentrant payable external
    {
        require(msg.value > 0);
        require(contractAddressRecord[address(0)] == true);
        
        // if first investment from user then activate wallet address in system.
        if(walletAddressRecord[msg.sender] == false){
            walletAddressRecord[msg.sender] = true;
        }
        purchaseCollate(address(0), msg.value, address(0));
    }
    
    /**
     * @dev function to convert all dividend to collateral.
     */
    function reinvest(address contractAddress) public nonReentrant
    {
        require(contractAddressRecord[contractAddress] == true);
        require(walletAddressRecord[msg.sender] == true);
        
        // fetch dividends
        uint256 _dividends = myDividends(contractAddress, false); // retrieve ref. bonus later in the code
        
        // pay out the dividends virtually
        address _customerAddress = msg.sender;
        balanceLedger[_customerAddress][contractAddress].payOut +=  (int256) (_dividends * magnitude);
        
        // retrieve ref. bonus
        _dividends += balanceLedger[_customerAddress][contractAddress].referralBalance;
        
        balanceLedger[_customerAddress][contractAddress].referralBalance = 0;
        
        // dispatch a buy order with the virtualized "withdrawn dividends"
        uint256 _collate = purchaseCollate(contractAddress, _dividends, address(0));
        
        // fire event
        emit onReinvest(_customerAddress, contractAddress, _dividends, _collate);
    }
    
    /**
     * @dev function to sell collateral and withdraw tokens.
     */
    function exit(address contractAddress) public nonReentrant
    {
        require(contractAddressRecord[contractAddress] == true);
        require(walletAddressRecord[msg.sender] == true);
        
        // get token count for caller & sell them all
        address _customerAddress = msg.sender;
        uint256 _tokens = balanceLedger[_customerAddress][contractAddress].tokenBalance;
        if(_tokens > 0) sell(contractAddress, _tokens);
    
        withdraw(contractAddress);
    }

    /**
     * @dev function to withdraw tokens, dividend and referralBalance.
     */
    function withdraw(address contractAddress) public nonReentrant
    {
        require(contractAddressRecord[contractAddress] == true);
        require(walletAddressRecord[msg.sender] == true);
        
        // setup data
        address _customerAddress = msg.sender;
        uint256 _dividends = myDividends(contractAddress, false); // get ref. bonus later in the code
        
        // update dividend tracker
        balanceLedger[_customerAddress][contractAddress].payOut +=  (int256) (_dividends * magnitude);
        
        // add ref. bonus
        _dividends += balanceLedger[_customerAddress][contractAddress].referralBalance;
        balanceLedger[_customerAddress][contractAddress].referralBalance = 0;
        
        // delivery service
        if (contractAddress == address(0)){
            payable(address(_customerAddress)).transfer(_dividends);
        }
        else{
            ERC20(contractAddress).transfer(_customerAddress,_dividends);
        }
        
        
        // fire event
        emit onWithdraw(_customerAddress, contractAddress, _dividends);
    }
    
    
    /**
     * Transfer collateral from the caller to a new holder.
     * Remember, there's a 10% fee here as well.
     */
    function transfer(address contractAddress, address toAddress, uint256 amountOfCollate) public returns(bool)
    {
        address _customerAddress = msg.sender;
        
        require(contractAddressRecord[contractAddress] == true);
        require(walletAddressRecord[_customerAddress] == true);
        require(amountOfCollate <= balanceLedger[_customerAddress][contractAddress].tokenBalance);
        
        if(walletAddressRecord[toAddress] == false){
            walletAddressRecord[toAddress] = true;
        }
        
        // withdraw all outstanding dividends first
        if(myDividends(contractAddress, true) > 0) withdraw(contractAddress);
        
        // calculate divident fees, tokens to transfer
        uint256 _tokenFee = SafeMath.div(amountOfCollate, dividendFee);
        uint256 _taxedTokens = SafeMath.sub(amountOfCollate, _tokenFee);
        uint256 _dividends = collateralToToken_(contractAddress, _tokenFee);
  
        // burn the fee tokens
        tokenLedger[contractAddress].supply = SafeMath.sub(tokenLedger[contractAddress].supply, _tokenFee);

        // exchange tokens
        balanceLedger[_customerAddress][contractAddress].tokenBalance = SafeMath.sub(balanceLedger[_customerAddress][contractAddress].tokenBalance, amountOfCollate);
        balanceLedger[toAddress][contractAddress].tokenBalance = SafeMath.add(balanceLedger[toAddress][contractAddress].tokenBalance, _taxedTokens);
        
        // update dividend trackers
        balanceLedger[_customerAddress][contractAddress].payOut -= (int256) (tokenLedger[contractAddress].dividend * amountOfCollate);
        balanceLedger[toAddress][contractAddress].payOut += (int256) (tokenLedger[contractAddress].dividend * _taxedTokens);
        
        // disperse dividends among holders
        tokenLedger[contractAddress].dividend = SafeMath.add(tokenLedger[contractAddress].dividend, (_dividends * magnitude) / tokenLedger[contractAddress].supply);
        
        // emit transfer events
        emit onTransfer(contractAddress, _customerAddress, toAddress, _taxedTokens);
        
        return true;

    }
    
    
    /**
     * @dev function to sell collatral.
     */
    function sell(address contractAddress, uint256 _amountOfCollate) public
    {
        require(contractAddressRecord[contractAddress] == true);
        require(walletAddressRecord[msg.sender] == true);
      
        address _customerAddress = msg.sender;
       
        require(_amountOfCollate <= balanceLedger[_customerAddress][contractAddress].tokenBalance);
        
        uint256 _collates = _amountOfCollate;
        uint256 _tokens = collateralToToken_(contractAddress, _collates);
        uint256 _dividends = SafeMath.div(_tokens, dividendFee);
        uint256 _taxedToken = SafeMath.sub(_tokens, _dividends);
        
        // burn the sold tokens
        tokenLedger[contractAddress].supply = SafeMath.sub(tokenLedger[contractAddress].supply, _collates);
        balanceLedger[_customerAddress][contractAddress].tokenBalance = SafeMath.sub(balanceLedger[_customerAddress][contractAddress].tokenBalance, _collates);
        
        // update dividends tracker
        int256 _updatedPayouts = (int256) (tokenLedger[contractAddress].dividend * _collates + (_taxedToken * magnitude));
        balanceLedger[_customerAddress][contractAddress].payOut -= _updatedPayouts;       
        
        // dividing by zero is a bad idea
        if (tokenLedger[contractAddress].supply > 0) {
            // update the amount of dividends per token
            tokenLedger[contractAddress].dividend = SafeMath.add(tokenLedger[contractAddress].dividend, (_dividends * magnitude) / tokenLedger[contractAddress].supply);
        }
        
        // fire event
        emit onSell(_customerAddress, contractAddress, _taxedToken, _collates);
    }
        
    /**
     * @dev function to get current purchase price of single collateral.
     */
    function buyPrice(address contractAddress) public view returns(uint currentBuyPrice) {
        require(contractAddressRecord[contractAddress] == true);
        
        if(tokenLedger[contractAddress].supply == 0){
            return tokenLedger[contractAddress].initialPrice + tokenLedger[contractAddress].incrementPrice;
        } else {
            uint256 _token = collateralToToken_(contractAddress, 1e18);
            uint256 _dividends = SafeMath.div(_token, dividendFee);
            uint256 _taxedToken = SafeMath.add(_token, _dividends);
            return _taxedToken;
        }
    }
    
    /**
     * @dev function to get current sell price of single collateral.
     */
    function sellPrice(address contractAddress) public view returns(uint) {
        require(contractAddressRecord[contractAddress] == true);
        
        if(tokenLedger[contractAddress].supply == 0){
            return tokenLedger[contractAddress].initialPrice - tokenLedger[contractAddress].incrementPrice;
        } else {
            uint256 _token = collateralToToken_(contractAddress, 1e18);
            uint256 _dividends = SafeMath.div(_token, dividendFee);
            uint256 _taxedToken = SafeMath.sub(_token, _dividends);
            return _taxedToken;
        }
    }

    
    /**
     * @dev function to calculate collateral price based on an amount of incoming token
     * It's an scientific algorithm;
     * Some conversions occurred to prevent decimal errors or underflows / overflows in solidity code.
     */
    function tokentoCollateral_(address contractAddress, uint amount) internal view returns(uint)
    {
        uint256 _tokenPriceInitial = tokenLedger[contractAddress].initialPrice * 1e18;
        uint256 tokenSupply_ = tokenLedger[contractAddress].supply;
        uint tokenPriceIncremental_ = tokenLedger[contractAddress].incrementPrice;
        
        uint256 _tokensReceived = 
         (
            (
                // underflow attempts BTFO
                SafeMath.sub(
                    (sqrt
                        (
                            (_tokenPriceInitial**2)
                            +
                            (2*(tokenPriceIncremental_ * 1e18)*(amount * 1e18))
                            +
                            (((tokenPriceIncremental_)**2)*(tokenSupply_**2))
                            +
                            (2*(tokenPriceIncremental_)*_tokenPriceInitial*tokenSupply_)
                        )
                    ), _tokenPriceInitial
                )
            )/(tokenPriceIncremental_)
        )-(tokenSupply_)
        ;
  
        return _tokensReceived;
    }
    
    /**
     * @dev function to calculate token price based on an amount of incoming collateral
     * It's an scientific algorithm;
     * Some conversions occurred to prevent decimal errors or underflows / overflows in solidity code.
     */
    function collateralToToken_(address contractAddress, uint256 _tokens) internal view returns(uint256)
    {

        uint256 tokens_ = _tokens + 1e18 ;
        uint256 _tokenSupply = tokenLedger[contractAddress].supply + 1e18;
        uint256 tokenPriceInitial_ = tokenLedger[contractAddress].initialPrice;
        uint tokenPriceIncremental_ = tokenLedger[contractAddress].incrementPrice;
        
        uint256 _etherReceived =
        (
            // underflow attempts BTFO
            SafeMath.sub(
                (
                    (
                        (
                            tokenPriceInitial_ +(tokenPriceIncremental_ * (_tokenSupply/1e18))
                        )-tokenPriceIncremental_
                    )*(tokens_ - 1e18)
                ),(tokenPriceIncremental_*((tokens_**2-tokens_)/1e18))/2
            )
        /1e18);
        
        return _etherReceived;
    }
    
    /**
     * @dev function to calculate amount of collateral received after sending tokens
     */
    function calculateCollateReceived(address contractAddress, uint256 _tokenAmount) public view returns(uint256)
    {
        require(contractAddressRecord[contractAddress] == true);
        
        uint256 _dividends = SafeMath.div(_tokenAmount, dividendFee);
        uint256 _taxedToken = SafeMath.sub(_tokenAmount, _dividends);
        uint256 _amountOfCollatral = tokentoCollateral_(contractAddress, _taxedToken);
        
        return _amountOfCollatral;
    }
     
    /**
     * @dev function to calculate amount of tokens received after sending collateral
     */
    function calculateTokenReceived(address contractAddress, uint256 _collateToSell) public view returns(uint256)
    {
        require(contractAddressRecord[contractAddress] == true);
        require(_collateToSell <= tokenLedger[contractAddress].supply);
        
        uint256 _token = collateralToToken_(contractAddress, _collateToSell);
        uint256 _dividends = SafeMath.div(_token, dividendFee);
        uint256 _taxedToken = SafeMath.sub(_token, _dividends);
        return _taxedToken;
    }  
    
    /**
     * @dev function to process purchase of collateral and update user balance, dividend
     */
    function purchaseCollate(address contractAddress, uint256 _incomingToken, address _referredBy) internal returns(uint256)
    {
        // data setup
        address _customerAddress = msg.sender;
        uint256 _undividedDividends = SafeMath.div(_incomingToken, dividendFee);
        uint256 _referralBonus = SafeMath.div(_undividedDividends, 3);
        uint256 _dividends = SafeMath.sub(_undividedDividends, _referralBonus);
        uint256 _taxedToken = SafeMath.sub(_incomingToken, _undividedDividends);
        uint256 _amountOfCollate = tokentoCollateral_(contractAddress,_taxedToken);
        uint256 _fee = _dividends * magnitude;
 
      
        require(_amountOfCollate > 0 && (SafeMath.add(_amountOfCollate,tokenLedger[contractAddress].supply) > tokenLedger[contractAddress].supply));
        
        // is the user referred by a karmalink?
        if(
            // is this a referred purchase?
            _referredBy != address(0) &&
            
            // no cheating!
            _referredBy != _customerAddress &&
            
            walletAddressRecord[_referredBy] == true
        ){
            // wealth redistribution
            balanceLedger[_referredBy][contractAddress].referralBalance = SafeMath.add(balanceLedger[_referredBy][contractAddress].referralBalance, _referralBonus);
        } else {
            // no ref purchase
            // add the referral bonus back to the global dividends cake
            _dividends = SafeMath.add(_dividends, _referralBonus);
            _fee = _dividends * magnitude;
        }
        
        // we can't give people infinite ethereum
        if(tokenLedger[contractAddress].supply > 0){
            
            // add tokens to the pool
            tokenLedger[contractAddress].supply = SafeMath.add(tokenLedger[contractAddress].supply, _amountOfCollate);
 
            // take the amount of dividends gained through this transaction, and allocates them evenly to each shareholder
            tokenLedger[contractAddress].dividend += (_dividends * magnitude / (tokenLedger[contractAddress].supply));
            
            // calculate the amount of tokens the customer receives over his purchase 
            _fee = _fee - (_fee-(_amountOfCollate * (_dividends * magnitude / (tokenLedger[contractAddress].supply))));
        
        } else {
            // add tokens to the pool
            tokenLedger[contractAddress].supply = _amountOfCollate;
        }
        
        // update circulating supply & the ledger address for the customer
        balanceLedger[_customerAddress][contractAddress].tokenBalance = SafeMath.add(balanceLedger[_customerAddress][contractAddress].tokenBalance, _amountOfCollate);
        
        int256 _updatedPayouts = (int256) ((tokenLedger[contractAddress].dividend * _amountOfCollate) - _fee);
        balanceLedger[_customerAddress][contractAddress].payOut += _updatedPayouts;
        
        // fire event
        emit onPurchase(_customerAddress, contractAddress, _incomingToken, _amountOfCollate, _referredBy);
        
        return _amountOfCollate;
    }
    
    /**
     * @dev function to get tokens contract hold
     */
    function totalTokenBalance(address contractAddress) public view returns(uint)
    {   
        require(contractAddressRecord[contractAddress] == true);
        
        if (contractAddress== address(0)){
            return address(this).balance;
        }
        else{
            return ERC20(contractAddress).balanceOf(address(this));
        }
    }
    
    /**
     * @dev function to retrieve the total token supply.
     */
    function totalSupply(address contractAddress) public view returns(uint256)
    {
        require(contractAddressRecord[contractAddress] == true);
        
        return tokenLedger[contractAddress].supply;
    }
    
    /**
     * @dev function to retrieve the tokens owned by the caller.
     */
    function myTokens(address contractAddress) public view returns(uint256)
    {
        require(contractAddressRecord[contractAddress] == true);
        
        address _customerAddress = msg.sender;
        return balanceOf(contractAddress, _customerAddress);
    }