contract Crowdsale is Owned {
    using SafeMath for uint;
    enum State { INIT, PRESALE, PREICO, PREICO_FINISHED, ICO_FIRST, ICO_SECOND, ICO_THIRD, STOPPED, CLOSED, EMERGENCY_STOP}
    uint public constant MAX_SALE_SUPPLY = 24 * (10**25);
    uint public constant DECIMALS = (10**18);
    State public currentState = State.INIT;
    IToken public token;
    uint public totalSaleSupply = 0;
    uint public totalFunds = 0;
    uint public tokenPrice = 1000000000000000000; //wei
    uint public bonus = 50000; //50%
    uint public currentPrice;
    address public beneficiary;
    mapping(address => uint) balances;
 
    address public foundersWallet; //replace
    uint public foundersAmount = 160000000 * DECIMALS;
    uint public maxPreICOSupply = 48 * (10**24);
    uint public maxICOFirstSupply = 84 * (10**24);
    uint public maxICOSecondSupply = 48 * (10**24);
    uint public maxICOThirdSupply = 24 * (10**24);
    uint public currentRoundSupply = 0;
    uint private bonusBase = 100000; //100%;
    modifier inState(State _state){
        require(currentState == _state);
        _;
    }
    modifier salesRunning(){
        require(currentState == State.PREICO 
        || currentState == State.ICO_FIRST
        || currentState == State.ICO_SECOND
        || currentState == State.ICO_THIRD);
        _;
    }
    modifier minAmount(){
        require(msg.value >= 0.2 ether);
        _;
    }
    
    event Transfer(address indexed _to, uint _value);
    function Crowdsale(address _foundersWallet, address _beneficiary){
        beneficiary = _beneficiary;
        foundersWallet = _foundersWallet;
    }
    function initialize(IToken _token)
    public
    only(owner)
    inState(State.INIT)
    {
        require(_token != address(0));
        token = _token;
        currentPrice = tokenPrice;
        _mint(foundersWallet, foundersAmount);
    }
    function setBonus(uint _bonus) public
    only(owner)
    {
        bonus = _bonus;
    }
    function setPrice(uint _tokenPrice)
    public
    only(owner)
    {
        currentPrice = _tokenPrice;
    }
    function setState(State _newState)
    public
    only(owner)
    {
        require(
        currentState == State.INIT && _newState == State.PRESALE
        || currentState == State.PRESALE && _newState == State.PREICO
        || currentState == State.PREICO && _newState == State.PREICO_FINISHED
        || currentState == State.PREICO_FINISHED && _newState == State.ICO_FIRST
        || currentState == State.ICO_FIRST && _newState == State.STOPPED
        || currentState == State.STOPPED && _newState == State.ICO_SECOND        
        || currentState == State.ICO_SECOND && _newState == State.STOPPED
        || currentState == State.STOPPED && _newState == State.ICO_THIRD
        || currentState == State.ICO_THIRD && _newState == State.CLOSED
        || _newState == State.EMERGENCY_STOP
        );
        currentState = _newState;
        if(_newState == State.PREICO 
        || _newState == State.ICO_FIRST
        || _newState == State.ICO_SECOND
        || _newState == State.ICO_THIRD){
            currentRoundSupply = 0;
        }
        if(_newState == State.CLOSED){
            _finish();
        }
    }
    function setStateWithBonus(State _newState, uint _bonus)
    public
    only(owner)
    {
        require(
        currentState == State.INIT && _newState == State.PRESALE
        || currentState == State.PRESALE && _newState == State.PREICO
        || currentState == State.PREICO && _newState == State.PREICO_FINISHED
        || currentState == State.PREICO_FINISHED && _newState == State.ICO_FIRST
        || currentState == State.ICO_FIRST && _newState == State.STOPPED
        || currentState == State.STOPPED && _newState == State.ICO_SECOND        
        || currentState == State.ICO_SECOND && _newState == State.STOPPED
        || currentState == State.STOPPED && _newState == State.ICO_THIRD
        || currentState == State.ICO_THIRD && _newState == State.CLOSED
        || _newState == State.EMERGENCY_STOP
        );
        currentState = _newState;
        bonus = _bonus;
        if(_newState == State.CLOSED){
            _finish();
        }
    }
    function mintPresale(address _to, uint _amount)
    public
    only(owner)
    inState(State.PRESALE)
    {
        require(totalSaleSupply.add(_amount) <= MAX_SALE_SUPPLY);
        totalSaleSupply = totalSaleSupply.add(_amount);
        _mint(_to, _amount);
    }
    function ()
    public
    payable
    salesRunning
    minAmount
    {
        _receiveFunds();
    }

    //==================== Internal Methods =================
    function _receiveFunds()
    internal
    {
        require(msg.value != 0);
        uint transferTokens = msg.value.mul(DECIMALS).div(currentPrice);
        require(totalSaleSupply.add(transferTokens) <= MAX_SALE_SUPPLY);
        uint bonusTokens = transferTokens.mul(bonus).div(bonusBase);
        transferTokens = transferTokens.add(bonusTokens);
        _checkMaxRoundSupply(transferTokens);
        totalSaleSupply = totalSaleSupply.add(transferTokens);
        balances[msg.sender] = balances[msg.sender].add(msg.value);
        totalFunds = totalFunds.add(msg.value);
        _mint(msg.sender, transferTokens);
        beneficiary.transfer(msg.value);
        Transfer(msg.sender, transferTokens);
    }
    function _mint(address _to, uint _amount)
    noAnyReentrancy
    internal
    {
        token.mint(_to, _amount);
    }
    function _checkMaxRoundSupply(uint _amountTokens)
    internal
    {
        if (currentState == State.PREICO) {
            require(currentRoundSupply.add(_amountTokens) <= maxPreICOSupply);
        } else if (currentState == State.ICO_FIRST) {
            require(currentRoundSupply.add(_amountTokens) <= maxICOFirstSupply);
        } else if (currentState == State.ICO_SECOND) {
            require(currentRoundSupply.add(_amountTokens) <= maxICOSecondSupply);
        } else if (currentState == State.ICO_THIRD) {
            require(currentRoundSupply.add(_amountTokens) <= maxICOThirdSupply);
        }
    }
    function _finish()
    noAnyReentrancy
    internal
    {
        token.start();
    }
}
function _receiveFunds()
internal
{
    require(msg.value != 0);
    uint transferTokens = msg.value.mul(DECIMALS).div(currentPrice);
    require(totalSaleSupply.add(transferTokens) <= MAX_SALE_SUPPLY);
    uint bonusTokens = transferTokens.mul(bonus).div(bonusBase);
    transferTokens = transferTokens.add(bonusTokens);
    _checkMaxRoundSupply(transferTokens);
    totalSaleSupply = totalSaleSupply.add(transferTokens);
    balances[msg.sender] = balances[msg.sender].add(msg.value);
    totalFunds = totalFunds.add(msg.value);
    _mint(msg.sender, transferTokens);
    beneficiary.transfer(msg.value);
    Transfer(msg.sender, transferTokens);
}

//==================== Internal Methods =================