contract Crowdsale is Presale, Pausable, CanReclaimToken, Whitelist {

  using SafeMath for uint256;
  address public whitelistAddress;
  address public wallet; //wallet where the funds collected are transfered
  MiniMeToken public token; //ERC20 Token
  uint256 private weiRaised = 0; //WeiRaised during the public Sale
  uint256 private cap = 0; //Cap of the public Sale in Wei
  bool private publicSaleInitialized = false;
  bool private finalized = false;
  uint256 private tokensSold = 0; //tokens sold during the entire sale
  uint256 private startTime; //start time of the public sale initialized after the presale is over
  uint256 private endTime; //endtime of the public sale
  uint256 public maxTokens;
  mapping(address => uint256) public contributions; //contributions of each investor
  mapping(address => uint256) public investorCaps; //for whitelisting
  address[] public investors; //investor list who participate in the ICO
  address[] public founders; //list of founders
  address[] public advisors; //list of advisors
  VestingTrustee public trustee;
  address public reserveWallet; //reserveWallet where the unsold tokens will be sent to

  //Rate for each tier (no of tokens for 1 ETH)
  //Max wei for each tier
  struct Tier {
    uint256 rate;
    uint256 max;
  }

  uint public privateSaleTokensAvailable;
  uint public privateSaleTokensSold = 0;
  uint public publicTokensAvailable;

  uint8 public totalTiers = 0; //total Tiers in the public sale
  bool public tiersInitialized = false;
  uint256 public maxTiers = 6; //max tiers that can be in the publicsale
  Tier[6] public tiers; //array of tiers

  event TokenPurchase(address indexed purchaser, uint256 value, uint256 amount);
  enum Stage { Preparing, Presale, PresaleFinished, PublicSale, Success, Finalized }

  function Crowdsale(
    uint256 _presaleStartTime, //presale start time
    uint256 _presaleDuration, //presale duration in days
    uint256 _presaleRate, // presale rate. ie No of tokens per 1 ETH
    uint256 _presaleCap, // max wei that can raised
    address erc20Token, // Token used for the crowdsale
    address _wallet,
    uint8 _tiers,
    uint256 _cap,
    address _reserveWallet)
    public
    Presale(_presaleStartTime, _presaleDuration, _presaleRate, _presaleCap)
    {
      require(_wallet != address(0));
      require(erc20Token != address(0));
      require(_tiers > 0 && _tiers <= maxTiers);
      require(_cap > 0);
      require(_reserveWallet != address(0));
      token = MiniMeToken(erc20Token);
      wallet = _wallet;
      totalTiers = _tiers;
      cap = _cap;
      reserveWallet = _reserveWallet;
      trustee = new VestingTrustee(erc20Token);
      maxTokens = 1000000000 * (10 ** 18); // 1 B tokens
      privateSaleTokensAvailable = maxTokens.mul(22).div(100);
      publicTokensAvailable = maxTokens.mul(28).div(100);
      super.addAddressToWhitelist(msg.sender);

    }

  function() public payable {
    buyTokens(msg.sender, msg.value);
  }

  function getStage() public constant returns(Stage) {
    if (finalized) return Stage.Finalized;
    if (!tiersInitialized || !Presale.hasStarted()) return Stage.Preparing;
    if (!Presale.hasEnded()) return Stage.Presale;
    if (Presale.hasEnded() && !hasStarted()) return Stage.PresaleFinished;
    if (!hasEnded()) return Stage.PublicSale;
    if (hasEnded()) return Stage.Success;
    return Stage.Preparing;
  }

  modifier inStage(Stage _stage) {
    require(getStage() == _stage);
    _;
  }

  // rates for each tier and total wei in that tiers
  // they are added up together
  function initTiers(uint256[] rates, uint256[] totalWeis) public onlyWhitelisted returns(uint256) {
    require(token.controller() == address(this));
    require(!tiersInitialized);
    require(rates.length == totalTiers && rates.length == totalWeis.length);
    uint256 tierMax = 0;

    for (uint8 i=0; i < totalTiers; i++) {

      require(totalWeis[i] > 0 && rates[i] > 0);

      tierMax = tierMax.add(totalWeis[i]);
      tiers[i] = Tier({
        rate: rates[i],
        max: tierMax
      });
    }

    require(tierMax == cap);
    tiersInitialized = true;
    return tierMax;
  }

  // function for whitelisting investors with caps
  function setCapForParticipants(address[] participants, uint256[] caps) onlyWhitelisted public  {
    require(participants.length <= 50 && participants.length == caps.length);
    for (uint8 i=0; i < participants.length; i++) {
      investorCaps[participants[i]] = caps[i];
    }
  }


  function addGrant(address assignee, uint256 value, bool isFounder) public onlyWhitelisted whenNotPaused {
    require(value > 0);
    require(assignee != address(0));
    uint256 start;
    uint256 cliff;
    uint256 vestingPercentage;
    uint256 initialTokens;
    if(isFounder) {
      start = now;
      cliff = start + 12*30 days; //12 months
      vestingPercentage = 20; //20%
      founders.push(assignee);
    }
    else {
      // for advisors
      // transfer 10% of the tokens at start
      initialTokens = value.mul(10).div(100);
      transferTokens(assignee, initialTokens);
      start = now;
      cliff = start + 6*30 days;  //6 months
      vestingPercentage = 15; //15% for each installments
      advisors.push(assignee);
    }

    uint256 end = now + 3 * 1 years; //3 years
    uint256 installmentLength = 6 * 30 days; // 6 month installments
    bool revokable = true;
    transferTokens(trustee, value.sub(initialTokens));
    trustee.grant(assignee, value, start, cliff, end, installmentLength, vestingPercentage, initialTokens, revokable);
  }

  // called by the owner to close the crowdsale
  function finalize() public onlyWhitelisted inStage(Stage.Success) {
    require(!finalized);
    //trustee's ownership is transfered from the crowdsale to owner of the contract
    trustee.transferOwnership(msg.sender);
    //enable token transfer
    token.enableTransfers(true);
    //generate the unsold tokens to the reserve
    uint256 unsold = maxTokens.sub(token.totalSupply());
    transferTokens(reserveWallet, unsold);

    // change the token's controller to a zero Address so that it cannot
    // generate or destroy tokens
    token.changeController(0x0);
    finalized = true;
  }

  //start the public sale manually after the presale is over, duration is in days
  function startPublicSale(uint _startTime, uint _duration) public onlyWhitelisted inStage(Stage.PresaleFinished) {
    require(_startTime >= now);
    require(_duration > 0);
    startTime = _startTime;
    endTime = _startTime + _duration * 1 days;
    publicSaleInitialized = true;
  }

  // total wei raised in the presale and public sale
  function totalWei() public constant returns(uint256) {
    uint256 presaleWei = super.totalWei();
    return presaleWei.add(weiRaised);
  }

  function totalPublicSaleWei() public constant returns(uint256) {
    return weiRaised;
  }
  // total cap of the presale and public sale
  function totalCap() public constant returns(uint256) {
    uint256 presaleCap = super.totalCap();
    return presaleCap.add(cap);
  }

  // Total tokens sold duing the presale and public sale.
  // Total tokens has to divided by 10^18
  function totalTokens() public constant returns(uint256) {
    return tokensSold;
  }

  // MAIN BUYING Function
  function buyTokens(address purchaser, uint256 value) internal  whenNotPaused returns(uint256) {
    require(value > 0);
    Stage stage = getStage();
    require(stage == Stage.Presale || stage == Stage.PublicSale);

    //the purchase amount cannot be more than the whitelisted cap
    uint256 purchaseAmount = Math.min256(value, investorCaps[purchaser].sub(contributions[purchaser]));
    require(purchaseAmount > 0);
    uint256 numTokens;

    //call the presale contract
    if (stage == Stage.Presale) {
      if (Presale.totalWei().add(purchaseAmount) > Presale.totalCap()) {
        purchaseAmount = Presale.capRemaining();
      }
      numTokens = Presale.buyTokens(purchaser, purchaseAmount);
    } else if (stage == Stage.PublicSale) {

      uint totalWei = weiRaised.add(purchaseAmount);
      uint8 currentTier = getTier(weiRaised); //get current tier
      if (totalWei >= cap) { // will TOTAL_CAP(HARD_CAP) of the public sale be reached ?
        totalWei = cap;
        //purchase amount can be only be (CAP - WeiRaised)
        purchaseAmount = cap.sub(weiRaised);
      }

      // if the totalWei( weiRaised + msg.value) fits within current cap
      // number of tokens would be rate * purchaseAmount
      if (totalWei <= tiers[currentTier].max) {
        numTokens = purchaseAmount.mul(tiers[currentTier].rate);
      } else {
        //wei remaining in the current tier
        uint remaining = tiers[currentTier].max.sub(weiRaised);
        numTokens = remaining.mul(tiers[currentTier].rate);

        //wei in the next tier
        uint256 excess = totalWei.sub(tiers[currentTier].max);
        //number of tokens  = wei remaining in the next tier * rate of the next tier
        numTokens = numTokens.add(excess.mul(tiers[currentTier + 1].rate));
      }

      // update the total raised so far
      weiRaised = weiRaised.add(purchaseAmount);
    }

    // total tokens sold in the entire sale
    require(tokensSold.add(numTokens) <= publicTokensAvailable);
    tokensSold = tokensSold.add(numTokens);

    // forward funds to the wallet
    forwardFunds(purchaser, purchaseAmount);
    // transfer the tokens to the purchaser
    transferTokens(purchaser, numTokens);

    // return the remaining unused wei back
    if (value.sub(purchaseAmount) > 0) {
      msg.sender.transfer(value.sub(purchaseAmount));
    }

    //event
    TokenPurchase(purchaser, numTokens, purchaseAmount);

    return numTokens;
  }



  function forwardFunds(address purchaser, uint256 value) internal {
    //new investor
    if (contributions[purchaser] == 0) {
      investors.push(purchaser);
    }
    //add contribution to the purchaser
    contributions[purchaser] = contributions[purchaser].add(value);
    wallet.transfer(value);
  }

  function changeEndTime(uint _endTime) public onlyWhitelisted {
    endTime = _endTime;
  }

  function changeFundsWallet(address _newWallet) public onlyWhitelisted {
    require(_newWallet != address(0));
    wallet = _newWallet;
  }

  function changeTokenController() onlyWhitelisted public {
    token.changeController(msg.sender);
  }

  function changeTrusteeOwner() onlyWhitelisted public {
    trustee.transferOwnership(msg.sender);
  }
  function changeReserveWallet(address _reserve) public onlyWhitelisted {
    require(_reserve != address(0));
    reserveWallet = _reserve;
  }

  function setWhitelistAddress(address _whitelist) public onlyWhitelisted {
    require(_whitelist != address(0));
    whitelistAddress = _whitelist;
  }

  function transferTokens(address to, uint256 value) internal {
    token.generateTokens(to, value);
  }

  function sendPrivateSaleTokens(address to, uint256 value) public whenNotPaused onlyWhitelisted {
    require(privateSaleTokensSold.add(value) <= privateSaleTokensAvailable);
    privateSaleTokensSold = privateSaleTokensSold.add(value);
    transferTokens(to, value);
  }

  function hasEnded() internal constant returns(bool) {
    return now > endTime || weiRaised >= cap;
  }

  function hasStarted() internal constant returns(bool) {
    return publicSaleInitialized && now >= startTime;
  }

  function getTier(uint256 _weiRaised) internal constant returns(uint8) {
    for (uint8 i = 0; i < totalTiers; i++) {
      if (_weiRaised < tiers[i].max) {
        return i;
      }
    }
    //wont reach but for safety
    return totalTiers + 1;
  }



  function getCurrentTier() public constant returns(uint8) {
    return getTier(weiRaised);
  }


  // functions for the mini me token
  function proxyPayment(address _owner) public payable returns(bool) {
    return true;
  }

  function onApprove(address _owner, address _spender, uint _amount) public returns(bool) {
    return true;
  }

  function onTransfer(address _from, address _to, uint _amount) public returns(bool) {
    return true;
  }

  function getTokenSaleTime() public constant returns(uint256, uint256) {
    return (startTime, endTime);
  }
}
function initTiers(uint256[] rates, uint256[] totalWeis) public onlyWhitelisted returns(uint256) {
  require(token.controller() == address(this));
  require(!tiersInitialized);
  require(rates.length == totalTiers && rates.length == totalWeis.length);
  uint256 tierMax = 0;

  for (uint8 i=0; i < totalTiers; i++) {

    require(totalWeis[i] > 0 && rates[i] > 0);

    tierMax = tierMax.add(totalWeis[i]);
    tiers[i] = Tier({
      rate: rates[i],
      max: tierMax
    });
  }

  require(tierMax == cap);
  tiersInitialized = true;
  return tierMax;
}

// rates for each tier and total wei in that tiers
// they are added up together