contract EXOTokenSale is Ownable {
    using SafeMath for uint256;

    string public constant name = "EXO_TOKEN_SALE";

    ///////////////////////
    // DATA STRUCTURES  ///
    ///////////////////////
    enum StageName {Pause, PreSale, Sale, Ended, Refund}
    struct StageProperties {
        uint256 planEndDate;
        address tokenKeeper;
    }
    
    StageName public currentStage;
    mapping(uint8   => StageProperties) public campaignStages;
    mapping(address => uint256)         public deposited;
    
    uint256 public weiRaised=0; //All raised ether
    uint256 public token_rate=1600; // decimal part of token per wei (0.3$ if 480$==1ETH)
    uint256 public minimum_token_sell=1000; // !!! token count - without decimals!!!
    uint256 public softCap=1042*10**18;//    500 000$ if 480$==1ETH
    uint256 public hardCap=52083*10**18;//25 000 000$ if 480$==1ETH
    address public wallet ; 
    address public ERC20address;

    ///////////////////////
    /// EVENTS     ///////
    //////////////////////
    event Income(address from, uint256 amount, uint64 timestamp);
    event NewTokenRate(uint256 rate);
    event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 weivalue, uint256 tokens);
    event FundsWithdraw(address indexed who, uint256 amount , uint64 timestamp);
    event Refunded(address investor, uint256 depositedValue);
    
    //20180501 = 1525132800
    //20180901 = 1535760000
    //20181231 = 1546214400

    function EXOTokenSale(address _wallet, address _preSaleTokenKeeper , address _SaleTokenKeeper) public {
        //costructor
        require(_wallet != address(0));
        wallet = _wallet;
        campaignStages[uint8(StageName.PreSale)] = StageProperties(1525132800, _preSaleTokenKeeper);
        campaignStages[uint8(StageName.Sale)]    = StageProperties(1535760000, _SaleTokenKeeper);
        currentStage = StageName.Pause;
    }

    //For disable transfers from incompatible wallet (Coinbase) 
    // or from a non ERC-20 compatible wallet
    //it may be purposefully comment this fallback function and recieve
    // Ether  direct through exchangeEtherOnTokens()
    function() public payable {
        exchangeEtherOnTokens(msg.sender);
    }

        // low level token purchase function
    function exchangeEtherOnTokens(address beneficiary) public payable  {
        emit Income(msg.sender, msg.value, uint64(now));
        require(currentStage == StageName.PreSale || currentStage == StageName.Sale);
        uint256 weiAmount = msg.value; //local
        uint256 tokens = getTokenAmount(weiAmount);
        require(beneficiary != address(0));
        require(token_rate > 0);//implicit enabling sell
        AbstractCon ac = AbstractCon(ERC20address);
        require(tokens >= minimum_token_sell.mul(10 ** uint256(ac.decimals())));
        require(ac.transferFrom(campaignStages[uint8(currentStage)].tokenKeeper, beneficiary, tokens));
        checkCurrentStage();
        weiRaised = weiRaised.add(weiAmount);
        deposited[beneficiary] = deposited[beneficiary].add(weiAmount);
        emit TokenPurchase(msg.sender, beneficiary, msg.value, tokens);
        if (weiRaised >= softCap) 
            withdrawETH();
    }

    //Stage time and conditions control
    function checkCurrentStage() internal {
        if  (campaignStages[uint8(currentStage)].planEndDate <= now) {
            // Allow refund if softCap is not reached during PreSale stage
            if  (currentStage == StageName.PreSale 
                 && (weiRaised + msg.value) < softCap
                ) {
                    currentStage = StageName.Refund;
                    return;
            }
            currentStage = StageName.Pause;
        }
        //Finish tokensale campaign when hardCap will reached
        if (currentStage == StageName.Sale 
            && (weiRaised + msg.value) >= hardCap
            ) { 
               currentStage = StageName.Ended;
        }
    }

    //for all discount logic
    function getTokenAmount(uint256 weiAmount) internal view returns(uint256) {
        return weiAmount.mul(token_rate);
    }

    function withdrawETH() internal {
        emit FundsWithdraw(wallet, this.balance, uint64(now));
        wallet.transfer(this.balance);// or weiAmount
    }

    //Set current stage of campaign manually
    function setCurrentStage(StageName _name) external onlyOwner  {
        currentStage = _name;
    }

    //Manually stages control
    function setStageProperties(
        StageName _name, 
        uint256 _planEndDate, 
        address _tokenKeeper 
        ) external onlyOwner {
        campaignStages[uint8(_name)] = StageProperties(_planEndDate, _tokenKeeper);
    } 

    //set   erc20 address for token process  with check of allowance 
    function setERC20address(address newERC20contract)  external onlyOwner {
        require(address(newERC20contract) != 0);
        AbstractCon ac = AbstractCon(newERC20contract);
        require(ac.allowance(campaignStages[uint8(currentStage)].tokenKeeper, address(this))>0);
        ERC20address = newERC20contract;
    }
    
    //refund if not softCapped
    function refund(address investor) external {
        require(currentStage == StageName.Refund);
        require(investor != address(0));
        assert(msg.data.length >= 32 + 4);  //Short Address Attack
        uint256 depositedValue = deposited[investor];
        deposited[investor] = 0;
        investor.transfer(depositedValue);
        emit Refunded(investor, depositedValue);
    }

    function setTokenRate(uint256 newRate) external onlyOwner {
        token_rate = newRate;
        emit NewTokenRate(newRate);
    }

    function setSoftCap(uint256 _val) external onlyOwner {
        softCap = _val;
    }

    function setHardCap(uint256 _val) external onlyOwner {
        hardCap = _val;
    }


    function setMinimumTokenSell(uint256 newNumber) external onlyOwner {
        minimum_token_sell = newNumber;
    }

    function setWallet(address _wallet) external onlyOwner {
        wallet = _wallet;
    } 

    function destroy()  external onlyOwner {
      if  (weiRaised >= softCap)
          selfdestruct(owner);
  } 

}
//...
function checkCurrentStage() internal {
    if  (campaignStages[uint8(currentStage)].planEndDate <= now) {
        // Allow refund if softCap is not reached during PreSale stage
        if  (currentStage == StageName.PreSale 
             && (weiRaised + msg.value) < softCap
            ) {
                currentStage = StageName.Refund;
                return;
        }
        currentStage = StageName.Pause;
    }
    //Finish tokensale campaign when hardCap will reached
    if (currentStage == StageName.Sale 
        && (weiRaised + msg.value) >= hardCap
        ) { 
           currentStage = StageName.Ended;
    }
}

//Stage time and conditions control