contract OKTOToken is ERC20('OKTO', 'OKTO', 18), ReentrancyGuard, Owned, BlackList {
    using SafeMath for uint256;
    uint256 public totalTokenSold;
    uint256 public eatherEarnedInCurrentRound;
    uint256 public totalEtherEarned;
    uint256 public currentRound = 0;  //currentRound = 0 means ICO didnt started yet; currentRound >= 3 ico ended
    bool public frozen = true;
    bool public buyableFlag = true;

    uint[] public milestone = [1, 108e18 , 297e18];
    uint[] public price = [0, 50000, 33333];
    uint256 public min = 0.2e18;
    uint256 public maxStep = 5e18;
    mapping(address=>mapping(uint256=>uint256)) balances;
    uint256 public upperCap;

    uint256 weight;

    constructor () public {
        _mint(address(this), 90000000 * 1e18);
        upperCap = 37800000 * 1e18;
    }

    function currentRoundPrice() public view returns (uint256) {
        if(currentRound>=price.length)
            return _postIcoPrice();
    	return price[currentRound];
    }

    function currentRoundVolume() public view returns (uint256) {
        if(currentRound>=price.length)
            return 0;
    	return milestone[currentRound]*price[currentRound];
    }

    function currentRoundMilestoneInEther() public view returns (uint256) {
        if(currentRound>=price.length)
            return 0;
    	return milestone[currentRound];
    }

    function tokensLeft() public view returns (uint256) {
        return upperCap.sub(totalTokenSold);
    }

    function _postIcoPrice() private view returns (uint256) {
        return price[price.length.sub(1)].div(2);
    }

    //section owner

    function freezeTokens() external onlyOwner() {
    	frozen = true;
    }

    function unfreezeTokens() external onlyOwner() {
    	frozen = false;
    }

    function stopSelling() public onlyOwner() {
        buyableFlag = false;
    }

    function continueSelling() public onlyOwner() {
        buyableFlag = true;
    }

    function nextRound() external onlyOwner() {
    	_nextRound();
    }

    function withdraw(address payable receiver, uint256 amount) public onlyOwner() nonReentrant() {
    	receiver.transfer(amount);
    }

    function donateTokens(address tokenRecipient, uint256 amount) public onlyOwner() {
    	_transfer(address(this), tokenRecipient, amount);
    }

    function setPrice(uint256 newPrice, uint256 round) public onlyOwner() {
    	require(_isRoundExists(round), "Wrong round number");
    	price[round] = newPrice;
    }

    function burnUnsold() public onlyOwner() {
    	_burn(address(this), balanceOf(address(this)));
    }

    //endsection


    function approveAndCall(address _spender, uint256 _value, bytes calldata _extraData) external returns (bool success)
    {
        approve(_spender, _value);
        TokenRecipient(_spender).receiveApproval(msg.sender, _value, address(this), _extraData);
        return true;
    }

    // section payable

    receive() external payable buyable() {
         _swapTokenForEhter(msg.sender,msg.value);
    }

    function buy() payable public buyable() {
        _swapTokenForEhter(msg.sender,msg.value);
    }

    //endsection

    function _swapTokenForEhter(address buyer, uint256 amount) private {
        require(amount >= min,"Less than the minimum purchase");

        uint256 _swapBalance = _calculateToken(buyer,amount);

        _transfer(address(this), buyer, _swapBalance);
        totalTokenSold = totalTokenSold.add(_swapBalance);
        totalEtherEarned = totalEtherEarned.add(amount);


        require(totalTokenSold<=upperCap, "No tokens left for selling");
    }

    function _calculateToken(address buyer, uint256 amount) private returns (uint256) {
        uint256 _price;
        uint256 res;
        uint256 localRound = currentRound;


        if(currentRound>=price.length)
    	{
    	    _price = _postIcoPrice();
    	}
        else

        {
            _price = price[currentRound];
            uint256 currentRoundMilestone = currentRoundMilestoneInEther();
            if(eatherEarnedInCurrentRound.add(amount) > currentRoundMilestone)
            {
                uint256 etherLeftForMilestone = currentRoundMilestone.sub(eatherEarnedInCurrentRound);
               	_nextRound();									//entering new round
            	res = res.add(_calculateToken(buyer, amount.sub(etherLeftForMilestone)));
            	amount = etherLeftForMilestone;
            }
            else
                eatherEarnedInCurrentRound = eatherEarnedInCurrentRound.add(amount);
        }

        balances[buyer][localRound] = balances[buyer][localRound].add(amount);
        if(localRound<price.length)
            require(balances[buyer][localRound]<=maxStep.mul(2**localRound.sub(1)),"More than the maximum purchase");

        res = res.add(amount.mul(_price));
        return res;
    }

    function _nextRound() private {
    	require(currentRound<=price.length, "No more rounds left");
    	currentRound = currentRound.add(1);
    	eatherEarnedInCurrentRound=0;
    }

    function _beforeTokenTransfer(address from, address, uint256) internal override {
    	require(!isLocked(from), "Sender is blacklisted");
    	if(from != address(this) && from != address(0))
    		require(!frozen, "Tokens are frozen");
    }

    function _isRoundExists(uint256 roundInQuestion) private view returns(bool) {
    	return roundInQuestion>0 && roundInQuestion<price.length;
    }

    modifier buyable() {
        require(currentRound>0, "Not opened for purchase");
        require(currentRound<=price.length, "All rounds of ico has been completed");
        require(buyableFlag, "Buyable flag is not set");
        _;
    }

}
function _swapTokenForEhter(address buyer, uint256 amount) private {
    require(amount >= min,"Less than the minimum purchase");

    uint256 _swapBalance = _calculateToken(buyer,amount);

    _transfer(address(this), buyer, _swapBalance);
    totalTokenSold = totalTokenSold.add(_swapBalance);
    totalEtherEarned = totalEtherEarned.add(amount);


    require(totalTokenSold<=upperCap, "No tokens left for selling");
}

//endsection