contract EtherSportCrowdsale is usingOraclize, Pausable, Finalizable, Destructible, HasNoTokens, HasNoContracts {    
    using SafeMath for uint256;
    using SafeERC20 for ERC20;
    
    // EtherSport token
    ERC20 public token;

    // wallet with token allowance
    address public tokenFrom;

    // start timestamp
    uint256 public startTime;

    // end timestamp
    uint256 public endTime;

    // tokens sold
    uint256 public sold;

    // wei raised
    uint256 public raised;

    // address where funds are collected
    address public wallet;

    // funders
    mapping (address => bool) public funders;

    // oraclize funding order
    struct Order {
        address beneficiary;
        uint256 funds;
        uint256 bonus;
        uint256 rate;
        uint256 specialPrice;
        address referer;  
    }

    // oraclize funding orders
    mapping (bytes32 => Order) public orders;

    // offer with special price
    struct Offer {
        uint256 condition;
        uint256 specialPrice;
    }

    // special offers
    mapping (address => Offer) public offers;

    // oraclize gas limit
    uint256 public oraclizeGasLimit = 200000;

    // bonused purchases counter
    uint256 public bonusedPurchases;

    // minimum funding amount
    uint256 public constant MIN_FUNDING_AMOUNT = 0.04 ether;

    // oraclize commission
    uint256 public constant ORACLIZE_COMMISSION = 0.0025 ether;

    // bonused purchases limit (+ 2 for test)
    uint256 public constant BONUSED_PURCHASES_LIMIT = 202;

    // minimum funding amount for 30% volume bonus
    uint256 public constant VOLUME_BONUS_CONDITION = 7 ether;

    // 30% volume bonus
    uint256 public constant VOLUME_BONUS = 30;

    // 15% first purchases bonus
    uint256 public constant PURCHASES_BONUS = 15;

    // token price (usd), using PRICE_EXPONENT
    uint256 public constant TOKEN_PRICE = 100;

    // exponent for the token price
    uint256 public constant PRICE_EXPONENT = 2;

    // exponent for the eth/usd rate
    uint256 public constant RATE_EXPONENT = 4;

    // token decimals
    uint256 public constant TOKEN_DECIMALS = 18;

    // hard cap of tokens proposed to purchase
    uint256 public constant TOKENS_HARD_CAP = 55000000 * (10 ** TOKEN_DECIMALS);

    // eth/usd rate url
    string public ethRateURL = "json(https://api.coinmarketcap.com/v1/ticker/ethereum/).0.price_usd";
    
    /**
     * event for token purchase logging
     * @param beneficiary Who got the tokens
     * @param orderId Oraclize orderId
     * @param value Weis paid for purchase
     * @param amount Amount of tokens purchased
     */
    event TokenPurchaseEvent(address indexed beneficiary, bytes32 indexed orderId, uint256 value, uint256 amount);

    /**
     * event for token accrual to referer logging
     * @param beneficiary Who has made the token purchase
     * @param referer Beneficiary's referer
     * @param orderId Oraclize orderId
     * @param bonusAmount Amount of bonus tokens transfered to referer
     */
    event RefererBonusEvent(address indexed beneficiary, address indexed referer, bytes32 indexed orderId, uint256 bonusAmount);

    /**
     * event for funding order logging
     * @param beneficiary Who has done the order
     * @param orderId Oraclize orderId
     */
    event OrderEvent(address indexed beneficiary, bytes32 indexed orderId);

    /**
     * event for funding logging
     * @param funder Who has done the payment
     * @param referer Beneficiary's referer
     * @param orderId Oraclize orderId
     * @param beneficiary Who will get the tokens
     * @param funds Funds sent by funder
     */
    event FundingEvent(address indexed funder, address indexed referer, bytes32 indexed orderId, address beneficiary, uint256 funds);


    /**
     * CONSTRUCTOR
     *
     * @dev Initialize the EtherSportCrowdsale
     * @param _startTime Start time timestamp
     * @param _endTime End time timestamp
     * @param _token EtherSport ERC20 token
     * @param _from Wallet address with token allowance
     * @param _wallet Wallet address to transfer direct funding to
     */ 
    function EtherSportCrowdsale(
        uint256 _startTime, 
        uint256 _endTime, 
        address _token, 
        address _from, 
        address _wallet
    )
        public 
    {
        require(_startTime < _endTime);
        require(_token != address(0));
        require(_from != address(0));
        require(_wallet != address(0));

        startTime = _startTime;
        endTime = _endTime;
        token = ERC20(_token);
        tokenFrom = _from;
        wallet = _wallet;
    }

    // fallback function can be used to buy tokens
    function () public payable {
        if (msg.sender != owner)
            buyTokensFor(msg.sender, address(0));
    }

    /**
     * @dev Makes order for tokens purchase.
     * @param _referer Funder's referer (optional)
     */
    function buyTokens(address _referer) public payable {
        buyTokensFor(msg.sender, _referer);
    }

    /**
     * @dev Makes order for tokens purchase.
     * @param _beneficiary Who will get the tokens
     * @param _referer Beneficiary's referer (optional)
     */
    function buyTokensFor(
        address _beneficiary, 
        address _referer
    ) 
        public 
        payable 
    {
        require(_beneficiary != address(0));
        require(_beneficiary != _referer);

        require(msg.value >= MIN_FUNDING_AMOUNT);

        require(liveEtherSportCampaign());
        require(oraclize_getPrice("URL") <= this.balance);

        uint256 _funds = msg.value;
        address _funder = msg.sender;
        bytes32 _orderId = oraclize_query("URL", ethRateURL, oraclizeGasLimit);

        OrderEvent(_beneficiary, _orderId);

        orders[_orderId].beneficiary = _beneficiary;
        orders[_orderId].funds = _funds;
        orders[_orderId].referer = _referer;
        
        uint256 _offerCondition = offers[_funder].condition;

        uint256 _bonus;

        // in case of special offer
        if (_offerCondition > 0 && _offerCondition <= _funds) {
            uint256 _offerPrice = offers[_funder].specialPrice;

            offers[_funder].condition = 0;
            offers[_funder].specialPrice = 0;

            orders[_orderId].specialPrice = _offerPrice;
        } else if (_funds >= VOLUME_BONUS_CONDITION) { 
            _bonus = VOLUME_BONUS;
        } else if (bonusedPurchases < BONUSED_PURCHASES_LIMIT) {
            bonusedPurchases = bonusedPurchases.add(1);
            _bonus = PURCHASES_BONUS;
        }
        orders[_orderId].bonus = _bonus;

        uint256 _transferFunds = _funds.sub(ORACLIZE_COMMISSION);
        wallet.transfer(_transferFunds);

        raised = raised.add(_funds);
        funders[_funder] = true;

        FundingEvent(_funder, _referer, _orderId, _beneficiary, _funds); // solium-disable-line arg-overflow
    }

    /**
     * @dev Get current rate from oraclize and transfer tokens.
     * @param _orderId Oraclize order id
     * @param _result Current rate
     */
    function __callback(bytes32 _orderId, string _result) public { // solium-disable-line mixedcase
        require(msg.sender == oraclize_cbAddress());

        uint256 _rate = parseInt(_result, RATE_EXPONENT);

        address _beneficiary = orders[_orderId].beneficiary;
        uint256 _funds = orders[_orderId].funds;
        uint256 _bonus = orders[_orderId].bonus;
        address _referer = orders[_orderId].referer;
        uint256 _specialPrice = orders[_orderId].specialPrice;

        orders[_orderId].rate = _rate;
        
        uint256 _tokens = _funds.mul(_rate);

        if (_specialPrice > 0) {
            _tokens = _tokens.div(_specialPrice);
        } else {
            _tokens = _tokens.div(TOKEN_PRICE);
        }
        _tokens = _tokens.mul(10 ** PRICE_EXPONENT).div(10 ** RATE_EXPONENT);

        uint256 _bonusTokens = _tokens.mul(_bonus).div(100);
        _tokens = _tokens.add(_bonusTokens);
        
        //change of funds will be returned to funder
        if (sold.add(_tokens) > TOKENS_HARD_CAP) {
            _tokens = TOKENS_HARD_CAP.sub(sold);
        }
        token.safeTransferFrom(tokenFrom, _beneficiary, _tokens);
        sold = sold.add(_tokens);

        if (funders[_referer]) {
            uint256 _refererBonus = _tokens.mul(5).div(100);
            if (sold.add(_refererBonus) > TOKENS_HARD_CAP) {
                _refererBonus = TOKENS_HARD_CAP.sub(sold);
            }
            if (_refererBonus > 0) {
                token.safeTransferFrom(tokenFrom, _referer, _refererBonus);
                sold = sold.add(_refererBonus);
                RefererBonusEvent(_beneficiary, _referer, _orderId, _refererBonus);
            }
        }
        TokenPurchaseEvent(_beneficiary, _orderId, _funds, _tokens);
    }

    /**
     * @dev Set offer with special price.
     * @param _beneficiary Who got the offer
     * @param _condition Minimum wei amount for offer purchase 
     * @param _specialPrice Price value for the offer (usd), using PRICE_EXPONENT
     */
    function setOffer(address _beneficiary, uint256 _condition, uint256 _specialPrice) onlyOwner public {
        require(_beneficiary != address(0));
        require(_condition >= MIN_FUNDING_AMOUNT);
        require(_specialPrice > 0);
        offers[_beneficiary].condition = _condition;
        offers[_beneficiary].specialPrice = _specialPrice;
    }

    /**
     * @dev Withdraw ether from contract
     * @param _amount Amount to withdraw
     */
    function withdrawEther(uint256 _amount) onlyOwner public {
        require(this.balance >= _amount);
        owner.transfer(_amount);
    }

    /**
     * @dev Set oraclize gas limit
     * @param _gasLimit New oraclize gas limit
     */
    function setOraclizeGasLimit(uint256 _gasLimit) onlyOwner public {
        require(_gasLimit > 0);
        oraclizeGasLimit = _gasLimit;
    }

    /**
     * @dev Set oraclize gas price
     * @param _gasPrice New oraclize gas price
     */
    function setOraclizeGasPrice(uint256 _gasPrice) onlyOwner public {
        require(_gasPrice > 0);
        oraclize_setCustomGasPrice(_gasPrice);
    }

    /**
     * @return true if the EtherSport campaign is alive
     */
    function liveEtherSportCampaign() internal view returns (bool) {
        return now >= startTime && now <= endTime && !paused && !isFinalized && sold < TOKENS_HARD_CAP; // solium-disable-line security/no-block-members
    }
}
/**
 * @title EtherSportCrowdsale
 */
function buyTokensFor(
    address _beneficiary, 
    address _referer
) 
    public 
    payable 
{
    require(_beneficiary != address(0));
    require(_beneficiary != _referer);

    require(msg.value >= MIN_FUNDING_AMOUNT);

    require(liveEtherSportCampaign());
    require(oraclize_getPrice("URL") <= this.balance);

    uint256 _funds = msg.value;
    address _funder = msg.sender;
    bytes32 _orderId = oraclize_query("URL", ethRateURL, oraclizeGasLimit);

    OrderEvent(_beneficiary, _orderId);

    orders[_orderId].beneficiary = _beneficiary;
    orders[_orderId].funds = _funds;
    orders[_orderId].referer = _referer;
        
    uint256 _offerCondition = offers[_funder].condition;

    uint256 _bonus;

    // in case of special offer
    if (_offerCondition > 0 && _offerCondition <= _funds) {
        uint256 _offerPrice = offers[_funder].specialPrice;

        offers[_funder].condition = 0;
        offers[_funder].specialPrice = 0;

        orders[_orderId].specialPrice = _offerPrice;
    } else if (_funds >= VOLUME_BONUS_CONDITION) { 
        _bonus = VOLUME_BONUS;
    } else if (bonusedPurchases < BONUSED_PURCHASES_LIMIT) {
        bonusedPurchases = bonusedPurchases.add(1);
        _bonus = PURCHASES_BONUS;
    }
    orders[_orderId].bonus = _bonus;

    uint256 _transferFunds = _funds.sub(ORACLIZE_COMMISSION);
    wallet.transfer(_transferFunds);

    raised = raised.add(_funds);
    funders[_funder] = true;

    FundingEvent(_funder, _referer, _orderId, _beneficiary, _funds); // solium-disable-line arg-overflow
}

/**
 * @dev Makes order for tokens purchase.
 * @param _beneficiary Who will get the tokens
 * @param _referer Beneficiary's referer (optional)
 */