/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/// Price calculator ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



	function RBPrice()
	{
	}

	RBPrice.prototype.tax = null;
	RBPrice.prototype.in_id = null;
	RBPrice.prototype.out_id = null;
	RBPrice.prototype.nights = null;
	RBPrice.prototype.guests = null;
	RBPrice.prototype.result = null;
	RBPrice.prototype.change = null;
	RBPrice.prototype.periods = null;
	RBPrice.prototype.currency = null;
	RBPrice.prototype.pack_ads = null;
	RBPrice.prototype.in_count = null;
	RBPrice.prototype.full_rule = null;
	RBPrice.prototype.out_count = null;
	RBPrice.prototype.linked_to = null;
	RBPrice.prototype.discounts = null;
	RBPrice.prototype.round_rule = null;
	RBPrice.prototype.package_id = null;
	RBPrice.prototype.exemptions = null;
	RBPrice.prototype.guest_types = null;
	RBPrice.prototype.exemption_id = null;
	RBPrice.prototype.addition_list = null;
	RBPrice.prototype.full_addition = null;
	RBPrice.prototype.full_transfer = null;
	RBPrice.prototype.tax_lump_data = null;
	RBPrice.prototype.advanced_price = null;
	RBPrice.prototype.property_price = null;
	RBPrice.prototype.last_day_period = null;
	RBPrice.prototype.by_rule_discount = null;
	RBPrice.prototype.first_day_period = null;
	RBPrice.prototype.by_package_discount = null;
	RBPrice.prototype.last_day_in_new_period = null;

	RBPrice.prototype.debug = false;
	RBPrice.prototype.first_exec = true;
	RBPrice.prototype.use_package = false;
	RBPrice.prototype.discount_enabled = true;

	RBPrice.prototype.tax_value = 0.00;
	RBPrice.prototype.rate_value = 0.00;
	RBPrice.prototype.rule_value = 0.00;
	RBPrice.prototype.total_value = 0.00;
	RBPrice.prototype.rr_discount = 0.00;
	RBPrice.prototype.transfer_value = 0.00;
	RBPrice.prototype.discount_value = 0.00;
	RBPrice.prototype.rate_value_txt = 0.00;
	RBPrice.prototype.additions_value = 0.00;
	RBPrice.prototype.total_group_price = 0.00;
	RBPrice.prototype.additions_value_txt = 0.00;

	RBPrice.prototype.rate_more = '';
	RBPrice.prototype.additions_more = '';

/////////////////////////////////////////////////////////////////////
/// Deprecated //////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
	RBPrice.prototype.postfix = '';
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

	RBPrice.prototype.exec = function()
	{
/*
		if (debug()) {
			this.debug = true;
		}
*/
		this.debug = '';

		if (!this.checkVars()) {
			return;
		}
		this.tax_value = 0.00;
		this.rate_value = 0.00;
		this.rule_value = 0.00;
		this.total_value = 0.00;
		this.rr_discount = 0.00;
		this.transfer_value = 0.00;
		this.discount_value = 0.00;
		this.rate_value_txt = 0.00;
		this.additions_value = 0.00;
		this.additions_value_txt = 0.00;

		if (this.linked_to == 'RB') {
			this.change = '<span style="font-size: 9px; letter-spacing: -1px; background-color: #e1ddcf;">&larr;&nbsp;' + this.GD('click_to_change') + '</span>';
			this.getGuests();
			this.getFormVars();
		}
		this.completeGuests();
/*
if ( debug() ) {
	alert(var_dump(this.guests));
}
*/
		if (this.guests.p > 0) {
			if (this.linked_to == 'RB') {
				this.checkPackage();
			}
			this.calcPackage();
			this.calcRate();
			this.calcAddition();

			if (this.linked_to == 'RB') {
				this.checkTransfer();
			}
			this.calcTransfer();
			this.calcDiscount();
		}
		switch (this.linked_to) {
			case'RB':
				this.showResults();
				break;
			default:
				this.genResults();
				break;
		}
	}

	RBPrice.prototype.GD = function(str, data)
	{
		if (this.linked_to == 'RB') {
			return global_dictionary.get(str, data);
		} else {
			return llt_res_object_unique.GD(str, data);
		}
	}

	RBPrice.prototype.getGuests = function()
	{
		var i, tmp;
		this.guests = {};
		for (i in this.guest_types) {
			this.guests[i] = parseInt($('Guest_' + i).value);
		}
	}

	RBPrice.prototype.getFormVars = function()
	{
		this.rate_id =			$('Rate').value;
		this.package_id =		$('PackageID').value;
		this.exemption_id =		$('Exemption').value;
		this.discount_id =		$('DiscountCode').value;
	}

	RBPrice.prototype.checkRules = function()
	{
		var old_rule, new_rule;
		if (!this.old_rule_data) {
			this.old_rule_data = {'ID': 0, 'DiscountID': 0};
		}
		if (this.old_rule_data.ID != $('rule_id').value) {
			if (this.old_rule_data.ID > 0) {
				old_rule = this.humahRule(this.old_rule_data);
			} else {
				old_rule = this.GD('none');
			}
			if (this.full_rule.ID > 0) {
				new_rule = this.humahRule();
			} else {
				new_rule = this.GD('none');
			}
			if (!confirm(this.GD('ask_about_prev_res_rule').replace(/\[:NL:\]/g, "\n").replace(/\[:OldRule:\]/, old_rule).replace(/\[:NewRule:\]/, new_rule))) {
				this.full_rule = this.old_rule_data;
				$('rule_id').value = this.old_rule_data.ID;
				this.by_rule_discount = this.old_rule_data.DiscountID;
				this.exec();
			}
		}
	}

	RBPrice.prototype.completeGuests = function()
	{
		var i;

		this.guests.adult = 0;
		this.guests.child = 0;
		this.guests.pet = 0;

		for (i in this.guests) {
			if (i == 'adult' || i == 'child' || i == 'pet' || i == 'p' || i == 'a' || i == 'c') {
				continue;
			}
			if (this.guest_types[i]['default']) {
				this.def_consider = this.guest_types[i].type;
			}
			if (this.guests[i]) {
				switch (this.guest_types[i].type) {
					case 'adult':
						this.guests.adult += parseInt(this.guests[i]);
						break;
					case 'child':
						this.guests.child += parseInt(this.guests[i]);
						break;
					case 'pet':
						this.guests.pet += parseInt(this.guests[i]);
						break;
				}
			}
		}
		this.guests.p = this.guests.adult + this.guests.child;
		this.guests.a = this.guests.adult;
		this.guests.c = this.guests.child;
	}

	RBPrice.prototype.discountControl = function(enable)
	{
		if (this.linked_to == 'RB') {
			if (enable) {
				$('discount_selector').style.display = 'block';
				$('discount_informer').style.display = 'none';
			} else {
				$('DiscountCode').value = '';
				this.discount_id = 0;
				$('discount_selector').style.display = 'none';
				$('discount_informer').style.display = 'block';
			}
		}
	}

	RBPrice.prototype.checkPackage = function()
	{
		var i;

		this.discountControl(true);

		if (GLOBALS.modified_ads) {
			for (i = 0; i < GLOBALS.modified_ads.length; i++) {
				$('addition_' + GLOBALS.modified_ads[i]).checked = false;
			}
		}
		GLOBALS.modified_ads = [];

		if (this.package_id != '' && this.package_id != 0) {
			this.use_package = package_data[this.rate_id][this.package_id];
		}
	}

	RBPrice.prototype.calcPackage = function()
	{
		var i, p, bit, target, ids, tmp, enable_transfer;

		this.pack_ads = {};

		if (!this.use_package || !this.package_id) {
			return;
		}
		p = this.use_package;
		enable_transfer = false;

		if (p.DiscountData) {
			this.by_package_discount = p.DiscountData.ID;
		} else {
			this.by_package_discount = 0;
		}

		if (!p.Discounts) {
			this.discount_enabled = false;
			this.discountControl(false);
		}
		for (bit in p) {
			if (bit == 'ID' || bit == 'Title' || bit == 'Image' || bit == 'Discounts' || bit == 'DiscountData') {
				continue;
			}
			for (target in p[bit]) {
				if (target == 'title' || target == 'description' || target == 'cancellation_policy') {
					continue;
				}
				ids = p[bit][target];

				switch (target) {
					case ('AC'):
						switch (bit) {
							case ('AD'):
// Arrival Day Additional Charges
								tmp = 'ad';
								break;
							case ('SO'):
// Stay Over Additional Charges
								tmp = 'so';
								break;
							case ('DD'):
// Departure Day Additional Charges
								tmp = 'dd';
								break;
							default:
								alert('Unknown bit ' + bit + ' (target = ' + target + ')');
								break;
						}
						for (i in ids) {
							if (this.linked_to == 'RB') {
								if ($('addition_' + ids[i])) {
									if (!$('addition_' + ids[i]).checked) {
										GLOBALS.modified_ads.push(ids[i]);
										$('addition_' + ids[i]).checked = true;
									}
								}
							}
							if (!this.pack_ads[ids[i]]) {
								this.pack_ads[ids[i]] = {};
							}
							this.pack_ads[ids[i]][tmp] = true;
						}
						break;
					case ('TR'):
						switch (bit) {
							case ('AD'):
								if (this.linked_to == 'RB') {
									if ($('in_t_id')) {
										if ($('in_t_id').value == '') {
											$('in_t_id').value = ids[0];
										}
										if ($('in_t_count').value == '') {
											$('in_t_count').value = this.guests.adult;
										}
										enable_transfer = true;
									}
								} else {
									this.in_id = ids[0];
									this.in_count = this.guests.adult;
								}
								break;
							case ('SO'):
								alert(this.GD('transfer_for_stay_over_period'));
								break;
							case ('DD'):
								if (this.linked_to == 'RB') {
									if ($('out_t_id')) {
										if ($('out_t_id').value == '') {
											$('out_t_id').value = ids[0];
										}
										if ($('out_t_count').value == '') {
											$('out_t_count').value = this.guests.adult;
										}
										enable_transfer = true;
									}
								} else {
									this.out_id = ids[0];
									this.out_count = this.guests.adult;
								}
								break;
							default:
								alert('Unknown bit ' + bit + ' (target = ' + target + ')');
								break;
						}
						break;
					default:
						alert('Unknown target ' + bit + ':' + target + ' in packet price culculation');
						break;
				}
			}
		}
		if (enable_transfer) {
			flipTab('add', 'transf');
			$('in_time_h').focus();
		}
	}

	RBPrice.prototype.calcRate = function()
	{
		var i, k, tmp, rate_tax_value, for_lump;

		rate_tax_value = 0.00;

		this.detail_tax = '';
		this.periods = {};
		this.tax_lump_data = {};
		this.last_day_period = 0;
		this.first_day_period = 0;

		for (i in this.property_price) {
			for_lump = {days: 1, day: i, key: 'stay'};

			if (!this.periods[this.property_price[i].period_id]) {
				this.periods[this.property_price[i].period_id] = 0;
			}
			if (this.property_price[i].period == 'day') {
				this.periods[this.property_price[i].period_id]++;
			} else if (this.property_price[i].period == 'week') {
				this.periods[this.property_price[i].period_id] += 7;
				for_lump.days = 7;
			} else if (this.property_price[i].period == 'month') {
				this.periods[this.property_price[i].period_id] += this.property_price[i].month_length;
				for_lump.days = this.property_price[i].month_length;
			}

			if (!this.first_day_period) {
				this.first_day_period = this.property_price[i].period_id;
			}
			this.last_day_period = this.property_price[i].period_id;
/*
	this.guests
			552 = 2
			557 = 0
			adult = 0
			child = 2
			pet = 0
			p = 2
			a = 0
			c = 2

	this.property_price[i]
		from = 1289347200
		to = 1289433600
		period_id = 498
		period = day
		OBJECT "price" 
			1 = 225
			2 = 225
			3 = 300
		type = a
		OBJECT "info" 
			min_stay = 1
			max_stay = 0
*/
			tmp = 0;
			switch (this.property_price[i].type) {
				case 'a':
					tmp += this.property_price[i].price[this.guests.p];
					break;
				case 'p':
					for (k in this.property_price[i].price) {
						if (this.guests[k]) {
							tmp += this.property_price[i].price[k] * this.guests[k];
						}
					}
					break;
				case 'r':
					tmp += this.property_price[i].price;
					break;
			}
			tmp = this.calcTax(this.tax.stay, tmp, this.exemptions[this.exemption_id], this.guests, for_lump);

			this.rate_value += tmp.price;
			rate_tax_value += tmp.tax;

this.detail_tax += tmp.tax + ';';
		}
		if (this.last_day_in_new_period) {
			this.periods[this.last_day_in_new_period] = 0;
			this.last_day_period = this.last_day_in_new_period;
		}
		if (this.linked_to == 'RB') {
			if (using_custom_rate) {
				custom_rate_price_enter = this.rate_value;
				using_custom_rate = false;
			}
			if (custom_rate_price_enter != this.rate_value) {
				custom_rate_price_enter = this.rate_value;
				$('custom_rate').value = '';
			}
			if (parseFloat($('custom_rate').value) > 0) {
				this.rate_more = ' per/day';

				for_lump = {days: this.nights, day: 1, key: 'custom_stay'};
				tmp = this.calcTax(this.tax.stay, parseFloat($('custom_rate').value), this.exemptions[this.exemption_id], this.guests, for_lump);

				this.rate_value_txt = tmp.price;
				this.rate_value = this.rate_value_txt * this.nights;
				rate_tax_value = tmp.tax * this.nights;
			} else {
				$('click_to_change_rate').innerHTML = this.change;
			}
		}
		this.tax_value += parseFloat(rate_tax_value);
		tmp = myParseFloat(parseFloat(this.rate_value) + parseFloat(rate_tax_value));

		if (this.discount_enabled && this.by_rule_discount > 0 && this.discounts[this.by_rule_discount]) {
			if (this.discounts[this.by_rule_discount].type == 'fixed') {
				this.rule_value = parseFloat(this.discounts[this.by_rule_discount].value);
			} else {
				this.rule_value = tmp * this.discounts[this.by_rule_discount].value / 100;
			}
			if (this.linked_to == 'RB') {
				rule_details = this.humahRule();
			}
		} else {
			if (this.linked_to == 'RB') {
				rule_details = this.GD('no_pricing_rule_applied');
			}
		}
		if (tmp < this.rule_value) {
			this.rule_value = tmp;
		}
		this.rate_value = parseFloat(this.rate_value.toFixed(2));
		this.rule_value = parseFloat(this.rule_value.toFixed(2));
		this.tax_value = parseFloat(this.tax_value.toFixed(2));

this.detail_tax += 'stay: ' + rate_tax_value + "\n";
	}

	RBPrice.prototype.humahRule = function()
	{
		var from = this.full_rule.DateFrom.split('-');
		var to = this.full_rule.DateTo.split('-');

		return parseRuleTpl(this.full_rule.Text, this.full_rule.Val, this.discounts[this.full_rule.DiscountID].title, from[1], from[2], to[1], to[2], this.full_rule.OnResTime, this.full_rule.WeekDays);
	}

	RBPrice.prototype.calcAddition = function()
	{
		var i, j, tmp, k, days, tmp_price, full_tmp_price, addition, lumb_flag, addition_tax, cur_periods, lump, pers, for_lump;

		addition_tax = 0;

		addition = [];
		if (this.linked_to == 'RB') {
			for (i in this.addition_list) {
				if ($('addition_' + this.addition_list[i]).checked) {
					addition.push(this.addition_list[i]);
				}
			}
		} else {
			for (i in this.pack_ads) {
				addition.push(i);
			}
		}
//var debug = '';

		lumb_flag = {};

		cur_periods = {};
		tmp = 0;
		for (i in this.periods) {
			cur_periods[i] = this.periods[i];
			tmp += this.periods[i];
		}
		cur_periods[0] = tmp;

		for (i in cur_periods) {
			if (!this.full_addition[i]) {
/*
if ( debug() ) {
	alert('Calc Additions debug warning: season ' + i + ' have no price');
}
*/
				continue;
			}
			for (j = 0; j < addition.length; j++) {
				if (this.full_addition[i][addition[j]]) {
					tmp = this.full_addition[i][addition[j]];
				} else {
/*
if ( debug() ) {
	alert('Calc Additions debug warning: season ' + i + ', addition: ' + j + ' have no price');
}
*/
					continue;
				}
				for (k in tmp.gt) {
					lump = false;
					tmp_price = 0;
					if (k == 0) {
						pers = this.guests.p;
					} else {
						if (this.guests[k] > 0) {
							pers = this.guests[k];
						} else {
							continue;
						}
					}
					lump_key = addition[j] + '_' + k;

					switch (tmp.gt[k].type) {
						case 'per_pers':
							tmp_price = tmp.gt[k].price * pers;
							break;
						case 'per_room':
							tmp_price = parseFloat(tmp.gt[k].price);
							break;
						case 'per_pers_lump':
/*
	Partly lump
*/
							if (!(lumb_flag[lump_key])) {
								tmp_price = tmp.gt[k].price * pers;
							}
							lumb_flag[lump_key] = true;
							lump = true;
							break;
						case 'per_room_lump':
/*
	Partly lump
*/
							if (!(lumb_flag[lump_key])) {
								tmp_price = parseFloat(tmp.gt[k].price);
							}
							lumb_flag[lump_key] = true;
							lump = true;
							break;
						default:
							alert('Unknown addition price type');
							break;
					}
					if (tmp_price) {
						if (lump) {
							k = 1;
						} else if (this.pack_ads && this.pack_ads[addition[j]]) {
							days = this.pack_ads[addition[j]];

							if (days.so) {
								k = cur_periods[i];

								if ((i == 0 || i == this.first_day_period) && !days.ad) {
									k -= 1;
								}
								if ((i == 0 || i == this.last_day_period) && days.dd) {
									k += 1;
								}
							} else {
								k = 0;
								if ((i == 0 || i == this.first_day_period) && days.ad) {
									k += 1;
								}
								if ((i == 0 || i == this.last_day_period) && days.dd) {
									k += 1;
								}
							}
						} else {
							k = cur_periods[i];
						}
						for_lump = {days: k, day: 1, key: addition[j]};
						full_tmp_price = this.calcTax(this.tax['addition_' + addition[j]], tmp_price, this.exemptions[this.exemption_id], this.guests, for_lump);
//	this.debug += ' - ' + k + ' - ' + addition[j] + ' - ' + "\n" + var_dump(full_tmp_price);
						this.additions_value += full_tmp_price.price * k;
						addition_tax += full_tmp_price.tax * k;

						this.additions_value = parseFloat(this.additions_value.toFixed(2));
						addition_tax = parseFloat(addition_tax.toFixed(2));
					}
				}
			}
		}
/*
	if (debug()) {
		alert(this.debug);
	}
*/
		if (this.linked_to == 'RB') {
			if (using_custom_additions) {
				custom_additions_price_enter = this.additions_value;
				using_custom_additions = false;
			}
			if (custom_additions_price_enter != this.additions_value) {
				custom_additions_price_enter = this.additions_value;
				$('custom_additions').value = '';
			}
			if (parseFloat($('custom_additions').value) > 0) {
				this.additions_more = ' per/day';

				for_lump = {days: this.nights, day: 1, key: 'custom_additions'};
				tmp = this.calcTax(this.tax.addition, parseFloat($('custom_additions').value), this.exemptions[this.exemption_id], this.guests, for_lump);

				this.additions_value_txt = tmp.price;
				this.additions_value = this.additions_value_txt * this.nights;
				addition_tax = tmp.tax;
			} else {
				$('click_to_change_additions').innerHTML = this.change;
			}
		}
		this.tax_value += addition_tax;
		this.tax_value = parseFloat(this.tax_value.toFixed(2));

this.detail_tax += 'addition: ' + addition_tax + "\n";
	}

	RBPrice.prototype.checkTransfer = function()
	{
		this.transfer_value = 0.00;

		if ($('in_t_id')) {
			this.in_id = parseInt($('in_t_id').value);
			this.in_count = $('in_t_count').value;
		}
		if ($('out_t_id')) {
			this.out_id = parseInt($('out_t_id').value);
			this.out_count = $('out_t_count').value;
		}
	}

	RBPrice.prototype.calcTransfer = function()
	{
		var t, in_value, out_value, tmp, for_lump;

		if (this.in_id && this.in_count) {
// don't change getting if 'in' object - stupid IE doesn't understand
			t = this.full_transfer['in'][this.in_id];
			tmp = Math.ceil(this.in_count / t.max_pers);
			in_value = t.price * tmp;

			in_value = parseFloat(in_value.toFixed(2));

			for_lump = {days: 1, day: 1, key: 'in_t_id'};

			in_value = this.calcTax(this.tax.transfer, in_value, this.exemptions[this.exemption_id], this.guests, for_lump);

			this.transfer_value += in_value.price;
			this.tax_value += in_value.tax;

			this.transfer_value = parseFloat(this.transfer_value.toFixed(2));
			this.tax_value = parseFloat(this.tax_value.toFixed(2));
		}
		if (this.out_id && this.out_count) {
			t = this.full_transfer.out[this.out_id];
			tmp = Math.ceil(this.out_count / t.max_pers);
			out_value = t.price * tmp;

			out_value = parseFloat(out_value.toFixed(2));

			for_lump = {days: 1, day: 1, key: 'out_t_id'};

			out_value = this.calcTax(this.tax.transfer, out_value, this.exemptions[this.exemption_id], this.guests, for_lump);

			this.transfer_value += out_value.price;
			this.tax_value += out_value.tax;

			this.transfer_value = parseFloat(this.transfer_value.toFixed(2));
			this.tax_value = parseFloat(this.tax_value.toFixed(2));
		}
try {
	this.detail_tax += 'tarnsfer: ' + (in_value.tax + out_value.tax) + "\n";
}catch (e) {}
	}

	RBPrice.prototype.calcDiscount = function()
	{
		var tmp;

		this.total_value = this.rate_value + this.additions_value + this.transfer_value + this.tax_value - this.rule_value;
		this.discount_value = 0.00;

		if (this.discount_id > 0) {
			if (this.discounts[this.discount_id].type == 'fixed') {
				tmp = parseFloat(this.discounts[this.discount_id].value);
			} else {
				tmp = this.total_value * this.discounts[this.discount_id].value / 100;
			}
			this.discount_value += parseFloat(tmp.toFixed(2));
		}
		if (this.by_package_discount > 0) {
			if (this.discounts[this.by_package_discount].type == 'fixed') {
				tmp = parseFloat(this.discounts[this.by_package_discount].value);
			} else {
				tmp = this.total_value * this.discounts[this.by_package_discount].value / 100;
			}
			this.discount_value += parseFloat(tmp.toFixed(2));
		}
		if (this.linked_to == 'RB') {
			if (owner_percent) {
				if ($('OwnerID').value) {
					tmp = this.total_value * owner_percent[$('OwnerID').value] / 100;
				}
				this.discount_value += parseFloat(tmp.toFixed(2));
			}
		}
		tmp = this.total_value - this.discount_value;
		tmp = this.roundRule(tmp);
		this.rr_discount = parseFloat(tmp.discount);
		this.discount_value += this.rr_discount;

		if (this.total_value < this.discount_value) {
			this.discount_value = this.total_value;
		}
		this.discount_value = parseFloat(parseFloat(this.discount_value).toFixed(2));
		this.total_value -= this.discount_value;
		this.total_value = parseFloat(this.total_value.toFixed(2));
// Out
		if (this.linked_to == 'RB' && !can_use_custom) {
			this.rate_more = '';
			this.additions_more = '';
		}
	}

	RBPrice.prototype.showResults = function()
	{
		$('money_rate').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + (this.rate_value_txt ? this.rate_value_txt : parseFloat(this.rate_value).toFixed(2)) + ' ' + this.rate_more;
		$('price_rate').value = this.rate_value;
		$('money_additions').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + (this.additions_value_txt ? this.additions_value_txt : parseFloat(this.additions_value).toFixed(2)) + ' ' + this.additions_more;
		$('price_additions').value = this.additions_value;
		$('money_transfer').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + parseFloat(this.transfer_value).toFixed(2);
		$('price_transfer').value = this.transfer_value;
		$('money_discount').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + parseFloat(this.discount_value).toFixed(2);
		$('price_discount').value = this.discount_value;
		$('money_rule').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + parseFloat(this.rule_value).toFixed(2);
		$('price_rule').value = this.rule_value;
		$('money_tax').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + parseFloat(this.tax_value).toFixed(2);
		$('price_tax').value = this.tax_value;

		$('money_total').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + parseFloat(this.total_value).toFixed(2);
		$('price_total').value = this.total_value;

		if ($('group_money_total')) {
			$('group_money_total').innerHTML = '&nbsp;' + GLOBALS['currency'] + ' ' + (parseFloat(this.total_value) + parseFloat(this.total_group_price)).toFixed(2);
		}
		if (this.first_exec) {
			this.first_exec = false;
		}
/*
	if (debug()) {
		alert(this.detail_tax);
	}
*/
	}

	RBPrice.prototype.genResults = function()
	{
		var i, tmp;

		tmp = ['tax_value', 'rate_value', 'rule_value', 'total_value', 'rr_discount', 'transfer_value', 'discount_value', 'additions_value', 'postfix'];

		this.result = {};

		for (i in tmp) {
			this.result[tmp[i]] = this[tmp[i]];
		}
	}

	RBPrice.prototype.checkVars = function()
	{
		var i, tmp

		tmp = ['nights', 'currency', 'advanced_price', 'guest_types', 'property_price'];

		for (i in tmp) {
			if (typeof(tmp[i]) != 'string') {
				continue;
			}
			if (this[tmp[i]] == null) {
				alert('Error: "' + tmp[i] + '" ("' + typeof(tmp[i]) + '") property is null. Price execution stopped.');
				return false;
			}
		}
		return true;
	}

	RBPrice.prototype.calcTax = function(tax, price, exemption, guests, for_lump)
	{
		if (!for_lump) {
			alert('Empty data for lump in tax calculation; json.js->calcTax()');
			return;
		}

		var ret, tmp, i, undef, key;

		ret = {'price': parseFloat(price), 'tax_price': parseFloat(price), 'tax': 0};

		if (typeof(exemption) == typeof(undef)) {
			exemption = {};
		} else {
			tmp = exemption.split(',');
			exemption = {};
			for (i in tmp) {
				exemption[tmp[i]] = true;
			}
		}

		for (i in tax) {
			if (typeof(tax[i]) == 'function') {
				continue;
			}
			if (typeof(exemption[i]) == typeof(undef)) {
				if (!this.tax_lump_data[i]) {
					this.tax_lump_data[i] = {};
				}
				switch (tax[i].type) {
					case 'percent':
						tmp = lltufRound(ret.price * tax[i].value / 100);
						break;
					case 'per_room':
						tmp = tax[i].value;
						break;
					case 'per_room_lump':
						key = i + '_' + for_lump.key;
						if (this.tax_lump_data[i][key]) {
							tmp = 0;
						} else {
							this.tax_lump_data[i][key] = true;
							tmp = parseFloat(tax[i].value);
						}
						break;
					case 'per_pers':
						tmp = guests.p * tax[i].value;
						break;
					case 'per_pers_lump':
						key = i + '_' + for_lump.key;
						if (this.tax_lump_data[i][key]) {
							tmp = 0;
						} else {
							this.tax_lump_data[i][key] = true;
							tmp = guests.p * tax[i].value;
						}
						break;
					case 'per_adult':
						tmp = guests.a * tax[i].value;
						break;
					case 'per_adult_lump':
						key = i + '_' + for_lump.key;
						if (this.tax_lump_data[i][key]) {
							tmp = 0;
						} else {
							this.tax_lump_data[i][key] = true;
							tmp = guests.a * tax[i].value;
						}
						break;
					case 'per_child':
						tmp = guests.c * tax[i].value;
						break;
					case 'per_child_lump':
						key = i + '_' + for_lump.key;
						if (this.tax_lump_data[i][key]) {
							tmp = 0;
						} else {
							this.tax_lump_data[i][key] = true;
							tmp = guests.c * tax[i].value;
						}
						break;
					default:
						alert('Unknown tax type "' + tax[i].type + '". Can\'t calc taxes. ' + typeof(tax[i]));
						break;
				}
				ret.tax += tmp;
				ret.tax = parseFloat(ret.tax.toFixed(2));
			}
		}
/*
		if (ret.tax > ret.price) {
			ret.tax = ret.price;
		}
*/
		ret.tax_price = parseFloat(ret.price + ret.tax).toFixed(2);

		return ret;
	}

	RBPrice.prototype.roundRule = function(price)
	{
		var ret, tmp;

		ret = {before: parseFloat(price), after: parseFloat(price), discount: 0};

		switch (this.round_rule) {
			case '0.1':
			case '10':
			case '100':
			case '1000':
				ret.after = (Math.floor(ret.before / this.round_rule) * this.round_rule).toFixed(2);
				break;
			case '0.5':
				tmp = Math.floor(ret.before * 10);
				ret.after = ((tmp - tmp % 5) / 10).toFixed(2);
				break;
			case '1':
				ret.after = Math.floor(ret.before);
				break;
		}
		ret.discount = (ret.before - ret.after).toFixed(2);

		return ret;
	}

	RBPrice.prototype.parseLOS = function(los)
	{
		var i, ret;

		ret = '';

		for (i in los) {
			if (los[i]) {
				if (ret != '') {
					ret += "\n";
				}
				ret += this.GD('error_los_' + i);
			}
		}
		if (ret != '') {
			if (!confirm(ret + "\n\n" + this.GD('are_you_sure'))) {
				$('cancel_reservation').value = 1;
				$f('create_reservation_form').submit();
			}
		}
	}



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// Site Integration JS lib //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



	llt_unique_close_calendar_by_blur_flag = false;
	llt_unique_month_names = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ];

	function lltUniqueCalendarControlShow(control_name, call, date, ignore_blur)
	{
		lltUniqueCalendarControlClose(control_name);
		var undef, tmp, d, m, y, html, show, i, use_width;

		html = '';
		show = true;

		if (typeof(date) == typeof(undef) || date === false) {
			tmp = document.getElementById(control_name).value.split(/\//);

			for (i in tmp) {
				if (typeof(tmp[i]) != 'string') {
					continue;
				}
				if (tmp[i] && tmp[i].length == 2 && tmp[i].charAt(0) == '0') {
					tmp[i] = tmp[i].charAt(1);
				}
			}
			d = parseInt(tmp[1]);
			m = parseInt(tmp[0]);
			y = parseInt(tmp[2]);

			tmp = new Date();
			if (isNaN(d)) {
				d = tmp.getDate();
			}
			if (isNaN(m)) {
				m = tmp.getMonth() + 1;
			}
			if (isNaN(y)) {
				y = tmp.getFullYear();
			}

			date = new Date();
			date.setDate(d);
			date.setMonth(m - 1);
			date.setYear(y);
		} else {
			date = date.split('-');
			if (date[2]) {
				d = date[0];
				m = date[1];
				y = date[2];
			} else {
				if (date[0] != m || isNaN(d)) {
					show = false;
					d = 1;
				}
				m = date[0];
				y = date[1];
			}
			date = new Date();
			date.setDate(d);
			date.setMonth(m - 1);
			date.setYear(y);
		}

		if (navigator.appName.match(/Internet\sExplorer/)) {
			use_width = 142;
		} else {
			use_width = 140;
		}

		html += '<iframe src="/blank.html" width="' + use_width + '" height="163" frameborder="0" style="position: absolute; z-index: 900;"></iframe>';
		html += '<div style="position: absolute; width: ' + use_width + 'px; border: solid 1px black; z-index: 901; text-align: center;">';

		var pref_date;
		if (date.getMonth() == 0) {
			pref_date = '12-' + (date.getFullYear() - 1);
		} else {
			pref_date = date.getMonth() + '-' + date.getFullYear();
		}
		html += '<div id="calendar_move_right_box_' + control_name + '" class="llt_calendar_move_right_box" onmouseover="this.style.backgroundColor = \'#666666\'; this.style.color = \'#ffffff\';" onmouseout="this.style.backgroundColor = \'#cccccc\'; this.style.color = \'#000000\';" onclick="lltUniqueCalendarControlShow(\'' + control_name + '\', \'' + call + '\', \'' + pref_date + '\', true); return false;"><</div>';

		html += '<div id="calendar_month_box_' + control_name + '" class="llt_calendar_month_box">' + llt_unique_month_names[date.getMonth()] + ' ' + date.getFullYear() + '</div>';

		var next_date;
		if (date.getMonth() == 11) {
			next_date = '1-' + (date.getFullYear() + 1);
		} else {
			next_date = (date.getMonth() + 2) + '-' + date.getFullYear();
		}
		html += '<div id="calendar_move_right_box_' + control_name + '" class="llt_calendar_move_right_box" onmouseover="this.style.backgroundColor = \'#666666\'; this.style.color = \'#ffffff\';" onmouseout="this.style.backgroundColor = \'#cccccc\'; this.style.color = \'#000000\';" onclick="lltUniqueCalendarControlShow(\'' + control_name + '\', \'' + call + '\', \'' + next_date + '\', true); return false;">></div>';

		html += '<div id="calendar_week_0_box_' + control_name + '" class="llt_calendar_week_box"><span style="color: #990000;">S</span></div>';
		html += '<div id="calendar_week_1_box_' + control_name + '" class="llt_calendar_week_box">M</div>';
		html += '<div id="calendar_week_2_box_' + control_name + '" class="llt_calendar_week_box">T</div>';
		html += '<div id="calendar_week_3_box_' + control_name + '" class="llt_calendar_week_box">W</div>';
		html += '<div id="calendar_week_4_box_' + control_name + '" class="llt_calendar_week_box">T</div>';
		html += '<div id="calendar_week_5_box_' + control_name + '" class="llt_calendar_week_box">F</div>';
		html += '<div id="calendar_week_6_box_' + control_name + '" class="llt_calendar_week_box"><span style="color: #990000;">S</span></div>';

		var tmp = date.getDate();
		date.setDate(1);
		var cd = date.getDay();
		var cur_month = date.getMonth();

		for (var i = 0; i < cd; i++) {
			html += '<div class="llt_calendar_week_box">&nbsp;</div>';
		}

		for (var i = 1; i < (43 - cd); i++) {
			date.setDate(i);
			if (date.getDate() == i && cur_month == date.getMonth()) {
				if (i == d && show) {
					html += '<div id="calendar_days_box_' + i + '_' + control_name + '" class="llt_calendar_days_box" style="background-color: #ffffff;" onmouseover="this.style.backgroundColor = \'#666666\'; this.style.color = \'#ffffff\';" onmouseout="this.style.backgroundColor = \'#ffffff\'; this.style.color = \'#000000\';" onclick="lltUniqueCalendarControlSetValue(\'' + control_name + '\', \'' + i + '\', \'' + (date.getMonth() + 1) + '\', \'' + date.getFullYear() + '\', \'' + call + '\'); lltUniqueCalendarControlClose(\'' + control_name + '\');">' + i + '</div>';
				} else {
					html += '<div id="calendar_days_box_' + i + '_' + control_name + '" class="llt_calendar_days_box" onmouseover="this.style.backgroundColor = \'#666666\'; this.style.color = \'#ffffff\';" onmouseout="this.style.backgroundColor = \'#dddddd\'; this.style.color = \'#000000\';" onclick="lltUniqueCalendarControlSetValue(\'' + control_name + '\', \'' + i + '\', \'' + (date.getMonth() + 1) + '\', \'' + date.getFullYear() + '\', \'' + call + '\'); lltUniqueCalendarControlClose(\'' + control_name + '\');">' + i + '</div>';
				}
			} else {
				html += '<div class="llt_calendar_week_box">&nbsp;</div>';
			}
		}

		html += '<div id="calendar_close_box_' + control_name + '" class="llt_calendar_close_box" onclick="lltUniqueCalendarControlClose(\'' + control_name + '\');">' + 'close' + '</div>';

		html += '</div>';

		document.getElementById('calendar_box_' + control_name).innerHTML = html;

		document.getElementById(control_name).focus();

		if (ignore_blur) {
			llt_unique_close_calendar_by_blur_flag = true;
		}
	}

	function lltUniqueCalendarControlSetValue(control_name, d, m, y, call)
	{
		document.getElementById(control_name).value = m + '/' + d + '/' + y;

		if (call != '') {
			eval(call);
		}
	}

	function lltUniqueCalendarControlClose(control_name)
	{
		if (llt_unique_close_calendar_by_blur_flag) {
			llt_unique_close_calendar_by_blur_flag = false;
		} else {
			document.getElementById('calendar_box_' + control_name).innerHTML = '';
			document.getElementById(control_name).blur();
		}
	}

	function lltufRound(x)
	{
		var m, res, i, check, prev;

		m = x * 100;
		m = '' + m;
		m = m.split(/\D/);

		if (m[1]) {
			i = 0;
			check = m[1].charAt(i);

			if (m[1].charAt(i + 1)) {
				if (check > 4) {
					res = parseInt(m[0]) + 1;
				} else if (check < 4) {
					res = parseInt(m[0]);
				} else {
					while (check == 4) {
						i++;
						check = m[1].charAt(i);
						if (!m[1].charAt(i + 1)) {
							if (check > 4) {
								res = parseInt(m[0]) + 1;
							} else if (check < 4) {
								res = parseInt(m[0]);
							}
							check = false;
						}
					}
					if (check > 4) {
						res = parseInt(m[0]) + 1;
					} else {
						res = parseInt(m[0]);
					}
				}
			} else {
				if (check > 4) {
					res = parseInt(m[0]) + 1;
				} else {
					res = parseInt(m[0]);
				}
			}
		} else {
			res = m[0];
		}
		res = res / 100;

//	alert(x + ' -> ' + res.toFixed(2));

		return parseFloat(res.toFixed(2));
	}

	function objLen(obj)
	{
		var ret = 0;
		for (var i in obj) {
			ret++;
		}
		return ret;
	}

	function getRandom(num)
	{
		num || (num = 8);
		var rvalue = 0;
		var max = 0;
		var min = 0;
		var str = "";
		var range = [{min:65, max:90}, {min:97, max:122}, {min:48, max:57}];
		var index = Math.floor(Math.random() * 2);
		for (var i = 0; i < num; ++i) {
			min = range[index].min;
			max = range[index].max;
			rvalue = min + Math.floor(Math.random() * (max + 1 - min));
			str += String.fromCharCode(rvalue);
			index = Math.floor(Math.random() * 3);
		}
		return str;
	}

	function maxValue(arr)
	{
		var res = 'start';
		for (var i = 0; i < arr.length; i++) {
			if (res == 'start' || res < arr[i]) {
				res = arr[i];
			}
		}
		return res;
	}

	function minValue(arr, zero)
	{
		var undef;
		if (typeof(undef) == typeof(zero)) {
			zero = false;
		}
		var res = 'start';
		for (var i = 0; i < arr.length; i++) {
			if (res == 'start' || (res > arr[i] && (arr[i] != 0 || zero))) {
				res = arr[i];
			}
		}
		return res;
	}

	function var_dump(obj, level)
	{
		var out = '';
		var space = '';
		var under;
		if (typeof(level) == typeof(under)) {
			level = 1;
		}
		for (var i = 0; i < level; i++) {
			space += ' ';
		}
		for (var id in obj) {
			if (typeof obj[id] == 'object') {
				out += space + 'OBJECT "' + id + '"' + " \n " + var_dump(obj[id], ++level);
			} else if (typeof obj[id] == 'function') {
				out += space + 'function' + "\n";
			} else {
				out += space + id + ' = ' + obj[id] + "\n";
			}
		}
		return out + "\n";
	}

	function debug()
	{
/**/
		return false;

/* Becouse of facebook intergation crushes

		if (top.document.location.href.match(/\?debug/)) {
			return true;
		} else {
			return false;
		}
/**/
	}

	function ksort(array)
	{
		var tmp = [];
		var res = [];
		for (var k = 0; k < array.length; k++) {
			tmp.push(k);
		}
		tmp.sort(sort_number);
		for (var k = 0; k < tmp.length; k++) {
			res[tmp[k]] = array[tmp[k]];
		}
		return res;
	}

	function sort_number(a, b)
	{
		return a - b
	}

if ( typeof($) != 'function' ) {
	$ = function(element)
	{
/*
		return document.getElementById(element);
/**/
		if (arguments.length > 1) {
			for (var i = 0, elements = [], length = arguments.length; i < length; i++)
				elements.push($(arguments[i]));
			return elements;
		}
		if (Object && Object.isString) {
			if (Object.isString(element))
				element = document.getElementById(element);
			return Element.extend(element);
		} else {
			return document.getElementById(element);
		}
/*
		if (!element) {
			return null;
		}
		if (element.htmlElement) {
			return Garbage.collect(element);
		}
		if ([window, document].contains(element)) {
			return element;
		}
		var type = $type(element);
		if (type == "string") {
			element = document.getElementById(element);
			type = element ? "element" : false;
		}
		if (type != "element") {
			return null;
		}
		if (element.htmlElement) {
			return Garbage.collect(element);
		}
		if (["object", "embed"].contains(element.tagName.toLowerCase())) {
			return element;
		}
		$extend(element, Element.prototype);
		element.htmlElement = function () {};
		return Garbage.collect(element);
/**/
	}
}

	function llt_unique_repeat(str, number)
	{
		var ret = '';
		for (i = 1; i <= number; i++) {
			ret += str;
		}
		return ret;
	}

	function getStageSize(str)
	{
		var x,y;
		if (self.innerHeight) {
			x = self.innerWidth;
			y = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) {
			x = document.documentElement.clientWidth;
			y = document.documentElement.clientHeight;
		} else if (document.body) {
			x = document.body.clientWidth;
			y = document.body.clientHeight;
		}
		if (str == 'x') {
			return parseInt(x);
		} else if (str == 'y') {
			return parseInt(y);
		} else {
			return [parseInt(x), parseInt(y)];
		}
	}

	function join(sep, array)
	{
		var ret = '';
		if (isArray(array)) {
			for (var i = 0; i < array.length; i++) {
				if (ret != '') {
					ret += sep;
				}
				ret += array[i];
			}
			return ret;
		} else {
			return false;
		}
	}

	function isArray(obj)
	{
		if (obj.length || obj.length === 0) {
			return true;
		} else {
			return false;
		}
	}

	function in_array(value, array)
	{
		var ret = false;
		for (var i = 0; i < array.length; i++) {
			if (array[i] == value) {
				ret = true;
			}
		}
		return ret;
	}

	function myParseFloat(num)
	{
		num = parseFloat(num) + '';
		var tmp = num.split('.');
		if (tmp[1]) {
			var ceil = tmp[0];
			tmp = tmp[1] + '';
			if (tmp.length == 1) {
				num = num + '0';
			} else if (tmp.length > 2) {
				var first = tmp.charAt(0);
				var second = tmp.charAt(1);
				var third = tmp.charAt(2);
				if (third >= 5) {
					if (second == '9') {
						if (first == '9') {
							ceil++;
							first = '0';
						} else {
							first++;
						}
						second = '0';
					} else {
						second++;
					}
				}
				num = ceil + '.' + first + second;
			}
		} else {
			num = num + '.00';
		}
		return num;
	}

	function setLoadEvent(func)
	{
		if (/WebKit/i.test(navigator.userAgent)) {
			 _timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					clearInterval(_timer);
					delete _timer;
					func();
				}
			}, 10);
		} else {
			window.onload = func;
		}
	}



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// big_int.js ///////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



	var biRadixBase = 2;
	var biRadixBits = 16;
	var bitsPerDigit = biRadixBits;
	var biRadix = 1 << 16; // = 2^16 = 65536
	var biHalfRadix = biRadix >>> 1;
	var biRadixSquared = biRadix * biRadix;
	var maxDigitVal = biRadix - 1;
	var maxInteger = 9999999999999998; 
	var maxDigits;
	var ZERO_ARRAY;
	var bigZero, bigOne;
	function setMaxDigits(value)
	{
		maxDigits = value;
		ZERO_ARRAY = new Array(maxDigits);
		for (var iza = 0; iza < ZERO_ARRAY.length; iza++) ZERO_ARRAY[iza] = 0;
		bigZero = new BigInt();
		bigOne = new BigInt();
		bigOne.digits[0] = 1;
	}
	setMaxDigits(20);
	var dpl10 = 15;
	var lr10 = biFromNumber(1000000000000000);
	function BigInt(flag)
	{
		if (typeof flag == "boolean" && flag == true) {
			this.digits = null;
		} else {
			this.digits = ZERO_ARRAY.slice(0);
		}
		this.isNeg = false;
	}
	function biFromDecimal(s)
	{
		var isNeg = s.charAt(0) == '-';
		var i = isNeg ? 1 : 0;
		var result;
		while (i < s.length && s.charAt(i) == '0') ++i;
		if (i == s.length) {
			result = new BigInt();
		} else {
			var digitCount = s.length - i;
			var fgl = digitCount % dpl10;
			if (fgl == 0) fgl = dpl10;
			result = biFromNumber(Number(s.substr(i, fgl)));
			i += fgl;
			while (i < s.length) {
				result = biAdd(biMultiply(result, lr10),
							   biFromNumber(Number(s.substr(i, dpl10))));
				i += dpl10;
			}
			result.isNeg = isNeg;
		}
		return result;
	}
	function biCopy(bi)
	{
		var result = new BigInt(true);
		result.digits = bi.digits.slice(0);
		result.isNeg = bi.isNeg;
		return result;
	}
	function biFromNumber(i)
	{
		var result = new BigInt();
		result.isNeg = i < 0;
		i = Math.abs(i);
		var j = 0;
		while (i > 0) {
			result.digits[j++] = i & maxDigitVal;
			i >>= biRadixBits;
		}
		return result;
	}
	function reverseStr(s)
	{
		var result = "";
		for (var i = s.length - 1; i > -1; --i) {
			result += s.charAt(i);
		}
		return result;
	}
	var hexatrigesimalToChar = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
	function biToString(x, radix)
	{
		var b = new BigInt();
		b.digits[0] = radix;
		var qr = biDivideModulo(x, b);
		var result = hexatrigesimalToChar[qr[1].digits[0]];
		while (biCompare(qr[0], bigZero) == 1) {
			qr = biDivideModulo(qr[0], b);
			digit = qr[1].digits[0];
			result += hexatrigesimalToChar[qr[1].digits[0]];
		}
		return (x.isNeg ? "-" : "") + reverseStr(result);
	}
	function biToDecimal(x)
	{
		var b = new BigInt();
		b.digits[0] = 10;
		var qr = biDivideModulo(x, b);
		var result = String(qr[1].digits[0]);
		while (biCompare(qr[0], bigZero) == 1) {
			qr = biDivideModulo(qr[0], b);
			result += String(qr[1].digits[0]);
		}
		return (x.isNeg ? "-" : "") + reverseStr(result);
	}
	var hexToChar = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
	function digitToHex(n)
	{
		var mask = 0xf;
		var result = "";
		for (i = 0; i < 4; ++i) {
			result += hexToChar[n & mask];
			n >>>= 4;
		}
		return reverseStr(result);
	}
	function biToHex(x)
	{
		var result = "";
		var n = biHighIndex(x);
		for (var i = biHighIndex(x); i > -1; --i) {
			result += digitToHex(x.digits[i]);
		}
		return result;
	}
	function charToHex(c)
	{
		var ZERO = 48;
		var NINE = ZERO + 9;
		var littleA = 97;
		var littleZ = littleA + 25;
		var bigA = 65;
		var bigZ = 65 + 25;
		var result;
		if (c >= ZERO && c <= NINE) {
			result = c - ZERO;
		} else if (c >= bigA && c <= bigZ) {
			result = 10 + c - bigA;
		} else if (c >= littleA && c <= littleZ) {
			result = 10 + c - littleA;
		} else {
			result = 0;
		}
		return result;
	}
	function hexToDigit(s)
	{
		var result = 0;
		var sl = Math.min(s.length, 4);
		for (var i = 0; i < sl; ++i) {
			result <<= 4;
			result |= charToHex(s.charCodeAt(i))
		}
		return result;
	}
	function biFromHex(s)
	{
		var result = new BigInt();
		var sl = s.length;
		for (var i = sl, j = 0; i > 0; i -= 4, ++j) {
			result.digits[j] = hexToDigit(s.substr(Math.max(i - 4, 0), Math.min(i, 4)));
		}
		return result;
	}
	function biFromString(s, radix)
	{
		var isNeg = s.charAt(0) == '-';
		var istop = isNeg ? 1 : 0;
		var result = new BigInt();
		var place = new BigInt();
		place.digits[0] = 1;
		for (var i = s.length - 1; i >= istop; i--) {
			var c = s.charCodeAt(i);
			var digit = charToHex(c);
			var biDigit = biMultiplyDigit(place, digit);
			result = biAdd(result, biDigit);
			place = biMultiplyDigit(place, radix);
		}
		result.isNeg = isNeg;
		return result;
	}
	function biDump(b)
	{
		return (b.isNeg ? "-" : "") + b.digits.join(" ");
	}
	function biAdd(x, y)
	{
		var result;
		if (x.isNeg != y.isNeg) {
			y.isNeg = !y.isNeg;
			result = biSubtract(x, y);
			y.isNeg = !y.isNeg;
		}
		else {
			result = new BigInt();
			var c = 0;
			var n;
			for (var i = 0; i < x.digits.length; ++i) {
				n = x.digits[i] + y.digits[i] + c;
				result.digits[i] = n & 0xffff;
				c = Number(n >= biRadix);
			}
			result.isNeg = x.isNeg;
		}
		return result;
	}
	function biSubtract(x, y)
	{
		var result;
		if (x.isNeg != y.isNeg) {
			y.isNeg = !y.isNeg;
			result = biAdd(x, y);
			y.isNeg = !y.isNeg;
		} else {
			result = new BigInt();
			var n, c;
			c = 0;
			for (var i = 0; i < x.digits.length; ++i) {
				n = x.digits[i] - y.digits[i] + c;
				result.digits[i] = n & 0xffff;
				if (result.digits[i] < 0) result.digits[i] += biRadix;
				c = 0 - Number(n < 0);
			}
			if (c == -1) {
				c = 0;
				for (var i = 0; i < x.digits.length; ++i) {
					n = 0 - result.digits[i] + c;
					result.digits[i] = n & 0xffff;
					if (result.digits[i] < 0) result.digits[i] += biRadix;
					c = 0 - Number(n < 0);
				}
				result.isNeg = !x.isNeg;
			} else {
				result.isNeg = x.isNeg;
			}
		}
		return result;
	}
	function biHighIndex(x)
	{
		var result = x.digits.length - 1;
		while (result > 0 && x.digits[result] == 0) --result;
		return result;
	}
	function biNumBits(x)
	{
		var n = biHighIndex(x);
		var d = x.digits[n];
		var m = (n + 1) * bitsPerDigit;
		var result;
		for (result = m; result > m - bitsPerDigit; --result) {
			if ((d & 0x8000) != 0) break;
			d <<= 1;
		}
		return result;
	}
	function biMultiply(x, y)
	{
		var result = new BigInt();
		var c;
		var n = biHighIndex(x);
		var t = biHighIndex(y);
		var u, uv, k;
		for (var i = 0; i <= t; ++i) {
			c = 0;
			k = i;
			for (j = 0; j <= n; ++j, ++k) {
				uv = result.digits[k] + x.digits[j] * y.digits[i] + c;
				result.digits[k] = uv & maxDigitVal;
				c = uv >>> biRadixBits;
			}
			result.digits[i + n + 1] = c;
		}
		result.isNeg = x.isNeg != y.isNeg;
		return result;
	}
	function biMultiplyDigit(x, y)
	{
		var n, c, uv;
		result = new BigInt();
		n = biHighIndex(x);
		c = 0;
		for (var j = 0; j <= n; ++j) {
			uv = result.digits[j] + x.digits[j] * y + c;
			result.digits[j] = uv & maxDigitVal;
			c = uv >>> biRadixBits;
		}
		result.digits[1 + n] = c;
		return result;
	}
	function arrayCopy(src, srcStart, dest, destStart, n)
	{
		var m = Math.min(srcStart + n, src.length);
		for (var i = srcStart, j = destStart; i < m; ++i, ++j) {
			dest[j] = src[i];
		}
	}
	var highBitMasks = new Array(0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800, 0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0, 0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF);
	function biShiftLeft(x, n)
	{
		var digitCount = Math.floor(n / bitsPerDigit);
		var result = new BigInt();
		arrayCopy(x.digits, 0, result.digits, digitCount,
				  result.digits.length - digitCount);
		var bits = n % bitsPerDigit;
		var rightBits = bitsPerDigit - bits;
		for (var i = result.digits.length - 1, i1 = i - 1; i > 0; --i, --i1) {
			result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) |
							   ((result.digits[i1] & highBitMasks[bits]) >>>
								(rightBits));
		}
		result.digits[0] = ((result.digits[i] << bits) & maxDigitVal);
		result.isNeg = x.isNeg;
		return result;
	}
	var lowBitMasks = new Array(0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF);
	function biShiftRight(x, n)
	{
		var digitCount = Math.floor(n / bitsPerDigit);
		var result = new BigInt();
		arrayCopy(x.digits, digitCount, result.digits, 0,
				  x.digits.length - digitCount);
		var bits = n % bitsPerDigit;
		var leftBits = bitsPerDigit - bits;
		for (var i = 0, i1 = i + 1; i < result.digits.length - 1; ++i, ++i1) {
			result.digits[i] = (result.digits[i] >>> bits) |
							   ((result.digits[i1] & lowBitMasks[bits]) << leftBits);
		}
		result.digits[result.digits.length - 1] >>>= bits;
		result.isNeg = x.isNeg;
		return result;
	}
	function biMultiplyByRadixPower(x, n)
	{
		var result = new BigInt();
		arrayCopy(x.digits, 0, result.digits, n, result.digits.length - n);
		return result;
	}
	function biDivideByRadixPower(x, n)
	{
		var result = new BigInt();
		arrayCopy(x.digits, n, result.digits, 0, result.digits.length - n);
		return result;
	}
	function biModuloByRadixPower(x, n)
	{
		var result = new BigInt();
		arrayCopy(x.digits, 0, result.digits, 0, n);
		return result;
	}
	function biCompare(x, y)
	{
		if (x.isNeg != y.isNeg) {
			return 1 - 2 * Number(x.isNeg);
		}
		for (var i = x.digits.length - 1; i >= 0; --i) {
			if (x.digits[i] != y.digits[i]) {
				if (x.isNeg) {
					return 1 - 2 * Number(x.digits[i] > y.digits[i]);
				} else {
					return 1 - 2 * Number(x.digits[i] < y.digits[i]);
				}
			}
		}
		return 0;
	}
	function biDivideModulo(x, y)
	{
		var nb = biNumBits(x);
		var tb = biNumBits(y);
		var origYIsNeg = y.isNeg;
		var q, r;
		if (nb < tb) {
			if (x.isNeg) {
				q = biCopy(bigOne);
				q.isNeg = !y.isNeg;
				x.isNeg = false;
				y.isNeg = false;
				r = biSubtract(y, x);
				x.isNeg = true;
				y.isNeg = origYIsNeg;
			} else {
				q = new BigInt();
				r = biCopy(x);
			}
			return new Array(q, r);
		}
		q = new BigInt();
		r = x;
		var t = Math.ceil(tb / bitsPerDigit) - 1;
		var lambda = 0;
		while (y.digits[t] < biHalfRadix) {
			y = biShiftLeft(y, 1);
			++lambda;
			++tb;
			t = Math.ceil(tb / bitsPerDigit) - 1;
		}
		r = biShiftLeft(r, lambda);
		nb += lambda;
		var n = Math.ceil(nb / bitsPerDigit) - 1;
		var b = biMultiplyByRadixPower(y, n - t);
		while (biCompare(r, b) != -1) {
			++q.digits[n - t];
			r = biSubtract(r, b);
		}
		for (var i = n; i > t; --i) {
			var ri = (i >= r.digits.length) ? 0 : r.digits[i];
			var ri1 = (i - 1 >= r.digits.length) ? 0 : r.digits[i - 1];
			var ri2 = (i - 2 >= r.digits.length) ? 0 : r.digits[i - 2];
			var yt = (t >= y.digits.length) ? 0 : y.digits[t];
			var yt1 = (t - 1 >= y.digits.length) ? 0 : y.digits[t - 1];
			if (ri == yt) {
				q.digits[i - t - 1] = maxDigitVal;
			} else {
				q.digits[i - t - 1] = Math.floor((ri * biRadix + ri1) / yt);
			}

			var c1 = q.digits[i - t - 1] * ((yt * biRadix) + yt1);
			var c2 = (ri * biRadixSquared) + ((ri1 * biRadix) + ri2);
			while (c1 > c2) {
				--q.digits[i - t - 1];
				c1 = q.digits[i - t - 1] * ((yt * biRadix) | yt1);
				c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2);
			}

			b = biMultiplyByRadixPower(y, i - t - 1);
			r = biSubtract(r, biMultiplyDigit(b, q.digits[i - t - 1]));
			if (r.isNeg) {
				r = biAdd(r, b);
				--q.digits[i - t - 1];
			}
		}
		r = biShiftRight(r, lambda);
		q.isNeg = x.isNeg != origYIsNeg;
		if (x.isNeg) {
			if (origYIsNeg) {
				q = biAdd(q, bigOne);
			} else {
				q = biSubtract(q, bigOne);
			}
			y = biShiftRight(y, lambda);
			r = biSubtract(y, r);
		}
		if (r.digits[0] == 0 && biHighIndex(r) == 0) r.isNeg = false;
		return new Array(q, r);
	}
	function biDivide(x, y)
	{
		return biDivideModulo(x, y)[0];
	}
	function biModulo(x, y)
	{
		return biDivideModulo(x, y)[1];
	}
	function biMultiplyMod(x, y, m)
	{
		return biModulo(biMultiply(x, y), m);
	}
	function biPow(x, y)
	{
		var result = bigOne;
		var a = x;
		while (true) {
			if ((y & 1) != 0) result = biMultiply(result, a);
			y >>= 1;
			if (y == 0) break;
			a = biMultiply(a, a);
		}
		return result;
	}
	function biPowMod(x, y, m)
	{
		var result = bigOne;
		var a = x;
		var k = y;
		while (true) {
			if ((k.digits[0] & 1) != 0) result = biMultiplyMod(result, a, m);
			k = biShiftRight(k, 1);
			if (k.digits[0] == 0 && biHighIndex(k) == 0) break;
			a = biMultiplyMod(a, a, m);
		}
		return result;
	}



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// barrett.js ///////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



	function BarrettMu(m)
	{
		this.modulus = biCopy(m);
		this.k = biHighIndex(this.modulus) + 1;
		var b2k = new BigInt();
		b2k.digits[2 * this.k] = 1;
		this.mu = biDivide(b2k, this.modulus);
		this.bkplus1 = new BigInt();
		this.bkplus1.digits[this.k + 1] = 1;
		this.modulo = BarrettMu_modulo;
		this.multiplyMod = BarrettMu_multiplyMod;
		this.powMod = BarrettMu_powMod;
	}
	function BarrettMu_modulo(x)
	{
		var q1 = biDivideByRadixPower(x, this.k - 1);
		var q2 = biMultiply(q1, this.mu);
		var q3 = biDivideByRadixPower(q2, this.k + 1);
		var r1 = biModuloByRadixPower(x, this.k + 1);
		var r2term = biMultiply(q3, this.modulus);
		var r2 = biModuloByRadixPower(r2term, this.k + 1);
		var r = biSubtract(r1, r2);
		if (r.isNeg) {
			r = biAdd(r, this.bkplus1);
		}
		var rgtem = biCompare(r, this.modulus) >= 0;
		while (rgtem) {
			r = biSubtract(r, this.modulus);
			rgtem = biCompare(r, this.modulus) >= 0;
		}
		return r;
	}
	function BarrettMu_multiplyMod(x, y)
	{
		var xy = biMultiply(x, y);
		return this.modulo(xy);
	}
	function BarrettMu_powMod(x, y)
	{
		var result = new BigInt();
		result.digits[0] = 1;
		var a = x;
		var k = y;
		while (true) {
			if ((k.digits[0] & 1) != 0) result = this.multiplyMod(result, a);
			k = biShiftRight(k, 1);
			if (k.digits[0] == 0 && biHighIndex(k) == 0) break;
			a = this.multiplyMod(a, a);
		}
		return result;
	}



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// rsa.js ///////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



	function RSAKeyPair(encryptionExponent, decryptionExponent, modulus)
	{
		this.e = biFromHex(encryptionExponent);
		this.d = biFromHex(decryptionExponent);
		this.m = biFromHex(modulus);
		this.chunkSize = 2 * biHighIndex(this.m);
		this.radix = 16;
		this.barrett = new BarrettMu(this.m);
	}
	function twoDigit(n)
	{
		return (n < 10 ? "0" : "") + String(n);
	}
	function encryptedString(key, s)
	{
		var a = new Array();
		var sl = s.length;
		var i = 0;
		while (i < sl) {
			a[i] = s.charCodeAt(i);
			i++;
		}
		while (a.length % key.chunkSize != 0) {
			a[i++] = 0;
		}
		var al = a.length;
		var result = "";
		var j, k, block;
		for (i = 0; i < al; i += key.chunkSize) {
			block = new BigInt();
			j = 0;
			for (k = i; k < i + key.chunkSize; ++j) {
				block.digits[j] = a[k++];
				block.digits[j] += a[k++] << 8;
			}
			var crypt = key.barrett.powMod(block, key.e);
			var text = key.radix == 16 ? biToDecimal(crypt) : biToString(e_string, key.radix); 
			result += text + " ";
		}
		return result.substring(0, result.length - 1);
	}
	function decryptedString(key, s)
	{
		var blocks = s.split(" ");
		var result = "";
		var i, j, block;
		for (i = 0; i < blocks.length; ++i) {
			var bi;
			if (key.radix == 16) {
				bi = biFromHex(blocks[i]);
			} else {
				bi = biFromString(blocks[i], key.radix);
			}
			block = key.barrett.powMod(bi, key.d);
			for (j = 0; j <= biHighIndex(block); ++j) {
				result += String.fromCharCode(block.digits[j] & 255, block.digits[j] >> 8);
			}
		}
		if (result.charCodeAt(result.length - 1) == 0) {
			result = result.substring(0, result.length - 1);
		}
		return result;
	}



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
