var processing_request = false;
var current_page = null;
var transitioning_pages = false;

var page_fade_duration = 300;
var list_fade_duration = 300;
var list_resize_duration_per_100_pixels = 300;
var list_resize_duration_min = 200;
var list_resize_duration_max = 1000;
var list_resize_immediate_if_over_max_duration = true;
var list_top_margin = 20;
var progress_indicator_fade_duration = 150;

var field_id_prefix = "search_";
var field_id_prefix_length = field_id_prefix.length;

var kRowFieldSpecTitle = 0;
var kRowFieldSpecKey = 1;
var kRowFieldSpecIndex = 2;

var kMaxProductNameLength = 20;

function ProductDatabase(database,
                         container_id,
                         show_all_results_when_no_filter_present,
                         row_field_spec,
                         collapsed_row_field_spec) {
	this.db = database;
	this.container = $(container_id);
    this.list = this.container.getElement("table");
    this.show_all_results_when_no_filter_present = show_all_results_when_no_filter_present;
	this.row_field_spec = row_field_spec;
	this.collapsed_row_field_spec = collapsed_row_field_spec;
	this.current_filter_keys = null;
	this.current_filter_values = null;
	var _this = this;
	
	this.GetDatabaseMapIndexForField = function(key) {
	    for (var i=0; i<this.db.index.length; i++) {
            if (this.db.index[i] == key)
                return i;
        }
        return -1;
	};
	
	// Setup fade effect for this page's container
	this.fx_list_opacity = new Fx.Tween(
	    _this.container, {
		property: "opacity",
		duration: page_fade_duration,
		onStart: function() {
		    _this.container.setStyles({
	            display: "block",
		        position: "relative"
	        });
		},
		onComplete: function() {
		    if (_this.container.getStyle("opacity") == 0 || _this.container.getSize().y <= 0) {
		        _this.container.setStyles({
		            display: "none",
		            position: "absolute"
		        });
		    }
		}
	});
	
	// Setup show all link
	this.container.getElements(".product-table-show-all").addEvent("click", function(the_event) {
    	// Show all collapsed rows
    	var rows = _this.container.getElements("tr");
    	for (var i=0; i<rows.length; i++) {
    		if (rows[i].hasClass("collapsed"))
    			continue;
    		
    		var fx = rows[i].retrieve("effect");
    		if (fx == null) {
    			if (i+1 >= rows.length)
    				continue;
    			var collapsed_div = rows[i+1].getElement("div.collapsed");
    			if (collapsed_div != null)
    				collapsed_div.setStyle("display", "block");
    		} else {
    			fx.show();
    		}
    	}
    	
    	return false;
    });
	
	// Setup hide all link
	this.container.getElements(".product-table-hide-all").addEvent("click", function(the_event) {
    	// Hide all expanded rows
    	var rows = _this.container.getElements("tr");
    	for (var i=0; i<rows.length; i++) {
    		if (rows[i].hasClass("collapsed"))
    			continue;
    		
    		var fx = rows[i].retrieve("effect");
    		if (fx == null) {
    			if (i+1 >= rows.length)
    				continue;
    			var collapsed_div = rows[i+1].getElement("div.collapsed");
    			if (collapsed_div != null)
    				collapsed_div.setStyle("display", "none");
    		} else {
    			fx.hide();
    		}
    	}
    	
    	return false;
    });
	
	// Get db indexes for the data points we'll be displaying in our search results list
    for (var i=0; i<this.row_field_spec.length; i++) {
        // Find map index for this field
        var key_index = this.GetDatabaseMapIndexForField(this.row_field_spec[i][kRowFieldSpecKey]);
        this.row_field_spec[i][kRowFieldSpecIndex] = key_index;
    }
    
    for (var i=0; i<this.collapsed_row_field_spec.length; i++) {
        // Find map index for this field
        var key_index = this.GetDatabaseMapIndexForField(this.collapsed_row_field_spec[i][kRowFieldSpecKey]);
        this.collapsed_row_field_spec[i][kRowFieldSpecIndex] = key_index;
    }
    
    // Get references to commonly needed database keys
    this.part_number_index = this.GetDatabaseMapIndexForField("carow_part_number");
    this.cat_image_index = this.GetDatabaseMapIndexForField("category_image");
    this.parent_cat_image_index = this.GetDatabaseMapIndexForField("parent_category_image");
    this.category_id_index = this.GetDatabaseMapIndexForField("category_id");
    this.parent_category_id_index = this.GetDatabaseMapIndexForField("parent_category_id");
    this.grandparent_category_id_index = this.GetDatabaseMapIndexForField("grandparent_category_id");
    this.category_name_index = this.GetDatabaseMapIndexForField("category_name");
    
    // Set the default data point for sorting our search results
    this.primary_sort = this.row_field_spec[0][kRowFieldSpecIndex];
    this.secondary_sort = this.primary_sort;
    this.sort_ascending = true;
    
    this.FindMatches = function(keys, values, key_to_skip) {
	    // Find all records in the database matching the specified search criteria
	    var matches = new Array();
	    
	    for (var i=0; i<this.db.map.length; i++) {
	        var product = this.db.map[i];
	        var match = true;
	        
	        if (keys != null && values != null) {
		        for (var j=0; j<keys.length; j++) {
		            // If we couldn't map this field to the database, skip it
		            if (keys[j] == -1)
		                continue;
		            
		            // Don't include the specified input in our matching criteria
		            if (key_to_skip != null) {
	                    if (keys[j] == key_to_skip)
	                        continue;
		            }
		            
		            // A value isn't specified for this field - skip matching
		            if (values[j] == null)
		                continue;
		            
		            // Matching fails if any of the specified fields don't match
		            if (product[keys[j]] != values[j]) {
		                match = false;
		                break;
		            }
		        }
	        }
	        
	        // Add product to our match array if this was a successful match
	        if (match)
	            matches.push(i);
	    }
	    
	    return matches;
	};
    
    this.RecreateListContents = function(keys, values) {
    	// Keep a copy of the current filter settings so that we can use them again if the user resorts by column
    	this.current_filter_keys = keys;
		this.current_filter_values = values;
		
        // Setup product table
	    var tbody = document.createElement("tbody");
        
        // Load all unique matches given the current selections
	    var matches = this.FindMatches(keys, values);
	    
	    // Reset sort settings
		this.primary_sort = this.row_field_spec[0][kRowFieldSpecIndex];
		this.secondary_sort = this.primary_sort;
		this.sort_ascending = true;
	    
	    // Remove sort indicator, if present
        $$(".sort-up").dispose();
        $$(".sort-down").dispose();
	    
	    // Sort matches
	    var _this = this;
	    matches.sort(function(a, b) {
	        var product_a = _this.db.map[a];
	        var primary_value_a = product_a[_this.primary_sort];
	        var secondary_value_a = product_a[_this.secondary_sort];
	        
	        var product_b = _this.db.map[b];
	        var primary_value_b = product_b[_this.primary_sort];
	        var secondary_value_b = product_b[_this.secondary_sort];
	        
	        var result = primary_value_a - primary_value_b;
	        if (result == 0)
	            result = secondary_value_a - secondary_value_b;
	        
	        return result;
	    });
	    
	    var matched_category_names = new Array();
	    for (var i=0; i<matches.length; i++) {
            var row_id = matches[i];
            
            // Add this category name's index value to our array if it's not already present
            var found = false;
            var match_category_name_index = this.db.map[row_id][this.category_name_index];
            for (var j=0; j<matched_category_names.length; j++) {
            	if (matched_category_names[j] == match_category_name_index) {
            		found = true;
            		break;
            	}
            }
            if (!found)
            	matched_category_names.push(match_category_name_index);
            
            // Main row
            var row = document.createElement("tr");
            row.id = this.db.prefix + "_row_" + row_id;
            var product = this.db.map[matches[i]];
            
            // Comparison checkbox
            var checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.id = "compare_" + this.db.prefix + "_" + row_id;
            checkbox.name = "compare_" + this.db.prefix + "_" + row_id;
            checkbox.value = database.db[this.part_number_index][product[this.part_number_index]];
            row.appendChild(this.TableCol(checkbox, null, "no_click compare_checkbox"));
            
	        for (var j=0; j<row_field_spec.length; j++) {
	            var key_index = row_field_spec[j][kRowFieldSpecIndex];
	            
	            // Don't display anything if a data point doesn't map to anything in our database
	            if (key_index < 0) {
    	            row.appendChild(this.TableCol(""));
    	        } else if (product[key_index] < 0) {
    	            row.appendChild(this.TableCol(""));
    	        } else {
    	        	var value = database.db[key_index][product[key_index]];
    	        	if (row_field_spec[j][kRowFieldSpecKey] == "category_name") {
    	        		if (value.length > kMaxProductNameLength) {
    	        			var index = kMaxProductNameLength - 3;
    	        			while (value[index-1] == ' ' && index > 1)
    	        				index--;
    	        			
    	        			value = value.substring(0, index) + "...";
    	        		}
    	        	}
    	        	
	                row.appendChild(this.TableCol(value));
    	        }
    	    }
    	    
    	    // Samples link
    	    if (this.db.in_cart[row_id]) {
    	    	var span = document.createElement("span");
    	    	span.className = "in_cart_message";
    	    	span.appendChild(document.createTextNode("In Cart"));
    	    	
    	    	var div = document.createElement("div");
    	    	div.appendChild(span);
    	    	
    	    	row.appendChild(this.TableCol(div, null, "no_click samples_link"));
    	    } else {
    	        var link = document.createElement("a");
    	        link.href = "#" + encodeURIComponent(database.db[this.part_number_index][product[this.part_number_index]]);
    	        //link.href = "#" + row_id;
    	        link.appendChild(document.createTextNode("Samples"));
    	    	
    	    	var div = document.createElement("div");
    	    	div.appendChild(link);
    	    	
                row.appendChild(this.TableCol(div, null, "no_click samples_link"));
    	    }
    	    
    	    if (i % 2 == 1)
	            row.className = "alt";
		    
	        tbody.appendChild(row);
	        
	        // Collapsed row
	        var expanded_information = document.createElement("div");
	        expanded_information.className = "expanded_information";
	        
	        // Determine if product image is available
	        var thumbnail = null;
	        if (product[this.cat_image_index] >= 0)
	            thumbnail = database.db[this.cat_image_index][product[this.cat_image_index]];
	        else if (product[this.parent_cat_image_index] >= 0)
	            thumbnail = database.db[this.parent_cat_image_index][product[this.parent_cat_image_index]];
	        
	        // Determine product category link
	        var is_tlc = false;
	        var product_grandparent_cat_id = database.db[this.grandparent_category_id_index][product[this.grandparent_category_id_index]];
	        for (var j=0; j<database.tlc.length; j++) {
	        	if (database.tlc[j] == product_grandparent_cat_id) {
	        		is_tlc = true;
	        		break;
	        	}
	        }
	        var category_link = "/product/productselect";
	        if (is_tlc) {
	        	category_link += "/" + database.db[this.parent_category_id_index][product[this.parent_category_id_index]];
	        	category_link += "/" + database.db[this.category_id_index][product[this.category_id_index]];
	        	category_link += "/0";
	        } else {
	        	category_link += "/" + product_grandparent_cat_id;
	        	category_link += "/" + database.db[this.parent_category_id_index][product[this.parent_category_id_index]];
	        	category_link += "/" + database.db[this.category_id_index][product[this.category_id_index]];
	        }
	        
	        // Display linked product category header & product image, if available
	        var link = document.createElement("a");
	        link.href = category_link;
	        link.appendChild(document.createTextNode(database.db[this.category_name_index][product[this.category_name_index]]));
	        var header = document.createElement("h3");
	        header.appendChild(link);
	        expanded_information.appendChild(header);
	        
	        if (thumbnail != null) {
	        	var img = document.createElement("img");
	            img.src = "/images/products/thumbnails/" + thumbnail;
	            
	        	link = document.createElement("a");
		        link.href = category_link;
		        link.appendChild(img);
		        
		        expanded_information.appendChild(link);
	        }
	        
	        // Right-hand information: the collapsed row detailed information
	        var expanded_product_details = document.createElement("div");
	        expanded_product_details.className = "expanded_product_details";
	        expanded_information.appendChild(expanded_product_details);
	        
	        // Add all of the other collapsed row's data points
	        for (var j=0; j<collapsed_row_field_spec.length; j++) {
	        	var title = collapsed_row_field_spec[j][kRowFieldSpecTitle];
	            var key_index = collapsed_row_field_spec[j][kRowFieldSpecIndex];
	            
	            // Don't display anything if a data point doesn't map to anything in our database
	            if (key_index >= 0) {
	                if (product[key_index] >= 0) {
	                	var dl_title = document.createElement("dt");
	                	dl_title.appendChild(document.createTextNode(title + ":"));
	                	
	                    var dl_definition = document.createElement("dd");
	                    dl_definition.appendChild(document.createTextNode(database.db[key_index][product[key_index]]));
    	                
    	                var dl = document.createElement("dl");
    	                dl.appendChild(dl_title);
    	                dl.appendChild(dl_definition);
    	                
    	                expanded_product_details.appendChild(dl);
    	            }
    	        }
    	        
	        }
	        
	        var clear = document.createElement("div");
	        clear.className = "clear";
	        expanded_information.appendChild(clear);
	        
	        var div = document.createElement("div");    // Effect is tied to the first div within the row
	        div.className = "collapsed";
	        div.appendChild(expanded_information);
	        
	        row = document.createElement("tr");
            row.id = this.db.prefix + "_row_collapsed_" + row_id;
	        row.className = "collapsed";
	        row.appendChild(this.TableCol(div, row_field_spec.length + 2));
	        
	        tbody.appendChild(row);
        }
        
        // Set our search result header
        var search_result_header = this.container.getElement(".categoryDescription");
        if (search_result_header != null) {
        	search_result_header.empty();
        	
        	var header_text = "";
        	if (matched_category_names.length > 4) {
	        	header_text += "Products were found in ";
	        	for (var i=0; i<4; i++)
			        header_text += (i > 0 ? ", " : "") + database.db[this.category_name_index][matched_category_names[i]];
			    header_text += ", and more";
        	} else if (matched_category_names.length == 1) {
        		header_text += "One product was found in "
					+ database.db[this.category_name_index][matched_category_names[0]];
        	} else if (matched_category_names.length == 2) {
        		header_text += "Products were found in "
					+ database.db[this.category_name_index][matched_category_names[0]]
					+ " and "
					+ database.db[this.category_name_index][matched_category_names[1]];
        	} else {
        		header_text += "Products were found in ";
	        	for (var i=0; i<matched_category_names.length; i++)
			        header_text += (i > 0 ? ", " : "") + (i > 0 && i == matched_category_names.length - 1 ? "and " : "") + database.db[this.category_name_index][matched_category_names[i]];
		    }
		    header_text += ".";
		    
		    search_result_header.appendText(header_text);
	    }
    	
    	// Create column headers
	    var thead = document.createElement("thead");
	    var row = document.createElement("tr");
	    row.className = "table-top";
	    
	    // Comparison checkbox
	    row.appendChild(document.createElement("th"));
	    
	    for (var i=0; i<this.row_field_spec.length; i++)
	        row.appendChild(
	            this.TableHeader(
	                this.row_field_spec[i][kRowFieldSpecTitle],
	                this.row_field_spec[i][kRowFieldSpecIndex]
	            )
	        );
	    
	    // Samples link
	    row.appendChild(document.createElement("th"));
	    
	    thead.appendChild(row);
    	
    	// Replace the current lists's data with our new results
    	this.list.empty();
	    this.list.appendChild(thead);
	    this.list.appendChild(tbody);
	    
	    // Setup the table collapse effects
	    setup_table_slide(this.list);
	    
	    // Setup our selection checkboxes
	    new setup_product_comparison_controls(this.container);
	    
	    // Setup our sample links
	    setup_request_sample_links(this.container, this);
	};
	
	this.HideList = function(param_1, param_2) {
		var immediate = false;
		var callback_func = null;
		
		if (typeof(param_1) == "function") {
			callback_func = param_1;
		} else if (typeof(param_1) == "boolean") {
			immediate = param_1;
			
			if (typeof(param_2) == "function")
				callback_func = param_2;
		}
		
		if (immediate) {
			// Fade out product list and switch to absolute
			// positioning so that the page may resize to accomodate other lists
			this.container.setStyles({
				position: "absolute",
				opacity: 0.0
				});
			
			// Call callback function, if specified
            if (callback_func != null)
	            callback_func();
		} else {
			// Don't continue if a request is already in progress
	        if (processing_request)
	            return;
	        processing_request = true;
	        
	        // Fade out current product list
	        var _this = this;
		    
	        // Switch to absolute positioning so that the page may resize to accomodate other lists
	        this.container.setStyle("position", "absolute");
    	    
	        // Call callback function, if specified
            if (callback_func != null)
	            callback_func();
		}
    };

    this.ShowList = function(param_1, param_2) {
		var immediate = false;
		var callback_func = null;
		
		if (typeof(param_1) == "function") {
			callback_func = param_1;
		} else if (typeof(param_1) == "boolean") {
			immediate = param_1;
			
			if (typeof(param_2) == "function")
				callback_func = param_2;
		}
		
		if (immediate) {
			// Fade in product list and switch all lists to absolute
			// positioning except for the one corresponding to this page
			this.container.setStyles({
			    display: "block",
				position: "relative",
				opacity: 1.0
				});
			
			// Call callback function, if specified
            if (callback_func != null)
	            callback_func();
		} else {
	        // Switch all lists to absolute positioning except for the one corresponding to this page
	        this.container.setStyle("position", "relative");
	        
            if (callback_func != null)
                callback_func();
            
            // Reset our processing flag
            processing_request = false;
		}
    };
    
    this.GetTableHeaderOnClickFunction = function(search_id) {
    	var _this = this;
    	
    	return function() {
    		// Assemble list of table rows on the page
    		var curr_tbody = _this.list.getElement("tbody");
            var sorted_list = new Array();
            for (var i=0; i<curr_tbody.rows.length; i++) {
                var item = new Object();
                item.row = curr_tbody.rows[i++];
                item.collapsed_row = curr_tbody.rows[i];
                
                var id_components = item.row.id.split("_");
                if (id_components.length <= 0)
                    continue;
                item.row_id = parseInt(id_components[id_components.length-1], 10);
                
                item.row.className = "";
                
                sorted_list.push(item);
            }
            
    	    // Reverse the sort order if we're already sorting on this column
    	    // "this" is the header dom element, "_this" is the ProductDatabase object
    	    var selected_header = $(_this.db.prefix + "_column_" + _this.primary_sort);
    	    if (this.className == "selected") {
    	        _this.sort_ascending = !_this.sort_ascending;
    	    } else {
    	    	_this.sort_ascending = true;
    	    	
		    	// Remove "selected" class from currently selected table header
	            if (selected_header != null)
	                selected_header.removeClass("selected");
		    }
		    
		    // Remove sort indicator
            var indicator = selected_header.getElement(".sort-up");
            if (indicator == null)
            	indicator = selected_header.getElement(".sort-down");
           	if (indicator != null)
           		indicator.dispose();
		    
		    // Update the data point for sorting our search results
	        _this.primary_sort = search_id;
	        
	        // Fade out product list
	        _this.HideList(function() {
	        	// Determine new sort order
	            // *** This currently assumes that product order = disctinct part number order
	            sorted_list.sort(function(a, b) {
	                var product_a = _this.db.map[a.row_id];
                    var primary_value_a = product_a[_this.primary_sort];
                    var secondary_value_a = product_a[_this.secondary_sort];
                    
	                var product_b = _this.db.map[b.row_id];
                    var primary_value_b = product_b[_this.primary_sort];
                    var secondary_value_b = product_b[_this.secondary_sort];
                    
                    if (_this.sort_ascending) {
	                    var result = primary_value_a - primary_value_b;
	                    if (result == 0)
	                        result = secondary_value_a - secondary_value_b;
                    } else {
	                    var result = primary_value_b - primary_value_a;
	                    if (result == 0)
	                        result = secondary_value_b - secondary_value_a;
                    }
                    
                    return result;
	            });
	            
	            // Add sorted contents to the new tbody
	            for (var i=0; i<sorted_list.length; i++) {
	                curr_tbody.appendChild(sorted_list[i].row);
	                curr_tbody.appendChild(sorted_list[i].collapsed_row);
	                
	                // Set odd/even coloring
	                if (i % 2 == 1)
	                    sorted_list[i].row.className = "alt";
	            }
	            
	            // Fade list back in now that it's been repopulated with data
	            _this.ShowList(function() {
	                var selected_header = $(_this.db.prefix + "_column_" + _this.primary_sort);
                    if (selected_header != null) {
                 		// Add "selected" class to the table header we're sorting on
                    	if (!selected_header.hasClass("selected"))
	                        selected_header.addClass("selected");
	                    
	                    selected_header.getElement("span").grab(new Element("div", {
	                    	"class": _this.sort_ascending ? "sort-up" : "sort-down",
	                    	"html": "   "
	                    }));
                    }
	            }, true);
	        }, true);
        }
    };
    
    this.TableHeader = function(contents, search_id) {
	    var col = document.createElement("th");
	    col.id = this.db.prefix + "_column_" + search_id;
	    var _this = this;
	    col.onclick = this.GetTableHeaderOnClickFunction(search_id);
	    
	    span = document.createElement("span");
	    span.appendChild(document.createTextNode(contents));
	    col.appendChild(span);
    	
	    if (this.primary_sort == search_id) {
	        col.className = "selected";
	        
	        $(span).grab(new Element("div", {
		    	"class": _this.sort_ascending ? "sort-up" : "sort-down",
		    	"html": "   "
		    }));
	    }
    	
	    return col;
    };
    
    this.SetTableHeaderEvents = function() {
    	var elements = this.list.getElements("th");
    	for (var i=1; i<elements.length - 1; i++) // Skip first and last headers
    		elements[i].onclick = this.GetTableHeaderOnClickFunction(this.row_field_spec[i-1][kRowFieldSpecIndex]);
    };

    this.TableCol = function(contents, colspan, class_name) {
        var col = document.createElement("td");
	    if (colspan != null)
	        col.colSpan = colspan;
	    if (class_name != null)
	        col.className = class_name;
	    
	    if (typeof(contents) == "string")
	        col.appendChild(document.createTextNode(contents));
	    else
	        col.appendChild(contents);
    	
	    return col;
    };
    
    // Setup any table headers that are initially visible
    this.SetTableHeaderEvents();
    
    // Setup any request samples links that are initially visible
    setup_request_sample_links(this.container, this);
}
