// based on json2 (http://www.JSON.org/json2.js)
if (!self.JSON)
(function(){

// Format integers to have at least two digits.
function f (n) { return n < 10 ? '0' + n : n }

var myName = 'JSON', Me = self[myName] = {},
escapeable = /[\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff\\]/g,
Object_hasOwnProperty = Object.hasOwnProperty,
Array_constructor = Array,
Date_constructor = Date,
String_constructor = String,
meta =
{
	'\b': '\\b',
	'\t': '\\t',
	'\n': '\\n',
	'\f': '\\f',
	'\r': '\\r',
	'"' : '\\"',
	'\\': '\\\\'
}

function quoteReplacer (a)
{
	return meta[a] || '\\u' + ('0000' + (+(a.charCodeAt(0))).toString(16)).slice(-4)
}

function quote (string)
{
	escapeable.lastIndex = 0
	return '"' + string.replace(escapeable, quoteReplacer) + '"'
}


function str (value)
{
	var i, k, v, length, partial, partial_length
	
	switch (typeof value)
	{
		case 'string':
			return quote(value)
		
		case 'number':
		case 'boolean':
		case 'null':
			return String_constructor(value)
		
		case 'undefined':
			return 'null'
		
		// Objects
		case 'object':
			// Null
			if (!value)
				return 'null'
			
			// Array
			partial = []
			if (value.constructor === Array_constructor)
			{
				length = value.length
				if (length === 0)
					return '[]'
				
				for (i = 0; i < length; i += 1)
					partial[i] = str(value[i])
				
				return '[' + partial.join(',') + ']'
			}
			
			// Date
			if (value.constructor === Date_constructor)
				return value.getUTCFullYear() + '-' +
						f(value.getUTCMonth() + 1) + '-' +
						f(value.getUTCDate()) + 'T' +
						f(value.getUTCHours()) + ':' +
						f(value.getUTCMinutes()) + ':' +
						f(value.getUTCSeconds()) + 'Z';
			
			// Plain object
			partial_length = 0
			for (k in value)
				if (Object_hasOwnProperty.call(value, k))
					partial[partial_length++] = quote(k) + ':' + str(value[k])
			
			return partial_length === 0 ? '{}' : '{' + partial.join(',') + '}'
	}
}

function parse (text)
{
	parse.lastError = null
	var harmful = text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
					  .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
					  .replace(/(?:^|:|,)(?:\s*\[)+/g, '')
					  .replace(/[\],:{}\s]+/g, '')
	
	if (harmful == '')
	{
		return eval('(' + text + ')')
	}
	
	throw new SyntaxError('harmful JSON: "' + harmful + '"')
}

Me.stringify = str
Me.parse = parse
	
})();

;(function(){

function Me ()
{
	this.state = 'init'
	this.listeners = []
}

Me.prototype =
{
	ready: function (f)
	{
		var state = this.state
		
		if (state == 'ready')
		{
			setTimeout(f, 0)
			return
		}
		
		this.listeners.push(f)
		
		if (state == 'init')
			this.bind()
	},
	
	bind: function (e)
	{
		this.onready()
	},
	
	onready: function ()
	{
		this.state = 'ready'
		
		var listeners = this.listeners
		for (var i = 0; i < listeners.length; i++)
			setTimeout(listeners[i], 0)
		
		this.listeners.length = 0
	},
	
	encode: function (k)
	{
		return ('x' + k).replace(/\W/g, function (m) { return '-' + m.charCodeAt(0) + '-' })
	},
	
	decode: function (k)
	{
		return k.substr(1).replace(/-(\d+)-/g, function (m, a) { return String.fromCharCode(a) })
	}
}

Me.staticMethods =
{
	backends: [],
	addBackend: function (o)
	{
		this.backends.push(o)
	},
	
	guess: function ()
	{
		// try to init one backend by one
		for (var i = 0; i < this.backends.length; i++)
		{
			var backend = new this.backends[i]
			if (backend.init())
				return backend
		}
	}
}

Object.extend(Me, Me.staticMethods)

Me.className = 'ClientStorage'
self[Me.className] = Me

})();
;(function(){

var Papa = ClientStorage

function Me ()
{
	Papa.call(this)
	this.constructor = Me
}

Me.prototype = new Papa()

Me.methods =
{
	init: function ()
	{
		return this.data = window.localStorage
	},
	
	get: function (k)
	{
		// https://bugs.webkit.org/show_bug.cgi?id=30996
		// to not to interfere with data method names
		k = 'x' + k
		return this.data.getItem(k)
	},
	
	set: function (k, v)
	{
		k = 'x' + k
		this.data.setItem(k, '' + v)
		return v
	},
	
	remove: function (k)
	{
		k = 'x' + k
		var data = this.data
		
		var v = data.getItem(k)
		data.removeItem(k)
		return v
	},
	
	length: function ()
	{
		return this.data.length
	},
	
	keys: function ()
	{
		var data = this.data
		
		var keys = []
		for (var i = 0, il = data.length; i < il; i++)
			keys[i] = data.key(i).substr(1)
		
		return keys
	},
	
	clear: function ()
	{
		this.data.clear()
	}
}

Object.extend(Me.prototype, Me.methods)

Me.className = 'LocalStorage'
Papa[Me.className] = Me

Papa.addBackend(Me)

})();
;(function(){

var Papa = ClientStorage

function Me ()
{
	Papa.call(this)
	this.constructor = Me
}

Me.prototype = new Papa.LocalStorage()

Me.methods =
{
	init: function ()
	{
		var data = window.globalStorage
		return this.data = data && data[location.hostname]
	},
	
	get: function (k)
	{
		k = 'x' + k
		var v = this.data.getItem(k)
		return v === null ? null : v.value
	},
	
	remove: function (k)
	{
		k = 'x' + k
		var data = this.data
		
		var v = data.getItem(k)
		if (v !== null)
			v = v.value
		data.removeItem(k)
		return v
	},
	
	clear: function ()
	{
		var data = this.data
		
		for (var i = 0, il = data.length; i < il; i++)
			// get the first key at every iteration
			data.removeItem(data.key(0))
	}
}

Object.extend(Me.prototype, Me.methods)

Me.className = 'GlobalStorage'
Papa[Me.className] = Me

Papa.addBackend(Me)

})();
;(function(){

var Papa = ClientStorage

function Me ()
{
	Papa.call(this)
	this.constructor = Me
}

Me.prototype = new Papa()

var counter = 0

Me.methods =
{
	init: function ()
	{
		return !!document.body.addBehavior
	},
	
	proxySrc: '/favicon.ico',
	
	bind: function ()
	{
		var iframe = document.createElement('iframe')
		document.getElementsByTagName('head')[0].appendChild(iframe)
		iframe.id = 'client-storage-' + ++counter
		iframe.src = this.proxySrc
		
		var me = this
		iframe.onreadystatechange = function ()
		{
			if (iframe.readyState != 'complete')
				return
			
			me.node = iframe.contentWindow.document.body
			me.node.addBehavior("#default#userData")
			
			me.onready()
		}
	},
	
	load: function ()
	{
		var node = this.node
		node.load('client-storage')
		return node.XMLDocument.documentElement
	},
	
	save: function ()
	{
		this.node.save('client-storage')
	},
	
	get: function (k)
	{
		return this.load().getAttribute(this.encode(k))
	},
	
	set: function (k, v)
	{
		this.load().setAttribute(this.encode(k), '' + v)
		this.save()
		return v
	},
	
	remove: function (k)
	{
		k = this.encode(k)
		var data = this.load()
		
		var v = data.getAttribute(k)
		data.removeAttribute(k)
		this.save()
		return v
	},
	
	length: function ()
	{
		return this.load().attributes.length
	},
	
	keys: function ()
	{
		var keys = []
		
		var attributes = this.load().attributes
		for (var i = 0, il = attributes.length; i < il; i++)
			keys[i] = this.decode(attributes[i].name)
		
		return keys
	},
	
	clear: function (k)
	{
		var data = this.load(), attributes = data.attributes
		for (var i = 0, il = attributes.length; i < il; i++)
			// get the first key at every iteration
			data.removeAttribute(attributes[0].name)
		
		this.save()
	}
}

Object.extend(Me.prototype, Me.methods)

Me.className = 'UserData'
Papa[Me.className] = Me

Papa.addBackend(Me)

})();
;(function(){

var Papa = ClientStorage

function Me ()
{
	Papa.call(this)
	this.constructor = Me
}

Me.prototype = new Papa()

var counter = 0

Me.methods =
{
	init: function ()
	{
		return navigator.plugins && navigator.plugins['Shockwave Flash']
	},
	
	proxySrc: '/lib-0.3/modules/client-storage/flash-9/proxy.swf',
	
	bind: function ()
	{
		var div = document.createElement('div')
		var id = 'client-storage-' + ++counter
		div.innerHTML = '<object class="client-storage" id="' + id + '" name="' + id + '" data="' + this.proxySrc + '"></object>'
		var movie = div.firstChild
		document.body.appendChild(movie)
		
		var me = this
		movie.onready = function ()
		{
			me.data = movie
			me.onready()
		}
	},
	
	get: function (k)
	{
		var v = this.data.getItem(this.encode(k))
		return v === undefined ? null : this.decode(v)
	},
	
	set: function (k, v)
	{
		this.data.setItem(this.encode(k), this.encode(v))
		return v
	},
	
	remove: function (k)
	{
		var v = this.get(k)
		this.data.removeItem(this.encode(k))
		return v
	},
	
	length: function ()
	{
		return this.data.length()
	},
	
	keys: function ()
	{
		var keys = this.data.keys()
		for (var i = 0, il = keys.length; i < il; i++)
			keys[i] = this.decode(keys[i])
		return keys
	},
	
	clear: function (k)
	{
		this.data.clear()
	}
}

Object.extend(Me.prototype, Me.methods)

Me.className = 'Flash9'
Papa[Me.className] = Me

Papa.addBackend(Me)

})();

var clientStorage = ClientStorage.guess()
if (!clientStorage)
	throw new Error('no client storge was found')

;(function () {

var myName = 'GlobalTimer', Me =
{
	fps: 60,
	total: 0,
	id: 0,
	timers: {},
	timer: null,
	
	tick: function (d)
	{
		var timer, timers = this.timers
		for (var id in timers)
			if ((timer = timers[id]))
				timer(d)
	},
	
	add: function (callback)
	{
		// if was no timers
		if (this.total++ <= 0)
		{
			if (this.timer !== null)
				throw new Error(myName + '.timer had been broken')
			var me = this
			this.timer = setInterval(function (d) { me.tick(d) }, 1000 / this.fps)
		}
		
		this.timers[++this.id] = callback
		return this.id
	},
	
	remove: function (id)
	{
		if (id in this.timers)
		{
			var callback = this.timers[id]
			delete this.timers[id]
			
			// if have deleted last timer
			if (--this.total <= 0)
			{
				clearInterval(this.timer)
				this.timer = null
			}
			
			return callback
		}
	},
	
	clear: function (d)
	{
		var timers = this.timers
		for (var id in timers)
			this.remove(id)
	}
}

self[myName] = Me

})();

;(function () {

var M = Math, myName = 'Motion', globalTimer = GlobalTimer
function Me (begin, end, duration, motion, onstep, onstop)
{
	var me = this, frame = 0, total = M.ceil(duration * globalTimer.fps), delta = end - begin
	
	this.onstop = onstop
	this.onstep = onstep
	this.step = function ()
	{
		me.onstep(motion(frame, begin, delta, total))
		if (frame++ >= total)
			me.stop(true)
	}
	
}

Me.prototype =
{
	start: function ()
	{
		if (!this.running)
		{
			this.running = true
			globalTimer.remove(this.timer)
			this.timer = globalTimer.add(this.step) // step is already a prepared callback
		}
		return this
	},
	
	stop: function (comleted)
	{
		if (this.running)
		{
			this.running = false
			globalTimer.remove(this.timer)
			if (this.onstop)
				this.onstop(comleted)
		}
		return this
	}
}

self[myName] = Me

})();

// ============================================================================================
// Easing Equations v2.0
// September 1, 2003
// (c) 2003 Robert Penner, all rights reserved. 
// This work is subject to the terms in http://www.robertpenner.com/easing_terms_of_use.html.
// Ported to JavaScript by Peter Leonov for Liby.js motion ability
// ============================================================================================
Motion.types=
(function(){var M=Math,e=M.cos,f=M.sin,g=M.sqrt,h=M.PI,i=M.pow,j=M.abs,k=M.asin,l={directJump:function(t,b,c,d){return c},linearTween:function(t,b,c,d){return c*t/d+b},easeInQuad:function(t,b,c,d){return c*(t/=d)*t+b},easeOutQuad:function(t,b,c,d){return-c*(t/=d)*(t-2)+b},easeInOutQuad:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t+b:-c/2*((--t)*(t-2)-1)+b},easeInCubic:function(t,b,c,d){return c*(t/=d)*t*t+b},easeOutCubic:function(t,b,c,d){return c*((t=t/d-1)*t*t+1)+b},easeInOutCubic:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t*t+b:c/2*((t-=2)*t*t+2)+b},easeInQuart:function(t,b,c,d){return c*(t/=d)*t*t*t+b},easeOutQuart:function(t,b,c,d){return-c*((t=t/d-1)*t*t*t-1)+b},easeInOutQuart:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t*t*t+b:-c/2*((t-=2)*t*t*t-2)+b},easeInQuint:function(t,b,c,d){return c*(t/=d)*t*t*t*t+b},easeOutQuint:function(t,b,c,d){return c*((t=t/d-1)*t*t*t*t+1)+b},easeInOutQuint:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t*t*t*t+b:c/2*((t-=2)*t*t*t*t+2)+b},easeInSine:function(t,b,c,d){return-c*e(t/d*(h/2))+c+b},easeOutSine:function(t,b,c,d){return c*f(t/d*(h/2))+b},easeInOutSine:function(t,b,c,d){return-c/2*(e(h*t/d)-1)+b},easeInExpo:function(t,b,c,d){return(t==0)?b:c*i(2,10*(t/d-1))+b},easeOutExpo:function(t,b,c,d){return(t==d)?b+c:c*(-i(2,-10*t/d)+1)+b},easeInCirc:function(t,b,c,d){return-c*(g(1-(t/=d)*t)-1)+b},easeOutCirc:function(t,b,c,d){return c*g(1-(t=t/d-1)*t)+b},easeInOutCirc:function(t,b,c,d){return((t/=d/2)<1)?-c/2*(g(1-t*t)-1)+b:c/2*(g(1-(t-=2)*t)+1)+b},easeInBounce:function(t,b,c,d){return c-l.easeOutBounce(d-t,0,c,d)+b},easeInOutExpo:function(t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*i(2,10*(t-1))+b;return c/2*(-i(2,-10*--t)+2)+b},easeInElastic:function(t,b,c,d,a,p){if(t==0)return b;if((t/=d)==1)return b+c;if(!p)p=d*.3;if(a<j(c)){var s=p/4;a=c}else s=p/(2*h)*k(c/a);return-(a*i(2,10*(t-=1))*f((t*d-s)*(2*h)/p))+b},easeOutElastic:function(t,b,c,d,a,p){if(t==0)return b;if((t/=d)==1)return b+c;if(!p)p=d*.3;if(a<j(c)){var s=p/4;a=c}else s=p/(2*h)*k(c/a);return a*i(2,-10*t)*f((t*d-s)*(2*h)/p)+c+b},easeInOutElastic:function(t,b,c,d,a,p){if(t==0)return b;if((t/=d/2)==2)return b+c;if(!p)p=d*(.3*1.5);if(a<j(c)){a=c;var s=p/4}else s=p/(2*h)*k(c/a);if(t<1)return-.5*(a*i(2,10*(t-=1))*f((t*d-s)*(2*h)/p))+b;else return a*i(2,-10*(t-=1))*f((t*d-s)*(2*h)/p)*.5+c+b},easeInBack:function(t,b,c,d,s){if(s==null)s=1.70158;return c*(t/=d)*t*((s+1)*t-s)+b},easeOutBack:function(t,b,c,d,s){if(s==null)s=1.70158;return c*((t=t/d-1)*t*((s+1)*t+s)+1)+b},easeInOutBack:function(t,b,c,d,s){if(s==null)s=1.70158;if((t/=d/2)<1)return c/2*(t*t*(((s*=(1.525))+1)*t-s))+b;else return c/2*((t-=2)*t*(((s*=(1.525))+1)*t+s)+2)+b},easeOutBounce:function(t,b,c,d){if((t/=d)<(1/2.75))return c*(7.5625*t*t)+b;else if(t<(2/2.75))return c*(7.5625*(t-=(1.5/2.75))*t+.75)+b;else if(t<(2.5/2.75))return c*(7.5625*(t-=(2.25/2.75))*t+.9375)+b;else return c*(7.5625*(t-=(2.625/2.75))*t+.984375)+b},easeInOutBounce:function(t,b,c,d){if(t<d/2)return l.easeInBounce(t*2,0,c,d)*.5+b;else return l.easeOutBounce(t*2-d,0,c,d)*.5+c*.5+b}};return l})();
;(function () {

var M = Motion, myName = 'Animation'

function Me (node, motion, duration, trans, unit)
{
	this.node = node
	switch (typeof motion)
	{
		case 'string':
			var name = motion
			if (!(motion = M.types[name]))
				throw new Error('Unknown motion type name "' + name + '"')
			break
		case 'function':
			break
		default:
			throw new Error('Motion type must be a string or a function, got "' + typeof motion + '"')
	}
	this.motion = motion
	this.duration = duration
	this.trans = trans
	this.unit = unit
	this.running = false
	this.completed = 0
	this.motions = []
	
	var me = this
	function complete () { me.complete() }
	for (var i = 0; i < trans.length; i++)
	{
		var tr = trans[i]
		function bakeStep (tr)
		{
			return function (value)
			{
				Me.setStyleProperty(node, tr.property, value, unit)
			}
		}
		this.motions[i] = new M(tr.begin, tr.end, duration, motion, bakeStep(tr), complete)
	}
	
}

Me.defaults = {unit: 'px', motion: 'linearTween', duration: 1}

Element.prototype.animate = function (motion, props, duration, unit)
{
	var defaults = Me.defaults
	if (!motion)
		motion = defaults.motion
	if (!duration)
		duration = defaults.duration
	if (!unit)
		unit = defaults.unit
	
	var trans = []
	for (var k in props)
	{
		var prop = props[k]
		if (prop.length == 2)
			trans.push({property: k, begin: prop[0], end: prop[1]})
		else
			trans.push({property: k, begin: Me.getStyleProperty(this, k), end: prop[0] || prop})
	}
	return new Me(this, motion, duration, trans, unit).start()
}


Me.prototype =
{
	oncomplete: function () {},
	start: function ()
	{
		if (!this.running)
		{
			this.running = true
			
			var motions = this.motions
			for (var i = 0; i < motions.length; i++)
				motions[i].start()
		}
		return this
	},
	
	stop: function ()
	{
		if (this.running)
		{
			this.running = false
			
			var motions = this.motions
			for (var i = 0; i < motions.length; i++)
				motions[i].stop()
		}
		return this
	},
	
	complete: function ()
	{
		if (++this.completed >= this.motions.length)
			this.oncomplete()
	}
}

Me.getStyleProperty = function (node, p)
{
	if (p == "top" && !node.style[p])
		return node.offsetTop
	
	if (p == "left" && !node.style[p])
		return node.offsetLeft
	
	
	if (p == "opacity" && isNaN(parseFloat(node.style[p])))
		return 1
	
	
	if (/scroll/.test(p))
		return node[p]
	
	return parseFloat(node.style[p]) || 0
},

Me.setStyleProperty = function (node, p, value, unit)
{
	try
	{
		if (/color/.test(p))
			return node.style[p] = 'rgb(' + parseInt(value) + ',' + parseInt(value) + ',' + parseInt(value) + ')'
		
		// for SVG elements
		if (p == 'r')
			return node.r.baseVal.value = value
		
		
		if (/scroll/.test(p))
			return node[p] = Math.round(value)
		
		if (p == 'opacity')
			return node.style[p] = value
		
		if ((p == 'width' || p == 'height') && value < 0)
			value = 0
		
		if (unit == 'em')
			return node.style[p] = Math.round(value * 100) / 100 + unit
		
		return node.style[p] = Math.round(value) + unit
	}
	catch (ex)
	{
		log('setStyleProperty(' + arguments +'): ' + ex)
		return value
	}
}

self[myName] = Me

})();

function RollingImagesLite (node, conf)
{
	this.conf = {duration: 1, animationType: 'easeOutBack'}
	Object.extend(this.conf, conf)
	this.current = null
	this.mainNode = node
	this.mainNode.RollingImagesLite = this
	
	
	var t = this
	function mouseup (e)
	{
		e.preventDefault()
		clearInterval(t.svInt)
		document.removeEventListener('mouseup', mouseup, false)
	}
	
	this.prevmousedown = function (e)
	{
		e.preventDefault()
		clearInterval(t.svInt)
		t.goPrev()
		t.svInt = setInterval(function () { t.goPrev() }, t.conf.duration * 1000 * 0.5 + 150)
		document.addEventListener('mouseup', mouseup, false)
	}
	
	this.nextmousedown = function (e)
	{
		e.preventDefault()
		clearInterval(t.svInt)
		t.goNext()
		t.svInt = setInterval(function () { t.goNext() }, t.conf.duration * 1000 * 0.5 + 150)
		document.addEventListener('mouseup', mouseup, false)
	}
	
	this.sync()
	if (this.conf.goInit !== false)
		this.goInit()
}

RollingImagesLite.prototype =
{
	sync: function ()
	{
		this.viewport = this.my('viewport')[0]
		if (!this.viewport)
			throw new Error('Can`t find viewport for ' + this.mainNode)
		if (!this.viewport.animate)
			throw new Error('Viewport can`t be animated!')
		
		this.points = this.my('point')
		this.buttons = this.my('button')
		this.aPrev = this.my('prev')[0]
		this.aNext = this.my('next')[0]
		
		// if syncing when pushed
		clearInterval(this.svInt)
		
		var t = this
		if (this.aPrev)
			this.aPrev.addEventListener('mousedown', this.prevmousedown, false)
		
		if (this.aNext)
			this.aNext.addEventListener('mousedown', this.nextmousedown, false)
		
		for (var i = 0, il = this.buttons.length; i < il; i++)
			this.buttons[i].onmousedown = function (fi) { return function () { t.goToFrame(fi) } } (i)
		
		this.updateNavigation()
	},
	
	goPrev: function () { if (this.current > 0) this.goToFrame(this.current - 1) },
	goNext: function () { if (this.current < this.points.length - 1) this.goToFrame(this.current + 1) },
	my: function (cn) { return this.mainNode.getElementsByClassName(cn) },
	
	goInit: function ()
	{
		return this.goToFrame(0, 'directJump')
	},
	
	goToFrame: function (n, anim, dur) { return this.points ? this.goToNode(this.points[n || 0], anim, dur) : null },
	
	goToNode: function (node, anim, dur)
	{
		if (!node)
			return null
		
		if (this.mainNode.onjump)
			if (this.mainNode.onjump(node) === false)
				return null
		
		// change number of current node
		for (var i = 0, il = this.points.length; i < il; i++)
			if (this.points[i] == node)
				this.setCurrent(i)
		
		return this.animateTo(node.offsetLeft, node.offsetTop, anim, dur)
	},
	
	animateTo: function (left, top, anim, dur)
	{
		if (this.animation)
			this.animation.stop()
		return this.animation = this.viewport.animate(anim || this.conf.animationType, {scrollLeft: left, scrollTop: top}, dur || this.conf.duration)
	},
	
	jumpTo: function (left, top) { this.viewport.scrollLeft = left; this.viewport.scrollTop = top },
	jumpToFrame: function (n)
	{
		var node = this.points[n]
		if (node)
		{
			this.setCurrent(n)
			this.jumpTo(node.offsetLeft, node.offsetTop)
		}
	},
	
	updateNavigation: function ()
	{
		for (var i = 0, il = this.buttons.length; i < il; i++)
			this.buttons[i].removeClassName('selected-button')
		
		var button = this.buttons[this.current]
		if (button)
			button.addClassName('selected-button')
		
		if (this.aPrev)
		{
			if (this.current > 0)
				this.aPrev.removeClassName('disabled')
			else
				this.aPrev.addClassName('disabled')
		}
		
		if (this.aNext)
		{
			if (this.current < this.points.length - 1)
				this.aNext.removeClassName('disabled')
			else
				this.aNext.addClassName('disabled')
		}
	},
	
	setCurrent: function (num)
	{
    // if (num == this.current)
		//	return
		
		this.current = num
		this.updateNavigation()
		
		var cp = this.points[this.current]
		if (this.onselect)
			this.onselect(cp, this.current)
		
		if (cp && cp.onselect)
			cp.onselect()
	}
}


;(function(){

var myProto =
{
	childIndexedPath: function (node)
	{
		var path = []
		path:
		for (;;)
		{
			if (node == this)
				break
			
			var parent = node.parentNode
			if (!parent)
				return null
			
			var childs = parent.childNodes
			for (var i = 0, num = 0, il = childs.length; i < il; i++)
			{
				var child = childs[i]
				if (child == node)
				{
					path.push(num)
					node = parent
					continue path
				}
				// to provide a path useful cross browser lets count only the elements
				else if (child.nodeType == 1)
					num++
			}
			// we'r here if child node lied to us about its parentNode
			return null
		}
		
		return path.reverse()
	},
	
	getChildByIndexedPath: function (path)
	{
		var node = this
		path:
		for (var i = 0, il = path.length; node && i < il; i++)
		{
			var n = path[i],
				childs = node.childNodes
			// if there is no children this loop will be skipped
			// so we can not go too deep
			for (var j = 0, jl = childs.length; j < jl; j++)
			{
				var child = childs[j]
				if (child.nodeType == 1)
					if (n == 0)
					{
						node = child
						continue path
					}
					else
						n--
			}
			// we are here only if child num goes out of bounds
			return null
		}
		return node
	}
}

Object.add(Element.prototype, myProto)
Object.add(document, myProto)


})();
;(function(){

var myName = 'Cloner'

function Me ()
{
	this.nodes = {}
	this.constructor = Me
}

var O = Object

Me.prototype =
{
	bind: function (root, nodes)
	{
		this.root = root
		this.nodes = nodes
		
		this.sync()
		
		return this
	},
	
	sync: function ()
	{
		this.paths = this.getPaths(this.root, this.nodes)
	},
	
	getPaths: function (root, nodes)
	{
		function walk (hash)
		{
			var paths = {}
			for (var k in hash)
			{
				var v = hash[k]
				
				if (v.constructor == O)
				{
					paths[k] = walk(v)
					continue
				}
				
				paths[k] = root.childIndexedPath(v)
			}
			return paths
		}
		
		return walk(nodes)
	},
	
	getNodes: function (root, paths)
	{
		function walk (hash)
		{
			var nodes = {}
			for (var k in hash)
			{
				var v = hash[k]
				
				if (v.constructor == O)
				{
					nodes[k] = walk(v)
					continue
				}
				
				nodes[k] = root.getChildByIndexedPath(v)
			}
			return nodes
		}
		
		return walk(paths)
	},
	
	create: function ()
	{
		var root = this.root.cloneNode(true),
			nodes = this.getNodes(root, this.paths)
		
		return {root: root, nodes: nodes}
	}
}

// Me.mixIn(EventDriven)
Me.className = myName
self[myName] = Me

})();
;(function(){

var myName = 'Popup'

function Me ()
{
	this.nodes = {}
	this.listeners = {}
	this.constructor = Me
}

Me.prototype =
{
	visible: false,
	
	onhide: function () { this.hide() },
	
	bind: function (nodes)
	{
		this.nodes = nodes
		
		var me = this
		this.listeners.click = function (e) { me.onhide() }
		this.listeners.key = function (e) { if (e.keyCode == 27){ me.hide(); e.preventDefault() }}
		
		nodes.window.addEventListener('click', function (e) { e.stopPropagation() }, false)
		
		return this
	},
	
	hide: function ()
	{
		if (!this.visible)
			return
		
		this.nodes.root.hide()
		this.visible = false
		
		var me = this
		setTimeout(function () { me.unbindListeners() }, 0)
	},
	
	show: function ()
	{
		if (this.visible)
			return
		
		var nodes = this.nodes
		nodes.root.show()
		nodes.front.style.top = window.pageYOffset + 'px'
		this.visible = true
		
		var me = this
		setTimeout(function () { me.bindListeners() }, 0)
	},
	
	bindListeners: function ()
	{
		document.addEventListener('click', this.listeners.click, false)
		document.addEventListener('keydown', this.listeners.key, false)
	},
	
	unbindListeners: function ()
	{
		document.removeEventListener('click', this.listeners.click, false)
		document.removeEventListener('keydown', this.listeners.key, false)
	}
}

// Me.mixIn(EventDriven)
Me.className = myName
self[myName] = Me

})();
;(function(){

var myName = 'IngredientPopup'

eval(NodesShortcut.include())

var Super = Popup,
	superProto = Super.prototype

function Me ()
{
	Super.apply(this)
}

Me.prototype = new Super()

var myProto =
{
	setIngredient: function (ingredient)
	{
		this.ingredient = ingredient
		this.render()
	},
	
	show: function ()
	{
		Statistics.ingredientPopupOpened(this.ingredient)
		return superProto.show.apply(this, arguments)
	},
	
	render: function ()
	{
		var clone = this.cloner.create()
		this.popupRoot.appendChild(clone.root)
		
		var nodes = clone.nodes
		nodes.root = clone.root
		
		// implies this.nodes = nodes
		this.bind(nodes)
		
		this.renderData()
		
		var popup = this
		this.ingredient.loadLocalData(function () { popup.renderLocalData() })
		
		this.renderPlugins()
	},
	
	renderData: function ()
	{
		this.nodes.image.src = this.ingredient.getMainImageSrc()
	},
	
	renderLocalData: function ()
	{
		var nodes = this.nodes
		var ingredient = this.ingredient
		
		var brand = ingredient.brand
		if (brand)
		{
			nodes.mark.appendChild(T(ingredient.brand))
			nodes.ingredientWindow.addClassName('branded')
			nodes.brand.appendChild(T(ingredient.mark))
			nodes.brand.href = Ingredient.ingredientsLinkByMark(ingredient.mark)
		}
		
		nodes.name.appendChild(T(ingredient.name))
		
		this.renderAllCocktailsLink(ingredient)
		
		nodes.text.innerHTML = ingredient.about
	},
	
	renderPlugins: function ()
	{
		var nodes = this.nodes,
			ingredient = this.ingredient
		
		this.renderSupplements(ingredient)
		
		var me = this
		setTimeout(function () { me.renderCocktails(nodes, ingredient) }, 0)
		require('Good', function () { me.renderWhereToBuy(nodes, ingredient) })
	},
	
	renderAllCocktailsLink: function (ingredient)
	{
		var nodes = this.nodes
		
		var count = Cocktail.getByIngredient(ingredient.name).length
		if (count == 0)
		{
			nodes.allCocktails.hide()
			return
		}
		
		nodes.allCocktails.show()
		
		var link = nodes.allCocktailsLink
		link.href = ingredient.combinatorLink()
		
		link.appendChild(T(' ' + count + ' ' + count.plural('коктейль', 'коктейля', 'коктейлей')))
		if (ingredient.decls)
			link.appendChild(T(' ' + ingredient.decls.t))
	},
	
	renderSupplements: function (ingredient)
	{
		var nodes = this.nodes
		
		if (Cocktail.getByIngredient(ingredient.name).length < 6)
		{
			nodes.combinations.hide()
			return
		}
		
		var coefficients = Ingredient.defaultSupplementCoefficients()
		var supplements = Cocktail.getSupplementNamesByIngredientName(ingredient.name, coefficients)
		
		var list = nodes.combinationsList
		for (var i = 0, il = supplements.length; i < il && i < 5; i++)
		{
			var supplement = supplements[i]
			
			var item = Nc('li', 'combination')
			list.appendChild(item)
			
			var query = ingredient.name + ' + ' + supplement
			var a = Nct('a', 'link', query)
			a.href = '/combinator.html#q=' + encodeURIComponent(query)
			item.appendChild(a)
		}
	},
	
	renderWhereToBuy: function (nodes, ingredient)
	{
		var good = Good.getBySellName(ingredient.name)[0]
		if (good)
		{
			nodes.ingredientWindow.addClassName('can-buy')
			nodes.buy.appendChild(T(good.name))
			nodes.buy.href = good.getHref()
		}
	},
	
	renderCocktails: function (nodes, ingredient)
	{
		var cocktails = Cocktail.getByGood(ingredient.name)
		cocktails.randomize()
		
		var cl = new CocktailList()
		cl.bind(nodes.cocktails)
		cl.configure({pageLength: 5, pageVelocity: 38})
		cl.setCocktails(cocktails)
	}
}

Object.extend(Me.prototype, myProto)

var myStatic =
{
	bind: function (nodes)
	{
		this.cache = {}
		
		var proto = this.prototype
		
		proto.popupRoot = nodes.root
		
		var cloner = proto.cloner = new Cloner()
		cloner.bind(nodes.popupMain, nodes.popupParts)
	},
	
	show: function (ingredient)
	{
		this.hide()
		
		var popup = this.cache[ingredient.name]
		if (!popup)
		{
			popup = this.cache[ingredient.name] = new this()
			popup.setIngredient(ingredient)
		}
		
		popup.show()
		this.popup = popup
		return popup
	},
	
	hide: function ()
	{
		var popup = this.popup
		if (!popup)
			return
		
		popup.hide()
		this.popup = null
	},
	
	bootstrap: function ()
	{
		var nodes =
		{
			root: document.body,
			popupMain: $('ingredient-info-popup'),
			popupParts:
			{
				window: $$('#ingredient-info-popup .popup-window')[0],
				front: $$('#ingredient-info-popup .popup-front')[0],
				ingredientWindow: $$('#ingredient-info-popup .popup-window .ingredient-window')[0],
				image: $$('#ingredient-info-popup .description .image')[0],
				mark: $$('#ingredient-info-popup .description .about .mark')[0],
				brand: $$('#ingredient-info-popup .description .about .brand .link')[0],
				buy: $$('#ingredient-info-popup .description .about .where-to-buy .link')[0],
				name: $$('#ingredient-info-popup .description .about .name')[0],
				text: $$('#ingredient-info-popup .description .about .text')[0],
				allCocktails: $$('#ingredient-info-popup .description .about .all-cocktails')[0],
				allCocktailsLink: $$('#ingredient-info-popup .description .about .all-cocktails .link')[0],
				combinations: $$('#ingredient-info-popup .description .about .combinations')[0],
				combinationsList: $$('#ingredient-info-popup .description .about .combinations .list')[0],
				
				cocktails:
				{
					root: $$('#ingredient-info-popup .cocktail-list')[0],
					viewport: $$('#ingredient-info-popup .cocktail-list .viewport')[0],
					surface: $$('#ingredient-info-popup .cocktail-list .surface')[0],
					prev: $$('#ingredient-info-popup .cocktail-list .prev')[0],
					next: $$('#ingredient-info-popup .cocktail-list .next')[0]
				}
			}
		}
		
		this.bind(nodes)
	}
}

Object.extend(Me, myStatic)

Me.className = myName
self[myName] = Me

})();


Array.prototype.without = function(index) {
	var tmp = [];
	for(var i = 0; i < this.length; i++){
		if(i != index) tmp.push(this[i]);
	}
	return tmp;
}

function CalculatorModel(view){
	
	this.cartData = {};
	
	this.dataListeners = {
		listeners: [view],
		modelChanged: function(cartData, dontSave){
			for(var i = 0; i < this.listeners.length; i++){
				this.listeners[i].modelChanged(cartData, dontSave);
			}
		}
	};
	
	this.getCartSum = function(){
		var sum = 0;
		for(var name in this.cartData.goods){
			var bottles = this.cartData.goods[name].bottles;
			for(var id in bottles){
				sum += Math.roundPrecision(bottles[id].vol[1]*bottles[id].count,2);
			}
		}
		return sum;
	};
	
	this.addDataListener = function(listener){
		this.dataListeners.listeners.push(listener);
	};
	
	this.initialize = function(cartData) {
		// десериализация полученного от контроллера набора
		if(cartData) {	
			this.cartData = Calculator.deSerializeCartData(cartData);
		} else {
			this.cartData = {};
			this.cartData.cocktails = [];
			this.cartData.goods = {};
		}
		
		this.dataListeners.modelChanged(this.cartData, true);
	};
	
	this.addCocktail = function(name){
		var cocktail = Cocktail.getByName(name)
		Statistics.cocktailAddedToCalculator(cocktail)
		if(cocktail) {
			var cs = this.cartData.cocktails;
			var found = false;
			for(var i = 0; i < cs.length; i++) if(cs[i][0] == cocktail) found = cs[i];
			var cartCount = (cocktail.cart && cocktail.cart.count) || 10
			if (found) {
				found[1] += cartCount
			} else {
				cs.push([cocktail, cartCount]);
			}
			// Оптимизируем весь набор по емкостям
			this.cartData.goods = this.goodsByCocktails(this.cartData.cocktails);
			this.dataListeners.modelChanged(this.cartData);
		}
	};
	
	this.deleteCocktail = function(cocktail){
		var cs = this.cartData.cocktails;
		for(var i = 0; i < cs.length; i++){
			if(cs[i][0] == cocktail){
				this.cartData.cocktails.splice(i,1);
				// Оптимизируем весь набор по емкостям
				this.cartData.goods = this.goodsByCocktails(this.cartData.cocktails);
				this.dataListeners.modelChanged(this.cartData);
				break;
			}
		}
	};
	
	this.goodQuantityChanged = function(name, bottleId, quantity){
		var bottle = null;
		if(this.cartData.goods[name].bottles[bottleId]){
			bottle = this.cartData.goods[name].bottles[bottleId];
		} else { // дополнительная бутылка
			bottle = this.bottleByIngredientAndVolume(name, bottleId);
			this.cartData.goods[name].bottles[bottleId] = bottle;
		}
		if(quantity == 0 && (Object.keysCount(this.cartData.goods[name].bottles) > 1)) {
			delete this.cartData.goods[name].bottles[bottleId];
		} else bottle.count = quantity;
		this.countDiffs(name);
		this.dataListeners.modelChanged(this.cartData);
	};
	
	/**
	 * Высчитываем недостаточность или избыточность объема напитка, 
	 * отвечающего данному ингредиенту и проставляем значок "больше" или "меньше"
	 * той бутылке, чей объем лучше покрывает разницу
	 * @param name - название ингредиента
	 */
	this.countDiffs = function(name) {
		var bottles = this.cartData.goods[name].bottles;
		var sum_vol = 0;
		var vol_arr = []; // массив всех объемов
		for(id in bottles){
			sum_vol += bottles[id].vol[0] * bottles[id].count;
			vol_arr.push(bottles[id].vol);
			delete bottles[id].diff;
		}
		var diff = sum_vol - this.cartData.goods[name].dose;
		var vol = this.findClosestVol(vol_arr, Math.abs(diff));
		var target = this.cartData.goods[name].bottles[vol[0]];
		if(diff < 0 || Math.abs(diff) >= target.vol[0]) target.diff = diff;
	};
	
	/**
	 * Полностью меняются данные о напитке данного ингредиента
	 * (например, сразу о нескольких бутылках)
	 * @param item - элемент хэша cartData.goods с ключом name
	 * @param name - название ингредиента, так же - ключ в хэше cartData.goods
	 */
	this.goodItemChanged = function(item, name){
		for(id in item.bottles){
			if(item.bottles[id].count == 0 && (Object.keysCount(item.bottles) > 1)){
				delete item.bottles[id];
			}
		}
		this.cartData.goods[name] = item;
		this.countDiffs(name);
		this.dataListeners.modelChanged(this.cartData);
	};
	
	this.getNewBottle = function(name, bottleId){
		return this.bottleByIngredientAndVolume(name, bottleId);
	};

    this.getItemFromCart = function(name){
        return this.cartData.goods[name];
    };
	
	/**
	 * Поменялось количество коктейля в калькуляторе
	 * @param cocktail - коктейль, элемент глобального хэша коктейлей
	 * @param quantity - новое количество
	 */
	this.cocktailQuantityChanged = function(cocktail, quantity){
		var cs = this.cartData.cocktails;
		for(var i = 0; i < cs.length; i++){
			if((cs[i][0] == cocktail) && (cs[i][1] != quantity)) {
				this.cartData.cocktails[i][1] = quantity;
				// Оптимизируем весь набор по емкостям
				this.cartData.goods = this.goodsByCocktails(this.cartData.cocktails);
				this.dataListeners.modelChanged(this.cartData);
				break;
			}
		}
	};
	
	this.isIngredientPresent = function(name){
		return this.cartData.goods[name];
	};
	
	
	this.goodsByCocktails = function (cocktailsAndQuant)
	{
		var res = {}
		
		for (var i = 0, il = cocktailsAndQuant.length; i < il; i++)
		{
			var item = cocktailsAndQuant[i],
				cocktail = item[0],
				quantity = item[1]
			
			var parts = Ingredient.mergeIngredientSets(cocktail.ingredients, cocktail.garnish)
			
			for (var j = 0, jl = parts.length; j < jl; j++)
			{
				var part = parts[j],
					name = part[0],
					dose = part[1],
					unit = part[2]
				
				var ingredient = Ingredient.getByName(name)
				if (!ingredient)
					continue
				
				var sum = res[name]
				if (sum)
					sum.dose += dose * quantity
				else
				{
					res[name] =
					{
						good: ingredient,
						bottles: null,
						dose: dose * quantity
					}
				}
			}
		}
		
		// calculate bottles
		for (var name in res)
		{
			var item = res[name]
			item.bottles = this.countOptimal(item.dose, item.good.volumes)
		}
		
		return res;
	}
	
	function noop () {  }
	
	this.countOptimal = function(max_vol, volumes){
		var vols = [], costs = [];
		var j = 0;
		for(var i = 0; i < volumes.length; i++) {
			if(volumes[i][2]) {
				vols[j] = volumes[i][0];
				costs[j] = volumes[i][1];
				j++;
			}
		}
		
		var vol_index = 0,
		vols_length = vols.length,
		biggest = vols[0],
		
		// calculating long tail
		tail = max_vol % (biggest * 2),
		big_bottles_count = Math.round((max_vol - tail) / biggest),
		
		stack = [],
		min = Infinity,
		the_one = [],
		answer = {}
		
		function walk (summ_vol, summ_cost, vols_length, vols, costs)
		{
			for (var i = 0; i < vols_length; i++)
			{
				var cost = costs[i],
				vol = vols[i],
				now_cost = summ_cost + cost,
				now_vol = summ_vol + vol
				
				if (now_cost >= min)
					continue
				
				stack[stack.length] = vol
				if (now_vol >= tail)
				{
					min = now_cost
					the_one = stack.slice()
					// console.info(now_vol, now_cost, stack.slice())
				}
				else
					walk(now_vol, now_cost, vols_length, vols, costs)
				stack.length--
			}
		}
		
		if (tail)
			walk(0, 0, vols_length, vols, costs)
		
		for (var i = 0; i < the_one.length; i++)
			if (answer[the_one[i]]){
				answer[the_one[i]].count++;
			} else {
				answer[the_one[i]] = {};
				answer[the_one[i]].count = 1;
			}
		
		if (big_bottles_count)
		{
			if (answer[biggest]) {
				answer[biggest].count += big_bottles_count;
			} else {
				answer[biggest] = {};
				answer[biggest].count = big_bottles_count;
			}
		}
		
		for(var i = 0; i < volumes.length; i++){
			noop() // for FF <= 3.5.2 with jit on
			var volume = volumes[i], val = volume[0]
			if(answer[val])
				answer[val].vol = volume;
		}
		
		return answer
	}
	
	this.bottleByIngredientAndVolume = function(ingred, vol){
		var res = {};
		var volumes = Ingredient.getByName(ingred).volumes;
		for(var i = 0; i < volumes.length; i++){
			if(volumes[i][0] == vol) {
				res.vol = volumes[i];
				break;
			}
		}
		return res;
	}
	
	this.findClosestVol = function(volumes, dose){
		var closest_idx = 0;
		for(var i = 0; i < volumes.length; i++) {
			if((volumes[i][0] > volumes[closest_idx][0]) && volumes[i][2]) closest_idx = i;
		}
		for(var i = 0; i < volumes.length; i++){
			if(volumes[i][2]) { // в наличии
				var gap = volumes[i][0] - dose;
				var closestGap = volumes[closest_idx][0] - dose;
				if((gap >= 0) && (gap < closestGap)) closest_idx = i;
			}
		}
		return volumes[closest_idx];
	}
};

function spaces(num){
	var letters = (num + "").split("");
	var res = letters.splice(0, letters.length % 3).concat([" "]);
	while(letters.length > 0) {
		res = res.concat(letters.splice(0, 3));
		if(letters.length > 0) res = res.concat([" "]);
	}
	return res.join("");
}

/**
 * This function merges nodes from parentNode with nodes given in nodesArray.
 * It deletes nodes those aren't in nodesArray, appends noes those are'n in parentNode
 * and doesn't touch nodes those are in both parentNode and nodesArray
 * @param parentNode - destination node for merging result
 * @param nodesArray - new state of parentNode that could be made
 */

function mergeNodes(parent, nodes)
{
	var focused, children = Array.copy(parent.childNodes)
	for (var i = 0; i < children.length; i++)
	{
		var child = children[i]
		if (child.focused)
			focused = child
		else
			parent.removeChild(child)
	}
	
	var i = 0
	if (focused)
	{
		for (; i < nodes.length; i++)
		{
			var node = nodes[i]
			if (node == focused)
			{
				i++
				break
			}
			parent.insertBefore(node, focused)
		}
	}
	
	for (; i < nodes.length; i++)
	{
		var node = nodes[i]
		if (node == focused)
			break
		parent.appendChild(node)
	}
}


function CalculatorView() {
	this.ID_COCKTAILS   = 'cart_cocktails';
	this.ID_INGREDS     = 'cart_ingredients';
	this.ID_SUM         = 'cart_sum';
	this.ID_CONTENTS    = 'cart_contents';
	this.ID_TOTALS      = 'cart_totals';
	this.ID_DROP_TARGET = 'cart_draghere';
	this.ID_PRINT_PLAN  = 'print_plan';
	this.ID_PRINT_PLAN_INACTIVE  = 'print_plan_inactive';
	this.CLASS_ADD_BTN  = '.bt-want-slap';
	this.NAME_ELEM      = 'cocktail_name';
	
	this.KEY_LEFT  = 37;
	this.KEY_RIGHT = 39;
	this.KEY_ENTER = 13;
	this.KEY_ESC   = 27;
	this.KEY_TAB   = 9;
	this.IGNORED_KEYS = [this.KEY_LEFT, this.KEY_RIGHT, this.KEY_ESC, this.KEY_ENTER, this.KEY_TAB];
	
	this.eventListener = null; // controller
	
	this.cocktailName = $(this.NAME_ELEM) ? $(this.NAME_ELEM).innerHTML : null;
	this.addBtn = $$(this.CLASS_ADD_BTN) ? $$(this.CLASS_ADD_BTN)[0] : null;

	
	var self = this;
	if(this.addBtn) this.addBtn.addEventListener('mousedown', function(e){
		self.eventListener.addCocktail(self.cocktailName);
	}, false);
	
	var dropTarget = $(this.ID_DROP_TARGET);
	var dragAnimation;
	
	$(this.ID_DROP_TARGET).onDrop = function(cocktailName){
		self.eventListener.addCocktail(cocktailName);
		return true;
	};
	
	$(this.ID_DROP_TARGET).onDragEnd = function(){
		dragAnimation.stop();
		this.style.height = ''
	};
	
	$(this.ID_DROP_TARGET).onDragStart = function(element){
		var h = element.offsetHeight + 50
		if (h < 100)
			h = 100
		dragAnimation = this.animate("easeInCubic", {height: [dropTarget.offsetHeight, h]}, 0.15);
	};
	
	$(this.ID_CONTENTS).onDrop = function(cocktailName){
		self.eventListener.addCocktail(cocktailName);
	};
	
	if($('order_button')){
		$('order_button').addEventListener('click', function(e){
			if(!Calculator.checkSum("order")){
				alert("Минимальная сумма заказа составляет "+ Calculator.getMinSum("order") + ". Добавьте что-нибудь еще ;-)");
			} else window.location.href="/order.html";
		}, false);
	}
  
	if($('call_barmen')){
		$('call_barmen').addEventListener('click', function(e){
			// if(!Calculator.checkSum("call_barmen")){
			//  alert("Минимальная сумма заказа составляет "+ Calculator.getMinSum("call_barmen") + ". Добавьте что-нибудь еще ;-)");
			// } else {
        window.location.href="/bars/moskva/inshaker.html";
      // }
		}, false);
	}
	
	/**
	 * Событие, поступающее от модели в случае ее изменения
	 * @param cartData - набор данных калькулятора
	 * @param init - true, если это первый проход по MVC
	 */
	this.modelChanged = function(cartData, init){ // model
		var barName = clientStorage.get('barName')
		this.renderCart(cartData);
		if(!init) this.eventListener.saveCartData(cartData); //save to storage
	};
	
	this.renderCart = function(cartData){
		// FIXME: dirty fix for too early call for renderCart()
		if (!$(this.ID_CONTENTS).style)
			return
		
		if(cartData.cocktails.length > 0) {
			$(this.ID_CONTENTS).style.display = "block";
			$(this.ID_TOTALS).style.display = "block";
			$(this.ID_PRINT_PLAN).style.display = "inline";
			$(this.ID_PRINT_PLAN_INACTIVE).style.display = "none";
			$(this.ID_DROP_TARGET).style.display = "none";
			
			var cocktailsParent = $(this.ID_COCKTAILS);
			var ingredsParent = $(this.ID_INGREDS);
			var sumParent = $(this.ID_SUM);
			
			var newCocktails = []
			for(var i = 0; i < cartData.cocktails.length; i++){
				var ingredElem = this._createCocktailElement(cartData.cocktails[i]);
				newCocktails.push(ingredElem)
			}
			mergeNodes(cocktailsParent, newCocktails)
			
			var newIngredients = [], sum = 0, items = []
			for (var name in cartData.goods)
				items.push(cartData.goods[name])
			
			var compareByGroup = Ingredient.compareByGroup
			items.sort(function (a, b) { return compareByGroup(a.good, b.good) })
			for(var i = 0; i < items.length; i++)
			{
				var item = items[i],
					bottles = item.bottles
				
				for (var id in bottles)
				{
					var bottle = bottles[id]
					sum += bottle.vol[1] * bottle.count
					newIngredients.push(this._createIngredientElement(item, bottle, item.good.name))
				}
			}
			sum = Math.roundPrecision(sum,2)
			mergeNodes(ingredsParent, newIngredients);
			sumParent.innerHTML = spaces(sum) + " р.";
		} else { // empty
			$(this.ID_CONTENTS).style.display = "none";
			$(this.ID_TOTALS).style.display = "none";
			$(this.ID_PRINT_PLAN).style.display = "none";
			$(this.ID_PRINT_PLAN_INACTIVE).style.display = "inline";
			$(this.ID_DROP_TARGET).style.display = "block";
		}
	};
	
	var _createCocktailElementCache = {};
	this._createCocktailElement = function(cocktailsItem){
		var cocktail = cocktailsItem[0];
		var quantity = cocktailsItem[1];
		
		var cacheKey = cocktail.name_eng;
		
		
		var li, input, txt;
		if (_createCocktailElementCache[cacheKey])
		{
			li = _createCocktailElementCache[cacheKey];
			input = li.childsCache.input;
			txt = li.childsCache.txt;
		}
		else
		{
			var self = this;
			
			li = _createCocktailElementCache[cacheKey] = document.createElement("li");
			
			var a = document.createElement("a");
			a.href = "/cocktails/"+cocktail.name_eng.htmlName()+".html";
			a.innerHTML = cocktail.name;
			li.appendChild(a);
			var label = document.createElement("label");
			input = document.createElement("input");
			input.type = "text";
			input.name = "portion";
			input.id = "input_" + cocktail.name_eng.htmlName();
			txt = document.createTextNode("");
			label.appendChild(input);
			label.appendChild(txt);
			li.appendChild(label);
			var button = document.createElement("button");
			button.className = "bt-del";
			button.title = "Удалить";
			button.innerHTML = "×";
			li.appendChild(button);
			
			li.childsCache = {input: input, txt: txt};
			
			button.addEventListener('mousedown', function(e){
				self.eventListener.deleteCocktail(cocktail);
			}, false);
			
			input.addEventListener('keyup', function(e){
				if(self.checkKey(e.keyCode) && self.validateNumeric(this.value)) {
					self.eventListener.cocktailQuantityChanged(cocktail, parseInt(this.value));
				}
			}, false);
			input.addEventListener('focus', function (e) { li.focused = true }, false)
			input.addEventListener('blur', function (e) { li.focused = false }, false)
		}
		
		if(input.value != quantity)
			input.value = quantity || "";
		var plurals = (cocktail.cart && cocktail.cart.plural) || ['порция', 'порции', 'порций']
		txt.nodeValue = " " + quantity.plural.apply(quantity, plurals);
		
		return li;
	};
	
	var _createIngredientElementCache = {};
	this._createIngredientElement = function(item, bottle, name){
		var cacheKey = name + ':' + bottle.vol[0];
		var li;
		if(_createIngredientElementCache[cacheKey])
			li = _createIngredientElementCache[cacheKey];
		else
		{
			li = _createIngredientElementCache[cacheKey] = document.createElement("li");
			var a  = document.createElement("a");
			a.innerHTML = item.good.brand || name;
			li.appendChild(a);
			
			var b = document.createElement("b");
			var h = Units.humanizeDose(bottle.vol[0], item.good.unit)
			b.innerHTML = h[0] + ' ' + h[1]
			li.appendChild(b);
			
			var label = document.createElement("label");
			var input = document.createElement("input");
			input.type = "text";
			input.name = "portion";
            var txt = document.createTextNode("");
			label.appendChild(input);
			label.appendChild(txt);
			li.appendChild(label);
			
			var iNameInput = document.createElement("input");
			iNameInput.type = "hidden";
            iNameInput.name = "ingredName";
            iNameInput.value = name;
            label.appendChild(iNameInput);

            var button = document.createElement("button");
			button.title = "Инфо";
			button.hide();
			button.innerHTML = "i";
			li.appendChild(button);
			
			// fires goodQuantityChanged
			input.addEventListener('keyup', function(e){
				if(self.checkKey(e.keyCode) && self.validateNumeric(this.value)) {
					var bottleId = bottle.vol[0];
					self.eventListener.goodQuantityChanged(name, bottleId, parseInt(this.value));
				}
			}, false)
			
			function showPopup (e)
			{
				IngredientPopup.show(Ingredient.getByName(name))
			}
			a.addEventListener('click', showPopup, false)
			button.addEventListener('click', showPopup, false)
			
			input.addEventListener('focus', function (e) { li.focused = true }, false)
			input.addEventListener('blur', function (e) { li.focused = false }, false)
			
			li.childsCache = {input: input, button: button, txt: txt, a: a};
		}
		
		var childsCache = li.childsCache,
			input = childsCache.input,
			button = childsCache.button,
			txt = childsCache.txt,
			a = childsCache.a
		
		{
			
			
			if (bottle.count != input.value)
				input.value = bottle.count;
			txt.nodeValue = " " + spaces(Math.roundPrecision(bottle.vol[1]*bottle.count,2)) + " р."
			
			// red/green balloon
			if(bottle.diff){
				button.show()
				if (bottle.diff > 0) {
					button.className = "bt-more";
					button.title = "много";
				} else {
					button.className = "bt-less";
					button.title = "мало";
				}
			}
			else button.hide();
		}
		
		return li;
	};
	
	this.checkKey = function(keyCode){
		if(this.IGNORED_KEYS.indexOf(keyCode) > -1) return false;
		return true;
	};
	
	this.validateNumeric = function(txt){
		if(txt.match(/^\d+$/)) return true;
		return false;
	};
};

function CalculatorController(model, view) {
	this.eventListener = model;
	view.eventListener = this;
  
	this.initialize = function(){
		var self = this;
		clientStorage.ready(function(){
			if(clientStorage.get(Calculator.CART)){
				self.eventListener.initialize(JSON.parse(clientStorage.get(Calculator.CART)));
			} else self.eventListener.initialize(null);
		});
	};

  this.setBarName = function(name){
    clientStorage.set('barName', name)
  };
	
	this.addCocktail = function(name){
		this.eventListener.addCocktail(name);
	};
	
	this.deleteCocktail = function(cocktail){
		this.eventListener.deleteCocktail(cocktail);
	};
	
	this.cocktailQuantityChanged = function(cocktail, quantity){
		this.eventListener.cocktailQuantityChanged(cocktail, quantity);
	};
	
	this.goodQuantityChanged = function(name, bottleId, quantity){
		this.eventListener.goodQuantityChanged(name, bottleId, quantity);
	};
	
	this.goodItemChanged = function(item, name){
		this.eventListener.goodItemChanged(item, name);
	};
	
	/**
	 * Сериализация набора данных калькулятора и сохранение
	 */
	this.saveCartData = function(cartData)
	{
		var cd = Calculator.serializeCartData(cartData)
		clientStorage.set(Calculator.CART, JSON.stringify(cd))
	};
	
	this.needNewBottle = function(name, bottleId){
		return this.eventListener.getNewBottle(name, bottleId);
	};
	
    this.getItemFromCart = function(name){
        return this.eventListener.getItemFromCart(name);
    };

	// для синхронности
	this.initialize();
};


Math.roundPrecision = function($num, $precision) {
	if (isNaN($precision)) $precision = 0;
 	return Math.round(($num * Math.pow(10, $precision))) / Math.pow(10, $precision);
};

var Calculator = {	
	_MIN_CALL_BARMEN_SUM : 25000,
	_MIN_ORDER_SUM  : 3000,
	
	init: function(){
		this.view       = new CalculatorView();
		this.model      = new CalculatorModel(this.view);
		this.controller = new CalculatorController(this.model, this.view);
	},
	
	getSum: function(){
		return this.model.getCartSum();
	},
	
	checkSum: function(context){
		var minSum = this.getMinSum(context);
		return (this.getSum() > minSum);
	},
	
	getShopList: function(){
		return this.model.cartData;
	},
	
	addChangeListener: function(listener){
		this.model.addDataListener(listener);
	},
	
	getMinSum: function(context){
		var value = "_MIN_" + context.toUpperCase() + "_SUM";
		return this[value] || 0;
	},
	
	isIngredientPresent: function(name){
		return this.model.isIngredientPresent(name);
	},

    showPopup: function(ingredName){
        this.view.showPopup(ingredName);
    },

    addCocktail: function(cocktailName){
        this.controller.addCocktail(cocktailName);
    },
	
	CART : 'cart',
	
	serializeCartData: function (cd)
	{
		var cocktails = [],
			goods = {}
		
		for (var i = 0; i < cd.cocktails.length; i++)
			cocktails[i] = [cd.cocktails[i][0].name, cd.cocktails[i][1]]
		
		for (var k in cd.goods)
		{
			goods[k] =
			{
				bottles: cd.goods[k].bottles,
				dose: cd.goods[k].dose
			}
		}
		
		return {cocktails: cocktails, goods: goods}
	},
	
	deSerializeCartData: function (cartData)
	{
		var dataCocktails = cartData.cocktails,
			cocktails = []
		for (var i = 0; i < dataCocktails.length; i++)
		{
			var cocktail = Cocktail.getByName(dataCocktails[i][0])
			if (cocktail)
				cocktails.push([cocktail, dataCocktails[i][1]])
		}
		
		var gds = {}
		for (var name in cartData.goods)
		{
			var good = Ingredient.getByName(name)
			if (good)
			{
				gds[name] = cartData.goods[name]
				gds[name].good = good
			}
		}
		
		return {cocktails: cocktails, goods: gds}
	}
};


;(function(){

var litre =
[
	[0, 1,     1000, 'мл'],
	[1, 1000,  1,    'л']
]

var gramme =
[
	[0,       1000,       1,        'г'],
	[1000,    1000000,    0.001,    'кг'],
	[1000000, 1000000000, 0.000001, 'т']
]

var humans =
{
	'л': litre,
	'г': gramme
}


var Me =
{
	humanizeDose: function (vol, unit)
	{
		var h = humans[unit]
		if (!h)
			return [vol, unit, 1]
		
		for (var i = 0, il = h.length; i < il; i++)
		{
			var scale = h[i],
				k = scale[2]
			
			if (scale[0] <= vol && vol < scale[1])
				return [vol * k, scale[3], k]
		}
		
		return [vol, unit, 1]
	}
}

Me.className = 'Units'
self[Me.className] = Me

})();
;(function(){

/**
 * Get element's absolute position. Properly handles Safari's body scroll*.
 * 
 * @param e - element
 * @return {Object} position - x,y
 */
function getPosition (n)
{
	var x = 0, y = 0, p
	for (;;)
	{
		x += n.offsetLeft
		y += n.offsetTop
		if ((p = n.offsetParent))
		{
			x -= n.scrollLeft
			y -= n.scrollTop
			n = p
		}
		else
			break
	}
	
	return {x:x, y:y};
}

function hook (e)
{
	var node = e.target
	if (node.__draggable)
	{
		var opts = node.__draggable
		node.__draggable = null
		new Me(node, opts[0], opts[1]).dispatch(e)
	}
}
document.addEventListener('mousedown', hook, false)

/**
 * Класс, который позволяет сделать элемент перетаскиваемым (создается его клон)
 * В результате перетаскивания у элемента-цели срабатывает метод onDrop(name)
 * 
 * @param element который мы хотим таскать
 * @param name - имя или идентификатор этого элемента (или любые данные)
 * @param dropTargets - массив элементов-целей, на которые можно перетаскивать
 */
var Me = self.Draggable = function (element, name, dropTargets){
	this.STYLE_CURSOR = 'drag-cursor';
	
	this.dragObject = null;
	var self = this;
	
	function elementWaits(e) {
		e.preventDefault();
		var s = self.startMouse
		if (Math.abs(s.x - e.pageX) > 4 || Math.abs(s.y - e.pageY) > 4)
			beginDrag(e)
	}
	
	function elementMove(e){
		e.preventDefault();
		if(self.dragObject){
			var delta = self.delta, style = self.style
			style.left = (e.pageX + delta.x)  + "px";
			style.top  = (e.pageY + delta.y) + "px";
			return false;
		}
	}
	
	function mouseDown (e)
	{
		self.movements = 0
		self.startMouse = {x:e.pageX, y:e.pageY}
		e.preventDefault()
		document.addEventListener('mousemove', elementWaits, false);
	}
	element.addEventListener('mousedown', mouseDown, false)
	this.dispatch = mouseDown
	
	function beginDrag(e) {
		var node = self.dragObject = element.cloneNode(true);
		self.style = node.style
		node.addClassName("dragging-object")
		var startPos = getPosition(element)
		self.delta = {x: startPos.x - e.pageX, y: startPos.y - e.pageY}
		
		document.body.appendChild(node)
		// document.body.addClassName(self.STYLE_CURSOR);
		for(var i = 0; i < dropTargets.length; i++){
			if(dropTargets[i].onDragStart) dropTargets[i].onDragStart(element);
		}
		
		document.removeEventListener('mousemove', elementWaits, false);
		document.addEventListener('mousemove', elementMove, false);
		elementMove.apply(this, [e])
	}
	
	document.addEventListener('mouseup', function(e){
		document.removeEventListener('mousemove', elementMove, false);
		document.removeEventListener('mousemove', elementWaits, false);
		if(self.dragObject) {
			try
			{
				// FIXME: self.dragObject may be not appended to body
				document.body.removeChild(self.dragObject);
			}
			catch (ex) {}
			// document.body.remClassName(self.STYLE_CURSOR);
			
			// dropping
			for(var i = 0; i < dropTargets.length; i++){
				if(dropTargets[i].style.display == "block"){
					var targPos    = getPosition(dropTargets[i]);
					var targWidth  = parseInt(dropTargets[i].offsetWidth);
					var targHeight = parseInt(dropTargets[i].offsetHeight);
					
					if(
						(e.pageX > targPos.x)                &&
						(e.pageX < (targPos.x + targWidth))  &&
						(e.pageY > targPos.y)                &&
						(e.pageY < (targPos.y + targHeight))){
						if (dropTargets[i].onDrop(name) === true)
							break;
					}
				}
			}
			
			for(var i = 0; i < dropTargets.length; i++)
				if (dropTargets[i].onDragEnd)
					dropTargets[i].onDragEnd();
			
			self.dragObject = null;
		}
	}, false);
}

})();
;(function(){

var myName = 'Cookie', day = 864e5, doc = document,
	// encode = encodeURIComponent, decode = decodeURIComponent
	encode = escape, decode = unescape

if (!self[myName]) self[myName] =
{
	days: 30,
	path: '/',
	set: function (name, value, days, path)
	{
		var d = new Date()
		days = days || this.days
		path = path || this.path
		
		d.setTime(d.getTime() + day * days)
		doc.cookie = encode(name) + '=' + encode(this.stringify(value)) + '; expires=' + d.toGMTString() + '; path=' + path
		return value
	},
	
	get: function (name)
	{
		var value, cookie = new RegExp('(^|;)\\s*' + encode(name) + '=([^;\\s]*)').exec(doc.cookie)
		return cookie ? this.parse(decode(cookie[2])) : null
	},
	
	erase: function (name)
	{
		var cookie = this.get(name) || true
		this.set(name, '', -1)
		return cookie
	},
	
	keys: function ()
	{
		var cookie, keys = [], rex = new RegExp('(?:^|;)\\s*([^=]+)=[^;\\s]*', 'g')
		while (cookie = rex.exec(doc.cookie))
			keys.push(cookie[1])
		
		return keys
	},
	
	clear: function ()
	{
		var keys = this.keys()
		for (var i = 0; i < keys.length; i++)
			this.erase(keys[i])
	},
	
	stringify: function (value) { return value },
	parse: function (value) { return value }
}

})();
// ported from rutils.rb
;(function(){

var LOWER =
{
	"і":"i","ґ":"g","ё":"yo","№":"#","є":"e",
	"ї":"yi","а":"a","б":"b",
	"в":"v","г":"g","д":"d","е":"e","ж":"zh",
	"з":"z","и":"i","й":"y","к":"k","л":"l",
	"м":"m","н":"n","о":"o","п":"p","р":"r",
	"с":"s","т":"t","у":"u","ф":"f","х":"h",
	"ц":"ts","ч":"ch","ш":"sh","щ":"sch","ъ":"'",
	"ы":"yi","ь":"","э":"e","ю":"yu","я":"ya"
}

var UPPER =
{
	"Ґ":"G","Ё":"YO","Є":"E","Ї":"YI","І":"I",
	"А":"A","Б":"B","В":"V","Г":"G",
	"Д":"D","Е":"E","Ж":"ZH","З":"Z","И":"I",
	"Й":"Y","К":"K","Л":"L","М":"M","Н":"N",
	"О":"O","П":"P","Р":"R","С":"S","Т":"T",
	"У":"U","Ф":"F","Х":"H","Ц":"TS","Ч":"CH",
	"Ш":"SH","Щ":"SCH","Ъ":"'","Ы":"YI","Ь":"",
	"Э":"E","Ю":"YU","Я":"YA"
}

var undef, myName = 'RuTils', Me = self[myName] = 
{
	// Заменяет кириллицу в строке на латиницу. Немного специфично потому что поддерживает
	// комби-регистр (Щука -> Shuka)
	translify: function (str)
	{
		var res = []
		
		for (var i = 0; i < str.length; i++)
		{
			var c = str.charAt(i), r
			
			if ((r = UPPER[c]) !== undef)
			{
				if (LOWER[str.charAt(i+1)] !== undef)
					r = r.toLowerCase().capitalize()
			}
			else
			{
				r = LOWER[c]
				if (r === undef)
					r = c
			}
			
			res[i] = r
		}
		
		return res.join('')
	},
	
	dirify: function (s)
	{
		return s.translify()
				.replace(/(\s&\s)|(\s&amp;\s)/g, ' and ')
				.replace(/\W/g, ' ')
				.replace(/^_+|_+$/g, '')
				.replace(/^\s+|\s+$/g, '') // trim
				.translify() // yes, second
				.replace(/[\s\-]+/g, '-')
				.toLowerCase()
	}
}

// log("Щука".trans() == 'schuka')
// log("апельсиновый сок".trans() == 'apelsinovyiy-sok')

String.prototype.translify = function ()
{
	return Me.translify(this);
}

String.prototype.trans = function ()
{
	return Me.dirify(this);
}


})();


