PivotTable = function(options)
{
	// PRIVATE VARIABLES
	var $FilterDiv, $ResultDiv;
	var _this = this;
	var oMetricRow;
	var oInterface = new Object();
	var aoDimensionRow = new Array();
	var nMaxRow;
	var nMaxCol;
	var aoSelectedColumns = new Array();
	var aoSelectedRows = new Array();
	var xhr;
	var sChart = undefined;										// which Chart was visible
	var sChartsContainer = "olaptable_tab_content_container";		// css class of charts container

	// PRIVATE FUNCTIONS
	
	function init()
	{
		options = $.extend({}, PivotTable.defOptions, options);
		
		$FilterDiv = $('#' + options.filterDiv);
		$ResultDiv = $('#' + options.resultDiv);
		
		//maximum number of rows and cols in selection
		nMaxRow = options.maxRowsInSelection;
		nMaxCol = options.maxColumnsInSelection;
		
		createFilterDOM();

		$.each(aoDimensionRow, function(index, oDimensionRow)
		{
			if (oDimensionRow.isRowSelected())
			{
				aoSelectedRows.push(oDimensionRow);
			}
			else if (oDimensionRow.isColumnSelected())
			{
				aoSelectedColumns.push(oDimensionRow);
			}
		});

		if (oMetricRow.isRowSelected())
		{
			aoSelectedRows.push(oMetricRow);
		}
		else if (oMetricRow.isColumnSelected())
		{
			aoSelectedColumns.push(oMetricRow);
		}
		
		//oOptions migrate oSetConfig
		
		var oSetConfig = new Object();
		oSetConfig.rows = options.filterData.defRow;
		oSetConfig.columns = options.filterData.defColumn;
		oSetConfig.filters = new Object();
		oSetConfig.filters.dims = new Array();
		
		$.each(options.filterData.dims, function(index, dim){
			var oTempObj = new Object();
			
			if(dim.defGroup){
				oTempObj.group = dim.defGroup;
			}else{
				oTempObj.group = dim.groups[0].name;
			}
			
			oTempObj.name = dim.name;
			
			oTempObj.options = new Array();
			
			if(dim.defGroup){
				$.each(dim.groups, function(index, group){
					if(dim.defGroup == group.name){
						$.each(group.options, function(index, option){
							oTempObj.options.push(option.code);
						});
					}
				});
			}else{
				$.each(dim.groups[0].options, function(index, option){
					oTempObj.options.push(option.code);
				});
			}
			
			oSetConfig.filters.dims.push(oTempObj);
			
		});
		
		oSetConfig.filters.metrics = options.filterData.metrics;
		
		_this.setConfig(oSetConfig);

	}
	
	function getDimRow(sDimensionName){
		var oRow = undefined;
		$.each(aoDimensionRow, function(index, oDimensionRow){
			var $Row = oDimensionRow.get$Tr();
			if($Row.data('name') == sDimensionName){
				oRow = oDimensionRow;
			}
		});
		
		return oRow;
		
	}
	
	/*
	 * send filter data
	 */
	function sendFilterData()
	{
		var bMetricRowSelected = false;
		var bMetricColSelected = false;
		
		if (!oMetricRow || aoDimensionRow.length == 0)
		{
			return;
		}
		else if (aoSelectedRows.length == 0 || aoSelectedColumns.length == 0)
		{
			return;
		}

		var oAjaxObj = {};
        
        if (options.filterData.ajaxParams)
        {
            if(typeof options.filterData.ajaxParams == 'function') 
            {
                oAjaxObj = options.filterData.ajaxParams ();
            }
            else
            {
                oAjaxObj = $.extend({}, options.filterData.ajaxParams);
            }
        }
            
		// selectd row and col value should assign here.
		oAjaxObj.rows = new Array();
		$.each(aoSelectedRows, function(index, oSelectedRow)
		{
			if(oSelectedRow.get$Tr().hasClass(PivotTable.oCSSClassess.metricRow))
			{
				bMetricRowSelected = true;
			}
			oAjaxObj.rows.push(oSelectedRow.get$Tr().data('name'));
		});

		oAjaxObj.columns = new Array();
		$.each(aoSelectedColumns, function(index, oSelectedColumn)
		{
			if(oSelectedColumn.get$Tr().hasClass(PivotTable.oCSSClassess.metricRow))
			{
				bMetricColSelected = true;
			}
			oAjaxObj.columns.push(oSelectedColumn.get$Tr().data('name'));
		});
		
		if (oAjaxObj.row == '' || oAjaxObj.column == '')
		{
			alert(PivotTable.osText.chooseRowCol);
			return;
		}

		oAjaxObj.filters = new Object();
		oAjaxObj.filters.dims = new Array();

		$.each(aoDimensionRow, function(index, oDimensionRow)
		{
			oAjaxObj.filters.dims.push(oDimensionRow.getFilterData());
		});

		oAjaxObj.filters.metrics = oMetricRow.getFilterData();

		oAjaxObj.filters = JSON.stringify(oAjaxObj.filters);
		oAjaxObj.rows = JSON.stringify(oAjaxObj.rows);
		oAjaxObj.columns = JSON.stringify(oAjaxObj.columns);
		
		// charts
		var $ChartsContainer = $('.' + sChartsContainer, $ResultDiv);
		if($ChartsContainer.length)
		{
			if($ChartsContainer.hasClass('line'))
				sChart = "line";
			else if($ChartsContainer.hasClass('pie'))
				sChart = "pie";
                        else if($ChartsContainer.hasClass('bar'))
				sChart = "bar";
                        else if($ChartsContainer.hasClass('stack_bar'))
				sChart = "stack_bar";
			else 
				sChart = undefined;
		};
		
		//abort previous ajax request if it is not done yet
		if(xhr && xhr.readystate != 4){
			xhr.abort();
		}

		//send new ajax request to the server
		xhr = $.ajax({
			url : options.filterData.ajaxURL,
			data : oAjaxObj,
			cache : false,
			error : function(jqXHR, textStatus, erroThrown)
			{
				//if request is aborted then alert will not displayed.
				if(textStatus!='abort'){
					alert(PivotTable.osText.error);
				}
			},
			success : function(data)
			{
				$ResultDiv.empty().html(data);
				
				switch (sChart){
				case "line" :
					var $Dashboard = $ResultDiv.closest('.dashboard');
					$('a.line_chart', $Dashboard).trigger('click');
					break;
				case "pie":
					var $Dashboard = $ResultDiv.closest('.dashboard');
					$('a.pie_chart', $Dashboard).trigger('click');
					break;
                                case "bar":
					var $Dashboard = $ResultDiv.closest('.dashboard');
					$('a.bar_chart', $Dashboard).trigger('click');
					break;
                                case "stack_bar":
					var $Dashboard = $ResultDiv.closest('.dashboard');
					$('a.stack_bar_chart', $Dashboard).trigger('click');
					break;
				}
			}
		});
		return false;
	}

	/*
	 * create Dom for UI
	 */
	function createFilterDOM()
	{

		// create array of dimension rows object
		var oDim = new Object();
		$.each(options.filterData.dims, function(index, oOptions)
		{
			oDim.options = oOptions;
			oDim.oInterface = oInterface;
			oDim.$FilterDiv = $FilterDiv;
			oDim.defRow = options.filterData.defRow;
			oDim.defColumn = options.filterData.defColumn;
			aoDimensionRow.push(new PivotTable.DimensionRow(oDim));
		});

		// create object of metric row
		var oMetric = new Object();
		oMetric.metrics = options.filterData.metrics;
		oMetric.oInterface = oInterface;
		oMetric.$FilterDiv = $FilterDiv;
		oMetric.defRow = options.filterData.defRow;
		oMetric.defColumn = options.filterData.defColumn;
		oMetricRow = new PivotTable.MetricRow(oMetric);

		// filter wrapper
		var $FilterWrapper = $('<div class="' + PivotTable.oCSSClassess.filterWrapper + '" />').appendTo($FilterDiv);
		
		//for IE to prevent text selection
		if($.browser.msie){
			disableTextSelectionIE($FilterWrapper[0]);			
		}

		// create filterTable
		var $FilterTable = $('<table cellspacing="0" cellpadding="0" border="0" class="' + PivotTable.oCSSClassess.filterContainer + '"></table>').appendTo($FilterWrapper);

		// append header row to filterTable
		var $HeaderRow = $('<tr class="' + PivotTable.oCSSClassess.titleRow + '"/>').appendTo($FilterTable);

		$('<td class="' + PivotTable.oCSSClassess.dimCell + '"></td>').appendTo($HeaderRow);
		$('<td class="' + PivotTable.oCSSClassess.optionsCell + '">' + PivotTable.osText.optionTitle + '</td>').appendTo($HeaderRow);
		$('<td class="' + PivotTable.oCSSClassess.selectionCell + '"/>').appendTo($HeaderRow);

		// append dimension rows to filterTable
		$.each(aoDimensionRow, function(index, oDimensionRow)
		{
			$FilterTable.append(oDimensionRow.get$Tr());
		});

		// append metric row to filterTable
		$FilterTable.append(oMetricRow.get$Tr());

	}
	;

	// PUBLLIC FUNCTIONS

	/**
	 * disable text selection for IE as it does support it via css
	 */
	function disableTextSelectionIE(target){

	    if (typeof target.onselectstart!="undefined"){
	    	target.onselectstart = function(){return false;};
	    }
	        
	    target.style.cursor = "default";
	}
	/**
	 * adds oRow in current selected Rows.
	 * 
	 * @param oRwo -
	 *            object of MetricRow or DimRow which is to be added in row
	 *            selection.
	 */
	function addRowInSelection(oRow)
	{
		if (oRow.isRowSelected())
		{
			return;
		}
		// if row is already selected as column then it will deselect as row and
		// selected as row
		if (oRow.isColumnSelected())
		{
			oRow.setColumnSelected(false);
			aoSelectedColumns.splice($.inArray(oRow, aoSelectedColumns), 1);
		}
		
		
		// updateView of lastSelected row if oRow is replacing it.
		if (aoSelectedRows.length == nMaxRow)
		{
			var oLastSelectedRow = aoSelectedRows.pop();
			oLastSelectedRow.setRowSelected(false);
			oRow.setRowSelected(true);
		}else{
			oRow.setRowSelected(true);
		}

		// update Array
		aoSelectedRows.push(oRow);

		// update Data
		sendFilterData();
	}

	/**
	 * remove selection of row as row
	 * 
	 * @param oRow
	 */
	function removeRowFromSelection(oRow)
	{
		oRow.setRowSelected(false);
		aoSelectedRows.splice($.inArray(oRow, aoSelectedRows), 1);
		sendFilterData();
	}

	/**
	 * clears current selection of Rows and adds oRow in selection
	 * 
	 * @param oRow
	 */
	function setSelectedRow(oRow)
	{
		// update UI
		if (oRow.isColumnSelected())
		{
			oRow.setColumnSelected(false);
			aoSelectedColumns.splice($.inArray(oRow, aoSelectedColumns), 1);
		}

		// deselect all row as row in UI
		oMetricRow.setRowSelected(false);
		$.each(aoDimensionRow, function(index, oDimensionRow)
		{
			oDimensionRow.setRowSelected(false);
		});

		// select oRow as selected row
		oRow.setRowSelected(true);

		// when row is selected as column and aoSelectedColumns is not changed then further process escaped.
		if($.inArray(oRow, aoSelectedRows) >= 0 && aoSelectedRows.length == 1){
			return;
		}else{ //when row is selected as column and aoSelectedColumns is changed then further process to be continued..
			aoSelectedRows = new Array();
		}

		// add oRow as selected row into array
		aoSelectedRows.push(oRow);

		// update data
		sendFilterData();
	}

	/**
	 * adds oRow in current selected columns.
	 * 
	 * @param oRow -
	 *            object of MetricRow or DimRow which is to be added in row
	 *            selection.
	 */
	function addColumnInSelection(oRow)
	{
		if (oRow.isColumnSelected())
		{
			return;
		}
		// if row is already selected as column then it will deselect as row and
		// selected as row
		if (oRow.isRowSelected())
		{
			oRow.setRowSelected(false);
			aoSelectedRows.splice($.inArray(oRow, aoSelectedRows), 1);
		}

		// updateView of lastSelected row if oRow is replacing it.
		if (aoSelectedColumns.length == nMaxCol)
		{
			var oLastSelectedColumn = aoSelectedColumns.pop();
			oLastSelectedColumn.setColumnSelected(false);
			oRow.setColumnSelected(true);
		}else{
			oRow.setColumnSelected(true);
		}

		// update array
		aoSelectedColumns.push(oRow);

		// update data
		sendFilterData();
	}

	/**
	 * remove selection of row as column
	 * 
	 * @param oRow
	 */
	function removeColumnFromSelection(oRow)
	{
		oRow.setColumnSelected(false);
		aoSelectedColumns.splice($.inArray(oRow, aoSelectedRows), 1);
		sendFilterData();
	}

	/**
	 * clears current selection of Rows and adds oRow in selection
	 * 
	 * @param oRow
	 */
	function setSelectedColumn(oRow)
	{
		// update UI
		if (oRow.isRowSelected())
		{
			oRow.setRowSelected(false);
			aoSelectedRows.splice($.inArray(oRow, aoSelectedRows), 1);
		}

		// deselect all row as row in UI
		oMetricRow.setColumnSelected(false);
		$.each(aoDimensionRow, function(index, oDimensionRow)
		{
			oDimensionRow.setColumnSelected(false);
		});

		// select oRow as selected row
		oRow.setColumnSelected(true);

		// when row is selected as column and aoSelectedColumns is not changed then further process escaped.   
		if($.inArray(oRow,aoSelectedColumns) >= 0 && aoSelectedColumns.length == 1){
			return;
		}else{//when row is selected as column and aoSelectedColumns is changed then further process to be continued..
			aoSelectedColumns = new Array();
		}

		// add oRow as selecte column into array.
		aoSelectedColumns.push(oRow);

		// update data
		sendFilterData();
	}
	
	//PUBLIC FUNCTIONS
	
	/**
	 * for setting configuration of checkboxes for row, calls row's public function setConfing with passing options as an argument
	 * @param options - it is same as the data passed to the ajax request.
	 * format of the options should be like this as below,
	 * 	columns: Array[2],
		filters: "{"dims":[{"options":["M1","M2","M3"],"name":"Period-1","group":"Months"},{"options":[],"name":"Period-2","group":"Quarters"},{"options":["M1","M2","M3"],"name":"Period-3","group":"Months"},{"options":["G","B"],"name":"Quality","group":null}],"metrics":["Leads","Wins","In Play","Lost"]}",
		rows: Array[1]
	 */
	this.setConfig = function(options){
		sChart = options.chart;
		var aoDimRowTemp = aoDimensionRow.slice(); //temparory array of row object, which is used for this function only.
		
		//call setConfig function of each row object with passing options as an argument.
		$.each(options.filters.dims, function(index, oDim){
			var oRow = getDimRow(oDim.name);
			oRow.setConfig(oDim);
			aoDimRowTemp.splice($.inArray(oRow,aoDimRowTemp), 1);
		});
		
		//call reset function of row which are not matched with given option name.
		if(aoDimRowTemp.length != 0){
			$.each(aoDimRowTemp, function(index, oDimRow){
				oDimRow.reset();
			});
		}
		
		//for metric row
		if(options.filters.metrics.length != 0){
			oMetricRow.setConfig(options.filters.metrics);
		}else{
			oMetricRow.reset();
		}
		
		var nItrate;
		if(options.rows.length != 0){
			//for first row call setSelectedRow
			if(options.rows[0] == PivotTable.osText.metrics){
				setSelectedRow(oMetricRow);
			}else{
				setSelectedRow(getDimRow(options.rows[0]));
			}
		
			//for rest of the rows call addRowInSelection
			
			
			//set itration time for loop
			if(options.rows.length > nMaxRow){
				nItrate = nMaxRow;
			}else{
				nItrate = options.rows.length;
			}
			
			var i;
			for(i=1; i<nItrate; i++){
				if(options.rows[i] == PivotTable.osText.metrics){
					addRowInSelection(oMetricRow);
				}else{
					addRowInSelection(getDimRow(options.rows[i]));
				}
			}
		}else{
			$.each(aoDimensionRow, function(index, oDimensionRow){
				removeRowFromSelection(oDimensionRow);
			});
			removeRowFromSelection(oMetricRow);
		}
		
		if(options.columns.length != 0){
			//for first Column call setSelectedColumn
			if(options.columns[0] == PivotTable.osText.metrics){
				setSelectedColumn(oMetricRow);
			}else{
				setSelectedColumn(getDimRow(options.columns[0]));
			}
			
			//for rest of the columns call addColumnInSelection
			
			//set itration time for loop
			if(options.columns.length > nMaxCol){
				nItrate = nMaxCol;
			}else{
				nItrate = options.columns.length;
			}
			
			var i;
			for(i=1; i<nItrate; i++){
				if(options.columns[i] == PivotTable.osText.metrics){
					addColumnInSelection(oMetricRow);
				}else{
					addColumnInSelection(getDimRow(options.columns[i]));
				}
			}
		}else{
			$.each(aoDimensionRow, function(index, oDimensionRow){
				removeColumnFromSelection(oDimensionRow);
			});
			removeColumnFromSelection(oMetricRow);
		}
		

	};
	

	// interface of functions..
	oInterface = {
		updateData : sendFilterData,
		addRowInSelection : addRowInSelection,
		setSelectedRow : setSelectedRow,
		addColumnInSelection : addColumnInSelection,
		setSelectedColumn : setSelectedColumn,
		removeRowFromSelection : removeRowFromSelection,
		removeColumnFromSelection : removeColumnFromSelection

	};

	init();

};

// match array
PivotTable.matchArray = function(array1, array2)
{
	var flag = true;
	if (array1.length != array2.length)
	{
		flag = false;
	}
	else
	{
		$.each(array1, function(index, value)
		{
			if ($.inArray(value, array2) == -1)
			{
				flag = false;
				return false;
			}
		});
	}
	
	return flag;
	
};

// string constants for UI
PivotTable.osText = {
	optionTitle : 'Filters Applied',
	row : 'Row',
	column : 'Column',
	metrics : 'Metrics',
	btnFilterTxt : 'Generate',
	chooseRowCol : 'Please select any row lor column fields to generate table',
	error : 'Sorry, we could not process your request, please try again later',
	all : 'Select All',
	none : 'Select None',
	chooseMetric : 'You must select Metric Row atleast as Row/Column'

};

//default options for PivtoTable
PivotTable.defOptions = {
	 maxRowsInSelection : 1,
	 maxColumnsInSelection : 1
};

// css class constants
PivotTable.oCSSClassess = {
	filterWrapper : 'filterWrapper',
	filterContainer : 'filterContainer',
	titleRow : 'titleRow',
	dimensionRow : 'dimensionRow',
	metricRow : 'metricRow',
	dimCell : 'dimCell',
	optionsCell : 'optionsCell',
	selectionCell : 'selectionCell',
	rowBox : 'rowBox',
	text : 'text',
	optionContainer : 'optionContainer',
	optionGroup : 'optionGroup',
	chkBox : 'chkBox',
	columnBox : 'columnBox',
	groupBox : 'groupBox',
	rowColWrapper : 'rowColWrapper',
	btnFilter : 'btnFilter',
	closeOptionContainer : 'closeOptionContainer',
	buttonSet : 'buttonSet',
	selected: 'selected',
	selectAll: 'selectAll',
	selectNone: 'selectNone'
};

// metric row class
PivotTable.MetricRow = function(oMetric)
{
	// PRIVATE VARIABLES
	var $Row = undefined;
	var oRowOptions = $.extend({}, oMetric);
	var $OptionCell = undefined;
	var $SelectionCell = undefined, $RowChk = undefined, $ColChk = undefined;
	var nCounter = 0;
	var $OptionContainer = undefined;
	var $SelectAll = undefined;
	var $SelectGroup = undefined;
	var $BtnCloseOptContainer = undefined;
	var aLastFilterData = null;

	var _this = this;

	// PRIVATE FUNCTIONS
	function init()
	{
		// createRow function will call to generate row relevant to array of
		// Metrics and return it back.
		createRow();

		// bindEvent function will call to bind events
		bindEvent();

		// update Row text
		updateRowText();

		
	}

	/*
	 * generated row's jquery object store to $Row of instance
	 */
	function createRow()
	{
		$Row = $('<tr class="' + PivotTable.oCSSClassess.metricRow + '"/>');
		$Row.append('<td class="' + PivotTable.oCSSClassess.dimCell + '">' + PivotTable.osText.metrics + '</td>').data('name', PivotTable.osText.metrics);

		$OptionCell = $('<td class="' + PivotTable.oCSSClassess.optionsCell + '"/>').appendTo($Row);
		$('<div class="' + PivotTable.oCSSClassess.text + '"/>').appendTo($OptionCell);

		$OptionContainer = $('<div class="' + PivotTable.oCSSClassess.optionContainer + '"/>').appendTo($OptionCell);

		var $GroupDiv = $('<div class="' + PivotTable.oCSSClassess.optionGroup + '"/>').appendTo($OptionContainer);

		$.each(oMetric.metrics, function(index, metric)
		{
			var $ChkBox = $('<div class="' + PivotTable.oCSSClassess.chkBox + '"/>').appendTo($GroupDiv);
			$ChkBox.append('<input type="checkbox" value="' + metric + '" name="' + metric + '"/>');
			$ChkBox.append('<label>' + metric + '</label>');
		});

		$BtnCloseOptContainer = $('<span class="' + PivotTable.oCSSClassess.closeOptionContainer + '">x</span>').appendTo($OptionContainer);

		//select all options link prepend to $OptionContainer
		$SelectNone = $('<a class="' + PivotTable.oCSSClassess.selectNone + '"> ' + PivotTable.osText.none + '</a>').prependTo($OptionContainer);
		$SelectAll = $('<a class="' + PivotTable.oCSSClassess.selectAll + '">' + PivotTable.osText.all + '</a>').prependTo($OptionContainer);

		$SelectionCell = $('<td class="' + PivotTable.oCSSClassess.selectionCell + '"/>').appendTo($Row);

		nCounter++;
		var sRowId = 'row' + Math.floor(Math.random() * 10001) + nCounter;
		var sColId = 'col' + Math.floor(Math.random() * 10001) + nCounter;
		$RowChk = $('<a id="' + sRowId + '" />').appendTo($SelectionCell);
		$('<span>' + PivotTable.osText.row + '</span>').appendTo($RowChk);
		$ColChk = $('<a id="' + sColId + '"/>').appendTo($SelectionCell);
		$('<span>' + PivotTable.osText.column + '</span>').appendTo($ColChk);

		$SelectGroup = $GroupDiv;

	}

	// bind necessary events.
	function bindEvent()
	{
		// click event will be bind to second cell of $Row, showOptionContainer
		// is handler of this event
		$OptionCell.click(showOptionContainer);
		
		//bind click event for selectAll and selectNone links
		$SelectAll.click(function(){
			$SelectGroup.find(':checkbox').attr('checked', true);
		});
		
		$SelectNone.click(function(){
			$SelectGroup.find(':checkbox').removeAttr('checked');
		});
        
		// bind change event to check box, it checks weather the checked check
		// box is last or not
		// if it is last then it does not allow to uncheck
		$OptionContainer.find('input:checkbox').bind('change', function()
		{
			if (!($SelectGroup.find(':checkbox').is(':checked')))
			{
				$(this).prop('checked', true);
				return;
			}
		});
		
		//bind click for label of checkbox
		$OptionContainer.find('label').click(function(){
			var $Input = $(this).prev('input');
			if($Input.attr('checked')){
				$Input.removeAttr('checked');
				if(!$SelectGroup.find(':checkbox').is(':checked')){
					$Input.attr('checked', true);
				}
			}else{
				$Input.attr('checked', true);
			}
		});

		// bind click event to close button of option container
		$BtnCloseOptContainer.click(function(e)
		{
			updateRowText();
			$OptionContainer.css('display', 'none');
		});

		// bind click event to row and column checkboxes of row.
		$RowChk.click(onRowBtnClick);
		$ColChk.click(onColumnBtnClick);
		
		// bind click event for document to close option container
		$(document).click(function(e){
			if ($(e.target).hasClass(PivotTable.oCSSClassess.optionsCell) || $(e.target).closest('.' + PivotTable.oCSSClassess.optionsCell).length == 1)
			{
				return;
			}
			else
			{
				if ($OptionContainer.css('display') != 'none')
				{
					$BtnCloseOptContainer.trigger('click');
				}
			}
		});
		
		//bind keydown event to document for close optionContiner if it is open
		$('body').keydown(function(e){
			e = e || window.event;
			
			if(e.which == 27){
				if($OptionContainer.css('display') != 'none'){
					$BtnCloseOptContainer.trigger('click');
				}
				e.preventDefault();
			}
		});
	}

	/**
	 * if click with pressing shift key call addRowInSelection function if click
	 * without pressing shift key call setSelectedRow function
	 * 
	 * @param e
	 */
	function onRowBtnClick(e)
	{
		//clicked with pressing shift key
		if (e.shiftKey)
		{
			if ($RowChk.hasClass(PivotTable.oCSSClassess.selected))
			{
				oMetric.oInterface.removeRowFromSelection(_this);
			}
			else
			{
				oMetric.oInterface.addRowInSelection(_this);
			}
		}
		//clicked without pressing shift key
		else
		{
			oMetric.oInterface.setSelectedRow(_this);
		}
	}

	/**
	 * if click with pressing shift key call addColumnInSelection function if
	 * click without pressing shift key call setSelectedColumn function
	 * 
	 * @param e
	 */
	function onColumnBtnClick(e)
	{
		//clicked with pressing shift key
		if (e.shiftKey)
		{
			if ($ColChk.hasClass(PivotTable.oCSSClassess.selected))
			{
				oMetric.oInterface.removeColumnFromSelection(_this);
			}
			else
			{
				oMetric.oInterface.addColumnInSelection(_this);
			}
		}
		//clicked without pressing shift key
		else
		{
			oMetric.oInterface.setSelectedColumn(_this);
		}
	}

	/**
	 * show option container
	 * 
	 * @param e
	 */
	function showOptionContainer(e)
	{
		if ($(e.target).closest('.' + PivotTable.oCSSClassess.optionContainer).length == 1)
		{
			return;
		}
		$('.' + PivotTable.oCSSClassess.optionContainer, oMetric.$FilterDiv).css('display', 'none');

		$OptionContainer.css({
			display : 'block',
			left : e.clientX
		});
	}

	/**
	 * 
	 * @returns {Array} of selected metrics in matric row
	 */
	function getFileterData()
	{
		var aMetricsToSend = new Array();
		$('input:checkbox', $OptionContainer).each(function()
		{
			if ($(this).is(':checked'))
			{
				aMetricsToSend.push($(this).val());
			}
		});

		return aMetricsToSend;
	}

	/**
	 * Updates a filterData if changed.
	 * 
	 * @returns true if data is changed, otherwise false
	 */
	function isUpdateFilterData()
	{
		var bUpdated = true;
		var aCurFilterData = getFileterData();

		if (!aLastFilterData)
		{
			bUpdated = true;
		}
		else
		{
			// check if data is updated.
			if(PivotTable.matchArray(aCurFilterData, aLastFilterData)){
				bUpdated = false;
			}
		}

		// update if it is updated.
		if (bUpdated)
		{
			aLastFilterData = aCurFilterData;
		}

		return bUpdated;
	}

	/**
	 * update text of row and if row is updated then call updateData function of
	 * interface.
	 */
	function updateRowText()
	{
		if (!isUpdateFilterData())
		{
			return;
		}

		// call updateData of PivotTable interface
		oMetric.oInterface.updateData();

		// is filterData Changed
		var sText = '';

		$OptionContainer.find(':checkbox').each(function()
		{
			if ($(this).is(':checked'))
			{
				sText += $(this).attr('name') + ', ';
			}
		});

		sText = $.trim(sText).replace(/(\s+)?.$/, ""); // remove comma at the
														// end from string
		$Row.find('.' + PivotTable.oCSSClassess.text).text(sText); // set text

	}

	// PUBLIC FUNCTIONS

	/**
	 * returns jQuery object of DOM element.
	 */
	this.get$Tr = function()
	{
		return $Row;
	};

	/**
	 * returns filter data(JavascriptObject) required to send an ajax request.
	 * e.g ["Leads", "Wins"]
	 */
	this.getFilterData = function()
	{
		return aLastFilterData;

	};

	/**
	 * @returns true if row button is selected for this row.
	 */
	this.isRowSelected = function()
	{
		if ($RowChk.hasClass(PivotTable.oCSSClassess.selected))
		{
			return true;
		}
		else
		{
			return false;
		}
	};

	/**
	 * @returns true if column button is selected for this row
	 */
	this.isColumnSelected = function()
	{
		if ($ColChk.hasClass(PivotTable.oCSSClassess.selected))
		{
			return true;
		}
		else
		{
			return false;
		}
	};

	/**
	 * Updates row button's selected status
	 * 
	 * @param bSelected
	 */
	this.setRowSelected = function(bSelected)
	{
		if (bSelected)
		{
			$RowChk.addClass(PivotTable.oCSSClassess.selected);
		}
		else
		{
			$RowChk.removeClass(PivotTable.oCSSClassess.selected);
		}
	};

	/**
	 * Updates column button's selected status
	 * 
	 * @param bSelected
	 */
	this.setColumnSelected = function(bSelected)
	{
		if (bSelected)
		{
			$ColChk.addClass(PivotTable.oCSSClassess.selected);
		}
		else
		{
			$ColChk.removeClass(PivotTable.oCSSClassess.selected);
		}
	};

	/**
	 * change status of check boxes of optionContainer related to given options
	 * @param aMetrics
	 */
	this.setConfig = function(aMetrics){
		//deselect all metrics
		$OptionContainer.find(':checkbox').each(function(){
			$(this).removeAttr('checked');
		});
		//select requested metrics
		$OptionContainer.find(':checkbox').each(function(){
			if($.inArray($(this).val(), aMetrics) != -1){
				$(this).attr('checked', true);
			}
		});
		//updateRowText
		updateRowText();
	};
	
	/**
	 * reset the row
	 */
	this.reset = function(){
		//reset row
		$OptionContainer.find(':checkbox').each(function(){
			$(this).removeAttr('checked');
		});
		
		$OptionContainer.find(':checkbox').each(function(){
			if($.inArray($(this).val(), oRowOptions.metrics) != -1){
				$(this).attr('checked', true);
			}
		});
	};
	
	init();

};

// dimensions row class
PivotTable.DimensionRow = function(oDim)
{
	// PRIVATE VARIABLES
	var _this = this;
	var $Row = undefined;
	var oRowOptions = $.extend({}, oDim);
	var $OptionContainer = undefined;
	var $SelectAll = undefined, $SelectNone = undefined;
	var $OptionCell = undefined;
	var $SelectionCell = undefined, $RowChk = undefined, $ColChk = undefined;
	var nCounter = 0;
	// will hold either this dimension is choosen as row or column value.
	var $SelectGroup = undefined;
	var $BtnCloseOptContainer = undefined;
	var oLastFilterData;
	var bInit = true;

	// PRIVATE FUNCTIONS
	function init()
	{
		// create Row function will call to generate row relevant to oOptions
		createRow();

		// bindEvent function will call to bind click event to row
		bindEvent();

	}

	/*
	 * create row relevant to the oOptions
	 */
	function createRow()
	{
		$Row = $('<tr class="' + PivotTable.oCSSClassess.dimensionRow + '"/>');
		
		var sRowName = oDim.options.desc;
		if(!sRowName)
		{
			sRowName = oDim.options.name;
		}
		
		$Row.data('name', oDim.options.name);
		
		$('<td class="' + PivotTable.oCSSClassess.dimCell + '">' + sRowName + '</td>').appendTo($Row);

		$OptionCell = $('<td class="' + PivotTable.oCSSClassess.optionsCell + '"/>').appendTo($Row);
		$('<div class="' + PivotTable.oCSSClassess.text + '"/>').appendTo($OptionCell);

		$OptionContainer = $('<div class="' + PivotTable.oCSSClassess.optionContainer + '"/>').appendTo($OptionCell);

		if (oDim.options.groups.length > 1) // if row has group of options more
		// than one then option container
		// generate like below
		{
			var $Select = $('<select class="' + PivotTable.oCSSClassess.groupBox + '"></select>').appendTo($OptionContainer);

			$.each(oDim.options.groups, function(index, group)
			{
				var sGroupName = group.name;
				$('<option>' + sGroupName + '</option>').appendTo($Select);

				var $GroupDiv = $('<div class="' + PivotTable.oCSSClassess.optionGroup + '" name="' + sGroupName + '"/>').appendTo($OptionContainer);

				$.each(group.options, function(index, option)
				{
					var $ChkBox = $('<div class="' + PivotTable.oCSSClassess.chkBox + '"/>').appendTo($GroupDiv);
					$ChkBox.append('<input type="checkbox" value="' + option.code + '" name="' + option.desc + '"/>');
					$ChkBox.append('<label>' + option.desc + '</label>');
				});
			});

		}
		else
		// if row has at least 1 group of options then option container generate
		// like below
		{
			$.each(oDim.options.groups, function(index, group)
			{
				var sGroupName = '';
				if (group.name)
				{
					sGroupName = ' ' + group.name;
				}

				var $GroupDiv = $('<div class="' + PivotTable.oCSSClassess.optionGroup + '" name="' + sGroupName + '"/>').appendTo($OptionContainer);

				$.each(group.options, function(index, option)
				{
					var $ChkBox = $('<div class="' + PivotTable.oCSSClassess.chkBox + '"/>').appendTo($GroupDiv);
					$ChkBox.append('<input type="checkbox" value="' + option.code + '" name="' + option.desc + '" />');
					$ChkBox.append('<label>' + option.desc + '</label>');
				});
				$SelectGroup = $GroupDiv;
			});

			// call updateRowText function manually because there is not select
			// box to choose group, and this function will called initially on
			// triggered change event of select box.
			updateRowText();
		}
		
		//select all options and none options link prepend to $OptionContainer
		$SelectNone = $('<a class="' + PivotTable.oCSSClassess.selectNone + '"> ' + PivotTable.osText.none + '</a>').prependTo($OptionContainer);
		$SelectAll = $('<a class="' + PivotTable.oCSSClassess.selectAll + '">' + PivotTable.osText.all + '</a>').prependTo($OptionContainer);

		// set default value for select box of option group, if value is not
		// there then set first group as default one.
		if (oDim.options.defGroup)
		{
			$OptionContainer.find('select').val(oDim.options.defGroup);
		}
		else
		{
			$OptionContainer.find('select').val(oDim.options.groups[0].name);
		}

		$BtnCloseOptContainer = $('<span class="' + PivotTable.oCSSClassess.closeOptionContainer + '">x</span>').appendTo($OptionContainer);

		$SelectionCell = $('<td class="' + PivotTable.oCSSClassess.selectionCell + '"/>').appendTo($Row);

		nCounter++;
		var sRowId = 'row' + Math.floor(Math.random() * 10001) + nCounter;
		var sColId = 'col' + Math.floor(Math.random() * 10001) + nCounter;
		$RowChk = $('<a id="' + sRowId + '" />').appendTo($SelectionCell);
		$('<span>' + PivotTable.osText.row + '</span>').appendTo($RowChk);
		$ColChk = $('<a id="' + sColId + '"/>').appendTo($SelectionCell);
		$('<span>' + PivotTable.osText.column + '</span>').appendTo($ColChk);

	}

	// bind events
	function bindEvent()
	{

		// bind click event to second cell of $Row, showOptionContainer is
		// handler of this event
		$OptionCell.click(showOptionContainer);
		
		//bind click event for selectAll and selectNone links
		$SelectAll.click(function(){
			$SelectGroup.find(':checkbox').attr('checked', true);
		});
		
		$SelectNone.click(function(){
			$SelectGroup.find(':checkbox').removeAttr('checked');
		});

		// bind change event to select box, onOptionGroupChange is handler of
		// this event
		$OptionCell.find('select').bind('change', onOptionGroupChange);
		$OptionCell.find('select').trigger('change');

		//bind click for label of checkbox
		$OptionContainer.find('label').click(function(){
			var $Input = $(this).prev('input');
			if($Input.attr('checked')){
				$Input.removeAttr('checked');
			}else{
				$Input.attr('checked', true);
			}
		});
		
		// bind click event to close button of option container
		$BtnCloseOptContainer.click(function(e)
		{
			updateRowText();
			$OptionContainer.css('display', 'none');
		});

		// bind click event for row and column check button
		$RowChk.click(onRowBtnClick);
		$ColChk.click(onColumnBtnClick);

		// bind click event for document to close option container
		$(document).click(function(e){
			if ($(e.target).hasClass(PivotTable.oCSSClassess.optionsCell) || $(e.target).closest('.' + PivotTable.oCSSClassess.optionsCell).length == 1)
			{
				return;
			}
			else
			{
				if ($OptionContainer.css('display') != 'none')
				{
					$BtnCloseOptContainer.trigger('click');
				}
			}
		});
		
		//bind keydown event to document for close optionContiner if it is open
		$('body').keydown(function(e){
			e = e || window.event;
			
			if(e.which == 27){
				if($OptionContainer.css('display') != 'none'){
					$BtnCloseOptContainer.trigger('click');
				}
				e.preventDefault();
			}
		});
	}

	/**
	 * if click with pressing shift key then call addRowInSelection function if
	 * click without pressing shift key then call setSelectedRow function
	 * 
	 * @param e
	 */
	function onRowBtnClick(e)
	{
		//if clicked with pressing shift key
		if (e.shiftKey)
		{
			if ($RowChk.hasClass(PivotTable.oCSSClassess.selected))
			{
				oDim.oInterface.removeRowFromSelection(_this);
			}
			else
			{
				oDim.oInterface.addRowInSelection(_this);
			}
		}
		//if clicked without pressing shift key
		else
		{
			oDim.oInterface.setSelectedRow(_this);
		}
	}

	/**
	 * if click with pressing shift key then call addColumnInSelection function
	 * if click without pressing shift key then call setselectedColumn function
	 * 
	 * @param e
	 */
	function onColumnBtnClick(e)
	{
		//if clicked with pressing shift key
		if (e.shiftKey)
		{
			if ($ColChk.hasClass(PivotTable.oCSSClassess.selected))
			{
				oDim.oInterface.removeColumnFromSelection(_this);
			}
			else
			{
				oDim.oInterface.addColumnInSelection(_this);
			}
		}
		//if clicked without pressing shift key
		else
		{
			oDim.oInterface.setSelectedColumn(_this);
		}
	}

	/**
	 * show option container
	 * 
	 * @param e
	 */
	function showOptionContainer(e)
	{
		if ($(e.target).closest('.' + PivotTable.oCSSClassess.optionContainer).length == 1)
		{
			return;
		}
		$('.' + PivotTable.oCSSClassess.optionContainer, oDim.$FilterDiv).css('display', 'none');
		$OptionContainer.css({
			display : 'block',
			left : e.clientX
		});
	}

	/*
	 * show option group related to value of select box checked all underline
	 * checkboxes of option group to true. call updateRowText function to update
	 * text of Row.
	 */
	function onOptionGroupChange()
	{
		var $this = $(this);
		var sGroupName = $this.val();

		$this.siblings('.' + PivotTable.oCSSClassess.optionGroup).css('display', 'none');

		if (sGroupName)
		{
			$this.siblings('.' + PivotTable.oCSSClassess.optionGroup).each(function()
			{
				if ($(this).attr('name') == sGroupName)
				{
					$SelectGroup = $(this);
					$SelectGroup.css('display', 'block');
					$SelectGroup.find('.' + PivotTable.oCSSClassess.chkBox).find(':checkbox').prop('checked', true);
				}
			});
		}
		else
		{
			$SelectGroup.find(':checkbox').prop('checked', true);
		}

		if (bInit)
		{
			updateRowText();
			bInit = false;
		}
	}

	/*
	 * update text of row. if row is choosen in row or column select box then
	 * prevent to deselect last checkbox of its options.
	 */
	function updateRowText(e)
	{
		if (!isUpdateFilterData())
		{
			return;
		}

		// call updateData function of oInterface.
		oDim.oInterface.updateData();

		var sText = '';
		var bAllTxt = true;

		$SelectGroup.find(':checkbox').each(function()
		{
			if ($(this).is(':checked'))
			{
				sText += $(this).attr('name') + ', ';
			}
			else
			{
				bAllTxt = false;
			}
		});

		if (bAllTxt)
		{
			if ($SelectGroup.attr('name') != undefined && $SelectGroup.attr('name') != '')
			{
				sText = 'All - ' + $SelectGroup.attr('name');
			}
			else
			{
				sText = 'All';
			}
		}

		if ($.trim(sText).indexOf(',', sText.length - 2) == sText.length - 2)
		{
			sText = $.trim(sText).replace(/(\s+)?.$/, ""); // remove comma at
															// end of the string
		}

		// on deselection of every options "None" is set to row text.
		if (!($SelectGroup.find(':checkbox').is(':checked')))
		{
			sText = 'None';
		}

		$Row.find('.' + PivotTable.oCSSClassess.text).text(sText);

	}

	/**
	 * returns filter data(JavascriptObject) required to send an ajax request.
	 * e.g {name: "Period", group: "Months", options: ["M1", "M2", "M3"]}
	 */
	function getFilterData()
	{
		var oRowObjToSend = new Object();
		oRowObjToSend.options = new Array();
		oRowObjToSend.name = $Row.data('name');

		if ($('select', $Row).val())
		{
			oRowObjToSend.group = $('select', $Row).val();
			$('[name=' + $('select', $Row).val() + ']', $Row).find('input:checkbox').each(function()
			{
				if ($(this).is(':checked'))
				{
					oRowObjToSend.options.push($(this).val());
				}
			});
		}
		else
		{
			oRowObjToSend.group = null;
			$('input:checkbox', $Row).each(function()
			{
				if ($(this).is(':checked'))
				{
					oRowObjToSend.options.push($(this).val());
				}
			});
		}

		return oRowObjToSend;
	}
	;

	/**
	 * Updates a filterData if changed.
	 * 
	 * @returns true if data is changed, otherwise false
	 */
	function isUpdateFilterData()
	{
		var bUpdated = true;
		var oCurFilterData = getFilterData();

		if (!oLastFilterData)
		{
			bUpdated = true;
		}
		else
		{
			// check if data is updated.
			if(matchObject(oCurFilterData, oLastFilterData)){
				bUpdated = false;
			}
		}

		// update if it is updated.
		if (bUpdated)
		{
			oLastFilterData = oCurFilterData;
		}

		return bUpdated;
	}
	
	// PUBLIC FUNCTIONS
	
	/**
	 * return jquery object or oRwo
	 */
	this.get$Tr = function()
	{
		return $Row;
	};

	/**
	 * match two object
	 * 
	 * @param obj1
	 * @param obj2
	 * @returns {Boolean} if objects are matched then retrun true else return
	 *          false
	 */
	function matchObject(obj1, obj2)
	{
		var flag = true;
		$.each(obj1, function(key)
		{
			if (obj1[key] instanceof Array)
			{
				flag = PivotTable.matchArray(obj1[key], obj2[key]);
				return false;
            }
			else if (obj1[key] != obj2[key])
			{
				flag = false;
				return false;
			}
		});
		return flag;
	}

	/**
	 * returns filter data(JavascriptObject) required to send an ajax request.
	 * e.g ["Leads", "Wins"]
	 */
	this.getFilterData = function()
	{
		return oLastFilterData;
	};

	/**
	 * @returns true if row button is selected for this row.
	 */
	this.isRowSelected = function()
	{
		if ($RowChk.hasClass(PivotTable.oCSSClassess.selected))
		{
			return true;
		}
		else
		{
			return false;
		}
	};

	/**
	 * @returns true if column button is selected for this row
	 */
	this.isColumnSelected = function()
	{
		if ($ColChk.hasClass(PivotTable.oCSSClassess.selected))
		{
			return true;
		}
		else
		{
			return false;
		}
	};

	/**
	 * Updates row button's selected status
	 * 
	 * @param bSelected
	 */
	this.setRowSelected = function(bSelected)
	{
		if (bSelected)
		{
			$RowChk.addClass(PivotTable.oCSSClassess.selected);
		}
		else
		{
			$RowChk.removeClass(PivotTable.oCSSClassess.selected);
		}

	};

	/**
	 * Updates column button's selected status
	 * 
	 * @param bSelected
	 */
	this.setColumnSelected = function(bSelected)
	{
		if (bSelected)
		{
			$ColChk.addClass(PivotTable.oCSSClassess.selected);
		}
		else
		{
			$ColChk.removeClass(PivotTable.oCSSClassess.selected);
		}
	};
	
	/**
	 * change status of check boxes of optionContainer related to given options
	 * @param oConfig
	 */
	this.setConfig = function(oConfig){
		if(oConfig.group){
			//set selected group as requested in options
			$OptionContainer.find('select').val(oConfig.group);
		
			//trigger change event of group.
			$OptionContainer.find('select').trigger('change');
		}
		
		//deselect all checkbox from optionContainer
		$OptionContainer.find(':checkbox').each(function(){
			$(this).removeAttr('checked');
		});
		
		//select the checkboxes requested in options.
		$OptionContainer.find(':checkbox').each(function(){
			if($.inArray($(this).val(), oConfig.options) != -1){
				$(this).attr('checked', true);
			}
		});
		
		//call updateRowText function
		updateRowText();
	};
	
	/**
	 * reset the row
	 */
	this.reset = function(){
		//reset row
		if(oRowOptions.options.defGroup){
			$OptionContainer.find('select').val(oRowOptions.options.defGroup);
		}else{
			$OptionContainer.find('select').val(oRowOptions.options.groups[0].name);
		}
	};

	init();
};

function TableModel(sTitle, aCells, aRowTitle, aColTitle)
{
	this.title = sTitle;
	this.cells = aCells;
	this.aHeaderRows = [];
	this.aHeaderCols = [];
	this.aColTitle = aColTitle || [];
	this.aRowTitle = aRowTitle || [];
	this._findHeaderRows();	
	this._findHeaderCols();
}

TableModel.prototype = {
	title : "",
	
	/**
	 * Holds each cell data. If the cell is merged with previous cell due to row span or col span it will hold null value.
	 * e.g. (0,0) cell is having row span = 2, then (0,1)'s value will be null.
	 */
	cells : undefined,
	
	/**
	 * Columns of the table, which acts as row headers. [rowIndex][colIndex]
	 */
	aHeaderRows: undefined,
	
	/**
	 * Rows of the table, which acts as column headers. [rowIndex][colIndex]
	 */
	aHeaderCols: undefined,
	
	/**
	 * Column title strings
	 */
	aColTitle : undefined,
	
	/**
	 * Row title strings
	 */
	aRowTitle : undefined,
	
	_copyHeaderCols : function() {
		var self = this;
		var a = [];
		$.each(self.aHeaderCols, function(index, aHeaderCol){
			a[index] = aHeaderCol.slice();
		});
		
		return a;
	},
	
	_copyHeaderRows : function() {
		var self = this;
		var a = [];
		$.each(self.aHeaderRows, function(index, aHeaderRow){
			a[index] = aHeaderRow.slice();
		});
		
		return a;
	},
	
	_findHeaderRows : function() {
		var self = this;
		var nCellsLength = self.cells.length;
		for(var i=0; i < nCellsLength; i++) //row index
		{
			var bHeaderRow = true;
			var nLength = self.cells[i].length;
			for(var j=0; j < nLength; j++) //col index
			{
				if(self.cells[i][j] && !self.cells[i][j].header){
					bHeaderRow = false;
					break;
				}
			}
			if(bHeaderRow){
				self.aHeaderRows.push(self.cells[i].slice());
			}			
		}
	},
	
	_findHeaderCols : function() {
		var self = this;
		var nHeaderCols = 0;
		
		var nLength = self.cells[0].length;
		var nCellsLength = self.cells.length;
		
		for(var i = 0; i < nLength; i++) //col index
		{
			var bHeaderCol = true;
			for(var j=0; j < nCellsLength; j++) //row index
			{
				if(self.cells[j][i] && !self.cells[j][i].header){
					bHeaderCol = false;
					break;
				}
			}
			if(bHeaderCol){
				nHeaderCols ++;
			}
		}
		
		for(var i=0; i < nCellsLength; i++) //row index
		{
			self.aHeaderCols[i] = [];
			for(var j=0; j < nHeaderCols; j++) //col index
			{
				self.aHeaderCols[i][j] = self.cells[i][j];
			}
		}
		
	},
	
	_splitColumnWise : function(bForPieChart)
	{
		var self = this;
		if(!bForPieChart && self.aHeaderRows.length < 2)
			return [self];
		
		var nFirstColIndex = self.aHeaderCols[0].length;
		var nLastColIndex = self.cells[0].length;
		var n = self.cells.length;
		
		var aResult = new Array();
		for(var i = nFirstColIndex; i < nLastColIndex; i++){
			if(self.cells[0][i] == null)
				continue;
			
			var nColSpan = self.cells[0][i].colspan;
			var aaCells = self._copyHeaderCols();
			
			for(var j = 1; j < n; j++){
				for(var k = i; k < i + nColSpan; k++){
					aaCells[j].push(self.cells[j][k]);
				}
			}
			
			if(self.aHeaderRows.length >= 2)
				aaCells.splice(0, 1);
			else
				aaCells[0][1] = self.cells[0][i];
			
			var aColTitle = self.aColTitle.slice();
			aColTitle.push(self.cells[0][i].f);		// this is undefined
			
			// this is going infinite
			aResult.push(new TableModel(self.title, aaCells, self.aRowTitle.slice(), aColTitle));
		}
		return aResult;
	},
	
	_splitRowWise : function()
	{
		var self = this;
		
		
		//If table needs to be split from column then don't split it yet.
		if(self.aHeaderRows.length > 1)
			return[self];
		
		//If table can't be split from row then return current reference only.
		if(self.aHeaderCols[0].length < 2)
			return [self];
		
		var nFirstRowIndex = self.aHeaderRows.length;
		var nLastRowIndex = self.cells.length;
		var n = self.cells[0].length;
		
		var aResult = new Array();
		for(var i = nFirstRowIndex; i < nLastRowIndex; i++){
			if(self.cells[i][0] == null)
				continue;
			
			var nRowSpan = self.cells[i][0].rowspan;
			var aaCells = self._copyHeaderRows();
			
			aaCells[0].splice(0, 1);
			
			for(var j = i; j < i + nRowSpan; j++){
				var aRow = [];
				for(var k  = 1; k < n; k++ ){
					aRow.push(self.cells[j][k]);
				}
				
				aaCells.push(aRow);
			}
			
			var aRowTitle = self.aRowTitle.slice();
			aRowTitle.push(self.cells[i][0].f);
			
			aResult.push(new TableModel(self.title, aaCells, aRowTitle, self.aColTitle.slice()));
		}
		return aResult;
	},
	
	_mergeColumnWise : function(aTarget, a, nInitRow)
	{
		if(!nInitRow)
			nInitRow = 0;
		$.each(a, function(i, element){
			var aRow = aTarget[nInitRow + i];
			if(!aRow)
				aTarget[nInitRow + i] = aRow = [];
			
			if(element instanceof Array) {
				$.merge(aRow, element);
			} else {
				aRow.push(element);
			}
		});
	},
	
	/**
	 * Will split this table model into table models such that no-cell will have colspan/rowspan > 1. 
	 * Row headers and column headers will be copied to the splited models
	 * @returns  {TableModel[2][2]} 
	 */
	split : function() {
		var self = this;		
		var aColSplitedTables = self._splitColumnWise();
		if(aColSplitedTables.length == 1){
			var aResult = [];
			self._mergeColumnWise(aResult, self._splitRowWise());
			return aResult;
		} else {
			var aResult = [];
			$.each(aColSplitedTables, function(){
				var table = this;
				self._mergeColumnWise(aResult, table.split());				
			});
			return aResult;
		}
	},
	
	splitForPieChart : function() {
		var self = this;
		var aTables = self.split();
		if(aTables.length == 1 && aTables[0].length == 1){
			return [self._splitColumnWise(true)];
		} else {
			var aResult = [];
			$.each(aTables, function(i){
				$.each(this, function(){
					var table = this;
					self._mergeColumnWise(aResult, table.splitForPieChart(), i);
				});
			});
			return aResult;
		}
	},
	
	splitForLineChart : function() {
		var self = this;
		var aResult = new Array();
		$.map(self.split(), function(aTableModel){
			aResult.push($.map(aTableModel, function(tableModel){
				return tableModel.transpose();
			}));
		});
		return aResult;
	},
	
	transpose : function() {
		var self = this;
		var aaTransposedCells = new Array();
		for(var i = 0; i < self.cells.length; i++){			
			for(var j=0; j < self.cells[i].length; j++) {
				var cell = $.extend({}, self.cells[i][j]);
				
				//swap row-span and col-span
				var temp = cell.rowspan;
				cell.rowspan = cell.colspan;
				cell.colspan = temp;
				
				if(i == 0) {
					aaTransposedCells[j] = new Array();
				}
				
				aaTransposedCells[j][i] = cell;
			}
		}
		return new TableModel(self.title, aaTransposedCells);
			
	},
	
	getGDataTable : function() {
		var self = this;
		
		if(self.aHeaderRows.length !== 1 || self.aHeaderCols[0].length !== 1)
			return;
		
		var oData = {
				cols : [],
				rows : []                 
		};
		
		$.each(self.aHeaderRows, function(index, aHeaderRow){
			$.each(aHeaderRow, function(i, oCell){
				oCell = $.extend({}, oCell);
				oCell.label = oCell.f;
				
				delete oCell.rowspan;
				delete oCell.colspan;
				delete oCell.header;
				
				if(i == 0)
					oCell.type = "string";
				else
					oCell.type = "number";
				
				oData.cols.push(oCell);
			});
		});
		
		var aCells = self.cells.slice();
		aCells.splice(0, 1);
		
		$.each(aCells, function(index, aRow){
			var a = aRow.slice();
			$.each(a, function(index, o){
				o = $.extend({}, o);
				delete o.colspan;
				delete o.rowspan;
				delete o.header;
			});
			
			oData.rows.push({c: a});
                        });
		
		return oData;
	},
	
        getSWFChartData : function(sChartType) {
		var self = this;
		
		if(self.aHeaderRows.length !== 1 || self.aHeaderCols[0].length !== 1)
			return;
		
                var oNData = {
                                barElements : [],
                                barLabels : []
                };
		
		$.each(self.aHeaderRows, function(index, aHeaderRow){
			$.each(aHeaderRow, function(i, oCell){
				oCell = $.extend({}, oCell);
                                if(i != 0)
                                {
                                    var length = colours.length;
                                    var color = chartingColors[oCell.f.toLowerCase()];
                                    if(color === undefined)
                                        color = colours[(i-1)%length]
                                    var be = new SwfChartElement(sChartType, oCell.f,color,[]);
                                    oNData.barElements.push(be);
                                }
			});
		});
		
		var aCells = self.cells.slice();
		aCells.splice(0, 1);
		
			$.each(aCells, function(index, aRow){
				var a = aRow.slice();
				var label = '';
	                        
	                        $.each(a, function(i, aInnerRow){
	                        	if(sChartType == 'pie')
	                        	{
		                            if(i==0)
		                            {
		                                oNData.barLabels.push(aInnerRow.v);
		                                label = aInnerRow.v;
		                            }
		                            else
		                            {
		                                var val = {};
		                                val.value = aInnerRow.v;
		                                val.label = label;
		                                
		                                if(val.value == undefined)
		                                    val.value = 0;
		                                
		                                oNData.barElements[i-1].values.push(val);
		                            }
	                        	}
	                        	else
	                        	{
	                        		if(i==0)
	                        		{
	                        			oNData.barLabels.push(aInnerRow.v);
	                        		}
	                        		else
	                        		{
	                        			var val = aInnerRow.v;
	                        			if(val == undefined)
	                        				val = 0;
	                        			oNData.barElements[i-1].values.push(val);
	                        		}
	                        	}
	                        });
			});
		
		return oNData;
	},
        
        getSWFStackBarChartData : function(sChartType) {
		var self = this;
		
		if(self.aHeaderRows.length !== 1 || self.aHeaderCols[0].length !== 1)
			return;
		
                var oNData = {
                                barElements : [],
                                barLabels : []
                };
		
                var be = new SwfChartElement(sChartType, "Legend",colours[0],[]);
                oNData.barElements.push(be);
                be.keys = [];
                be.values = [];
                var length = colours.length;
                
		$.each(self.aHeaderRows, function(index, aHeaderRow){
			$.each(aHeaderRow, function(i, oCell){
				oCell = $.extend({}, oCell);
                                if(i != 0)
                                {
                                    var key = new Object();
                                    var color = chartingColors[oCell.f.toLowerCase()];
                                    if(color === undefined)
                                        color = colours[(i-1)%length];
                                    
                                    key.colour = color;
                                    
                                    key.text = oCell.f;
                                    key["font-size"] = 12;
                                    be.keys.push(key);
                                }
			});
		});
		
		var aCells = self.cells.slice();
		aCells.splice(0, 1);
		
		$.each(aCells, function(index, aRow){
			var a = aRow.slice();
                        var valueList = [];
                        $.each(a, function(i, aInnerRow){
                            if(i==0)
                            {
                                oNData.barLabels.push(aInnerRow.v);
                            }
                            else
                            {
                                var val = aInnerRow.v;
                                if(val == undefined)
                                    val = 0;
                                var value = new Object();
                                value.colour = be.keys[valueList.length].colour;
                                value.val = val;
                                valueList.push(value);
                            }
                        });
                        be.values.push(valueList);
		});
		return oNData;
	},
        
	getTitle : function(){
		var self = this;
		
		var sRowTitle = "";
		$.each(self.aRowTitle, function(index, sTitle){
				sRowTitle += "," + sTitle;
		});
		
		var sColTitle = "";
		$.each(self.aColTitle, function(index, sTitle){
			sColTitle += "," + sTitle;
		});
		
		if(!self.title && sRowTitle)
			sRowTitle.slice(1);
		
		if(!self.title && !sRowTitle && sColTitle)
			sColTitle.slice(1);
		
		return self.title + sRowTitle + sColTitle;
	}
};

$.fn.lineChart = function(options){
	var $this = this;
	var bRendered = false;
	var nHeight = options.min_height || 450;
	var nWidth = options.min_width || 450;
	
	$this.data('api', {
		model : options.tableModel,
		render : function()
		{
			if(bRendered)
				return;
			
			bRendered = true;
			var aTables = options.tableModel.splitForLineChart();
			$.each(aTables, function(index, aTable){
				var nLength = aTable.length;
				var $ChartRow = $('<div class="chart_row"/>').appendTo($this);
				var nChartContainerWidth = $this.width();
				var nChartWidth = nChartContainerWidth/nLength;
				
				if(nChartWidth < nWidth)
					nChartWidth = nWidth;
				
				$ChartRow.width(nChartWidth * nLength);
				
				$.each(aTable, function(i, table){
//					var gDataTable = table.getGDataTable();
//					var dt = new google.visualization.DataTable(gDataTable);
//					var options = {
//							title : table.getTitle(),
//							height : nHeight,
//							width : nChartWidth
//					};
					
//					var $Chart = $('<div class="single_chart"/>').appendTo($ChartRow);
//					var chart = new google.visualization.LineChart($Chart[0]);
//					chart.draw(dt, options);
                                        var randStr = "chart_"+ Math.random().toString();
                                        var $ChartNew = $("<div id='"+randStr+"'/>").appendTo($ChartRow);
                                        var oNData = table.getSWFChartData("line");
                                        var swftitle = new SwfChartTitle(table.title);
                                        var data = new SwfChartData(swftitle,oNData.barElements,oNData.barLabels, randStr);
                                        chartData[randStr] = data;
                                        swfobject.embedSWF((contextRoot ? contextRoot : "../..") + "/open-flash-chart.swf", randStr, nChartWidth, nHeight, "9.0.0","expressInstall.swf", {"get-data":"open_flash_chart_data", "id":randStr}, {wmode: "transparent"});
				});
                                $("<div style='clear: both;'></div>").appendTo($ChartRow);
                            });
		}
	});
};

$.fn.pieChart = function(options){
	var $this = this;
	var bRendered = false;
	var nHeight = options.min_height || 200;
	var nWidth = options.min_width || 400;
	
	$this.data('api', {
		model: options.tableModel,
		render : function()
		{
			if(bRendered)
				return;
			
			bRendered = true;
			var aTables = options.tableModel.splitForPieChart();
			
			$.each(aTables, function(index, aTable){
				var nLength = aTable.length;
				var $ChartRow = $('<div class="chart_row"/>').appendTo($this);
				var nChartContainerWidth = $this.width();
				var nChartWidth = nChartContainerWidth/nLength;
				
				if(nChartWidth < nWidth)
					nChartWidth = nWidth;
				
				$ChartRow.width(nChartWidth * nLength);
				$.each(aTable, function(i, table){
//					var gDataTable = table.getGDataTable();
					
//					var dt = new google.visualization.DataTable(gDataTable);
//					var options = {
//						title : table.getTitle(),
//						height : nHeight,
//						width : nChartWidth
//					};					
//					var $ChartContainer = $('<div class="single_chart"><div/>').appendTo($ChartRow);
//                                        var $Chart = $("<div></div>").appendTo($ChartContainer);                                        
//					var chart = new google.visualization.PieChart($Chart[0]);
//					chart.draw(dt, options);
                                        var oNData = table.getSWFChartData("pie");
                                        oNData.barElements[0].colours = [];
                                        $.each(oNData.barLabels,function(ind,val){
                                            var color = chartingColors[val.toLowerCase()];
                                            if(color === undefined)
                                            {
                                                color = colours[ind];
                                            }
                                            oNData.barElements[0].colours[ind] = color;
                                        });
                                        oNData.barElements[0].tip = "#val# of #total#<br>#percent#";
                                        var randStr = "chart_"+ Math.random().toString();
                                        var $ChartNew = $("<div id='"+randStr+"'/>").appendTo($ChartRow);
                                        var swftitle = new SwfChartTitle(oNData.barElements[0].text);
                                        var data = new SwfChartData(swftitle,oNData.barElements,oNData.barLabels, randStr);
                                        chartData[randStr] = data;
                                        swfobject.embedSWF((contextRoot ? contextRoot : "../..") + "/open-flash-chart.swf", randStr, nChartWidth, nHeight, "9.0.0","expressInstall.swf", {"get-data":"open_flash_chart_data", "id":randStr}, {wmode: "transparent"});
				});
                                $("<div style='clear: both;'></div>").appendTo($ChartRow);
			});
		}
	});
};

$.fn.barChart = function(options){
	var $this = this;
	var bRendered = false;
	var nHeight = options.min_height || 450;
	var nWidth = options.min_width || 450;
	
	$this.data('api', {
		model : options.tableModel,
		render : function()
		{
			if(bRendered)
				return;
			
			bRendered = true;
			var aTables = options.tableModel.splitForLineChart();
			
			$.each(aTables, function(index, aTable){
				var nLength = aTable.length;
				var $ChartRow = $('<div class="chart_row"/>').appendTo($this);
				var nChartContainerWidth = $this.width();
				var nChartWidth = nChartContainerWidth/nLength;
				
				if(nChartWidth < nWidth)
					nChartWidth = nWidth;
				
				$ChartRow.width(nChartWidth * nLength);
				
				$.each(aTable, function(i, table){
					var randStr = "chart_"+ Math.random().toString();
                                        var $ChartNew = $("<div id='"+randStr+"'/>").appendTo($ChartRow);
                                        var oNData = table.getSWFChartData("bar");
                                        var swftitle = new SwfChartTitle(table.title);
                                        var data = new SwfChartData(swftitle,oNData.barElements,oNData.barLabels, randStr);
                                        chartData[randStr] = data;
                                        swfobject.embedSWF((contextRoot ? contextRoot : "../..") + "/open-flash-chart.swf", randStr, nChartWidth, nHeight, "9.0.0","expressInstall.swf", {"get-data":"open_flash_chart_data", "id":randStr}, {wmode: "transparent"});
                                    }); 
                                $("<div style='clear: both;'></div>").appendTo($ChartRow);
                            });
		}
	});
};

$.fn.stackBarChart = function(options){
	var $this = this;
	var bRendered = false;
	var nHeight = options.min_height || 450;
	var nWidth = options.min_width || 450;
	
	$this.data('api', {
		model : options.tableModel,
		render : function()
		{
			if(bRendered)
				return;
			
			bRendered = true;
			var aTables = options.tableModel.splitForLineChart();
			
			$.each(aTables, function(index, aTable){
				var nLength = aTable.length;
				var $ChartRow = $('<div class="chart_row"/>').appendTo($this);
				var nChartContainerWidth = $this.width();
				var nChartWidth = nChartContainerWidth/nLength;
				
				if(nChartWidth < nWidth)
					nChartWidth = nWidth;
				
				$ChartRow.width(nChartWidth * nLength);
				
				$.each(aTable, function(i, table){
					var randStr = "chart_"+ Math.random().toString();
                                        var $ChartNew = $("<div id='"+randStr+"'/>").appendTo($ChartRow);
                                        var oNData = table.getSWFStackBarChartData("bar_stack");
                                        var swftitle = new SwfChartTitle(table.title);
                                        var data = new SwfChartData(swftitle,oNData.barElements,oNData.barLabels, randStr);
                                        data.setYAxisForStackChart();
                                        chartData[randStr] = data;
                                        swfobject.embedSWF((contextRoot ? contextRoot : "../..") + "/open-flash-chart.swf", randStr, nChartWidth, nHeight, "9.0.0","expressInstall.swf", {"get-data":"open_flash_chart_data", "id":randStr}, {wmode: "transparent"});
                                    }); 
                                $("<div style='clear: both;'></div>").appendTo($ChartRow);
                            });
		}
	});
};

var chartData = new Object();

function SwfChartData(swfBarTitle, aElements, aLables, sChartid)
{
    this.elements = aElements;
    var axisLable = new Object();
    axisLable.labels = aLables;
    var oXaxis = new Object();
    oXaxis.labels = axisLable;
    this.x_axis = oXaxis;
    this._setYAxis();
    this.bg_colour="#FFFFFF";
    this.chartid = sChartid;
}

SwfChartData.prototype = {
    _setYAxis : function(){
        var self = this;
        
        if(self.elements[0].type == "pie")
            return;
        
        var maxValue = 0;
        $.each(self.elements, function(i, element){
            $.each(element.values, function(j, value){
                if(maxValue < value)
                    maxValue = value;
            });
        });
        
        self._setMaxValue(maxValue);
        
    },
    
    _setMaxValue : function(maxValue){
        var self = this;
        var newMaxValue ;
        
        if(maxValue == undefined && maxValue < 1)
        {
            newMaxValue = 1;
        }
        else
        {
            var str = maxValue.toString();
            newMaxValue = Math.pow(10, str.length);             
        }
        
        while(true)
        {
            if(newMaxValue/2 > maxValue)
            {
                newMaxValue = newMaxValue/2;
                continue;
            }
            break;
        }
        
        var step = newMaxValue/5;
        self.y_axis = new Object();
        self.y_axis.max = newMaxValue;
        self.y_axis.steps = step;
    },
    
    setYAxisForStackChart : function(){
        var self = this;
        var maxValue = 0;
        $.each(self.elements, function(i, element){
            $.each(element.values, function(j, values){
                var sumValues = 0 ;
                $.each(values, function(k, value){
                    sumValues += value.val;
                });
                if(maxValue < sumValues)
                    maxValue = sumValues;
            });
        });
        self._setMaxValue(maxValue);
    }
};

function SwfChartTitle(sTitle)
{
    this.text=sTitle;
    this.style="{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}";
}


function SwfChartElement(sType, sLegend, sColor, aValues)
{
    this.type= sType;
    this.colour=sColor;
    this.text = sLegend;
    this.values=aValues;
}

function open_flash_chart_data(data)
{
    return JSON.stringify(chartData[data[0]]);
}

OFC = {};
 
OFC.jquery = {
    name: "jQuery",
    version: function(src) { return $('#'+ src)[0].get_version() },
    rasterize: function (src, dst) { $('#'+ dst).replaceWith(OFC.jquery.image(src)) },
    image: function(src) { return "<img src='data:image/png;base64," + document.getElementById(src).get_img_binary() + "' />"},
    popup: function(src) {
        var img_win = window.open();
        with(img_win.document) {
            write("<html><head><title>Charts: Export as Image<\/title><\/head><body>" + Control.OFC.image(src) + "<\/body><\/html>") }
     }
}
if (typeof(Control == "undefined")) {var Control = {OFC: OFC.jquery}}
 
function save_image(data) {
    if(data[0])
        OFC.jquery.popup(data[0]); 
}

var colours = ['#FF00FF', '#00009A', '#CF2F00', '#006500', '#CF9A2F', '#FF0000', '#65009A', '#0000FF', '#FF6500', '#C25C5C', '#3F2F6C', '#4374A0', '#FF8532','#650000', '#FF0080', '#D066FF', '#32CCFF', '#CC6666', '#329999','#008000','#000080','#800000','#BA55D3','#FA8072','#1E90FF','#3333FF','#006600','#999900'];
var chartingColors = new Object();
