// deep copy using JSON-like lib ;-)
function cloneObject(obj){
	return Object.parse(Object.stringify(obj));
}

function lengthOf(obj){
	var length = 0;
	for(prop in obj) length++;
	return length;
}

Array.prototype.uniq = function(){
	var tmp = [];
	for(var i = 0; i < this.length; i++){
		if(tmp.indexOf(this[i]) == -1) tmp.push(this[i]);
	}
	return tmp;
}


/**
 * Общие функции для компонентов, работающих с товарами (уровень view)
 */

Number.prototype.toFloatString = function(){
	if(this.toString() != parseInt(this)) return this.toString();
	return this + ".0";
}

var GoodHelper = {
	PATH_VOLUMES : '/i/merchandise/volumes/',
    CART : 'cart',

    serializeCartData: function(cd) {
        for(var i = 0; i < cd.cocktails.length; i++){
			// cocktail -> name
			cd.cocktails[i][0] = cd.cocktails[i][0].name;
		}
		for(ingred in cd.goods){
			cd.goods[ingred].good = null;
		}
        return cd;
    },

	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}
    },

    ingredientLink: function(ingred){
        return "/cocktails.html#state=byIngredients&ingredients=" + ingred;                
    },
     
	isBottled: function(good){
        if((good.volumes.length == 1) &&
            (good.unit == "шт") && (good.volumes[0][0] == 1)) return false;
        return true;
    },

    /**
	 * Возвращает числительное для данного объема
	 * @param vol - значение объема
	 * @param unit - единица измерения
	 */
	pluralTxt: function(vol, unit){
		if(unit == "кубики") return vol.plural("кубик", "кубика", "кубиков");
        if(unit == "штуки") return vol.plural("штука", "штуки", "штук");
        if(unit == "порция") return vol.plural("порция", "порции", "порций");
		return unit;
	},
	
    normalVolumeTxt: function(vol, unit){
        switch(unit){
            case "мл": if(vol >= 1000) { vol /= 1000; unit = "л";  }; break;
            case  "л": if(vol < 1)     { vol *= 1000; unit = "мл"; }; break;
              
            case "гр": if(vol >= 1000) { vol /= 1000; unit = "кг"; }; break;
            case "кг": if(vol < 1)     { vol *= 1000; unit = "гр"; }; break;
        }

        return vol + " " + unit;
    },

    normalVolumeTxtParsed: function(txt){
		var arr  = txt.match(/^(.+)\ (.+)/);
        return this.normalVolumeTxt(arr[1], arr[2]);
    },

	/**
	 * Возвращает предполагаемое название емкости
	 * для заданного ингредиента
	 * @param ingred - название ингредиента
	 * @param unit - единица измерения ингредиента
	 * @param {Number} vol - объем емкости
	 */
	bottleTxt: function(ingred, unit, vol){
		if(ingred == "Ред Булл") return "Банка ";
		if(ingred == "Кола") {
			if([0.33, 0.15].indexOf(vol) > -1) return "Банка ";
			return "Бутылка ";
		}
		if(unit == "л")     return "Бутылка ";
		if(ingred == "Лед") return "Пакетик ";
		return "";
	},
	
	/**
	 * Возвращает адрес картинки для товара по заданному
	 * названию ингредиента, объекту товара и элементу массива объемов товара
	 * @param name - название ингредиента
	 * @param good - объект товара
	 * @param vol - элемент массива объемов товара
	 */
	goodPicSrc: function(name, good, vol){
		if(!vol) { 
			var i = 0;
			while(!good.volumes[i][2]) i++;
			vol = good.volumes[i];
		}
		return this.PATH_VOLUMES + (good.brand ? good.brand.trans() : name.trans()) + "_" + vol[0].toFloatString().replace(".", "_") + "_big.png";
	},
	
	getIngredText: function(name){
		var brand = Ingredient.getByName(name).brand || "";
		if(brand.indexOf(name) > -1) name = "";
		var gap = "";
		if(brand && name) gap = " ";
		return name + (brand ? gap + brand : "");
	},
	
	shortName: function(name){
		if(name == "Черносмородиновый ликер") return "Черносмородин. ликер";
		return name;
	}
};

;(function(){

function noop () {  }

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


self.DataFilter = {
	good_paths: [],
	
	/**
	 * Подбор товаров и их емкостей под коктейли
	 * @param goods - хэш товаров
	 * @param cocktailsAndQuant - массив вида [[<cocktail1>, <quantity1>], [<cocktail2>, <quantity2>]]
	 * @return хэш (ключ - ингредиент), включающий для каждого эл-та товар, дозировку, бутылки
	 */
	goodsByCocktails: function(goods, cocktailsAndQuant){
		var res = {};
		for(var i = 0; i < cocktailsAndQuant.length; i++){
			var cocktail = cocktailsAndQuant[i][0];
			var quantity = cocktailsAndQuant[i][1];
			for(var j = 0; j < cocktail.ingredients.length; j++){
				
				var ingred = cocktail.ingredients[j][0];
				
				if(goods[ingred]) {
					var dose = this._parseDose(goods[ingred].unit, cocktail.ingredients[j][1]);
					if(!res[ingred]) {
						res[ingred] = {};
						res[ingred].good = goods[ingred];
						res[ingred].bottles = {};
						res[ingred].dose = 0;
					}
					res[ingred].dose += dose*quantity;
				}
			}
		}
		
		// предлагаем бутылки
		for(ingred in res){
			var dose = res[ingred].dose;
			var vols = res[ingred].good.volumes;
			res[ingred].bottles = this.countOptimal(dose, vols);
		}
		return res;
	},
	
	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
	},
	
	bottleByIngredientAndVolume: function(goods, ingred, vol){
		var res = {};
		var volumes = goods[ingred].volumes;
		for(var i = 0; i < volumes.length; i++){
			if(volumes[i][0] == vol) {
				res.vol = volumes[i];
				break;
			}
		}
		return res;
	},
	
	cocktailsByLetter: function (set, letter){
		var res = [];	
		var reg = new RegExp("^(" + letter.toUpperCase() + ")");
		for(var i = 0; i < set.length; i++) {
			if(set[i].name.match(reg)){
				res.push(set[i]);
			}
		}
		return res;
	},
	
	cocktailsByTag: function (set, tag) {
		var res = [];
		for(var i = 0; i < set.length; i++){
			if(set[i].tags.indexOf(tag) > -1){
				res.push(set[i]);
			}
		}
		return res;
	},
	
	cocktailsByStrength: function(set, strength) {
		var res = [];
		for(var i = 0; i < set.length; i++){
			if(set[i].strength == strength) {
				res.push(set[i]);
			}
		}
		return res;
	},
	
	cocktailsByIngredients: function(set, ingredients) {
		var res = [];
		for(var i = 0; i < set.length; i++) {
			var good = 0;
			for(var j = 0; j < set[i].ingredients.length; j++) {
				for(var k = 0; k < ingredients.length; k++){
					if(set[i].ingredients[j][0] == ingredients[k]) good++;
				}
			}
			if(good == ingredients.length) res.push(set[i]);
		}
		return res;
	},
	
	nameSort: function(a, b){
		if(a.name > b.name) return 1;
		else if(a.name == b.name) return 0;
		else return -1;
	},
	
	/**
	 * Нормализация объема относительно заданной единицы
	 * @param normUnit - заданная единица (напр., "л")
	 * @param txt - текст объема (напр., "15 мл")
	 * @return нормализованное значение (напр., 0.015)
	 */
	_parseDose: function(normUnit, txt){
		var arr = txt.match(/^(.+)\ (.+)/);
		var vol = arr[1];
		var unit = arr[2];
		if(unit == "мл" && normUnit == "л") return vol/1000;
		else if((unit.indexOf("кубик") > -1) && normUnit == "кубиков") return parseInt(vol);
		else if(unit == "шт" && normUnit == "шт") return this._parseDecimal(vol);
		else if(unit == "капли" && normUnit == "л") return vol/40000;
		else if(unit == normUnit) return parseFloat(vol);
	},
	
	/**
	 * Парсинг значений объема, заданных в виде дробей
	 * @param volume - например, "1/2"
	 * @return число типа float наподобие 0.5
	 */
	_parseDecimal: function(volume){
		if(volume.indexOf("/") > -1) return eval(volume);
		else return parseFloat(volume);
	},
	
	/**
	 * Нахождение подходящей емкости по заданному объему
	 * @param volumes массив объемов вида [<емкость>, <цена>, <наличие>]
	 * @param dose объем, под который ищем емкость
	 * @return volume элемент массива объемов
	 */
	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];
	}
}

})();
/**
 * Реализация постоянного хранилища данных
 *
 * На основе библиотеки Storage.js v1.1
 * (c) 2008, Ilya Kantor (http://browserpersistence.ru)
 */

Storage = {
    swfUrl: "/js/common/storage.swf",    
    init: function(onready) {                
       	var browser = navigator.userAgent;
		var rx = Programica.userAgentRegExps;
		if(rx.Gecko.test(browser)) this.globalStorage(onready);
		else if(rx.MSIE.test(browser)) this.userData(onready);
		else this.flash8(onready); 
    }
};

/**
 * HTML5 standard
 * @browsers Firefox 2+, MSIE 8
 */
Storage.globalStorage = function(onready) {
    var storage = globalStorage[location.hostname];
    Storage = {
        get:    function(key)        { return storage[key] ? String(storage[key]) : null; },
        put:    function(key, value) { storage[key] = value; },
        remove: function(key)        { delete storage[key]; },    
        clear:  function()           { for(i in storage) delete storage[i]; },

        getKeys: function() {
            var res = [];
            for(i in storage) res.push(i);
            return res;
        }
    }
    onready();
};

/**
 * @browsers MSIE 5+
 */
Storage.userData = function(onready) {
    var namespace = "data";

    if (!document.body.addBehavior) {            
        throw new Error("No addBehavior available");
    }

	var e = document.createElement("iframe");
	e.setAttribute('id', 'storageFrame');
	e.style.border = '0';
	e.style.width  = '0';
	e.style.height = '0';
	var iframe = document.body.appendChild(e);
	iframe.src='/js/common/proxy.html';
	
	iframe.addEventListener('load', function(e){
		var storage = iframe.contentWindow.document.getElementById('storageElement');
		storage.load(namespace);

	    Storage = {
	        get: function(key) {
	            return storage.getAttribute(key);
	        },

	        put: function(key, value) {
	            storage.setAttribute(key, value);
	            storage.save(namespace);
	        },

	        remove: function(key) {
	            storage.removeAttribute(key);
	            storage.save(namespace);
	        },

	        clear: function() {
	            var attrs = storage.XMLDocument.documentElement.attributes;

	            for(var i = 0; i < attrs.length; i++) storage.removeAttribute(attrs[i].name);
	            storage.save(namespace);
	        },

	        getKeys: function() {
	            var res = [];
	            var attrs = storage.XMLDocument.documentElement.attributes;

	            for(var i = 0; i < attrs.length; i++) res.push(attrs[i].name);
	            return res;
	        }
	    }
	    onready();
	}, false);
};

/**
 * Flash 8
 * @browsers any
 */
Storage.flash8 = function(onready) { 
    var movie = null;
    var swfId = "StorageMovie";
    
	while(document.getElementById(swfId)) swfId = '_' + swfId;
    
	var swfUrl = Storage.swfUrl;
    
    // first setup storage, make it ready to accept back async call
    Storage = {       
        get:    function(key)        { return movie.get(key)},
		put:    function(key, value) { movie.put(key, value); },
        remove: function(key)        { movie.remove(key); },
        clear:  function()           { movie.clear(); },
        
        getKeys: function() {
            return movie.getkeys();  // lower case in flash to evade ExternalInterface bug         
        },
        
        ready: function() {
            movie = document[swfId];
            onready();
        }
    }
    
    // embed flash into document
    var protocol = window.location.protocol == 'https' ? 'https' : 'http';
    var containerStyle = "width:0; height:0; position: absolute; z-index: 10000; top: -1000px; left: -1000px;";        
    var objectHTML = '<embed src="' + swfUrl + '" '
                              + ' bgcolor="#ffffff" width="0" height="0" '
                              + 'id="' + swfId + '" name="' + swfId + '" '
                              + 'swLiveConnect="true" '
                              + 'allowScriptAccess="sameDomain" '
                              + 'type="application/x-shockwave-flash" '
                              + 'pluginspage="' + protocol +'://www.macromedia.com/go/getflashplayer" '
                              + '></embed>';                    
    var div = document.createElement("div");
    div.setAttribute("id", swfId + "Container");
    div.setAttribute("style", containerStyle);
    div.innerHTML = objectHTML;
    document.body.appendChild(div);
};
    

;(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);
}

})();
/**
 * Cookie functionality. 
 * All cookies are saved with "path=/"
 */
var Cookie = {
  set: function(name, value) {
    return (document.cookie = escape(name) + '=' + escape(value || '') + "; path=/");
  },

  get: function(name) {
    var cookie = document.cookie.match(new RegExp('(^|;)\\s*' + escape(name) + '=([^;\\s]*)'));
    return (cookie ? unescape(cookie[2]) : null);
  },

  remove: function(name) {
    var cookie = Cookie.get(name) || true;
    Cookie.set(name, '', -1);
    return cookie;
  },
  
  getKeys: function(){
	var res = [];
	var arr = document.cookie.match(/(\w+)=[\w\d\S]*;*/ig);
	for(var i = 0; i < arr.length; i++){
		res.push(arr[i].split("=")[0])
	}
	return res;
  },
  
  clear: function(){
	var keys = Cookie.getKeys();
	for(var i = 0; i < keys.length; i++) Cookie.remove(keys[i]); 
  }
};

;(function(){

var myName = 'Theme'
var Me = self[myName] =
{
	initialize: function (db)
	{
		this.db = db
	},
	
	bind: function ()
	{
		var db = this.db
		
		for (var k in db)
		{
			var item = db[k]
			if (!item.href)
				continue
			
			var node = $(k)
			if (node)
			{
				node.href = item.href
			}
		}
	}
}



Me.initialize({
	"spotlighted":{"href":"/cocktails.html#state=byIngredients&marks=Angostura"},
	"branded-image":{"href":"/cocktails.html#state=byIngredients&marks=Angostura"}
})


})();

;(function(){ try {
	var m = /\btheme=(\d\d\d\d\.\d\d)/.exec(location.hash)
	if (m)
	{
		$('theme-stylesheet').href = '/t/theme/' + m[1] + '/theme.css'
		document.cookie = 'theme=' + m[1]// + '; expires=' + new Date()
	}
} catch (ex) {} })();


function CalculatorModel(view){
	var allGoods = Ingredient.getAllByNameHash()
	this.cartData = {};
	
	this.optimalGoods = {};
	
	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 = GoodHelper.deSerializeCartData(cartData);
		} else {
			this.cartData = {};
			this.cartData.cocktails = [];
			this.cartData.goods = {};
		}		
		this.optimalGoods = DataFilter.goodsByCocktails(allGoods, this.cartData.cocktails);
		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];
			if (found) {
				found[1] += 10
			} else {
				cs.push([cocktail, 10]); // сразу 10
			}
			// Оптимизируем весь набор по емкостям
			this.cartData.goods = DataFilter.goodsByCocktails(allGoods, this.cartData.cocktails);
			this.optimalGoods = cloneObject(this.cartData.goods);
			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 = DataFilter.goodsByCocktails(allGoods, this.cartData.cocktails);
				this.optimalGoods = cloneObject(this.cartData.goods);
				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 = DataFilter.bottleByIngredientAndVolume(allGoods, name, bottleId);
			this.cartData.goods[name].bottles[bottleId] = bottle;
		}
		if(quantity == 0 && (lengthOf(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 = DataFilter.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 && (lengthOf(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 DataFilter.bottleByIngredientAndVolume(allGoods, 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 = DataFilter.goodsByCocktails(allGoods, this.cartData.cocktails);
				this.optimalGoods = cloneObject(this.cartData.goods);
				this.dataListeners.modelChanged(this.cartData);
				break;
			}
		}
	};
	
	this.isIngredientPresent = function(name){
		return this.cartData.goods[name];
	};
};

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("");
}

function validateNumeric(txt){
	if(txt.match(/^\d+$/)) return true;
	return false;
};

/**
 * 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 insertChild(presentIngreds, parentNode, node)
{
    var insertedIngredient = Ingredient.getByName(node.getElementsByTagName("input")[1].value)
    var closestGap = Infinity
    var closestNode = null
    var sGap = null // signed

    for(var i = 0; i < presentIngreds.length; i++)
    {
        sGap = presentIngreds[i][1].listOrder() - insertedIngredient.listOrder()
        var gap = Math.abs(sGap)
        if(gap < closestGap)
        {
            closestGap = gap
            closestNode = presentIngreds[i][0] 
        }
    }
    if(sGap < 0) parentNode.insertBefore(node, closestNode)
    else if(closestNode) insertAfter(node, closestNode)
    else parentNode.appendChild(node)
}

function insertAfter(new_node, existing_node) 
{
    if (existing_node.nextSibling) 
        existing_node.parentNode.insertBefore(new_node, existing_node.nextSibling)
    else existing_node.parentNode.appendChild(new_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.INGRED_POPUP   = 'shop-cocktail';
	
	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.lastShownIngred = "";
	this.cocktailName = $(this.NAME_ELEM) ? $(this.NAME_ELEM).innerHTML : null;
	this.addBtn = cssQuery(this.CLASS_ADD_BTN) ? cssQuery(this.CLASS_ADD_BTN)[0] : null;
	this.itemFromPopup = [];

	
	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);
	}
 
  this.initBarChanger = function(barName) {
    var editing = false
    var nodes  = { bill: $('b-bill'), 
                   name: cssQuery("#b-bill .b-title h1")[0], 
                   edit: cssQuery("#b-bill .b-title label")[0], 
                   tip:  cssQuery("#b-bill .b-title small")[0],
                   input:cssQuery("#b-bill .b-title input")[0] } 

    var styles = { editing: 'editing-bar-name', unnamed: 'unnamed' }

    if(nodes.name && nodes.edit) {
        nodes.name.addEventListener('click', function(e) {
          this.hide()
          nodes.bill.addClassName(styles.editing)
          nodes.edit.show()
          editing = true
          e.stopPropagation()
          retainFocus()
        }, false)
        
        function retainFocus(){ setTimeout(function(){
          nodes.input.focus()
          nodes.input.value = nodes.input.value
        }, 2) }

        nodes.input.addEventListener('keyup', function(e) {
          if(e.keyCode == self.KEY_ENTER) finishEditing()
        }, false)
  
        document.body.addEventListener('click', function(e){
          if(editing && e.target != nodes.input) finishEditing()
        }, false)

        function finishEditing(){
          nodes.edit.hide()
          nodes.bill.remClassName(styles.editing)
          nodes.name.remClassName(styles.unnamed)
          nodes.name.innerHTML = nodes.input.value || nodes.tip.innerHTML
          nodes.name.show()
          editing = false
          self.eventListener.setBarName(nodes.name.innerHTML)
        }

        function checkEmptiness(){ setTimeout(function(){ 
          nodes.tip.setVisible(!nodes.input.value.length)
        }, 1) }

        nodes.input.addEventListener('keypress', checkEmptiness, false)
        nodes.input.addEventListener('keydown', checkEmptiness, false)
        
        if(barName) {
            nodes.name.innerHTML = barName
            nodes.input.value    = barName
            nodes.tip.hide()
        } else {
            nodes.name.innerHTML = "Назови свой бар"
            nodes.name.addClassName(styles.unnamed)
        }
    }
  }
  	
	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);
	}
	
	$('good_cancel').addEventListener('mousedown', function(e){
		$(self.INGRED_POPUP).hide();
	}, false);
	
	cssQuery("#shop-cocktail .opacity")[0].addEventListener('click', function(e){
		$(self.INGRED_POPUP).hide();
	}, false);

    document.documentElement.addEventListener('keyup', function(e){
        if(e.keyCode == self.KEY_ESC) $(self.INGRED_POPUP).hide();
    }, false);
	
    this.showPopup = function(ingred){
        $(this.INGRED_POPUP).show();
        this.renderPopup(this.eventListener.getItemFromCart(ingred), ingred);
    };
	
	/**
	 * Событие, поступающее от модели в случае ее изменения
	 * @param cartData - набор данных калькулятора
	 * @param init - true, если это первый проход по MVC
	 */
	this.modelChanged = function(cartData, init){ // model
		var barName = Storage.get('barName')
		this.renderCart(cartData);
		if(!init) this.eventListener.saveCartData(cartData); //save to storage
		else this.initBarChanger(barName)
	};
	
	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) + " р.";
			
			if(cartData.goods[this.lastShownIngred]) {
				this.renderPopup(cartData.goods[this.lastShownIngred], this.lastShownIngred);
			} 
		} 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 || "";
		txt.nodeValue = " " + quantity.plural("порция", "порции", "порций");
		
		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");
			b.innerHTML = GoodHelper.normalVolumeTxt(bottle.vol[0], item.good.unit);
			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)
			{
				self.renderPopup(item, name);
				$(self.INGRED_POPUP).show();
			}
			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;
	};
	
	var _createPopupIngredientElementCache = {};
	this._createPopupIngredientElement = function(item, bottle, volume, name, bottleId){
		var cacheKey = name + ':' + volume;
		
		var dl;
		if (_createPopupIngredientElementCache[cacheKey])
			dl = _createPopupIngredientElementCache[cacheKey]
		else
		{
				dl         = document.createElement("dl");
			var dt         = document.createElement("dt");
			var icon       = document.createElement("i");
			var a          = document.createElement("a");
			var dd         = document.createElement("dd");
			var strong     = document.createElement("strong");
			
			_createPopupIngredientElementCache[cacheKey] = dl
			
			icon.className = 'icon'
			
			a.innerHTML      = GoodHelper.bottleTxt(name, item.good.unit, volume[0]) + GoodHelper.normalVolumeTxt(volume[0], item.good.unit);
			strong.innerHTML = volume[1] + " р.";
			
			
			dl.appendChild(dt);
			dt.appendChild(icon);
			dt.appendChild(a);
			dl.appendChild(dd);
			dd.appendChild(strong);
		}
		
		return dl;
	};
	
	this.setPicture = function(name, good, vol){
		$('good_picture').src = GoodHelper.goodPicSrc(name, good, vol);
	};
	
	this.renderPopup = function(item, name){
		Statistics.ingredientPopupOpened(Ingredient.getByName(name))
		this.itemFromPopup = [cloneObject(item), name];
		item = cloneObject(item);
		
		
		$('good_name').innerHTML = item.good.brand || name;
		if(item.good.mark){ // branded
			$('good_composition').style.display = "block";
            $('good_mark').href = Ingredient.ingredientsLinkByMark(item.good.mark);
			$('good_mark').innerHTML = item.good.mark;
            var clicker = function(e) {
                window.location.href = this.href;
                window.location.reload();
            }
            $('good_mark').addEventListener('click', clicker, false);
			$('good_ingredient').innerHTML = name;
			$('good_ingredient').href = GoodHelper.ingredientLink(name);
            $('good_ingredient').addEventListener('click', clicker, false);
		} else $('good_composition').style.display = "none";
		
		$('good_desc').innerHTML = item.good.desc;
		$('good_picture').src = GoodHelper.goodPicSrc(name, item.good); 
		
		var summ = 0;
		var have = 0;
		
		this.lastShownIngred = name;
	};
	
	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;
		Storage.init(function(){
			if(Storage.get(GoodHelper.CART)){
				self.eventListener.initialize(Object.parse(Storage.get(GoodHelper.CART)));
			} else self.eventListener.initialize(null);
		});
	};

  this.setBarName = function(name){
    Storage.put('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 = cloneObject(cartData);
	    cd = GoodHelper.serializeCartData(cd);	
		Storage.put(GoodHelper.CART, Object.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);
    }
};


