/* $Id: shopping-cart.js 1014 2009-10-05 22:42:36Z sdalu $ */

/*
 * Copyright (c)  Stephane D'Alu  2009
 * http://www.sdalu.com/
 */

if (!Array.prototype.map) {
    Array.prototype.map = function(fun /*, thisp */) {
	var len = this.length;
	if (typeof fun != "function")
	    throw new TypeError();

	var res = new Array(len);
	var thisp = arguments[1];
	for (var i = 0; i < len; i++)
	    if (i in this)
		res[i] = fun.call(thisp, this[i], i, this);
	return res;
    };
}




Ext.namespace('ShoppingCart', 'ShoppingCart.Tpl');
var SC = ShoppingCart;




/*
 * Cart
 */
SC.Cart = function(config) {
    Ext.apply(this, config || {});

    var me = this;

    this.addEvents('update', 'delete', 'clear');

    this.reader = new Ext.data.JsonReader({
	storeId         : 'cart',
	root            : 'data',
	successProperty : 'success',
	totalProperty   : 'count',
	idProperty      : 'pk',
	fields        : [
	   'pk', 'reference', 'thumb', 'name', 'format', 'support', 'info',
            { name: 'quantity', type: 'int'   },
            { name: 'price',    type: 'float' } ] });
    this.writer = new Ext.data.JsonWriter({
	    listful       : true,
	    encode        : true,
	    writeAllFields: true
	});
    this.store =  new Ext.data.Store({
        url       : SC.REMOTE_URL,
	restful   : false,
	reader    : this.reader,
	writer    : this.writer,
       	autoSave  : false,
	listeners : {
	    load: function(store) {
                store.each(function(rec) {
		    me.fireEvent('update', rec); }); },
	    add : function(store, recs, idx) {
		Ext.each(recs, function(rec) {
		    me.fireEvent('update', rec); }); },
	    update : function(store, rec, op) {
		if (op == Ext.data.Record.EDIT)
		    me.fireEvent('update', rec); },
	    remove : function(store, rec, idx) {
		me.fireEvent('delete', rec); },
	    clear : function(store) {
	        me.fireEvent('clear'); }
	},
	getByItem : function(item) {
	    var idx = this.findBy(function(rec, id) {
	        return ((rec.get('reference') == item.reference) &&
			(rec.get('format')    == item.format   ) &&
			(rec.get('support')   == item.support  )); });
	    return this.getAt(idx); }
	});
};
Ext.extend(SC.Cart, Ext.util.Observable, {
    tpl_info      : new Ext.Template('({format} / {support})'),
    tpl_name      : new Ext.Template('N°{reference}'),
    url_thumbnail : function() { return null; },
    pricer        : function() { return null; },
    getCount      : function() { return this.store.getCount();   },    
    isEmpty       : function() { return this.getCount() == 0;    },
    getTotal      : function() {
        var cost = 0;
	this.store.each(function(rec) {
	    cost += rec.get('price') * rec.get('quantity'); });
	return cost; },
    getByRef      : function(ref) {
	return this.store.query('reference', ref); },
    load          : function(opt) {
	return this.store.load(opt);    },
    save          : function() {
	this.store.on('exception', 
	    function(proxy, type, action, options, res, arg) {
	        if (type == 'remote') alert(res.message);
                else                  alert('Unable to contact server');
		      }, this, { single: true });
        this.store.save();
	 },
    add           : function(item, info) {
	var rec = this.store.getByItem(item);
	if (rec) {
	    var quantity = rec.get('quantity') + 1;
	    rec.set('quantity', quantity);
	} else {
	    var price = this.pricer(item);
	    if (! price) throw new SC.Error('price_missing');
	    rec = new this.store.recordType({
		reference: item.reference,
		support  : item.support,
		format   : item.format,
		info     : this.tpl_info.apply(item),
		name     : this.tpl_name.apply(item),
		price    : price,
		thumb    : this.url_thumbnail(item.reference),
		quantity : 1 });
	    this.store.add(rec);
	} },
    remove        : function(item, info) {
	var rec, quantity;
	if ((! (rec = this.store.getByItem(item))) ||
	    ((quantity = rec.get('quantity')) <= 0))
	    return;
	rec.set('quantity', --quantity);
        },
    trash         : function(item, info) {
	var rec = this.store.getByItem(item);
	if (! rec) return;
	this.store.remove(rec);
    },
    empty         : function(info) {
	this.store.each(function(rec) { this.remove(rec); }, this.store);
    },
    order	  : function() {
        var winpar = "width=950,height=600,scrollbars,location=0,resizable,status=0";
        var params        = {
	    cmd		  : '_cart',
	    custom        : this.bank.origin,
	    invoice	  : (new Date).format('Y.m.d-H:i:s'),
	    upload	  : 1,
	    business	  : this.bank.id,
	    currency_code : 'EUR',
	    lc		  : 'FR',
	    handling_cart : SC.shipper(this) };

	if (this.bank.banner)
	    params.cpp_header_image = this.bank.banner;
	if (this.bank.notify)
	    params.notify_url       = this.bank.notify;


        var counter       = 1;
	this.store.each(function(rec) {
	    var price    = rec.get('price');
	    var quantity = rec.get('quantity');
	    if (quantity <= 0) return true;
	    params['item_name_'   + counter] = rec.get('support') + 
		' No' + rec.get('reference') + ' en ' + rec.get('format');
	  //params['item_number_' + counter] = rec.get('reference') + '/' +
	  //	rec.get('support') + '/' + rec.get('format');
	    params['quantity_'    + counter] = quantity;
	    params['amount_'      + counter] = price;
	    counter  += 1;
       });
	window.open ("https://www.paypal.com/cgi-bin/webscr?" +
		     Ext.urlEncode(params), "paypal", winpar);
    }
});




//== Templates =========================================================


SC.Tpl.cart_item = new Ext.XTemplate(
    '<div class="sc-cart-item" onselectstart="return false" ',
    //    '<tpl if="id">id="{id}" </tpl>',
          'sc-photo-ref="{reference}" ',
          'sc-buy-format="{format}" sc-buy-support="{support}">',
      '<table><tr>',
        '<td class="sc-thumbnail" rowspan="4">',
          '<tpl if="thumb"><img src="{thumb}" alt="img"/></tpl></td>',
        '<td class="sc-name" colspan="3">{name}</td>',
        '<td class="sc-panel" rowspan="4"><table>',
          '<tr><td class="sc-action sc-inc"><span>▲</span></td></tr>',
          '<tr><td class="sc-action sc-del"><span>x</span></td></tr>',
          '<tr><td class="sc-action sc-dec"><span>▼</span></td></tr>',
        '</table></td>',
      '</tr><tr>',
        '<td class="sc-info" colspan="3">{info}</td>',
      '</tr><tr class="sc-object">',
        '<td class="sc-quantity">{quantity}</td>',
        '<td class="sc-operator">x</td>',
        '<td class="sc-price">{price:eurMoney}</td>',
      '</tr><tr class="sc-cost">',
        '<td></td>',
        '<td class="sc-operator">=</td>',
        '<td class="sc-price">{[Ext.util.Format.eurMoney(values.quantity * values.price)]}</td>',
      '</tr></table>',
    '</div>');


SC.Tpl.buy_me = new Ext.XTemplate(
    '<div class="sc-buy">',
      '<div class="sc-buy-ref sc-action"><span>√</span></div>',
      '<div class="sc-buy-quick sc-action sc-inc"><span>+</span></div>',
      '<div class="sc-buy-panel">',
        '<div class="sc-title">Photo N°{reference}</div>',
        '<table class="sc-panel" onselectstart="return false">',
          '<tr><th>Format</th><th colspan="3">Quantité</th></tr>',
          '<tpl for="choices"><tr sc-buy-format="{format}" ',
                                 'sc-buy-support="{support}">',
            '<td class="sc-format">{format}</td>',
            '<td class="sc-action sc-dec">◀</td>',
            '<td class="sc-quantity">0</td>',
            '<td class="sc-action sc-inc">▶</td>',
          '</tr></tpl>',
        '</table>',
      '</div>',
    '</div>');

SC.Tpl.checkout_summary = new Ext.XTemplate(
    '<table class="sc-cart-checkout-summary">',
      '<tr class="sc-object">',
        '<td class="sc-description">',
          '<tpl if="quantity&gt;1">{quantity} objets en commande</tpl>',
          '<tpl if="quantity&lt;=1">{quantity} object en commande</tpl>',
        '</td>',
        '<td></td>',
        '<td class="sc-price">{basket:eurMoney}</td>',
      '</tr><tr class="sc-shipping">',
        '<td class="sc-description">Frais de livraison</td>',
        '<td class="sc-operator">+</td>',
        '<td class="sc-price">{shipping:eurMoney}</td>',
      '</tr><tr class="sc-cost">',
        '<td class="sc-description">Total</td>',
        '<td class="sc-operator">=</td>',
        '<td class="sc-price">{cost:eurMoney}</td>',
      '</tr>',
    '</table>');






SC.l10n = new Ext.data.ArrayStore({
    storeId : 'l10n',
    idIndex : 0,
    fields  : [ 'key', 'msg' ],
    get     : function(id) { return this.getById(id).get('msg'); },
    data    : [
       [ 'cart-is-empty', 'Le panier est vide' ],
       [ 'trash-ref-q', 'Supprimer toutes les commandes relatives à la photo N°{0}?' ],
       [ 'cart_empty_q',             'Vider le panier?' ],
       [ 'cart_confirm_q',           'Confirmer le panier?' ],
       [ 'oops',                     'Oups!' ],
       [ 'price_missing',            'L\'objet choisi n\'éxiste pas?!' ],
       [ 'provide_missing',          'L\'objet choisi n\'éxiste pas?!' ],
       [ 'provide_too_many',         'L\'objet choisi dispose de plusieurs prix?!' ],
       [ 'bug_cart_item_price',      'Impossible de trouver le prix unitaire de l\'objet dans le panier.' ] ]
    });





//== Class =============================================================

/*
 * Bug reporting
 */

SC.Error = function(id) { this.id = id; };

SC.oops = function(id) {
    var win = new ModalWin('sc-window-error');
    win.create(SC.l10n.get('oops'), SC.l10n.get(id));
    win.onHide = this.destroy;
    win.show();
};

SC.shipper       = function() { return 0 };
SC.pricing       = new Ext.data.ArrayStore({
    storeId  : 'pricing',
    getPrice : function(item) {
	var rec = this.getAt(this.findBy(function(rec) {
	    return ((rec.get('format')  == item.format) &&
		    (rec.get('support') == item.support)); }));
	return rec ? rec.get('price') : null;
	},
    fields  : [ 'ratio', 'format', 'support',
                { name: 'price', type: 'float' } ]
});

SC.get_default_type = function() {
    return Ext.query('#cart-default input[type=radio]:checked')[0]
              .value.split(/,/).map(function(elt) {
		  var r=elt.match(/([^|]*)\|([^|]*)/); 
		  return { format: r[2], support: r[1] }; });
};


SC.init = function(url, pricing, shipping, bank) {
    SC.REMOTE_URL = url;
    SC.pricing.loadData(pricing);
    SC.shipper = shipping;
    SC.bank    = bank;
};

SC.start = function() {
    var cart = new SC.Cart({
	pricer        : SC.pricing.getPrice.createDelegate(SC.pricing),
	url_thumbnail : function(reference) {
	  return Ext.query('[sc-photo-ref='+reference+'] .frame img')[0].src;},
	bank          : SC.bank
    });
    Ext.getDoc().on('unload', function() { cart.save(); });


    /*
     * Gallery component
     */
    //==== Update view ====
    cart.on('update', function(rec, info) {
	Ext.select('.sc-item[sc-photo-ref='+rec.get('reference')+']')
           .each(function() {
	    this.select('[sc-buy-format=' +rec.get('format') +']' +
			'[sc-buy-support='+rec.get('support')+'] .sc-quantity')
		.update(rec.get('quantity').toString());
	    this.select('.sc-buy-ref').show() }); });
    cart.on('delete' , function(rec) {
	Ext.select('.sc-item[sc-photo-ref='+rec.get('reference')+']')
           .each(function() {
	    this.select('[sc-buy-format=' +rec.get('format') +']' +
			'[sc-buy-support='+rec.get('support')+'] .sc-quantity')
		.update('0');
	    this.select('.sc-buy-ref').hide(); }); });
    cart.on('clear', function() {
	Ext.select('.sc-item').each(function() {
	    this.select('.sc-buy .sc-quantity').update('0');
	    this.select('.sc-buy-ref').hide(); }); });

    //==== Action ====
    Ext.select('.gallery').each(function(gallery) {
        gallery.on('click', function(e, t) {
	    if (! e.getTarget('.sc-action')) return;
	    var info      = { src: t };
	    var item      = Ext.fly(t);
	    var reference = item.getAttribute('sc-photo-ref');
	    var ratio     = item.getAttribute('sc-photo-ratio') || '2:3';
	    var format    = null;
	    var support   = null;

	    if (e.getTarget('.sc-buy-quick')) {
		var choices  = SC.get_default_type();
		var items    = SC.pricing.queryBy(function(rec) {
	  	    // Find the correct support/format according
		    //  to photo ratio and pricing list
			if (rec.get('ratio') != ratio) return false;
			var support = rec.get('support');
			var format  = rec.get('format');
			var ok      = false;
			Ext.each(choices, function(c) {
				if (ok = ((c.support == support) &&
					  (c.format  == format )))
				    return false; });
			return ok;
		    });
		switch(items.getCount()) {
		case  1: format   = items.first().get('format');
 	  	         support  = items.first().get('support');
		         break;
		case  0: SC.oops('provide_missing');  return;
		default: SC.oops('provide_too_many'); return;
		}
	    } else if (e.getTarget('.sc-buy-panel')) {
		var props = Ext.fly(e.getTarget('.sc-action')).parent();
		format    = props.getAttribute('sc-buy-format');
		support   = props.getAttribute('sc-buy-support');
	    } else if (e.getTarget('.sc-buy-ref')) {
		var msg = String.format(SC.l10n.get('trash-ref-q'), reference);
		if (confirm(msg)) {
		    cart.getByRef(reference).each(function(rec) {
			cart.trash(rec.data); });
		}
		return;
	    }
	    
	    var p_item  = { reference : reference,
			    format    : format,
			    support   : support };
	    try {
		var action = Ext.fly(e.getTarget('.sc-action'));
		if (action.hasClass('sc-inc')) cart.add   (p_item,info);
		if (action.hasClass('sc-dec')) cart.remove(p_item,info);
	    } catch(e) {
		if (e instanceof SC.Error) { SC.oops(e.id); }
		else { throw e; }
	    }
	    }, this, { delegate: '.sc-item' });
	});
    


    /*
     * Cart component
     */
    //==== Update view ====
    var cartEl      = Ext.select('.sc-cart').first();
    var cartEmptyCE = cartEl.select('.sc-cart-empty', true);
    cartEmptyCE.setVisibilityMode(Ext.Element.DISPLAY);
    cart.on('update', function(rec, info) {
	cartEmptyCE.stopFx().hide();
	var citem = cartEl.select(
	  	     '[sc-photo-ref='  +rec.get('reference')+']'+
		     '[sc-buy-format=' +rec.get('format')   +']'+
		     '[sc-buy-support='+rec.get('support')  +']').first();
	if (citem) {
	    var div = document.createElement('div');
	    SC.Tpl.cart_item.overwrite(div, rec.data);
	    citem.replaceWith(div.children[0]);
	} else {
	    var where =cartEl.select('[sc-photo-ref='+rec.get('reference')+']')
		             .last();
	    var data  = SC.Tpl.cart_item.apply(rec.data);
	    citem = where ? where.insertSibling(data, 'after')
		          : cartEl.insertHtml('beforeEnd', data, true); 
	}
	citem.scrollIntoView(cartEl);
	if (info && info.src) {
	    var src = Ext.fly(info.src);
	    if (src.is('.sc-item')) src.transfert(cartEl);
	} });
    cart.on('delete' , function(rec) {
	cartEl.select(
	     '[sc-photo-ref='  +rec.get('reference')+']'+
	     '[sc-buy-format=' +rec.get('format')   +']'+
	     '[sc-buy-support='+rec.get('support')  +']')
	  .fadeOut (     {easing: 'easeIn',  duration: 0.5, concurrent: true})
	  .slideOut('t', {easing: 'easeOut', duration: 0.5, remove: true,
	     callback: function() { 
		if (!cartEl.select('.sc-cart-item').getCount())
		    cartEmptyCE.fadeIn({easing: 'easesIn', duration: 0.3});
	     } }); });
    cart.on('clear', function() {
	cartEl.select('.sc-cart-item')
	  .fadeOut (     {easing: 'easeIn',  duration: 0.5, concurrent: true})
	  .slideOut('t', {easing: 'easeOut', duration: 0.5, remove: true,
	     callback: function() { 
		if (!cartEl.select('.sc-cart-item').getCount())
		    cartEmptyCE.fadeIn({easing: 'easesIn', duration: 0.3});
	     } }); });

    //==== Action ====
    cartEl.on('click', function(e, t) {
        if (! e.getTarget('.sc-action')) return;
	var info    = { src: t };
	var item    = Ext.fly(t);
	var p_item  = { reference : item.getAttribute('sc-photo-ref'  ),
			format    : item.getAttribute('sc-buy-format' ),
			support   : item.getAttribute('sc-buy-support') };
	try {
	    var action = Ext.fly(e.getTarget('.sc-action'));
	    if (action.hasClass('sc-inc')) cart.add   (p_item, info);
	    if (action.hasClass('sc-dec')) cart.remove(p_item, info);
	    if (action.hasClass('sc-del')) cart.trash (p_item, info);
	} catch(e) {
	    if (e instanceof SC.Error) { SC.oops(e.id); }
	    else { throw e;	}
	}
      }, this, { delegate: '.sc-cart-item' });


    /*
     * Checkout component
     */
    //==== Update view ====
    var updateCheckout = function() {
	var basket   = cart.getTotal();
	var shipping = basket ? SC.shipper(cart) : 0;
	var cost     = basket + shipping;
	var quantity = cart.getCount();
	Ext.select('.sc-cart-checkout').each(function() {
	    if (cost) { this.dom.removeAttribute('disabled') }
	    else      { this.set({ disabled: 'disabled' })   }
	    SC.Tpl.checkout_summary.overwrite(this, {
	        basket: basket, shipping: shipping, cost: cost,
			quantity: quantity });
        });
    };
    cart.on('update', updateCheckout);
    cart.on('delete', updateCheckout);
    cart.on('clear',  updateCheckout);


    //==== Action ====
    Ext.select('.cart-checkout-empty').on('click', function() {
	if (cart.isEmpty()) return;
	if (! confirm(SC.l10n.get('cart_empty_q'))) return;
	cart.empty();
	//cart.save();
    });
    Ext.select('.cart-checkout-validate').on('click', function() {
	if (cart.isEmpty()) return;
	if (! confirm(SC.l10n.get('cart_confirm_q'))) return;
	cart.save();
	cart.order();
    });

    cart.load();
};


SC.prepare = function() {
    Ext.select('.sc-item').each(function() {
	var ref   = this.getAttribute('sc-photo-ref');
	var ratio = this.getAttribute('sc-photo-ratio') || '2:3';
	var list  = SC.pricing.query('ratio', ratio).toArray();
	SC.Tpl.buy_me.append(this, { reference: ref, choices: list });

	var trigger         = this.select('.sc-buy-quick', true).first(); 
	var popup           = this.select('.sc-buy-panel', true).first();
	var checked         = this.select('.sc-buy-ref',   true).first();
	var task            = new Ext.util.DelayedTask(function() {
		popup.stopFx().fadeIn({easing: 'easeIn', duration: 0.3}); });

	checked.hover(function() { 
		if (!popup.isVisible()) task.delay(2000);
	  }, function(evt) {
		task.cancel();
	  });
	
	trigger.hover(function() { 
		if (!popup.isVisible()) task.delay(1000);
	  }, function(evt) {
		task.cancel();              // Cancel scheduled task
		if (popup.isVisible() &&    // but if popup already visible
		    (evt.getRelatedTarget() != popup.dom)) // and we jumped
		    popup.stopFx().ghost(); // outside popup, hide it
	  });
	
        popup.hover(function() {
            // nothing
          }, function(evt) {
		if (evt.getRelatedTarget() == trigger.dom) // If on trigger
		    return;                                // don't hide
		popup.stopFx().ghost();
          });
    });

    // Insert checkout summary
    Ext.select('.sc-cart-checkout').each(function() {
	this.set({ disabled: 'disabled' });
	SC.Tpl.checkout_summary.append(this, {
		basket: 0, shipping: 0, cost: 0, quantity: 0, });
	});
};

