contract ETLTokenPresale is Pausable, usingOraclize {
    using SafeMath for uint256;

    ETLToken public tokenReward;

    // mappings for future airdrop
    mapping (address => uint256) freezeBalances;
    mapping (address => uint256) freezeTime;

    uint256 public minimalPrice = 10000000000000; // 0.00001 ETH
    uint256 public tokensRaised; // all total amount of tokens
    uint256 public ETHUSD; // current ETH price in USD
    uint256 public startPresaleTime; // value of time when presale part is started
    uint256 public secPerBlock = 14; // 14 seconds per minde block

    uint256 constant public expiredTime = 1546300800; // 1 January 2019
    uint256 constant public twoWeeks = 1209600; // 2 weeks = 60*60*24*14 = 1209600 => 1209600 seconds
    uint256 constant public tenZero = 10000000000; // for decimals
    uint256 constant public loyaltyCap = 200000000000000; // 2 000 000 tokens
    uint256 constant public presaleCap = 400000000000000; // 4 000 000 tokens

    bool public presaleFinished = false;
    bool public loyaltyPart = true;
    bool public oraclizeOn = false;
    
    event LogPriceUpdated(string price);
    event LogNewOraclizeQuery(string description);

    // Modifier to make a function callable only when the Oraclizer is turn off.
    modifier whenOraclizeOff() {
        require(!oraclizeOn);
        _;
    }

    // Modifier to make a function callable only when the Oraclizer is turn on.
    modifier whenOraclizeOn() {
        require(oraclizeOn);
        _;
    }
    
    // Modifier to make a function callable only when the presale is not finished.
    modifier whenNotFinished() {
        require(!presaleFinished);
        _;
    }

    function ETLTokenPresale(address _tokenReward) public {
        tokenReward = ETLToken(_tokenReward);
    }

    // View function that shows current bonus value
    function getBonus() public view returns (uint256) {
        if (loyaltyPart) return 5;
        else if (!loyaltyPart && block.number <= startPresaleTime.add(twoWeeks.div(secPerBlock))) return 5;
        return 3;
    }
    
    // View function that shows current token price value
    function getPrice() public view returns (uint256) {
        if (loyaltyPart == true) return 1;
        return 8;
    }
    
    // Function activate, when ethers will be recieved to presale smart contract address
    function () public payable {
        buy(msg.sender);
    }

    function buy(address buyer) whenNotPaused whenNotFinished public payable {
        require(buyer != address(0));
        require(msg.value != 0);
        require(msg.value >= minimalPrice);

        uint256 tokens;
        
        // payment logic of loyalty part
        if (loyaltyPart) {
            if (tokensRaised >= loyaltyCap) {
                loyaltyPart = false;
                startPresaleTime = block.number;
            }
            
            tokens = msg.value.mul(ETHUSD).div(getPrice()).mul(10).div(tenZero);
            tokensRaised = tokensRaised.add(tokens);

            require (tokensRaised <= presaleCap); 

            if (tokensRaised == presaleCap) {
                presaleFinished = true;
            }
            
        // payment logic of presale part
        } else {
            
            tokens = msg.value.mul(ETHUSD).div(getPrice()).mul(10).div(tenZero);
            uint256 bonusTokens = tokens.mul(getBonus()).div(10);
            freezeBalances[msg.sender] = freezeBalances[msg.sender].add(bonusTokens);
            freezeTime[msg.sender] = expiredTime;
            tokensRaised = tokensRaised.add(tokens).add(bonusTokens);
            
            require (tokensRaised <= presaleCap); 

            if (tokensRaised == presaleCap) {
                presaleFinished = true;
            }
        }
        
        // transfer tokens to investor
        tokenReward.transfer(buyer, tokens);
        // transfer ethers to owner
        owner.transfer(msg.value);
    }

    // Callback for obtaining a new ETH/USD quote price from oraclizer
    function __callback(bytes32 myid, string result) whenOraclizeOn {
        if (msg.sender != oraclize_cbAddress()) {
            revert();
        }
        ETHUSD = stringToUint(result);
        LogPriceUpdated(result);
        if (oraclize_getPrice("URL") > this.balance) {
            LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
        } else {
            LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
            oraclize_query(86400, "URL", "json(https://api.kraken.com/0/public/Ticker?pair=ETHUSD).result.XETHZUSD.c.0");
        }
    }

    // Obtaining a new ETH/USD quote price from oraclizer
    function updatePrice() onlyOwner whenOraclizeOn payable external {
        if (oraclize_getPrice("URL") > this.balance) {
            LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
        } else {
            LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
            oraclize_query(86400, "URL", "json(https://api.kraken.com/0/public/Ticker?pair=ETHUSD).result.XETHZUSD.c.0");
        }
    }

    // Transfrom string to uint for obtaining a new ETH/USD quote price from oraclizer
    function stringToUint(string s) public pure returns (uint result) {
        bytes memory b = bytes(s);
        uint i;
        result = 0;
        for (i = 0; i < b.length; i = i.add(1)) {
            uint c = uint(b[i]);
            if (c == 46) {
                break;
            }
            if (c >= 48 && c <= 57) {
                result = result.mul(10).add(c.sub(48));
                require(result != 0);
            }
        }
    }
    
    // Setting a new ETH/USD quote price by owner
    function updatePriceManualy(uint256 _ETHUSD) onlyOwner external {
        ETHUSD = _ETHUSD;
    }

    // Setting a new ETH/USD quote price by owner
    function transferFunds() external onlyOwner {
        owner.transfer(address(this).balance);
    }

    // Allow to set new value of minimal purchase by owner
    function updateMinimal(uint256 _minimalPrice) external onlyOwner {
        minimalPrice = _minimalPrice;
    }

    // Allow to turn Oraclize on by owner
    function turnOnOraclize() whenOraclizeOff external onlyOwner {
        oraclizeOn = true;
    }

    // Allow to turn Oraclize off by owner
    function turnOffOraclize() whenOraclizeOn external onlyOwner {
        oraclizeOn = false;
    }

    // Allow to set new value of seconds to mined block by owner
    function updateSecPerBlock(uint256 _secPerBlock) external onlyOwner {
        secPerBlock = _secPerBlock;
    }

    // Allow to finish loyalty part and start Presale part by owner
    function startPresale() external onlyOwner {
        loyaltyPart = false;
        startPresaleTime = block.number;
    }

    // Allow to transfer tokens without any payments by owner
    function transferTokens(uint256 _tokens) external onlyOwner {
        uint256 tokens = _tokens.mul(100000000); // decimals = 8
        tokenReward.transfer(owner, tokens); 
    }

    // Allow to airdrop with less than 15 address by owner
    function airdrop(address[] _array1, uint256[] _array2) external onlyOwner {
        require(_array1.length <= 15);
        address[] memory arrayAddress = _array1;
        uint256[] memory arrayAmount = _array2;
        uint256 arrayLength = arrayAddress.length.sub(1);
        uint256 i = 0;
       
        while (i <= arrayLength) {
            tokenReward.transfer(arrayAddress[i], arrayAmount[i]);
            i = i.add(1);
        }  
    }

    // View function that shows time of freezing for future airdrop
    function freezeTimeOf(address _investor) public view returns (uint256 balance) {
        return freezeTime[_investor];
    }

    // View function that shows investor of freezing for future airdrop
    function freezeBalancesOf(address _investor) public view returns (uint256 balance) {
        return freezeBalances[_investor];
    }
   
    // Allow to send additional ethers to presale contract without getting tokens by owner
    function addEther() onlyOwner external payable {
       
    }

}
function () public payable {
    buy(msg.sender);
}

// Function activate, when ethers will be recieved to presale smart contract address