contract AdvanceCrowdsale is Operator, Crowdsale, CappedCrowdsale, TimedCrowdsale, PausableCrowdsale, FinalizableCrowdsale {
	uint256 private _tokenPriceInCents = 100; // default value 1 WDS = 100 (1 cent)

	uint256 private _bonusRate; // no bonus if value = 0

	// using for buyable token by btc
	// How many token units a buyer gets per satoshi.
	// The rate is the conversion between satoshi and the smallest and indivisible token unit.
	// So, if you are using a rate of 1 with a ERC20Detailed token with 3 decimals called TOK
	// 1 satoshi will give you 1 unit, or 0.001 TOK.
	uint256 private _btcRate;

	address private _lockFactoryAddress;

	// satoshi to uint token
	uint256 public constant SATOSHI_CAST_UNIT = 10**10;

	uint256 public constant PRICE_COEFF = 100; // allow to set minimum token price is 0.001 USD

	uint256 public constant BONUS_COEFF = 1000; // Values should be 10x percents, value 1000 = 100%

	modifier onlyLockFactory() {
		require(msg.sender == _lockFactoryAddress, "AdvanceCrowsale: caller is not lock factory.");
		_;
	}

	constructor(uint256 _rate, uint256 _startBtcRate, address payable _wallet, IERC20 _token, uint256 _cap, uint256 _openingTime, uint256 _closingTime, address _lockFactory)
		public
		Crowdsale(_rate, _wallet, _token)
		CappedCrowdsale(_cap)
		TimedCrowdsale(_openingTime, _closingTime)
	{
		require(_startBtcRate != 0, "AdvanceCrowsale: BTC rate is 0.");
		require(_lockFactory != address(0), "AdvanceCrowsale: Lock factory is zero address.");
		_btcRate = _startBtcRate;
		_lockFactoryAddress = _lockFactory;
	}

	/**
	 * @return  number of token units a buyer gets per satoshi.
	 */
	function btcRate() public view returns (uint256) {
		return _btcRate;
	}

	/**
	 * @return the token price in cents
	 */
	function tokenPriceInCents() public view returns (uint256) {
		return _tokenPriceInCents;
	}

	/**
	 * @return the token bonus rate
	 */
	function bonusRate() public view returns (uint256) {
		return _bonusRate;
	}

	/**
	 * setRate set token price in cents, 1 WDS = ? cents
	 * @param _price new token price
	 */
	function setTokenPriceInCents(uint256 _price) public onlyOwner {
		require(_price > 0, "AdvanceCrowsale: price is zero");

		uint256 _oldPrice = _tokenPriceInCents;
		_tokenPriceInCents = _price;

		// update exchange rate in wei
		uint256 _newRate = rate().mul(_oldPrice).div(_tokenPriceInCents);
		super._setRate(_newRate);
		// update exchange rate in satoshi
		_btcRate = _btcRate.mul(_oldPrice).div(_tokenPriceInCents);
	}

	/**
	 * setRateByCents set new exchange rate by ETH/USD and BTC/USD rate
	 * @param _newRatePerCent new eth rate by 1 cent
	 * @param _newBtcRatePerCent new eth rate by 1 cent
	 */
	function setExchangeRateByCents(uint256 _newRatePerCent, uint256 _newBtcRatePerCent) public onlyOperator {
		require(_newRatePerCent > 0, "AdvanceCrowsale: rate is zero");
		require(_newBtcRatePerCent > 0, "AdvanceCrowsale: btc rate is zero");
		uint256 _newRate = _newRatePerCent.mul(PRICE_COEFF).div(_tokenPriceInCents);
		super._setRate(_newRate);
		_btcRate = _newBtcRatePerCent.mul(PRICE_COEFF).div(_tokenPriceInCents);
	}

	/**
	 * setBonusRate set new bonus rate
	 * @param _newBonusRate new bonus rate
	 */
	function setBonusRate(uint256 _newBonusRate) public onlyOwner {
		require(_newBonusRate <= BONUS_COEFF, "AdvanceCrowsale: Bonus exceeds the allowed limit");
		_bonusRate = _newBonusRate;
	}

	/**
	 * setOpeningTime change sale opening time
	 * can reschedule before sale start
	 * @param _openingTime new opening time
	 */
	function setOpeningTime(uint256 _openingTime) public onlyOwner {
		super._rescheduleOpeningTime(_openingTime);
	}

	/**
	 * setClosingTime extend sale closing time
	 * @param _closingTime new closing time
	 */
	function setClosingTime(uint256 _closingTime) public onlyOwner {
		super._rescheduleClosingTime(_closingTime);
	}

	/**
	 * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
	 * Use `super` in contracts that inherit from Crowdsale to extend their validations.
	 * Example from CappedCrowdsale.sol's _preValidatePurchase method:
	 *     super._preValidatePurchase(beneficiary, tokenAmount);
	 *     require(tokenRaised().add(tokenAmount) <= cap);
	 * @param _beneficiary Address performing the token purchase
	 * @param tokenAmount Value in token involved in the purchase
	 */
	function preValidatePurchase(address _beneficiary, uint256 tokenAmount) external view returns (bool) {
		bool success = tokenRaised().add(tokenAmount) <= cap();
		if (!success) {
			return false;
		}
		return true;
	}

	/**
	 * @dev Override for extensions that require an internal state to check for validity (current user contributions,
	 * etc.)
	 * @param beneficiary Address receiving the tokens
	 * @param tokenAmount Value in token involved in the purchase
	 */
	function updatePurchasingState(address beneficiary, uint256 tokenAmount) external onlyLockFactory returns (bool) {
		require(tokenAmount != 0, "AdvanceCrowsale: token amount is 0.");
		super._updatePurchasingState(beneficiary, tokenAmount);
		return true;
	}

	/**
	 * override _getTokenAmount
	 * get total token with bonus rate
	 */
	function estimateTokenAmountInWei(uint256 weiAmount) external view returns (uint256) {
		return weiAmount.mul(_computeEthExchangeRate());
	}

	/**
	 * _computeExchangeRate calculate new exchange rate with bonus tokens
	 * @return new exchange rate
	 */
	function _computeEthExchangeRate() internal view returns (uint256) {
		uint256 _currentRate = rate();
		if (_bonusRate == 0) {
			return _currentRate;
		}
		uint256 _extraRate = _currentRate.mul(_bonusRate).div(BONUS_COEFF);
		return _currentRate.add(_extraRate);
	}

	/**
	 * @dev Override to extend the way in which ether is converted to tokens.
	 * @param satoshiAmount Value in satoshi to be converted into tokens
	 * @return Number of tokens that can be purchased with the specified satoshiAmount
	 */
	function estimateTokenAmountInSatoshi(uint256 satoshiAmount) external view returns (uint256) {
		return satoshiAmount.mul(_computeBtcExchangeRate()).mul(SATOSHI_CAST_UNIT);
	}

	/**
	 * _computeExchangeRate calculate new exchange rate with bonus tokens
	 * @return new exchange rate
	 */
	function _computeBtcExchangeRate() internal view returns (uint256) {
		if (_bonusRate == 0) {
			return _btcRate;
		}
		uint256 _extraRate = _btcRate.mul(_bonusRate).div(BONUS_COEFF);
		return _btcRate.add(_extraRate);
	}
}
function estimateTokenAmountInSatoshi(uint256 satoshiAmount) external view returns (uint256) {
	return satoshiAmount.mul(_computeBtcExchangeRate()).mul(SATOSHI_CAST_UNIT);
}

/**
 	 * @dev Override to extend the way in which ether is converted to tokens.
 	 * @param satoshiAmount Value in satoshi to be converted into tokens
 	 * @return Number of tokens that can be purchased with the specified satoshiAmount
 	 */