AvailabilityApp = Class.create();
AvailabilityApp.prototype = {

	stateDictionary: null,
	states: null,
		
	stateSelector: null,
	date: null,
	listingPanel: null,
		
	options: null,
	productWeights: null,
		
	initialize: function(feedUrl, listingPanel, options) {
		this.listingPanel = $(listingPanel);
		
		this.options = options || {};
		
		this.config = this.options.config || AvailabilityApp.Configurations.Location;
		if (this.config && typeof this.config.validateOptions === 'function') {
		    this.config.validateOptions(this.options);
		}
		
		if (this.options.productOrder) {
		    this.productOrder = {};
		    for (var i = 0; i < this.options.productOrder.length; i++) {
		      this.productOrder[this.options.productOrder[i]] = i;
		    }
		}
		
		this.locationDictionary = this.options.locations || {
			CH: {name: "Bahnhofstrasse", stores: []},
			CH: {name: "Rue De Rive", stores: []},
			CH: {name: "Glattzentrum", stores: []}},

		this.locations = [],
			
		this._populateStores(feedUrl);
	},
	
	_processInventory: function(inventory) {
		
		this.date = new Date(inventory.date);
		
		//associate stores with state
		for (var locationId in inventory.locations) {
			var location = inventory.locations[locationId];
			this.locationDictionary[locationId].stores = location;
		}
		
		if (this.config && typeof this.config.processInventory === 'function') {
		    this.config.processInventory.call(this, inventory)
	    }
	},
	
    // Request the availability information
    // Proceed to process the inventory if successful or handle the failure
	_populateStores: function(feedUrl) {
			
		new Ajax.Request(feedUrl + "?z=" + new Date().getTime(), {
			method: 'GET',
			onSuccess: function(response) {
				
				try {
					var inventory = eval('(' + response.responseText + ')');
				} catch(e) {
					this.showError();
					return;
				}
				
				this._processInventory(inventory);
			}.bind(this),
			onFailure: this._showError.bind(this),
			onException: function(response, e) {
				throw e;
			}
		})
		
	},
	
	// If options provide an onFailure callback, execute it
	_showError: function() {
		if (typeof(this.options.onFailure) === 'function') {
			this.options.onFailure(this.listingPanel);
		}
	},
	
    // Whether the specified product is in stock at store with specified id
    // 
    // Do not bother specifying a productId if the store's avaiability does
    // not offer several productId to pick from
    isAvailableProduct_atStoreWithId: function(productId, storeId) {
        
        var found_store = null;
        
        for (var locationId in this.locationDictionary) {
			var location = this.locationDictionary[locationId];
			foundStore = location.stores.find(function(store) {return store.storeid == storeId;})
			
			if (foundStore) {
			    break;
			}
		}
		
		if (!foundStore) {
			return false;
		} else {
		    return this.isAvailableProduct_atStore(productId, foundStore)
		}
    },
    
    // Whether or not any product is avilable at the speicifed store
    // True if any product is, false if none of them are
    isAnyProductAvailableAtStore: function(store) {
        return $H(store.available).any(function(product) { return product[1]; });
    },
    
    // Whether or not a specified product is available at a provided store
    // 
    // Do not bother specifying a productId if the store's avaiability does
    // not offer several productId to pick from
	isAvailableProduct_atStore: function(productId, store) {
        
        if (!store) {
            return false;
        }
        
        // This code works us aroudn the old style available: true|false and the
        // new optional style available: {"p1": true|false, ... } for specifying
        // the availability of multiple SKUs in the feed
        if (!!productId) {
            // if no productId was specified, we should not expect a collection to pick from
            if ( typeof foundStore.available !== "boolean") {
                throw "Asked for availbility at store where several products are offered, but did not specify a product ID.";
            } else {
                return !!foundStore.available;
            }
        } else {
            // if a productId was specified, we should expect a collection to pick from
            if (typeof foundStore.available === "boolean") {
                throw "Asked for a specific product's availability but only a single product is offered.";
            } else {
                return !!foundStore.available[productId];
            }
        }
    },
    
    showAvailabilityWithin: function(id) {
		if (this.config && typeof this.config.showAvailabilityWithin === 'function') {
		    this.config.showAvailabilityWithin.call(this, id);
		}
	}
	
}

AvailabilityApp.months = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet',
		 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
	
AvailabilityApp.days = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 
	   'Jeudi', 'Vendredi', 'Samedi'];

AvailabilityApp.Configurations = {
	
	Location: {
		
		// Throws error if provided options are not valid for this config
		validateOptions: function(options) {
			if (!options.locationSelector && !options.locationId) {
				throw "Location scoped application requires either an explicit 'locationId' or a 'locationSelector' form element";
			}
		},
		
		//Creates a location selector for changing the location selected
		_createLocationSelector: function(inventory, preferredLocationId) {
			
			if (!this.options.locationSelector) {
				return;
			}
			
			var createOption = function(value, name, selected) {

				var option = document.createElement('option');
				option.setAttribute('value', value);
				option.appendChild(document.createTextNode(name));
				
				if (selected) {
					option.setAttribute('selected', 'selected');
				}
				return option;
			}
		
			var showLocation = function(evt) {
				var locationId = Event.element(evt).value;
				if (locationId in this.locationDictionary) {
					this.showAvailabilityWithin(locationId)
				}
			}.bind(this)
			
			//order by state name
			//add state to dropdown (if it has stores)
			$H(this.locationDictionary).sortBy(function(location) {
				return location.value.name;
			}).select(function(location) {
				return location[1].stores.length > 0;
			}).each(function(location) {
				var locationName = location[1].name;
				var selected = (location[0] == preferredLocationId);
				this.options.locationSelector.appendChild(createOption(location[0], locationName, selected));
			}.bind(this));
		
			Event.observe(this.options.locationSelector, 'change', showLocation);
		},
		
		processInventory: function(inventory) {

			var preferredLocationId = this.config._getPreferredLocation.call(this);

			this.config._createLocationSelector.call(this, inventory, preferredLocationId);
			
			if (this.locationDictionary[preferredLocationId]) {
				this.config.showAvailabilityWithin.call(this, preferredLocationId);
			}
			
			this.options.onSuccess(this.listingPanel);
		},
		
		showAvailabilityWithin: function(locationId) {
			this.config._setPreferredLocation.call(this, locationId);

			var location = this.locationDictionary[locationId];
			// adding a hidden caption for accessibility
			var caption = document.createElement('caption');
			if (this.options.tableTitle) {
				caption.innerHTML = this.options.tableCaption;
			} else {
				caption.innerHTML = "iPhone 3G S availability at local Apple Stores";
			}

			var date = '';

            var dayDictionary = this.options.days || AvailabilityApp.days;
			var day = dayDictionary[this.date.getDay()];

            var monthDictionary = this.options.months || AvailabilityApp.months;
			var month = monthDictionary[this.date.getMonth()];
			
			var dayOfMonth = this.date.getDate();
			
			if (this.options.dateFormatter) {
		        date = this.options.dateFormatter(day, month, dayOfMonth, this.date);
		    } else {
		        date = day + ', ' + month + ' ' + dayOfMonth;
		    }

            var multipleProductAvailability = typeof location.stores[0].available !== "boolean"

            if (multipleProductAvailability) {
                var storeListing = $(document.createElement('table'));
				// Need a thead & caption for proper structure
				var tableCaption = $(document.createElement('caption'));
				storeListing.appendChild(caption);
				
				var tableHead = $(document.createElement('thead'));
				storeListing.appendChild(tableHead);
                
                //You need a tbody to append rows into a table in IE
                var tableBody = $(document.createElement('tbody'));
                storeListing.appendChild(tableBody);

                var productEntryTemplate = $(document.createElement('td'));
                productEntryTemplate.addClassName('availability');
                
                var headerRow = document.createElement('tr');
                
                var storeHeader = $(document.createElement('th'));
                storeHeader.addClassName('store-header');
                if (this.options.storesText) {
                    storeHeader.innerHTML = this.options.storesText + ' &mdash; ' + date;
                } else {
                    storeHeader.innerHTML = location.name + " Stores &mdash; " + date;
                }
                headerRow.appendChild(storeHeader);
                
                for (var i = 0; i < this.options.productOrder.length; i++) {
                    var productId = this.options.productOrder[i];
                    
                    var productName = this.options.productNames[productId];
                    
                    var productHeader = $(document.createElement('th'));
                    productHeader.addClassName(productId + "-availability");
                    productHeader.innerHTML = productName;
                    headerRow.appendChild(productHeader);                    
                }
                
                tableHead.appendChild(headerRow);
                
            } else {
                var storeListing = $(document.createElement('ul'));
            }
            
            storeListing.addClassName('stores');

            location.stores.sort(function(a,b) {
				var cityA = a.city;
				var cityB = b.city;

				if (cityA == cityB) {
					return 0;
				} else if (cityA < cityB) {
					return -1;
				} else {
					return 1;
				}

			});

            for (var i = 0; i < location.stores.length; i++) {
                var store = location.stores[i];

                if (multipleProductAvailability) {
                    tableBody.appendChild(this.config._createMultipleProductEntry.call(this, store, productEntryTemplate));
                } else {
                    storeListing.appendChild(this.config._createSingleProductEntry.call(this, store));
                }
			}
			
			this.listingPanel.innerHTML = "";
			// this.listingPanel.appendChild(heading);
			// this.listingPanel.appendChild(date);
			this.listingPanel.appendChild(storeListing);
		},
		
		// Creates a node representing the availability for a store with multiple products
		// productEntryTemplate: the node to clone for each product cell
		//      This cuts down on the calls to addClassName which were
		//      disturbingly high in the profiling.
		_createMultipleProductEntry: function(store, productEntryTemplate) {
		    var storeEntry = document.createElement('tr');
            var storeInfo = document.createElement('td');
            storeInfo.innerHTML = store.city +', ' + '<a href="' + store.url + '">' + store.name + '</a>';
            storeEntry.appendChild(storeInfo);
            
            var products = $A($H(store.available));
            // if given some way to sort the products, make sure we sort them
            if (!!this.productOrder) {
                products.sort(function(a,b) {
                    
                    var aWeight = this.productOrder[a[0]];
                    var bWeight = this.productOrder[b[0]];
                    
                    if (aWeight == bWeight) {
                        return 0;
                    } else if (aWeight > bWeight) {
                        return 1;
                    } else {
                        return -1;
                    }
                }.bind(this));
            } else {
                throw "productOrder option needs to be provided when multiple products are available."
            }
            
            for (var i = 0; i < products.length; i++) {
                var available = products[i][1];

                var productEntry = productEntryTemplate.cloneNode(false);
                if (available) {
                    productEntry.addClassName('available');
                    productEntry.innerHTML = this.options.availableText;
                } else {
                    productEntry.innerHTML = this.options.unavailableText;
                }
                storeEntry.appendChild(productEntry);
            }
            return storeEntry;
		},
		
		// Creates the node representing the availability for a store with a single product
		_createSingleProductEntry: function(store) {
            var storeEntry = document.createElement('li');
            if (store.available) {
                var availability = '<strong class="availability" title="' + this.options.availableText + '">' + this.options.availableText + '</strong>';
                Element.addClassName(storeEntry, 'available');
            } else {
                var availability = '<span class="availability" title="' + this.options.unavailableText + '">' + this.options.unavailableText + '</span>';
            }
            storeEntry.innerHTML = availability + ' ' + store.city +', ' + '<a href="' + store.url + '">' + store.name + '</a>';
            return storeEntry;
		},
		
		// Save the preferred locationId
		_setPreferredLocation: function(locationId) {
			var today = new Date();
			var expiration = new Date(today.setDate(today.getDate() + 5));
			document.cookie = 'preferredLocation=' + locationId + '; expires=' + expiration.toUTCString() + '; path=/; domain=apple.com';
		},

        // Load the preferred locationId
		_getPreferredLocation: function() {
			
			var locationId = this.options.locationId;
			//if a location was explicitly specified in code use it
			if (locationId && this.locationDictionary[locationId]) {
				return locationId;
			}
			
			//use ?state=FOO as preferred stateId
			locationId = document.location.search.toQueryParams().state
			if (locationId && this.locationDictionary[locationId]) {
				return locationId;
			}
			
			//or if not found try a cookie
			var cookies = document.cookie.split(';');

			for(var i = 0; i < cookies.length; i++) {
				var match = cookies[i].match(/preferredLocation=(\w{2})/i);
				if (match && match[1] in this.locationDictionary) {
					locationId = match[1];
				}
			} 
			return locationId;
		}
		
	},
	
	Store: {
		
		// Throws error if provided options are not valid for this config
		validateOptions: function(options) {
			if (!options.storeId) {
				throw "Store scoped applications requires a storeId";
			}
		},
		
		processInventory: function(inventory) {
			this.config.showAvailabilityWithin.call(this, this.options.storeId);
		},
		
		showAvailabilityWithin: function(storeId) {
			//need to find the state in the dictionary keyed by state, quick and dirty
			for (var locationId in this.locationDictionary) {
				var location = this.locationDictionary[locationId];
				var foundStore = location.stores.find(function(store) {
					return store.storeid == storeId;
				})
				if (foundStore) {
					break;
				}
			}
			
			if (!foundStore) {
				this.showError();
				return;
			}
			
			this.options.onSuccess(foundStore, this.date, this.listingPanel);
		}
		
	}
	
}
