contract ClinicAllCrowdsale is Crowdsale, FinalizableCrowdsale, MintedCrowdsale, ManagedWhitelist, Limited {
  constructor
  (
    uint256 _tokenLimitSupply,
    uint256 _rate,
    address _wallet,
    address _privateSaleWallet,
    ERC20 _token,
    uint256 _openingTime,
    uint256 _closingTime,
    uint256 _discountTokenAmount,
    uint256 _discountTokenPercent,
    uint256 _preSaleClosingTime,
    uint256 _softCapLimit,
    ClinicAllRefundEscrow _vault,
    uint256 _buyLimitSupplyMin,
    uint256 _buyLimitSupplyMax,
    uint256 _kycLimitEliminator
  )
  Crowdsale(_rate, _wallet, _token)
  TimedCrowdsale(_openingTime, _closingTime)
  public
  {
    privateSaleWallet = _privateSaleWallet;
    tokenSupplyLimit = _tokenLimitSupply;
    discountTokenAmount = _discountTokenAmount;
    discountTokenPercent = _discountTokenPercent;
    preSaleClosingTime = _preSaleClosingTime;
    softCapLimit = _softCapLimit;
    vault = _vault;
    buyLimitSupplyMin = _buyLimitSupplyMin;
    buyLimitSupplyMax = _buyLimitSupplyMax;
    kycLimitEliminator = _kycLimitEliminator;
  }

  using SafeMath for uint256;

  // refund vault used to hold funds while crowdsale is running
  ClinicAllRefundEscrow public vault;

  /*/
  *  Properties, constants
  /*/
  //address public walletPrivateSaler;
  // Limit of tokens for supply during ICO public sale
  uint256 public tokenSupplyLimit;
  // Limit of tokens with discount on current contract
  uint256 public discountTokenAmount;
  // Percent value for discount tokens
  uint256 public discountTokenPercent;
  // Time when we finish pre sale
  uint256 public preSaleClosingTime;
  // Minimum amount of funds to be raised in weis
  uint256 public softCapLimit;
  // Min buy limit for each investor
  uint256 public buyLimitSupplyMin;
  // Max buy limit for each investor
  uint256 public buyLimitSupplyMax;
  // KYC Limit Eliminator for small and big investors
  uint256 public kycLimitEliminator;
  // Address where private sale funds are collected
  address public privateSaleWallet;
  // Private sale tokens supply limit
  uint256 public privateSaleSupplyLimit;

  // Public functions

  /*/
  *  @dev CrowdSale manager is able to change rate value during ICO
  *  @param _rate wei to CHT tokens exchange rate
  */
  function updateRate(uint256 _rate) public
  onlyManager
  {
    require(_rate != 0, "Exchange rate should not be 0.");
    rate = _rate;
  }

  /*/
  *  @dev CrowdSale manager is able to change min and max buy limit for investors during ICO
  *  @param _min Minimal amount of tokens that could be bought
  *  @param _max Maximum amount of tokens that could be bought
  */
  function updateBuyLimitRange(uint256 _min, uint256 _max) public
  onlyOwner
  {
    require(_min != 0, "Minimal buy limit should not be 0.");
    require(_max != 0, "Maximal buy limit should not be 0.");
    require(_max > _min, "Maximal buy limit should be greater than minimal buy limit.");
    buyLimitSupplyMin = _min;
    buyLimitSupplyMax = _max;
  }

  /*/
  *  @dev CrowdSale manager is able to change Kyc Limit Eliminator for investors during ICO
  *  @param _value amount of tokens that should be as eliminator
  */
  function updateKycLimitEliminator(uint256 _value) public
  onlyOwner
  {
    require(_value != 0, "Kyc Eliminator should not be 0.");
    kycLimitEliminator = _value;
  }

  /**
  * @dev Investors can claim refunds here if crowdsale is unsuccessful
  */
  function claimRefund() public {
    require(isFinalized, "Claim refunds is only possible if the ICO is finalized.");
    require(!goalReached(), "Claim refunds is only possible if the soft cap goal has not been reached.");
    uint256 deposit = vault.depositsOf(msg.sender);
    vault.withdraw(msg.sender);
    weiRaised = weiRaised.sub(deposit);
    ClinicAllToken(token).burnAfterRefund(msg.sender);
  }

  /**
  @dev Owner can claim full refund if a crowdsale is unsuccessful
  @param _txFee Transaction fee that will be deducted from an invested sum
  */
  function claimRefundChunk(uint256 _txFee, uint256 _chunkLength) public onlyOwner {
    require(isFinalized, "Claim refunds is only possible if the ICO is finalized.");
    require(!goalReached(), "Claim refunds is only possible if the soft cap goal has not been reached.");
    uint256 _weiRefunded;
    address[] memory _refundeesList;
    (_weiRefunded, _refundeesList) = vault.withdrawChunk(_txFee, _chunkLength);
    weiRaised = weiRaised.sub(_weiRefunded);
    for (uint256 i = 0; i < _refundeesList.length; i++) {
      ClinicAllToken(token).burnAfterRefund(_refundeesList[i]);
    }
  }


  /**
  * @dev Get refundee list length
  */
  function refundeesListLength() public onlyOwner view returns (uint256) {
    return vault.refundeesListLength();
  }

  /**
  * @dev Checks whether the period in which the crowdsale is open has already elapsed.
  * @return Whether crowdsale period has elapsed
  */
  function hasClosed() public view returns (bool) {
    return ((block.timestamp > closingTime) || tokenSupplyLimit <= token.totalSupply());
  }

  /**
  * @dev Checks whether funding goal was reached.
  * @return Whether funding goal was reached
  */
  function goalReached() public view returns (bool) {
    return token.totalSupply() >= softCapLimit;
  }

  /**
  * @dev Checks rest of tokens supply.
  */
  function supplyRest() public view returns (uint256) {
    return (tokenSupplyLimit.sub(token.totalSupply()));
  }

  //Private functions

  function _processPurchase(
    address _beneficiary,
    uint256 _tokenAmount
  )
  internal
  doesNotExceedLimit(_beneficiary, _tokenAmount, token.balanceOf(_beneficiary), kycLimitEliminator)
  {
    super._processPurchase(_beneficiary, _tokenAmount);
  }

  function _preValidatePurchase(
    address _beneficiary,
    uint256 _weiAmount
  )
  internal
  onlyIfWhitelisted(_beneficiary)
  isLimited(_beneficiary)
  {
    super._preValidatePurchase(_beneficiary, _weiAmount);
    uint256 tokens = _getTokenAmount(_weiAmount);
    require(tokens.add(token.totalSupply()) <= tokenSupplyLimit, "Total amount fo sold tokens should not exceed the total supply limit.");
    require(tokens >= buyLimitSupplyMin, "An investor can buy an amount of tokens only above the minimal limit.");
    require(tokens.add(token.balanceOf(_beneficiary)) <= buyLimitSupplyMax, "An investor cannot buy tokens above the maximal limit.");
  }

  /**
   * @dev Te way in which ether is converted to tokens.
   * @param _weiAmount Value in wei to be converted into tokens
   * @return Number of tokens that can be purchased with the specified _weiAmount with discount or not
   */
  function _getTokenAmount(uint256 _weiAmount)
  internal view returns (uint256)
  {
    if (isDiscount()) {
      return _getTokensWithDiscount(_weiAmount);
    }
    return _weiAmount.mul(rate);
  }
  /**
   * @dev Public method where ether is converted to tokens.
   * @param _weiAmount Value in wei to be converted into tokens
   */
  function getTokenAmount(uint256 _weiAmount)
  public view returns (uint256)
  {
    return _getTokenAmount(_weiAmount);
  }

  /**
   * @dev iternal method returns total tokens amount including discount
   */
  function _getTokensWithDiscount(uint256 _weiAmount)
  internal view returns (uint256)
  {
    uint256 tokens = 0;
    uint256 restOfDiscountTokens = discountTokenAmount.sub(token.totalSupply());
    uint256 discountTokensMax = _getDiscountTokenAmount(_weiAmount);
    if (restOfDiscountTokens < discountTokensMax) {
      uint256 discountTokens = restOfDiscountTokens;
      //get rest of WEI
      uint256 _rate = _getDiscountRate();
      uint256 _discointWeiAmount = discountTokens.div(_rate);
      uint256 _restOfWeiAmount = _weiAmount.sub(_discointWeiAmount);
      uint256 normalTokens = _restOfWeiAmount.mul(rate);
      tokens = discountTokens.add(normalTokens);
    } else {
      tokens = discountTokensMax;
    }

    return tokens;
  }

  /**
   * @dev iternal method returns discount tokens amount
   * @param _weiAmount An amount of ETH that should be converted to an amount of CHT tokens
   */
  function _getDiscountTokenAmount(uint256 _weiAmount)
  internal view returns (uint256)
  {
    require(_weiAmount != 0, "It should be possible to buy tokens only by providing non zero ETH.");
    uint256 _rate = _getDiscountRate();
    return _weiAmount.mul(_rate);
  }

  /**
   * @dev Returns the discount rate value
   */
  function _getDiscountRate()
  internal view returns (uint256)
  {
    require(isDiscount(), "Getting discount rate should be possible only below the discount tokens limit.");
    return rate.add(rate.mul(discountTokenPercent).div(100));
  }

  /**
   * @dev Returns the exchange rate value
   */
  function getRate()
  public view returns (uint256)
  {
    if (isDiscount()) {
      return _getDiscountRate();
    }

    return rate;
  }

  /**
   * @dev Returns the status if the ICO's private sale has closed or not
   */
  function isDiscount()
  public view returns (bool)
  {
    return (preSaleClosingTime >= block.timestamp);
  }

  /**
   * @dev Internal method where owner transfers part of tokens to reserve
   */

  function transferTokensToReserve(address _beneficiary) private
  {
    require(tokenSupplyLimit < CappedToken(token).cap(), "Token's supply limit should be less that token' cap limit.");
    // calculate token amount to be created
    uint256 _tokenCap = CappedToken(token).cap();
    uint256 tokens = _tokenCap.sub(tokenSupplyLimit);

    _deliverTokens(_beneficiary, tokens);
  }

  /**
  * @dev Enable transfers of tokens between wallets
  */
  function transferOn() public onlyOwner
  {
    ClinicAllToken(token).transferOn();
  }

  /**
  * @dev Disable transfers of tokens between wallets
  */
  function transferOff() public onlyOwner
  {
    ClinicAllToken(token).transferOff();
  }

  /**
   * @dev Internal method where owner transfers part of tokens to reserve and finish minting
   */
  function finalization() internal {
    if (goalReached()) {
      transferTokensToReserve(wallet);
      vault.close();
    } else {
      vault.enableRefunds();
    }
    MintableToken(token).finishMinting();
    super.finalization();
  }

  /**
  * @dev Overrides Crowdsale fund forwarding, sending funds to vault.
  */
  function _forwardFunds() internal {
    super._forwardFunds();
    vault.depositFunds(msg.sender, msg.value);
  }

  /**
  * @dev Throws if operator is not whitelisted.
  */
  modifier onlyPrivateSaleWallet() {
    require(privateSaleWallet == msg.sender, "Wallet should be the same as private sale wallet.");
    _;
  }

  /**
  * @dev Public method where private sale manager can transfer tokens to private investors
  */
  function transferToPrivateInvestor(
    address _beneficiary,
    uint256 _value
  )
  public
  onlyPrivateSaleWallet
  onlyIfWhitelisted(_beneficiary)
  returns (bool)
  {
    ClinicAllToken(token).transferToPrivateInvestor(msg.sender, _beneficiary, _value);
  }

  /**
  * @dev Public method where private sale manager can transfer the rest of tokens form private sale wallet available to crowdsale
  */
  function redeemPrivateSaleFunds()
  public
  onlyPrivateSaleWallet
  {
    uint256 _balance = ClinicAllToken(token).balanceOf(msg.sender);
    privateSaleSupplyLimit = privateSaleSupplyLimit.sub(_balance);
    ClinicAllToken(token).burnPrivateSale(msg.sender, _balance);
  }

  /**
  * @dev Internal method where private sale manager getting private sale limit amount of tokens
  * @param privateSaleSupplyAmount value of CHT tokens to add for private sale
  */
  function allocatePrivateSaleFunds(uint256 privateSaleSupplyAmount) public onlyOwner
  {
    require(privateSaleSupplyLimit.add(privateSaleSupplyAmount) < tokenSupplyLimit, "Token's private sale supply limit should be less that token supply limit.");
    privateSaleSupplyLimit = privateSaleSupplyLimit.add(privateSaleSupplyAmount);
    _deliverTokens(privateSaleWallet, privateSaleSupplyAmount);
  }

  /**
  @dev Owner can withdraw part of funds during of ICO
  @param _value Transaction amoun that will be deducted from an vault sum
  */
  function beneficiaryWithdrawChunk(uint256 _value) public onlyOwner {
    vault.beneficiaryWithdrawChunk(_value);
  }

  /**
  @dev Owner can withdraw all funds during or after of ICO
  */
  function beneficiaryWithdrawAll() public onlyOwner {
    vault.beneficiaryWithdrawAll();
  }

  /**
  @dev Owner can do manual refund here if investore has "BAD" money
  @param _payee address of investor that needs to refund with next manual ETH sending
  */
  function manualRefund(address _payee) public onlyOwner {

    uint256 deposit = vault.depositsOf(_payee);
    vault.manualRefund(_payee);
    weiRaised = weiRaised.sub(deposit);
    ClinicAllToken(token).burnAfterRefund(_payee);
  }

}
/// @title ClinicAll crowdsale contract
/// @dev  ClinicAll crowdsale contract
function _getTokenAmount(uint256 _weiAmount)
internal view returns (uint256)
{
  if (isDiscount()) {
    return _getTokensWithDiscount(_weiAmount);
  }
  return _weiAmount.mul(rate);
}

/**
 * @dev Te way in which ether is converted to tokens.
 * @param _weiAmount Value in wei to be converted into tokens
 * @return Number of tokens that can be purchased with the specified _weiAmount with discount or not
 */