contract ZxcCrowdsale
{
  using SafeMath for uint256;

  /**
   * @dev Token being sold.
   */
  Zxc public token;

  /**
   * @dev Xcert KYC token.
   */
  Xcert public xcertKyc;

  /**
   * @dev Start time of the presale.
   */
  uint256 public startTimePresale;

  /**
   * @dev Start time of the token sale with bonus.
   */
  uint256 public startTimeSaleWithBonus;

  /**
   * @dev Start time of the token sale with no bonus.
   */
  uint256 public startTimeSaleNoBonus;

  /**
   * @dev Presale bonus expressed as percentage integer (10% = 10).
   */
  uint256 public bonusPresale;

  /**
   * @dev Token sale bonus expressed as percentage integer (10% = 10).
   */
  uint256 public bonusSale;

  /**
   * @dev End timestamp to end the crowdsale.
   */
  uint256 public endTime;

  /**
   * @dev Minimum required wei deposit for public presale period.
   */
  uint256 public minimumPresaleWeiDeposit;

  /**
   * @dev Total amount of ZXC tokens offered for the presale.
   */
  uint256 public preSaleZxcCap;

  /**
   * @dev Total supply of ZXC tokens for the sale.
   */
  uint256 public crowdSaleZxcSupply;

  /**
   * @dev Amount of ZXC tokens sold.
   */
  uint256 public zxcSold;

  /**
   * @dev Address where funds are collected.
   */
  address public wallet;

  /**
   * @dev How many token units buyer gets per wei.
   */
  uint256 public rate;

  /**
   * @dev An event which is triggered when tokens are bought.
   * @param _from The address sending tokens.
   * @param _to The address receiving tokens.
   * @param _weiAmount Purchase amount in wei.
   * @param _tokenAmount The amount of purchased tokens.
   */
  event TokenPurchase(
    address indexed _from,
    address indexed _to,
    uint256 _weiAmount,
    uint256 _tokenAmount
  );

  /**
   * @dev Contract constructor.
   * @param _walletAddress Address of the wallet which collects funds.
   * @param _tokenAddress Address of the ZXC token contract.
   * @param _xcertKycAddress Address of the Xcert KYC token contract.
   * @param _startTimePresale Start time of presale stage.
   * @param _startTimeSaleWithBonus Start time of public sale stage with bonus.
   * @param _startTimeSaleNoBonus Start time of public sale stage with no bonus.
   * @param _endTime Time when sale ends.
   * @param _rate ZXC/ETH exchange rate.
   * @param _presaleZxcCap Maximum number of ZXC offered for the presale.
   * @param _crowdSaleZxcSupply Supply of ZXC tokens offered for the sale. Includes _presaleZxcCap.
   * @param _bonusPresale Bonus token percentage for presale.
   * @param _bonusSale Bonus token percentage for public sale stage with bonus.
   * @param _minimumPresaleWeiDeposit Minimum required deposit in wei.
   */
  constructor(
    address _walletAddress,
    address _tokenAddress,
    address _xcertKycAddress,
    uint256 _startTimePresale,  // 1529971200: date -d '2018-06-26 00:00:00 UTC' +%s
    uint256 _startTimeSaleWithBonus, // 1530662400: date -d '2018-07-04 00:00:00 UTC' +%s
    uint256 _startTimeSaleNoBonus,  //1530748800: date -d '2018-07-05 00:00:00 UTC' +%s
    uint256 _endTime,  // 1531872000: date -d '2018-07-18 00:00:00 UTC' +%s
    uint256 _rate,  // 10000: 1 ETH = 10,000 ZXC
    uint256 _presaleZxcCap, // 195M
    uint256 _crowdSaleZxcSupply, // 250M
    uint256 _bonusPresale,  // 10 (%)
    uint256 _bonusSale,  // 5 (%)
    uint256 _minimumPresaleWeiDeposit  // 1 ether;
  )
    public
  {
    require(_walletAddress != address(0));
    require(_tokenAddress != address(0));
    require(_xcertKycAddress != address(0));
    require(_tokenAddress != _walletAddress);
    require(_tokenAddress != _xcertKycAddress);
    require(_xcertKycAddress != _walletAddress);

    token = Zxc(_tokenAddress);
    xcertKyc = Xcert(_xcertKycAddress);

    uint8 _tokenDecimals = token.decimals();
    require(_tokenDecimals == 18);  // Sanity check.
    wallet = _walletAddress;

    // Bonus should be > 0% and <= 100%
    require(_bonusPresale > 0 && _bonusPresale <= 100);
    require(_bonusSale > 0 && _bonusSale <= 100);

    bonusPresale = _bonusPresale;
    bonusSale = _bonusSale;

    require(_startTimeSaleWithBonus > _startTimePresale);
    require(_startTimeSaleNoBonus > _startTimeSaleWithBonus);

    startTimePresale = _startTimePresale;
    startTimeSaleWithBonus = _startTimeSaleWithBonus;
    startTimeSaleNoBonus = _startTimeSaleNoBonus;
    endTime = _endTime;

    require(_rate > 0);
    rate = _rate;

    require(_crowdSaleZxcSupply > 0);
    require(token.totalSupply() >= _crowdSaleZxcSupply);
    crowdSaleZxcSupply = _crowdSaleZxcSupply;

    require(_presaleZxcCap > 0 && _presaleZxcCap <= _crowdSaleZxcSupply);
    preSaleZxcCap = _presaleZxcCap;

    zxcSold = 71157402800000000000000000;

    require(_minimumPresaleWeiDeposit > 0);
    minimumPresaleWeiDeposit = _minimumPresaleWeiDeposit;
  }

  /**
   * @dev Fallback function can be used to buy tokens.
   */
  function()
    external
    payable
  {
    buyTokens();
  }

  /**
   * @dev Low level token purchase function.
   */
  function buyTokens()
    public
    payable
  {
    uint256 tokens;

    // Sender needs Xcert KYC token.
    uint256 balance = xcertKyc.balanceOf(msg.sender);
    require(balance > 0);

    uint256 tokenId = xcertKyc.tokenOfOwnerByIndex(msg.sender, balance - 1);
    uint256 kycLevel = uint(xcertKyc.tokenDataValue(tokenId, 0));

    if (isInTimeRange(startTimePresale, startTimeSaleWithBonus)) {
      require(kycLevel > 1);
      require(msg.value >= minimumPresaleWeiDeposit);
      tokens = getTokenAmount(msg.value, bonusPresale);
      require(zxcSold.add(tokens) <= preSaleZxcCap);
    }
    else if (isInTimeRange(startTimeSaleWithBonus, startTimeSaleNoBonus)) {
      require(kycLevel > 0);
      tokens = getTokenAmount(msg.value, bonusSale);
    }
    else if (isInTimeRange(startTimeSaleNoBonus, endTime)) {
      require(kycLevel > 0);
      tokens = getTokenAmount(msg.value, uint256(0));
    }
    else {
      revert("Purchase outside of token sale time windows");
    }

    require(zxcSold.add(tokens) <= crowdSaleZxcSupply);
    zxcSold = zxcSold.add(tokens);

    wallet.transfer(msg.value);
    require(token.transferFrom(token.owner(), msg.sender, tokens));
    emit TokenPurchase(msg.sender, msg.sender, msg.value, tokens);
  }

  /**
   * @return true if crowdsale event has ended
   */
  function hasEnded()
    external
    view
    returns (bool)
  {
    bool capReached = zxcSold >= crowdSaleZxcSupply;
    bool endTimeReached = now >= endTime;
    return capReached || endTimeReached;
  }

  /**
   * @dev Check if currently active period is a given time period.
   * @param _startTime Starting timestamp (inclusive).
   * @param _endTime Ending timestamp (exclusive).
   * @return bool
   */
  function isInTimeRange(
    uint256 _startTime,
    uint256 _endTime
  )
    internal
    view
    returns(bool)
  {
    if (now >= _startTime && now < _endTime) {
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * @dev Calculate amount of tokens for a given wei amount. Apply special bonuses depending on
   * @param weiAmount Amount of wei for token purchase.
   * @param bonusPercent Percentage of bonus tokens.
   * @return Number of tokens with possible bonus.
   */
  function getTokenAmount(
    uint256 weiAmount,
    uint256 bonusPercent
  )
    internal
    view
    returns(uint256)
  {
    uint256 tokens = weiAmount.mul(rate);

    if (bonusPercent > 0) {
      uint256 bonusTokens = tokens.mul(bonusPercent).div(uint256(100)); // tokens * bonus (%) / 100%
      tokens = tokens.add(bonusTokens);
    }

    return tokens;
  }
}
/**
 * @title ZXC crowdsale contract.
 * @dev Crowdsale contract for distributing ZXC tokens.
 * Start timestamps for the token sale stages (start dates are inclusive, end exclusive):
 *   - Token presale with 10% bonus: 2018/06/26 - 2018/07/04
 *   - Token sale with 5% bonus: 2018/07/04 - 2018/07/05
 *   - Token sale with 0% bonus: 2018/07/05 - 2018/07/18
 */
function getTokenAmount(
  uint256 weiAmount,
  uint256 bonusPercent
)
  internal
  view
  returns(uint256)
{
  uint256 tokens = weiAmount.mul(rate);

  if (bonusPercent > 0) {
    uint256 bonusTokens = tokens.mul(bonusPercent).div(uint256(100)); // tokens * bonus (%) / 100%
    tokens = tokens.add(bonusTokens);
  }

  return tokens;
}

/**
 * @dev Calculate amount of tokens for a given wei amount. Apply special bonuses depending on
 * @param weiAmount Amount of wei for token purchase.
 * @param bonusPercent Percentage of bonus tokens.
 * @return Number of tokens with possible bonus.
 */