contract FourtySevenTokenCrowdsale is Ownable {
  using SafeMath for uint256;

  struct TimeBonus {
    uint256 bonusPeriodEndTime;
    uint percent;
    bool isAmountDependent;
  }

  struct AmountBonus {
    uint256 amount;
    uint percent;
  }

  // true for finalised crowdsale
  bool public isFinalised;

  // The token being sold
  MintableToken public token;

  // start and end timestamps where pre-investments are allowed (both inclusive)
  uint256 public preSaleStartTime;
  uint256 public preSaleEndTime;

  // start and end timestamps where main-investments are allowed (both inclusive)
  uint256 public mainSaleStartTime;
  uint256 public mainSaleEndTime;

  // maximum amout of wei for pre-sale and main sale
  uint256 public preSaleWeiCap;
  uint256 public mainSaleWeiCap;

  // address where funds are collected
  address public wallet;

  // address where final 10% of funds will be collected
  address public tokenWallet;

  // how many token units a buyer gets per wei
  uint256 public rate;

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

  TimeBonus[] public timeBonuses;
  AmountBonus[] public amountBonuses;

  uint256 public preSaleBonus;
  uint256 public preSaleMinimumWei;
  uint256 public defaultPercent;

  /**
   * 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);
  event FinalisedCrowdsale(uint256 totalSupply, uint256 minterBenefit);

  function FourtySevenTokenCrowdsale(uint256 _preSaleStartTime, uint256 _preSaleEndTime, uint256 _preSaleWeiCap, uint256 _mainSaleStartTime, uint256 _mainSaleEndTime, uint256 _mainSaleWeiCap, uint256 _rate, address _wallet, address _tokenWallet) public {

    // can't start pre-sale in the past
    require(_preSaleStartTime >= now);

    // can't start main sale in the past
    require(_mainSaleStartTime >= now);

    // can't start main sale before the end of pre-sale
    require(_preSaleEndTime < _mainSaleStartTime);

    // the end of pre-sale can't happen before it's start
    require(_preSaleStartTime < _preSaleEndTime);

    // the end of main sale can't happen before it's start
    require(_mainSaleStartTime < _mainSaleEndTime);

    require(_rate > 0);
    require(_preSaleWeiCap > 0);
    require(_mainSaleWeiCap > 0);
    require(_wallet != 0x0);
    require(_tokenWallet != 0x0);

    preSaleBonus = 30;
    preSaleMinimumWei = 4700000000000000000;
    defaultPercent = 0;

    timeBonuses.push(TimeBonus(86400 * 3, 15, false));
    timeBonuses.push(TimeBonus(86400 * 7, 10, false));
    timeBonuses.push(TimeBonus(86400 * 14, 5, false));
    timeBonuses.push(TimeBonus(86400 * 28, 0, true));

    amountBonuses.push(AmountBonus(25000 ether, 15));
    amountBonuses.push(AmountBonus(5000 ether, 10));
    amountBonuses.push(AmountBonus(2500 ether, 5));
    amountBonuses.push(AmountBonus(500 ether, 2));

    token = createTokenContract();

    preSaleStartTime = _preSaleStartTime;
    preSaleEndTime = _preSaleEndTime;
    preSaleWeiCap = _preSaleWeiCap;
    mainSaleStartTime = _mainSaleStartTime;
    mainSaleEndTime = _mainSaleEndTime;
    mainSaleWeiCap = _mainSaleWeiCap;
    rate = _rate;
    wallet = _wallet;
    tokenWallet = _tokenWallet;
    isFinalised = false;
  }

  // creates the token to be sold.
  // override this method to have crowdsale of a specific mintable token.
  function createTokenContract() internal returns (MintableToken) {
    return new FSBToken();
  }

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

  // low level token purchase function
  function buyTokens(address beneficiary) public payable {

    require(beneficiary != 0x0);
    require(msg.value != 0);
    require(!isFinalised);

    uint256 weiAmount = msg.value;

    validateWithinPeriods();
    validateWithinCaps(weiAmount);

    // calculate token amount to be created
    uint256 tokens = weiAmount.mul(rate);

    uint256 percent = getBonusPercent(tokens, now);

    // add bonus to tokens depends on the period
    uint256 bonusedTokens = applyBonus(tokens, percent);

    // update state
    weiRaised = weiRaised.add(weiAmount);
    token.mint(beneficiary, bonusedTokens);
    TokenPurchase(msg.sender, beneficiary, weiAmount, bonusedTokens);

    forwardFunds();
  }

  // owner can mint tokens during crowdsale withing defined caps
  function mintTokens(address beneficiary, uint256 weiAmount, uint256 forcePercent) external onlyOwner returns (bool) {

    require(forcePercent <= 100);
    require(beneficiary != 0x0);
    require(weiAmount != 0);
    require(!isFinalised);

    validateWithinCaps(weiAmount);

    uint256 percent = 0;

    // calculate token amount to be created
    uint256 tokens = weiAmount.mul(rate);

    if (forcePercent == 0) {
      percent = getBonusPercent(tokens, now);
    } else {
      percent = forcePercent;
    }

    // add bonus to tokens depends on the period
    uint256 bonusedTokens = applyBonus(tokens, percent);

    // update state
    weiRaised = weiRaised.add(weiAmount);
    token.mint(beneficiary, bonusedTokens);
    TokenPurchase(msg.sender, beneficiary, weiAmount, bonusedTokens);
  }

  // finish crowdsale,
  // take totalSupply as 90% and mint 10% more to specified owner's wallet
  // then stop minting forever

  function finaliseCrowdsale() external onlyOwner {
    require(!isFinalised);
    uint256 totalSupply = token.totalSupply();
    uint256 minterBenefit = totalSupply.mul(10).div(90);
    token.mint(tokenWallet, minterBenefit);
    token.finishMinting();
    FinalisedCrowdsale(totalSupply, minterBenefit);
    isFinalised = true;
  }

  // set new dates for pre-salev (emergency case)
  function setPreSaleParameters(uint256 _preSaleStartTime, uint256 _preSaleEndTime, uint256 _preSaleWeiCap, uint256 _preSaleBonus, uint256 _preSaleMinimumWei) public onlyOwner {
    require(!isFinalised);
    require(_preSaleStartTime < _preSaleEndTime);
    require(_preSaleWeiCap > 0);
    preSaleStartTime = _preSaleStartTime;
    preSaleEndTime = _preSaleEndTime;
    preSaleWeiCap = _preSaleWeiCap;
    preSaleBonus = _preSaleBonus;
    preSaleMinimumWei = _preSaleMinimumWei;
  }

  // set new dates for main-sale (emergency case)
  function setMainSaleParameters(uint256 _mainSaleStartTime, uint256 _mainSaleEndTime, uint256 _mainSaleWeiCap) public onlyOwner {
    require(!isFinalised);
    require(_mainSaleStartTime < _mainSaleEndTime);
    require(_mainSaleWeiCap > 0);
    mainSaleStartTime = _mainSaleStartTime;
    mainSaleEndTime = _mainSaleEndTime;
    mainSaleWeiCap = _mainSaleWeiCap;
  }

  // set new wallets (emergency case)
  function setWallets(address _wallet, address _tokenWallet) public onlyOwner {
    require(!isFinalised);
    require(_wallet != 0x0);
    require(_tokenWallet != 0x0);
    wallet = _wallet;
    tokenWallet = _tokenWallet;
  }

  // set new rate (emergency case)
  function setRate(uint256 _rate) public onlyOwner {
    require(!isFinalised);
    require(_rate > 0);
    rate = _rate;
  }

  // set token on pause
  function pauseToken() external onlyOwner {
    require(!isFinalised);
    FSBToken(token).pause();
  }

  // unset token's pause
  function unpauseToken() external onlyOwner {
    FSBToken(token).unpause();
  }

    // set token Ownership
  function transferTokenOwnership(address newOwner) external onlyOwner {
    FSBToken(token).transferOwnership(newOwner);
  }

  // @return true if main sale event has ended
  function mainSaleHasEnded() external constant returns (bool) {
    return now > mainSaleEndTime;
  }

  // @return true if pre sale event has ended
  function preSaleHasEnded() external constant returns (bool) {
    return now > preSaleEndTime;
  }

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

  // we want to be able to check all bonuses in already deployed contract
  // that's why we pass currentTime as a parameter instead of using "now"

  function getBonusPercent(uint256 tokens, uint256 currentTime) public constant returns (uint256 percent) {
    //require(currentTime >= preSaleStartTime);
    bool isPreSale = currentTime >= preSaleStartTime && currentTime <= preSaleEndTime;
    if (isPreSale) {
      return preSaleBonus;
    } else {
      uint256 diffInSeconds = currentTime.sub(mainSaleStartTime);
      for (uint i = 0; i < timeBonuses.length; i++) {
        if (diffInSeconds <= timeBonuses[i].bonusPeriodEndTime && !timeBonuses[i].isAmountDependent) {
          return timeBonuses[i].percent;
        } else if (timeBonuses[i].isAmountDependent) {
          for (uint j = 0; j < amountBonuses.length; j++) {
            if (tokens >= amountBonuses[j].amount) {
              return amountBonuses[j].percent;
            }
          }
        }
      }
    }
    return defaultPercent;
  }

  function applyBonus(uint256 tokens, uint256 percent) internal constant returns (uint256 bonusedTokens) {
    uint256 tokensToAdd = tokens.mul(percent).div(100);
    return tokens.add(tokensToAdd);
  }

  function validateWithinPeriods() internal constant {
    // within pre-sale or main sale
    require((now >= preSaleStartTime && now <= preSaleEndTime) || (now >= mainSaleStartTime && now <= mainSaleEndTime));
  }

  function validateWithinCaps(uint256 weiAmount) internal constant {
    uint256 expectedWeiRaised = weiRaised.add(weiAmount);

    // within pre-sale
    if (now >= preSaleStartTime && now <= preSaleEndTime) {
      require(weiAmount >= preSaleMinimumWei);
      require(expectedWeiRaised <= preSaleWeiCap);
    }

    // within main sale
    if (now >= mainSaleStartTime && now <= mainSaleEndTime) {
      require(expectedWeiRaised <= mainSaleWeiCap);
    }
  }

}
/**
 * @title Crowdsale
 * @dev Modified contract for managing a token crowdsale.
 * FourtySevenTokenCrowdsale have pre-sale and main sale periods, where investors can make
 * token purchases and the crowdsale will assign them tokens based
 * on a token per ETH rate and the system of bonuses.
 * Funds collected are forwarded to a wallet as they arrive.
 * pre-sale and main sale periods both have caps defined in tokens
 */
function preSaleHasEnded() external constant returns (bool) {
  return now > preSaleEndTime;
}

// @return true if pre sale event has ended