contract YRXCrowdsale is Ownable {
  using SafeMath for uint256;

  // true for finalised presale
  bool public isPreSaleFinalised;
  // true for finalised crowdsale
  bool public isFinalised;
  // The token being sold
  YRXToken public YRX;
  // address where funds are collected
  address public wallet;

  // amount of raised money in wei
  uint256 public weiRaised;

  // current amount of purchased tokens for pre-sale and main sale
  uint256 public preSaleTotalSupply;
  uint256 public mainSaleTotalSupply;
  // current amount of minted tokens for bounty
  uint256 public bountyTotalSupply;
  uint256 private mainSaleTokensExtra;

  event WalletAddressChanged(address _wallet);           // Triggered upon owner changing the wallet address
  event AmountRaised(address beneficiary, uint amountRaised); // Triggered upon crowdfund being finalized
  event Mint(address indexed to, uint256 amount);
  /**
   * event for token purchase logging
   * @param purchaser who paid for the tokens
   * @param beneficiary who got the tokens
   * @param value weis paid for purchase
   * @param amount amount of tokens purchased
   */
  event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);

  modifier nonZeroAddress(address _to) {                 // Ensures an address is provided
    require(_to != 0x0);
    _;
  }

  modifier nonZeroAmount(uint _amount) {                 // Ensures a non-zero amount
    require(_amount > 0);
    _;
  }

  modifier nonZeroValue() {                              // Ensures a non-zero value is passed
    require(msg.value > 0);
    _;
  }

  modifier crowdsaleIsActive() {                         // Ensures the crowdfund is ongoing
    require(!isFinalised && (isInPreSale() || isInMainSale()));
    _;
  }

  function YRXCrowdsale(address _wallet, address _token) public {

    /* default start-end periods dates
      _preSaleStartTime =     '29 Nov 2017 00:00:00 GMT' 1511913600
      _preSaleEndTime =       '10 Jan 2018 23:59:59 GMT' 1515628799
      _mainSaleStartTime =    '11 Jan 2018 00:00:00 GMT' 1515628800
      _mainSaleEndTime =      '11 May 2018 00:00:00 GMT' 1525996800
    */

    // check dates
    require(mainSaleStartTime() >= now);                 // can't start main sale in the past
    require(preSaleEndTime() < mainSaleStartTime());      // can't start main sale before the end of pre-sale
    require(preSaleStartTime() < preSaleEndTime());       // the end of pre-sale can't happen before it's start
    require(mainSaleStartTime() < mainSaleEndTime());     // the end of main sale can't happen before it's start

    // create token contract
    YRX = YRXToken(_token);
    wallet = _wallet;
    isPreSaleFinalised = false;
    isFinalised = false;

    // current amount of purchased tokens for pre-sale and main sale
    preSaleTotalSupply = 0;
    mainSaleTotalSupply = 0;
    bountyTotalSupply = 0;
    mainSaleTokensExtra = 0;
  }

  // -------------------------------------------------
  // Changes main contribution wallet
  // -------------------------------------------------
  function changeWalletAddress(address _wallet) external onlyOwner {
    wallet = _wallet;
    WalletAddressChanged(_wallet);
  }


  function maxTokens() public pure returns(uint256) {
    return 510000000 * 1 ether;
  }

  function preSaleMaxTokens() public pure returns(uint256) {
    return 51000000 * 1 ether;
  }

  function mainSaleMaxTokens() public view returns(uint256) {
    return 433500000  * 1 ether + mainSaleTokensExtra;
  }

  function bountyMaxTokens() public pure returns(uint256) {
    return 25500000 * 1 ether;
  }

  function preSaleStartTime() public pure returns(uint256) {
    return 1511913600;
  }

  function preSaleEndTime() public pure returns(uint256) {
    return 1515628799;
  }

  function mainSaleStartTime() public pure returns(uint256) {
    return 1515628800;
  }

  function mainSaleEndTime() public pure returns(uint256) {
    return 1525996800;
  }

  function rate() public pure returns(uint256) {
    return 540;
  }

  function discountRate() public pure returns(uint256) {
    return 1350;
  }

  function discountICO() public pure returns(uint256) {
    return 60;
  }

  function isInPreSale() public constant returns(bool){
    return now >= preSaleStartTime() && now <= preSaleEndTime();
  }

  function isInMainSale() public constant returns(bool){
    return now >= mainSaleStartTime() && now <= mainSaleEndTime();
  }

  function totalSupply() public view returns(uint256){
    return YRX.totalSupply();
  }

  // fallback function can be used to buy tokens
  function () public payable {
    buyTokens(msg.sender);
  }

  // low level token purchase function
  function buyTokens(address beneficiary) public crowdsaleIsActive nonZeroAddress(beneficiary) nonZeroValue payable {
    uint256 weiAmount = msg.value;

    // calculate token amount to be created
    uint256 tokens = weiAmount * rate();
    if (isInPreSale()) {
      require(!isPreSaleFinalised);
      tokens = weiAmount * discountRate();
      require(tokens <= preSaleTokenLeft());
    }

    if (isInMainSale()) {
      // tokens with discount
      tokens = weiAmount * discountRate();
      require(mainSaleTotalSupply + tokens <= mainSaleMaxTokens());
    }

    // update state
    weiRaised = weiRaised.add(weiAmount);
    if (isInPreSale())
      preSaleTotalSupply += tokens;
    if (isInMainSale())
      mainSaleTotalSupply += tokens;

    forwardFunds();
    if (!YRX.transferFromCrowdsale(beneficiary, tokens)) {revert();}
    TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
  }

  // send ether to the fund collection wallet
  function forwardFunds() internal {
    wallet.transfer(msg.value);
  }

  // mint tokens to beneficiary
  function mint(address beneficiary, uint256 amount) public onlyOwner crowdsaleIsActive nonZeroAddress(beneficiary) nonZeroAmount(amount) {
    // check period
    bool withinPreSalePeriod = isInPreSale();
    bool withinMainSalePeriod = isInMainSale();
    if (withinPreSalePeriod) {
      require(!isPreSaleFinalised);
      require(amount <= preSaleTokenLeft());
    }
    if (withinMainSalePeriod) {
      require(amount <= (mainSaleMaxTokens() - mainSaleTotalSupply));
    }

    if (withinPreSalePeriod)
      preSaleTotalSupply += amount;
    if (withinMainSalePeriod)
      mainSaleTotalSupply += amount;

    if (!YRX.transferFromCrowdsale(beneficiary, amount)) {revert();}
    Mint(beneficiary, amount);
  }

  function preSaleTokenLeft() public constant returns(uint256){
    return preSaleMaxTokens() - preSaleTotalSupply;
  }

  // finalise presale
  function finalisePreSale() public onlyOwner {
    require(!isFinalised);
    require(!isPreSaleFinalised);
    require(now >= preSaleStartTime()); // can finalise presale only after it starts

    if (preSaleTokenLeft() > 0) {
      mainSaleTokensExtra = preSaleTokenLeft();
    }

    isPreSaleFinalised = true;
  }

  // finalase crowdsale (mainsale)
  function finalise() public onlyOwner returns(bool success){
    require(!isFinalised);
    require(now >= mainSaleStartTime()); // can finalise mainsale (crowdsale) only after it starts
    AmountRaised(wallet, weiRaised);
    isFinalised = true;
    return true;
  }

  // mint bounty tokens to beneficiary
  function mintBounty(address beneficiary, uint256 amount) public onlyOwner crowdsaleIsActive nonZeroAddress(beneficiary) nonZeroAmount(amount) {
    require(amount <= (bountyMaxTokens() - bountyTotalSupply));

    bountyTotalSupply += amount;
    if (!YRX.transferFromCrowdsale(beneficiary, amount)) {revert();}
    Mint(beneficiary, amount);
  }

}
/**
 * @title YRXCrowdsale
 */
function mint(address beneficiary, uint256 amount) public onlyOwner crowdsaleIsActive nonZeroAddress(beneficiary) nonZeroAmount(amount) {
  // check period
  bool withinPreSalePeriod = isInPreSale();
  bool withinMainSalePeriod = isInMainSale();
  if (withinPreSalePeriod) {
    require(!isPreSaleFinalised);
    require(amount <= preSaleTokenLeft());
  }
  if (withinMainSalePeriod) {
    require(amount <= (mainSaleMaxTokens() - mainSaleTotalSupply));
  }

  if (withinPreSalePeriod)
    preSaleTotalSupply += amount;
  if (withinMainSalePeriod)
    mainSaleTotalSupply += amount;

  if (!YRX.transferFromCrowdsale(beneficiary, amount)) {revert();}
  Mint(beneficiary, amount);
}

// mint tokens to beneficiary