/******************************************/
/*                  AJAX                  */
/******************************************/
function AJAX()	{
	var object;
	try	{
		object = new XMLHttpRequest();
	}
	catch(e)	{
		try	{
			object = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch(e)	{
			try	{
				object = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch(e)	{}
		}
	}
	return object;
}

/******************************************/
/*              CONFIGURATION             */
/******************************************/
function NarrowingConfiguration()	{
	
	var list, pannel, pageSize;
	var paginations = [];
	
	var filtersMultiple = [];
	var filtersRange = [];
	
	this.setList = function( DOMElement )	{
		list = DOMElement;
	};
	this.getList = function()	{
		return list;
	};
	
	this.setPannel = function( DOMElement )	{
		pannel = DOMElement;
	};
	this.getPannel = function()	{
		return pannel;
	};
	
	this.addPagination = function( DOMElement )	{
		var pagination = new NarrowingPagination( DOMElement );
		paginations.push( pagination );
	};
	this.getPagination = function()	{
		return paginations;
	};
	
	this.setPageSize = function( cols, rows )	{
		pageSize = { 'cols': cols, 'rows': rows };
	};
	this.getPageSize = function()	{
		return pageSize;
	};
	
	/*
		ADD MULTIPLE
		*** Creates a new special multiple filter
		Parameters:
			name - the name of the filter that is multiple
		Returns:
			Null
	*/
	this.addMultipleFilter = function( name )	{
		var multiple = {
			'name': name
		};
		filtersMultiple.push( multiple );
	};
	
	/*
		ADD RANGE
		*** Creates a new special range filter
		Parameters:
			name - the name of the filter that is range
			interval - the intervals between is put the 
	*/
	this.addRangeFilter = function( name, interval, before, after )	{
		var range = {
			'name': name,
			'interval': interval,
			'before': before,
			'after': after
		};
		filtersRange.push( range );
	};
	
	/*
		GET MULTIPLE FILTERS
		*** Returns the array with the multiple filters
		Paramters:
			None
		Returns:
			Array
	*/
	this.getMultipleFilters = function()	{
		return filtersMultiple;
	};
	
	/*
		GET RANGE FILTERS
		*** Returns the array with the range filters
		Paramters:
			None
		Returns:
			Array
	*/
	this.getRangeFilters = function()	{
		return filtersRange;
	};
	
}

/******************************************/
/*                 DOMAIN                 */
/******************************************/
function NarrowingDOMController()	{
	
	var allItems = [];
	var appliedFilters = [];
	var selectedItems = [];
	
	/*
		ADD ITEM
		*** Adds a new item to the allItems array
		Parameters:
			newItem - Item object that will be added to the array
		Returns:
			Null
	*/
	this.addItem = function( NarrowingItem )	{
		var attributes = NarrowingItem.getAllAttributes(); // Get the attributes of the item
		allItems.push( NarrowingItem ); // Add the item to the array
		selectedItems.push( NarrowingItem ); // Also to the selected (this method only runs at app startup)
	};
	
	/*
		GET ITEMS
		*** Returns the items filtered by the array of applied filters
		Parameters:
			None
		Returns:
			Array
	*/
	this.getItems = function( sortCriteria )	{
		switch( sortCriteria )	{
			case 'id':
				return defaultSorting( selectedItems );
				break;
			case 'name':
				return sortItemsByName( selectedItems );
				break;
		}
		return selectedItems;
	};
	
	/*
		APPLY FILTER
		*** Adds a filter to the applied filter array. getItems will not return items that dont match this new filter
		Parameters:
			name - name of the filter to add
			value - value of the filter to add
		Returns:
			Null
	*/
	this.applyFilter = function( name, value )	{
		var filter = new NarrowingFilter( name, value ); // Create new object
		appliedFilters.push( filter ); // And add to the array
		getFilteredItems(); // Refresh the selected items
	};
	
	/*
		REMOVE FILTER
		*** Removes the filters that match the name sent in the parameters
		Parameters:
			name - the name of the filters to remove
		Returns:
			Null
	*/
	this.removeFilter = function( name )	{
		var newFilters = []; // This array will be storing all applied filters but the onee with the name like the parameter
		for( var i = 0; i < appliedFilters.length; i++ )	{ // Go through
			if( appliedFilters[i].getName() != name )	{ // If the name is different to the one we want to remove
				newFilters.push( appliedFilters[i] ); // Add to the new array
			}
		}
		appliedFilters = newFilters; // Then set the appliedFilters array to the new one without the filter we wanted to remove
		getFilteredItems(); // Refresh the selected items
	};
	
	/*
		GET ALL FILTERS
		*** Return all possible filters for the currently selected items
		Parameters:
			None
		Returns:
			Array
	*/
	this.getAllFilters = function()	{
		var returnFilters = [];
		for( var i = 0; i < selectedItems.length; i++ )	{
			var attributes = selectedItems[i].getAllAttributes();
			for( var j = 0; j < attributes.length; j++ )	{
				if( !filterExists( returnFilters, attributes[j].name, attributes[j].value ) )	{
					var filter = new NarrowingFilter( attributes[j].name, attributes[j].value );
					filter.increaseItemCount();
					returnFilters.push( filter );
				}
				else	{
					var filter = getFilterFromArray( returnFilters, attributes[j].name, attributes[j].value );
					filter.increaseItemCount();
				}
			}
		}
		return returnFilters;
	};
	
	/*
		GET APPLIED FILTERS
		*** Returns the applied filters
		Parameters:
			None
		Returns:
			Array
	*/
	this.getAppliedFilters = function()	{
		return appliedFilters;
	};
	
	/*
		GET FILTERED ITEMS
		*** Gets all the items and the applied filters and returns the items that are matching the filters
		Parameters:
			None
		Returns:
			Array
	*/
	function getFilteredItems()	{
		var returnItems = []; // The array that will be filled with the items that match
		for( var i = 0; i < allItems.length; i++ )	{ // For each one of all the items
			var add = true; // Set add as true
			for( var j = 0; j < appliedFilters.length; j++ )	{ // Check every filters
				if( !appliedFilters[j].matchItem( allItems[i] ) )	{ // And if one of the filters doesnt match
					add = false; // Set add to false
				}
			}
			if( add )	{ // If at this point add is true
				returnItems.push( allItems[i] ); // Then add the item to the array
			}
		}
		selectedItems = returnItems; // Set the selected to the new
	}
	
	/*
		FILTER EXISTS
		*** Checks if the filter is already added inside the array
		Paramters:
			array - the list of filters where we will look
			name - name of the filter to be checked
			value - value of the filter to be checked
		Returns:
			Boolean
	*/
	function filterExists( array, name, value )	{
		for( var i = 0; i < array.length; i++ )	{ // Check all the filters
			if( array[i].getName() == name && array[i].getValue() == value )	{ // And if the current one matches the name and value we search
				return true; // Return true
			}
		}
		return false; // If not found return false
	}
	
	/*
		GET FILTER FROM ARRAY
		*** Returns a filter if it is inside the array snt by parameter
		Parameters:
			array - the list of filters where we will look
			name - name of the filter we are looking for
			value - value of the filter we are looking for
		Returns:
			[ NarrowingFilter || Null ]
	*/
	function getFilterFromArray( array, name, value )	{
		for( var i = 0; i < array.length; i++ )	{
			if( array[i].getName() == name && array[i].getValue() == value )	{
				return array[i];
			}
		}
	}
	
	/*
		DEFAULT SORTING
		*** Returns an array of items sorted by its id
		Parameters:
			items - the unsorted items
		Returns:
			Array
	*/
	function defaultSorting( items )	{
		for( var i = 0; i < items.length; i++ )	{
			items[i].toString = function()	{
				return this.getName();
			};
		}
		items.sort();
		return items;
	}
	
	/*
		SORT BY NAME
		*** Returns items sorted by name
	*/
	function sortItemsByName(items)	{
		for( var i = 0; i < items.length; i++ )	{
			items[i].toString = function()	{
				return this.getDisplayName();
			};
		}
		items.sort();
		return items;
	}
	
}

/******************************************/
/*                 FILTER                 */
/******************************************/
function NarrowingFilter( name, value )	{
	
	var itemCount = 0; // The number of items that currently match this filter
	
	/*
		GET NAME
		*** Returns the name of the filter
		Parameters:
			None
		Returns:
			String
	*/
	this.getName = function()	{
		return name;
	};
	
	/*
		GET VALUE
		*** Returns the value of the filter
		Parameters:
			None
		Returns:
			String
	*/
	this.getValue = function()	{
		return value;
	};
	
	/*
		MATCH ITEM
		*** Returns true if the attribute of the item that has the filter name has the same value as the filter does
		Parameters:
			Item
		Returns:
			Boolean
	*/
	this.matchItem = function( itemCheck )	{
		var attributes = itemCheck.getAttribute( name );
		for( var i = 0; i < attributes.length; i++ )	{
			if( attributes[i] == value )	{
				return true;
			}
		}
		return false;
	};
	
	/*
		GET ITEM COUTN
		*** Returns the items that match this filter
		Paramters:
			None
		Returns:
			Null
	*/
	this.getItemCount = function()	{
		return itemCount;
	};
	
	/*
		RESET ITEM COUTN
		*** Sets the item count to 0
		Paramters:
			None
		Returns:
			Null
	*/
	this.resetItemCount = function()	{
		itemCount = 0;
	};
	
	/*
		INCREASE ITEM COUTN
		*** Adds one to the item count
		Paramters:
			None
		Returns:
			Null
	*/
	this.increaseItemCount = function()	{
		itemCount++;
	};
	
	/*
		TO STRING
		*** Used to sort the filters when needed. Checks if the value is numeric before returning
		Parameters:
			None
		Returns:
			String
	*/
	this.toString = function()	{
		var returnValue = value;
		if( !isNaN( parseFloat( returnValue ) ) )	{
			returnValue = parseFloat( returnValue ) + '';
			for( var i = 0; i < ( 20 - returnValue.length ); i++ )	{
				returnValue = '0' + returnValue;
			}
		}
		else if( !isNaN( parseFloat( returnValue.substring( 1 ) ) ) )	{
			returnValue = parseFloat( returnValue.substring( 1 ) ) + '';
			for( var i = 0; i < ( 20 - returnValue.length ); i++ )	{
				returnValue = '0' + returnValue;
			}
		}
		return returnValue;
	};
	
}

/******************************************/
/*                  ITEM                  */
/******************************************/
function NarrowingItem( name, cell )	{
	
	var attributes = [];
	
	/*
		ADD ATTRIBUTE
		*** Adds and attribute to the attributes array only if it deosnt already exists
		Parameters:
			name - name of the attribute to be added
			value - value of the attribute to be added
		Returns:
			Null
	*/
	this.addAttribute = function( name, value )	{
		if( value != '' )	{
			var attribute = { 'name': name, 'value': value }; // Create the attribute object
			attributes.push( attribute ); // And add it to the array 
		}
	};
	
	/*
		GET ATTRIBUTE
		*** Returns an array of values for the name given
		Parameters:
			name - name of the attributes requested
		Returns:
			Array
	*/
	this.getAttribute = function( name )	{
		var returnArray = []; // This is the array that will be returned with the values
		for( var i = 0; i < attributes.length; i++ )	{ // Go through all the attributes
			if( attributes[i].name.toUpperCase() == name.toUpperCase() )	{ // If the name is the one we are looking for
				returnArray.push( attributes[i].value ); // Add to the array
			}
		}
		return returnArray; // Return all the values in a package
	};
	
	/*
		GET ALL ATTRIBUTES
		*** Returns an array with all the attributes
		Parameters:
			None
		Returns:
			Array
	*/
	this.getAllAttributes = function()	{
		return attributes; // All the attirubtes
	};
	
	/*
		GET CELL
		*** Returns the cell for this item
		Parameters:
			None
		Returns:
			DOMElementDiv
	*/
	this.getCell = function()	{
		return cell;
	};
	
	/*
		GET ID
		*** Returns the id for this item
		Parameters:
			None
		Returns:
			String
	*/
	this.getName = function()	{
		return name;
	};
	
	this.getDisplayName = function()	{
		var displayName = (cell.getElementsByTagName('a')[1]) ? cell.getElementsByTagName('a')[1].innerHTML : cell.getElementsByTagName('a')[0].innerHTML;
		return displayName;
	};
	
}

/******************************************/
/*                  LIST                  */
/******************************************/
function NarrowingList( DOMElement, Paginations, cols, rows )	{
	
	var pages = [];
	var currentPage = 0;
	var cellCount = 0;
	
	var realRows = rows;
	
	/*
		ADD CELL
		*** Adds a new dom element to be displayed on the list
		Parameters:
			cell - dom elemen to be added to the list
		Returns:
			Null
	*/
	this.addCell = function( cell )	{
		var lastPage = pages[ pages.length - 1 ]; // Get the last page
		if( !lastPage.addCell( cell ) )	{ // If the cell cant be added is because the page is full
			var newPage = new NarrowingPage( cols, rows ); // Create a new empty page
			DOMElement.appendChild( newPage.getDOMElement() );
			newPage.addCell( cell );
			pages.push( newPage ); // Add it to the array
		}
		cellCount++;
		showLinks();
		selectPage( 0 );
	};
	
	/*
		RESET
		*** Deletes the list HTML , resets the pages array and the cell count
		Parameters:
			None
		Returns:
			Null
	*/
	this.reset = function()	{
		DOMElement.innerHTML = ''; // Removes the element html
		var newPage = new NarrowingPage( cols, rows ); // Create the first page
		DOMElement.appendChild( newPage.getDOMElement() ); // Put it into the element
		pages = [ newPage ]; // And add it to the array
		cellCount = 0; // Reset cell count
	};
	
	/*
		CHANGE STATUS
		*** Changes the text in the status bar of the pagination
		Parameters:
			code - the text that will appear in the status bar
		Returns:
			Null
	*/
	this.changeStatus = function( code )	{
		for( var i = 0; i < Paginations.length; i++ )	{
			Paginations[i].changeStatus( code );
		}
	};
	
	/*
		SET SHOW ALL
		*** Breaks the rows limit and displays all the items in the list
		Parameters:
			None
		Returns:
			Null
	*/
	this.setShowAll = function()	{
		rows = 999999;
	};
	
	/*
		REMOVE SHOW ALL
		*** Takes back the original page size
		Parameters:
			None
		Returns:
			Null
	*/
	this.removeShowAll = function()	{
		rows = realRows;
	};
	
	/*
		SHOW LINKS
		*** Set the links in the pagination for the current pages
		Parameters:
			None
		Returns:
			Null
	*/
	function showLinks()	{
		for( var i = 0; i < Paginations.length; i++ )	{
			Paginations[i].showLinks( pages.length, selectPage, nextPage, previousPage ); // Send the parameters to the pagination
		}
	}
	
	/*
		SELECT PAGE
		*** Shows the page by a given index and changes the pagination
		Parameters:
			pageNumber - number of the page to be selected
		Returns:
			Null
	*/
	function selectPage( pageNumber )	{
		for( var i = 0; i < Paginations.length; i++ )	{
			Paginations[i].selectPage( pageNumber ); // Set the selected link in the pagination
		}
		currentPage = pageNumber; // Set the current page to the number from the parameter
		for( var i = 0; i < pages.length; i++ )	{ // Pass through pages
			if( i == pageNumber )	{ // And if the page in this iteration matches the parameter
				pages[i].show(); // Show the page
			}
			else	{ // If not
				pages[i].hide(); // Hides
			}
		}
		updateStatus();
	}
	
	/*
		UPDATE STATUS
		*** Chenges the code inside the pagination's status bar to show the items that are being displayed
		Parameters:
			None
		Returns:
			Null
	*/
	function updateStatus()	{
		var pageCellCount = pages[ currentPage ].countCells();
		var from = ( ( rows * cols ) * currentPage ) + 1;
		var to = ( from + pageCellCount ) - 1;
		if( cellCount == 1 )	{
			var newStatus = 'Item 1';
		}
		else if( cellCount == 2 && ( rows * cols ) == 2 )	{
			var newStatus = 'Items 1 and 2';
		}
		else if( pageCellCount == 1 )	{
			var newStatus = 'Item ' + from + ' out of ' + cellCount;
		}
		else if( pageCellCount == 2 )	{
			var newStatus = 'Items ' + from + ' and ' + to + ' out of ' + cellCount;
		}
		else	{
			var newStatus = 'Items from ' + from + ' to ' + to + ' out of ' + cellCount;
		}
		for( var i = 0; i < Paginations.length; i++ )	{
			Paginations[i].changeStatus( newStatus );
		}
	}
	
	/*
		NEXT PAGE
		*** Select the page that corresponds to the index + 1
		Parameters:
			None
		Returns:
			Null
	*/
	function nextPage()	{
		var nextPageIndex = currentPage + 1; // Auxiliar variable for next page index
		if( nextPageIndex != pages.length )	{ // If there is a next page
			currentPage = nextPageIndex; // Set the current page to the next page
			selectPage( currentPage ); // And select the page
		}
	}
	
	/*
		PREVIOUS PAGE
		*** Select the page that corresponds to the index - 1
		Parameters:
			None
		Returns:
			Null
	*/
	function previousPage()	{
		var previousPageIndex = currentPage - 1; // Auxiliar variable for the previous page
		if( previousPageIndex != -1 )	{ // If the index is not less than 0
			currentPage = previousPageIndex; // Set the current page to the prvious
			selectPage( currentPage ); // And select the page
		}
	}
	
	// Add one page to the list when returning the object
	var newPage = new NarrowingPage( cols, rows );
	DOMElement.appendChild( newPage.getDOMElement() );
	pages.push( newPage );
	
	return this;
	
}

/******************************************/
/*                 LOADER                 */
/******************************************/
function NarrowingLoader( Configuration )	{
	
	var List = new NarrowingList( Configuration.getList(), Configuration.getPagination(), Configuration.getPageSize().cols, Configuration.getPageSize().rows ); // List object
	var Pannel = new NarrowingPannel( Configuration.getPannel() ); // Pannel object
	
	var DOMController = new NarrowingDOMController(); // Domain controller
	var UIController = new NarrowingUIController( DOMController, List, Pannel ); // UI controller
	
	var requestCount = -1; // Use this to count how many request I have done
	
	var filtersMultiple = Configuration.getMultipleFilters(); // List of special multiple filters
	var filtersRange = Configuration.getRangeFilters(); // List of special range filters
	
	/*
		INIT
		*** This method will start all the feature
		Parameters:
			None
		Returns:
			Null
	*/
	this.init = function()	{
		UIController.changeStatus( 'Loading...' );
		var links = getPageLinks(); // Get the links of netsuite
		savePage( document.body.innerHTML );
		updateStatus( links.length );
		if( window.addEventListener )	{ // For not internet explorer browsers
			NS_getPagesHTML( links );
		}
		else if( window.attachEvent )	{ // For internet explorer
			IE_getPagesHTML( links );
		}
	};
	
	/*
		SORT BY NAME LINK
		*** Identifies a DOM element as a sort by name link and sets the proper event listeners
		Parameters:
			DOMElement - sort by name link
		Returns:
			Null
	*/
	this.sortByNameLink = function( DOMElement )	{
		UIController.sortByNameLink( DOMElement );
	};
	
	/*
		SORT BY PRICE LINK
		*** Identifies a DOM element as a sort by price link and sets the proper event listeners
		Parameters:
			DOMElement - sort by price link
		Returns:
			Null
	*/
	this.sortByPriceLink = function( DOMElement )	{
		UIController.sortByPriceLink( DOMElement );
	};
	
	/*
		VIEW ALL ITEMS LINK
		*** Identifies a DOM element as a view all items link and sets the proper event listeners
		Parameters:
			DOMElement - view all items link
		Returns:
			Null
	*/
	this.viewAllItemsLink = function( DOMElement )	{
		UIController.viewAllItemsLink( DOMElement );
	};
	
	/*
		GET PAGE LINKS
		*** Gets from the DOM the links that need to be revised
		Parameters:
			None
		Returns:
			Array
	*/
	function getPageLinks()	{
		var returnLinks = []; // These are the links that will be returned this page included
		var addedString = ''; // Auxiliar variable to check if the urls are already added
		var links = document.getElementsByTagName( 'a' ); // Get all the links from the DOM
		for( var i = 0; i < links.length; i++ )	{ // Fro each link
			if( links[i].href.indexOf( 'range=' ) != -1  )	{ // If it is a pagination link, doesnt have images and is not in the aux string
				links[i].parentNode.parentNode.style.display = 'none';
				if( links[i].getElementsByTagName( 'img' ).length == 0 && addedString.indexOf( ';' + links[i].href + ';' ) == -1 )	{
					returnLinks.push( links[i].href ); // Add to the array
					addedString += ';' + links[i].href + ';'; // And to the string
				}
			}
		}
		return returnLinks; // Return the links
	}
	
	/*
		NS GET PAGES HTML
		*** Runs all ajax requests at the same time and gets the code where the items are
		Parameters:
			links - the links that need to be revised
		Returns:
			Null
	*/
	function NS_getPagesHTML( links )	{
		for( var i = 0; i < links.length; i++ )	{ // For each one of the links
			var ajax = new AJAX(); // Create an AJAX object
			ajax.open( 'GET', links[i], true ); // And go search the page
			ajax.onreadystatechange = function()	{ // Set save page as callback method
				if( this.readyState == 4 )	{
					savePage( this.responseText );
					updateStatus( links.length );
				}
			};
			ajax.send( null ); // Then send
		}
	}
	
	/*
		IE GET PAGES HTML
		*** Runs one ajax request at a time. Internet Explorer only
		Parameters:
			None
		Returns:
			Null
	*/
	function IE_getPagesHTML( links )	{
		if( requestCount != links.length )	{ // If the pages retrieved quantity is less than the one we need
			var ajax = new AJAX(); // Create an AJAX object
			ajax.open( 'GET', links[ requestCount ], true ); // And go search the page
			ajax.onreadystatechange = function()	{ // Set save page as callback method
				if( this.readyState == 4 )	{
					savePage( this.responseText );
					updateStatus( links.length );
					IE_getPagesHTML( links ); // When we get a page we call the method again
				}
			};
			ajax.send( null ); // Then send
		}
	}
	
	/*
		SAVE PAGE
		*** Gets the items from a plain text page and sends them to the dom controller
		Parameters:
			html - page html that came from an ajax request
		Returns:
			Null
	*/
	function savePage( html )	{
		var temp = document.createElement( 'div' ); // Create a new DOM element to deposit the code
		temp.innerHTML = html; // Put the code
		var itemNodes = temp.getElementsByTagName( 'item' ); // Get all the item nodes inside that element
		for( var i = 0; i < itemNodes.length; i++ )	{ // Then for each item node
			
			var name = itemNodes[i].getAttribute( 'name' );
			var cell = getElementByIdFromResponse( temp, name ); // Get the cell using the appropiate method
			var itemObject = new NarrowingItem( name, cell );
			
			var attributes = itemNodes[i].getAttribute( 'fields' ).split( ']' );
			for( var j = 0; j < ( attributes.length - 1 ); j++ )	{
				var attname = attributes[j].split( ':' )[0].substring( 1 );
				var attvalues = parseAttribute( attname, attributes[j].split( ':' )[1] );
				for( var k = 0; k < attvalues.length; k++ )	{
					itemObject.addAttribute( attname, attvalues[k] );
				}
			}
			
			DOMController.addItem( itemObject );
			
		}
		UIController.update(); // Update the ui
	}
	
	/*
	*/
	function updateStatus( maxRequests )	{
		requestCount++;
		if( requestCount == maxRequests )	{
		}
		else if( DOMController.getAppliedFilters().length == 0 )	{
			UIController.changeStatus( 'Loading... ' + Math.round( ( 100 / maxRequests ) * requestCount ) + '%' );
		}
	}
	
	/*
		PARSE ATTRIBUTE
		*** Checks if the attirubte that came by parameter is a special attribute
		Parameters:
			name - name of the attribute to check
			value - value of the attribute, this will vary if the attribute is special
		Returns:
			Object
	*/
	function parseAttribute( name, value )	{
		if( isMultiple( name ) )	{
			return value.split( ':' );
		}
		else if( isRange( name ) )	{
			var range = getRange( name );
			return [ parseRange( range, value ) ];
		}
		else	{
			return [ value ];
		}
	}
	
	/*
		GET ELEMENT BY ID FROM RESPONSE
		*** Given the texte response from an ajax returns a DOM element matching the id parameter
		Parameters:
			id - the id of the element that will be returned
			response - a node outside the DOM
		Returns:
			DOM Element
	*/
	function getElementByIdFromResponse( response, id )	{
		var elements = response.getElementsByTagName( '*' );
		for( var i = 0; i < elements.length; i++ )	{
			if( elements[i].id == id )	{
				return elements[i];
			}
		}
	}
	
	/*
		GET NUMBER
		*** Given a string return a float
		Parameters:
			string - string to be parsed
		Returns:
			Float
	*/
	function getNumber( string )	{
		var valid = '.0123456789';
		var newString = '0';
		for( var i = 0; i < string.length; i++ )	{
			if( valid.indexOf( string.charAt( i ) ) != -1 )	{
				newString += string.charAt( i );
			}
		}
		return parseFloat( newString );
	}
	
	/* SPECIAL FILTER MANAGEMENT */
	
	/*
		IS MULTIPLE
		*** Returns a boolean depending if a multiple filter exists or not
		Parameters:
			name - name of the multiple value we are looking for
		Returns:
			Boolean
	*/
	function isMultiple( name )	{
		for( var i = 0; i < filtersMultiple.length; i++ )	{
			if( filtersMultiple[i].name == name )	{
				return true;
			}
		}
		return false;
	}
	
	/*
		IS RANGE
		*** Returns a boolean depending if a range filter exists or not
		Parameters:
			name - name of the range value we are looking for
		Returns:
			Boolean
	*/
	function isRange( name )	{
		for( var i = 0; i < filtersRange.length; i++ )	{
			if( filtersRange[i].name == name )	{
				return true;
			}
		}
		return false;
	}
	
	/*
		GET RANGE
		*** Given a name returns a range filter that matches
		Parameters:
			name - name of the multiple value we are looking for
		Returns:
			[ Object || Null ]
	*/
	function getRange( name )	{
		for( var i = 0; i < filtersRange.length; i++ )	{
			if( filtersRange[i].name == name )	{
				return filtersRange[i];
			}
		}
	}
	
	/*
		PARSE RANGE
		*** Transforms the value into a range formatted new value
		Parameters:
			range - range object
			value - string value like $1,300.20
		Returns:
			String
	*/
	function parseRange( range, value )	{
		value = ( value.indexOf( '<span' ) != -1 ) ? value.split( '>' )[1].split( '-' )[0] : value;
		value = getNumber( value );
		if( value != 0 )	{
			for( var i = 0; i < 6000; i += range.interval )	{
				if( i <= value && ( i + range.interval ) > value )	{
					value = range.before + i + ' - ' + range.before + ( (i-1) + range.interval ) + range.after;
					return value;
				}
			}
		}
		return '$1500+';
	}
	
	return this;
	
}

/******************************************/
/*                  PAGE                  */
/******************************************/
function NarrowingPage( cols, rows )	{
	
	var cellCount = 0;
	var DOMElement = document.createElement( 'div' );
	
	/*
		GET DOM ELEMENT
		*** Returns the page element with the table inside
		Parameters:
			None
		Returns:
			DOMElementDiv
	*/
	this.getDOMElement = function()	{
		return DOMElement;
	};
	
	/*
		ADD CELL
		*** Adds a new table cell to the pages dom element checking the number of columns and rows
		Parameters:
			cell - dom element to add inside the new table cell
		Returns:
			Boolean
	*/
	this.addCell = function( cell )	{
		if( cellCount == ( rows * cols ) )	{ // If the quantity of cells already added overpasses the number of items to display 
			return false; // Return false
		}
		else	{ // If theres room for more items
			if( tr.cells.length == cols )	{ // If the last TR element has reached the max number of columns replace it for a new empty one
				tr = table.insertRow( table.rows.length ); // Add a new TR element at the end of the table
			}
			var td = tr.insertCell( tr.cells.length ); // Add a new TD element
			td.appendChild( cell.cloneNode( true ) ); // Add a COPY of the cell to the TD element
			cellCount++; // Count one more item added
			return true;
		}
	};
	
	/*
		COUNT CELLS
		*** Returns the number of cells that are being displayed in this page
		Parameters:
			None
		Returns:
			Integer
	*/
	this.countCells = function()	{
		return cellCount;
	};
	
	/*
		SHOW
		*** Shows the page and allows having a transition effect
		Parameters:
			None
		Returns:
			Null
	*/
	this.show = function()	{
		DOMElement.className = 'list-page list-page-block';
	};
	
	/*
		HIDE
		*** Hides the page and allows having a transition effect
		Parameters:
			None
		Returns:
			Null
	*/
	this.hide = function()	{
		DOMElement.className = 'list-page list-page-none';
	};
	
	// Create, style and append the table and one row when returning the object
	var table = document.createElement( 'table' );
	var tr = table.insertRow( 0 );
	DOMElement.appendChild( table );
	DOMElement.className = 'list-page';
	
	return this;
	
}

/******************************************/
/*               PAGINATION               */
/******************************************/
function NarrowingPagination( DOMElement )	{
	
	var linkList = [];
	var linkContainer = document.createElement( 'div' );
	var statusBar = document.createElement( 'div' );
	
	/*
		SHOW LINKS
		*** Displays links for a given number of pages. Throws the links inside linkContainer element
		Parameters:
			pageCount - number of links to display
			selectPageMethod - function to be called when a link is clicked
			nextPageMethod - function to be called when the next page link is clicked
			previousPageMethod - function to be called when the previous page link is clicked
		Returns:
			Null
	*/
	this.showLinks = function( pageCount, selectPageMethod, nextPageMethod, previousPageMethod )	{
		linkContainer.innerHTML = ''; // Reset the container HTML
		linkList = []; // Reset the link list
		
		var a = document.createElement( 'a' ); // Create link DOM element for the previous page button
		a.href = 'javascript:void(0)'; // Disable link
		a.className = 'pagination-link pagination-link-previous'; // Set class for formatting
		a.innerHTML = 'Previous'; // Set HTML
		a.onclick = previousPageMethod; // Add onclick event to change the page
		linkContainer.appendChild( a ); // Append the link to the container
		
		for( var i = 0; i < pageCount; i++ )	{
			var a = document.createElement( 'a' ); // Create link DOM element
			a.href = 'javascript:void(0)'; // Disable link
			a.className = 'pagination-link page'; // Set class for formatting
			a.innerHTML = ( i + 1 ); // Set HTML for displaying the page number
			a.pageNumber = i; // Set the number of page that the link corresponds to
			a.onclick = function()	{ // Add onclick event to change the page
				selectPageMethod( this.pageNumber );
			};
			linkContainer.appendChild( a ); // Append the link to the container
			linkList.push( a );
		}
		
		var a = document.createElement( 'a' ); // Create link DOM element for the next page button
		a.href = 'javascript:void(0)'; // Disable link
		a.className = 'pagination-link pagination-link-next'; // Set class for formatting
		a.innerHTML = 'Next'; // Set HTML
		a.onclick = nextPageMethod; // Add onclick event to change the page
		linkContainer.appendChild( a ); // Append the link to the container
		
		this.selectPage( 0 ); // Show the first link as selected
	};
	
	/*
		SELECT PAGE
		*** Changes the layout of the of the page selected
		Parameters:
			pageNumber - number of the page selected (corresponds to the page link index inside the array)
		Returns:
			Null
	*/
	this.selectPage = function( pageNumber )	{
		for( var i = 0; i < linkList.length; i++ )	{
			if( i == pageNumber )	{
				linkList[i].className = 'pagination-link pagination-link-page pagination-link-selected'; // Set class for selected
			}
			else	{
				linkList[i].className = 'pagination-link pagination-link-page'; // Set class for not selected
			}
		}
	};
	
	/*
		CHANGE STATUS
		*** Changes the HTML inside the status bar
		Parameters:
			code - HTML code that will be displayed
		Returns:
			Null
	*/
	this.changeStatus = function( code, onOffLink )	{
		statusBar.innerHTML = code;
		if( onOffLink )	{
			statusBar.appendChild( onOffLink );
		}
	};
	
	// Deploy and style the necessary dom elements when returning the element
	linkContainer.className = 'pagination-link-container';
	statusBar.className = 'pagination-status';
	DOMElement.appendChild( linkContainer );
	DOMElement.appendChild( statusBar );
	
	return this;
	
}

/******************************************/
/*                 PANNEL                 */
/******************************************/
function NarrowingPannel( DOMElement )	{
	
	var boxes = [];
	
	/*
		SHOW FILTER BOXES
		*** Adds all the filters to the pannel and the onclick events for applying and removing
		Paramaters:
			allFilters - an array with all the available filters including applied
			appliedFitlers - an array with only the applied filters
			applyFilterMethod - the method to apply a filter
			removeFilterMethod - the method to remove a filter
		Returns:
			Null
	*/
	this.showFilterBoxes = function( allFilters, appliedFilters, applyFilterMethod, removeFilterMethod )	{
		allFilters.sort(); // Sort the items inside the filters to get them in order on the layout
		for( var i = 0; i < allFilters.length; i++ )	{ // For each one on the filters
			var box = getBox( allFilters[i].getName() ) || addBox( allFilters[i].getName() ); // Get the box for putting the link inside
			var select = box.getElementsByTagName( 'select' )[0];
			select.filterName = box.filterName;
			
			var a = document.createElement( 'option' ); // Create the link dom element
			a.filterName = allFilters[i].getName(); // Set the current filter name as an attribute for the dom element
			a.filterValue = allFilters[i].getValue(); // As well as the value
//			a.href = 'javascript:void(0)'; // Disable the link
			a.innerHTML = allFilters[i].getValue() /*+ ' <span>(' + allFilters[i].getItemCount() + ')</span>'; // Set the HTML to the filter value*/
			a.className = 'pannel-link'; // Set a class name to manage layout outside the scripts
			
			select.onchange = function()	{ // Set the onclick event to the apply filter method. If it must have the remove method it will be added in the boxLookover method
				if( this.selectedIndex == 0 )	{
					removeFilterMethod( this.filterName );
				}
				else	{
					applyFilterMethod( this.options[ this.selectedIndex ].filterName, this.options[ this.selectedIndex ].filterValue );
				}
			};
			select.appendChild( a ); // Add to the box
		}
		boxLookover( appliedFilters, removeFilterMethod );
		appendBoxes();
	};
	
	/*
		RESET
		*** Deletes the pannel HTML and resets the boxes array
		Parameters:
			None
		Returns:
			Null
	*/
	this.reset = function()	{
		DOMElement.innerHTML = '';
		boxes = [];
	};
	
	/*
		BOX LOOKOVER
		*** Basically checks the boxes that have only one link and sets the remove method or a class poiting that is a came-along filter
		Parameters:
			appliedFilters - the array with applied filters in the system
			removeFilterMethod - the method to remove a filter
		Returns:
			Null
	*/
	function boxLookover( appliedFilters, removeFilterMethod )	{
		for( var i = 0; i < boxes.length; i++ )	{
			var select = boxes[i].getElementsByTagName( 'select' )[0];
			var links = boxes[i].getElementsByTagName( 'option' );
			if( links.length == 2 )	{
				if( filterIsApplied( appliedFilters, links[1].filterName ) )	{
					select.selectedIndex = 1;
					boxes[i].className = 'pannel-box pannel-box-remove';
				}
				else	{
					boxes[i].className = 'pannel-box pannel-box-lonely';
					select.removeChild( links[0] );
				}
			}
		}
	}
	
	/*
		APPEND BOXES
		*** Puts all the boxes sorted by name into the pannel element
		Parameters:
			None
		Returns:
			Null
	*/
	function appendBoxes()	{
		boxes.sort(); // Sorts the boxes by name (NOT WORKING IN INTERNET EXPLORER TO STRING NOT BEING USED)
		for( var i = 0; i < boxes.length; i++ )	{
			DOMElement.appendChild( boxes[i] ); // Append each box
		}
	}
	
	/*
		GET BOX
		*** Returns a box if exists inside the array if not it returns false
		Parameters:
			name - box name to be found (key)
		Returns:
			[ DOMElement || false ]
	*/
	function getBox( name )	{
		for( var i = 0; i < boxes.length; i++ )	{ // Go through all the boxes
			if( boxes[i].filterName == name )	{ // If the box name matches the parameter
				return boxes[i]; // Return the box
			}
		}
		return false; // If the script gets to this point return false
	}
	
	/*
		ADD BOX
		*** Creates a dom element for a box and adds it to the box array. Also creates a heading
		Parameters:
			name - name to be assigned to the box
		Returns:
			Null
	*/
	function addBox( name )	{
		var box = document.createElement( 'div' ); // Create new dom element for a box
		var heading = document.createElement( 'h6' ); // Create a heading for the box
		
		var select = document.createElement( 'select' );
		var option = document.createElement( 'option' );
		option.innerHTML = 'Show All ' + name + 's'; 
		select.appendChild( option );
		
		heading.innerHTML = name; // Set the HTML for the heading, same as box name
		heading.className = 'pannel-box-heading'; // Set a class name to be formatted outside the script
		box.filterName = name; // Set box name as an attribute for the dom element
		box.className = 'pannel-box'; // Set a class name to be formatted outside the script
		box.appendChild( heading ); // Put the heading inside the box
		
		box.appendChild( select ); // Put the heading inside the box
		
		box.toString = function()	{ // Set the toString method to be used when sorting (NOT WORKING IN INTERNET EXPLORER)
			return this.filterName;
		};
		boxes.push( box ); // Add the box to the boxes array
		return box; // Return the box
	}
	
	/*
		FILTER IS APPLIED
		*** Returns a boolean depending if a filter given is inside an array given
		Parameters:
			list - array filled of filters to be checked
			filter - filter to be found inside the array
		Returns:
			Boolean
	*/
	function filterIsApplied( list, filterName )	{
		for( var i = 0; i < list.length; i++ )	{ // Go through all the items of the array
			if( list[i].getName() == filterName )	{ // If the list item's name is matching the filter name
				return true; // Returns true
			}
		}
		return false; // If not returns false
	}
	
}

/******************************************/
/*                   UI                   */
/******************************************/
function NarrowingUIController( DOMController, List, Pannel )	{
	
	var currentSort = 'name';
	
	/*
		UPDATE
		*** Gets all objects again and restarts the display
		Parameters:
			None
		Returns:
			Null
	*/
	this.update = function()	{
		updateList( currentSort );
		updatePannel();
	};
	
	/*
		CHANGE STATUS
		*** Changes the text in the status bar of the pagination
		Parameters:
			code - the text that will appear in the status bar
		Returns:
			Null
	*/
	this.changeStatus = function( code )	{
		List.changeStatus( code );
	};
	
	/*
		VIEW ALL ITEMS LINK
		*** Identifies a DOM element as a view all items link and sets the proper event listeners
		Parameters:
			DOMElement - view all items link
		Returns:
			Null
	*/
	this.viewAllItemsLink = function( DOMElement )	{
		DOMElement.innerHTML = 'Turn Off';
		DOMElement.href = 'javascript:void(0)';
		DOMElement.onclick = function()	{
			showAllItems( DOMElement );
		};
	};
	
	/*
		SHOW ALL ITEMS
		*** Breaks the rows limit and displays all the items in the list
		Parameters:
			DOMElement - show all items link
		Returns:
			Null
	*/
	function showAllItems( DOMElement )	{
		List.setShowAll();
		updateList( currentSort );
		DOMElement.innerHTML = 'Turn On';
		DOMElement.onclick = function()	{
			showPaginatedItems( DOMElement );
		};
	}
	
	/*
		SHOW PAGINATED ITEMS
		*** Resets the page size and updates
		Parameters:
			DOMElement - show all items link
		Returns:
			Null
	*/
	function showPaginatedItems( DOMElement )	{
		List.removeShowAll();
		updateList( currentSort );
		DOMElement.innerHTML = 'Turn Off';
		DOMElement.onclick = function()	{
			showAllItems( DOMElement );
		};
	}
	
	/*
		UPDATE LIST
		*** Gets the cells again and restarts the list
		Parameters:
			None
		Returns:
			Null
	*/
	function updateList( sortCriteria )	{
		List.reset();
		var items = DOMController.getItems( sortCriteria );
		for( var i = 0; i < items.length; i++ )	{
			List.addCell( items[i].getCell() );
		}
	}
	
	/*
		UPDATE PANNEL
		*** Gets the filters again and restarts the pannel
		Parameters:
			None
		Returns:
			Null
	*/
	function updatePannel()	{
		Pannel.reset();
		var allFilters = DOMController.getAllFilters();
		var appliedFilters = DOMController.getAppliedFilters();
		var applyFilterMethod = applyFilter;
		var removeFilterMethod = removeFilter;
		Pannel.showFilterBoxes( allFilters, appliedFilters, applyFilterMethod, removeFilterMethod );
	}
	
	/*
		APPLY FILTER
		*** Calls the methods in the dom controller and updates the list and pannel
		Parameters:
			name - name of the filter to apply
			value - value of the filter to apply
		Returns:
			Null
	*/
	function applyFilter( name, value )	{
		DOMController.applyFilter( name, value );
		updateList( currentSort );
		updatePannel();
	}
	
	/*
		REMOVE FILTER
		*** Calls the methods in the dom controller and updates the list and pannel
		Parameters:
			name - name of the filter to remove
		Returns:
			Null
	*/
	function removeFilter( name )	{
		DOMController.removeFilter( name );
		updateList( currentSort );
		updatePannel();
	}
	
}