(function ($) {
	// fixRowsCols jQueryUI plugin
	// Fix the rows and columns of a table with the command as follows:
	// $('#tableID').fixRowsCols({r:rowsToFix, c:colsToFix, h:heightInPX})
	// r (int) the number of rows to fix (default is 1)
	// c (int) the number of columns to fix (default is 1)
	// h (int) the height of the table in pixels
	
    $.fn.fixRowsCols = function (options) {
        var settings = $.extend({
            // These are the defaults.
            r: 1,
            c: 1,
            h: 0
        }, options);
		
		if (!$(this).is("table")) {
			// console.log("Tried to fix rows and columns on an element that is not a table.");
			return false;
		}
		
		if ( $(this).height() < settings.h ) {
			settings.h = 0;
		}
		
		// IDs and Objects and Classes attempt to follow this convention:
		// ft = Fixed Table.
		// _d = DIV
		// _sv = scroller vertical
		// _sh = scroller horizontal
		// _t = TABLE

		// Get the ID of the table object we're splitting up.
        var tID = $(this).attr("id");
		
        // First, we prepare the six divisions which will hold the four tables.
        // Create fixed_dg_wrap : wraps the whole thing
        var ft_d_wrap = $("<div class='fixed_dg_wrap'></div>");

        // Create fixed_dg_labelHeadings     
        var ft_d_labelHeadings = $("<div class='fixed_dg_labelHeadings'></div>");

        // Create fixed_dg_rowLabels
        var ft_d_rowLabels = $("<div class='fixed_dg_rowLabels'></div>");

        // Create the vertical scroll-slider div and horizontal scroll bar divs
        var ft_sv_wrap = $("<div class='fixed_dg_scrollv_wrap'></div>");
        var ft_sv = $("<div class='fixed_dg_scrollv'></div>");
        var ft_sh_wrap = $("<div class='fixed_dg_scrollh_wrap'></div>");
        var ft_sh = $("<div class='fixed_dg_scrollh'></div>");

        // Create the container for scrolling horizontal content
        var ft_sh_container = $("<div class='fixed_dg_scrollh_container'></div>")

        // Create fixed_dg_colsContainer : This holds both dgColHeadings and dgData
        var ft_d_colsContainer = $("<div class='fixed_dg_colsContainer'></div>");

        // Create fixed_dg_colHeadings
        var ft_d_colHeadings = $("<div class='fixed_dg_colHeadings'></div>");

        // Create fixed_dg_data
        var ft_d_data = $("<div class='fixed_dg_data'></div>");
        
        // Create fixed_dg_footer
        var ft_d_footer = $("<div class='fixed_dg_footer'></div>");
        
		// Attach the divisions, including scrollbar objects, to the overall wrapper
        $(ft_d_wrap).append(ft_d_labelHeadings)
            .append(ft_d_rowLabels)
            .append(ft_sv_wrap)
            .append(ft_sh_container)
            .append(ft_d_footer);

        $(ft_sv_wrap).append(ft_sv);
        $(ft_sh_wrap).append(ft_sh);

        $(ft_sh_container).append(ft_d_colsContainer);
        $(ft_sh_container).append(ft_sh_wrap);

        $(ft_d_colsContainer).append(ft_d_colHeadings)
            .append(ft_d_data);

        // Create the five tables which will be populated with chunks of the table object
        // Create _labelheadings (table with fixed rows and columns)
        var ft_t_labelHeads = $("<table><thead></thead><tbody></tbody><tfoot></tfoot></table>").attr("id", tID + "_labelheadings").addClass("ops_datagrid");

        // Create _rowLabels (table with fixed columns)
        var ft_t_rowLabels = $("<table><thead></thead><tbody></tbody><tfoot></tfoot></table>").attr("id", tID + "_rowLabels").addClass("ops_datagrid");

        // Create _colHeadings (table with fixed rows)
        var ft_t_colHeadings = $("<table><thead></thead><tbody></tbody><tfoot></tfoot></table>").attr("id", tID + "_colHeadings").addClass("ops_datagrid");
        
        // Create ops_datagrid_footer (the footer, removed and placed underneath the whole setup)
        var ft_t_footer = $("<table><thead></thead><tbody></tbody><tfoot></tfoot></table>").attr("id", tID + "_footer").addClass("ops_datagrid");

        // The _data table is made by cloning the source object, and what's left after it's cut up is a table of unfixed cells.

        // We clone the whole table and slice it up into chunks. ops_datagrid_data is the clone.
        // We split using the fixed col count first to produce _rowLabels _data.
        // Then we split _rowLabels by the number of fixed rows into _labelheadings,
        // and split _data by the number of split rows into _colHeadings.

        // Clone the table to ops_datagrid_data.
        var ft_t_data = $(this).clone( true ).attr("id", tID + "_data");

        // Immediate take out the footer of the source table and place into the new footer area
        $(ft_t_data).find("tfoot").each(function () {
            $(ft_t_footer).append($(this));
        });
        
        // If we are fixing columns...
        if (settings.c > 0) {

            // First, go through each column of _data.
            // Work on the THEAD first, if one exists, and for each nth-child TH, write into the corresponding row in 
			// _rowLabels, removing from _data as we go (automatic with the 'append' jQueryUI function)

            // If there is a THEAD, work on this first
            if ($(ft_t_data).find("thead").length > 0) {
                for (var c = 1; c <= settings.c; c++) {

                    // Pop out the TH's that head up the column
                    var row = 0;

                    $(ft_t_data).find("thead th:nth-child(1)").each(function () {

                        if (c == 1) {

                            // If this is the first column, then the labelsrow table needs new TR's
                            var tr = $("<tr></tr>").attr("id", tID + "_labels_hr" + row);
                            $(ft_t_rowLabels).find("thead").append(tr);
                        }

                        // Move the TH cell over to the label table's corresponding row
                        $(ft_t_rowLabels).find("#" + tID + "_labels_hr" + row).append($(this));

                        row++;
                    });
                }
            }

            // Then work on each row of the TBODY, writing each nth-child TD to the corresponding row in _rowLabels,
			// up until nth-child(cols), removing from _data as we go (automatic with the 'append' function)

            if ($(ft_t_data).find("tbody").length > 0) {
                for (var c = 1; c <= settings.c; c++) {

                    // Pop out the TD's that head up the column
                    var row = 0;

                    $(ft_t_data).find("tbody td:nth-child(1)").each(function () {

                        if (c == 1) {

                            // If this is the first column, then the labelsrow table needs new TR's
                            var tr = $("<tr></tr>").attr("id", tID + "_labels_dr" + row);
                            $(ft_t_rowLabels).find("tbody").append(tr);
                        }

                        // Move the TD cell over to the label table's corresponding row
                        $(ft_t_rowLabels).find("#" + tID + "_labels_dr" + row).append($(this));

                        row++;
                    });
                }
            }
        }
        // Should leave us with _data and _rowLabels.

        // Then it's the rows we need to fix.  We take both tables (ft_t_rowLabels and ft_t_data) and pop
        // the rows out of the THEAD, if one exists, up to the number of fixed rows
        // (into ft_t_labelHeads and ft_t_colHeadings respectively).
        // If there are more fixed rows than rows inside the THEAD, carry on popping out the data rows
        // until we hit settings.r.

		// If we have rows to fix...
        if (settings.r > 0) {

            // Process ft_t_rowLabels
            var r = settings.r;

			// The slice function takes the number of elements from first parameter up to (not including)
			// the second parameter.
			
			// Go through the thead, if one exists
            $(ft_t_rowLabels).find("thead tr").slice(0, r).each(function () {
                $(ft_t_labelHeads).find("thead").append($(this));
                r--;
            });

			// If we processed any TH in the THEAD, and we still have rows to fix, carry on into the TBODY
            if (r > 0) {
                $(ft_t_rowLabels).find("tbody tr").slice(0, r).each(function () {
                    $(ft_t_labelHeads).find("tbody").append($(this));
                });
            }
			// We now have ft_t_rowLabels and ft_t_labelHeads

            // Process ft_t_data
            r = settings.r;

			// Go through the thead, if one exists
            $(ft_t_data).find("thead tr").slice(0, r).each(function () {
                $(ft_t_colHeadings).find("thead").append($(this));
                r--;
            });

			// If we processed any TH in the THEAD, and we still have rows to fix, carry on into the TBODY
            if (r > 0) {
                $(ft_t_data).find("tbody tr").slice(0, r).each(function () {
                    $(ft_t_colHeadings).find("tbody").append($(this));
                });
            }
        }

		// The table carving is completed, so assign the tables to their appropriate parent divisions
        $(ft_d_labelHeadings).append(ft_t_labelHeads);
        $(ft_d_rowLabels).append(ft_t_rowLabels);
        $(ft_d_colHeadings).append(ft_t_colHeadings);
        $(ft_d_data).append(ft_t_data);
        $(ft_d_footer).append(ft_t_footer);

		// Attach the wrapper, with all its contents, to the source table's parent element, 
		// then hide the source table.
        $(this).parent().append(ft_d_wrap);
		$(this).addClass("fixed_dg_source");
		
		// Rename all form elements from the source table.  POST data must come from the copied elements only
		// Note that SELECTs and TEXTAREAs are under the scope of the :input filter
		// Renaming rather than destroying allows printing
		$(this).find("*").filter(":input").each( function() {

			// using .is with CSS attribute selector confirms the attribute exists
			if ( $(this).is( '[id]' ) ) {
				$(this).attr('id', $(this).attr('id') + '_source' ) ;
			}
			if ( $(this).is( '[name]' ) ) {
				$(this).attr('name', $(this).attr('name') + '_source' ) ;
			}
		}) ;

        // We've set up the tables, now apply positioning, sizing, etc.
        // set up margins/paddings/etc.
		
		// fixCellWidths and fixCellHeights attempts to set dimensions of columns and rows by the largest
		// cell to keep up the illusion of a single table, all lined up
        fixCellWidths($(ft_t_labelHeads).attr("id"), $(ft_t_rowLabels).attr("id"), true);
        fixCellWidths($(ft_t_colHeadings).attr("id"), $(ft_t_data).attr("id"), true);

        fixCellHeights($(ft_t_colHeadings).attr("id"), $(ft_t_labelHeads).attr("id"));
        fixCellHeights($(ft_t_rowLabels).attr("id"), $(ft_t_data).attr("id"));

		// We also need to set margins to push the table elements around into the correct place so the 
		// new 'table' looks right
        var rowLabelsW = $(ft_t_rowLabels).width() + 2;		// width in px + a gap to make fix area clearer
        var colHeadsH = $(ft_t_colHeadings).height();		// height in px + a gap to make fix area clearer

        $(ft_sh_container).css("margin-left", rowLabelsW + "px");
        $(ft_t_rowLabels).css("margin-top", colHeadsH + "px");
        $(ft_t_data).css("margin-top", colHeadsH + "px");

        // Set static heights only if a height is provided
		if(settings.h > 0) {
			$(ft_d_rowLabels).css("height", settings.h + "px");
			$(ft_d_colsContainer).css("height", settings.h + "px");
		}

        var viewportSize = $(ft_d_colsContainer).innerHeight() - colHeadsH;

        //scroll parts
        var scrollPaneX = ft_d_colsContainer,
            scrollContentData = ft_t_data, 
            scrollContentX = $("#" + $(ft_t_data).attr("id") + ", #" + $(ft_t_colHeadings).attr("id")),
            scrollContentY = $("#" + $(ft_t_rowLabels).attr("id") + ", #" + $(ft_t_data).attr("id"));
       
        //build horizontal slider
        var scrollbar = $(ft_sh).slider({
            slide: function (event, ui) {
                if (scrollContentData.width() > scrollPaneX.width()) {
                    scrollContentX.css("margin-left", Math.round(
                    ui.value / 100 * (scrollPaneX.width() -  scrollContentData.width())) + "px");
                } else {
                    scrollContentX.css("margin-left", 0);
                }
            }
        });

        //append icon to handle
        var handleHelper = scrollbar.find(".ui-slider-handle")
            .mousedown(function () {
            scrollbar.width(handleHelper.width());
        })
            .mouseup(function () {
            scrollbar.width("100%");
        })
            .append("<span class='ui-icon ui-icon-grip-dotted-vertical'></span>")
            .wrap("<div class='ui-handle-helper-parent'></div>").parent();

        //change overflow to hidden now that slider handles the scrolling
        scrollPaneX.css("overflow", "hidden");

		// setup vertical slider
        var scrollbarY = $(ft_sv).slider({
            orientation: "vertical",
            range: false,
            min: 0,
            max: 100,
            value: 100,
            slide: function (event, ui) {
                var d = scrollContentData.height() - scrollPaneX.height();
                if (d > 0) {
                    scrollContentY.css("margin-top", (colHeadsH + (-1 * (((d + colHeadsH) * (100 - ui.value)) / 100))) + "px");
                } else {
                    scrollContentY.css("margin-top", colHeadsH);
                }
            }
        });

        //append icon to vertical handle
        var handleHelperY = scrollbarY.find(".ui-slider-handle")
            .mousedown(function () {
            scrollbarY.height(handleHelperY.height());
        })
            .mouseup(function () {
            scrollbarY.height("100%");
        })
            .append("<span class='ui-icon ui-icon-grip-dotted-horizontal'></span>")
            .wrap("<div class='ui-handle-helper-parent'></div>").parent();
        
        //change handle position on window resize
        $(window).resize(function () {
            resetValue();
            sizeScrollbar();
            sizeScrollbarY();
       //     reflowContent();
        });
		
        //init scrollbar size
        setTimeout(sizeScrollbar, 10); //safari wants a timeout
        setTimeout(sizeScrollbarY, 10); //safari wants a timeout

		// Bind mouse wheel to the vertical slider
		$(ft_sv).bind('mousewheel DOMMouseScroll', function (e) {
			var delta = 0, element = $(this), value, result, oe;
			oe = e.originalEvent; // for jQuery >=1.7
			value = element.slider('value');

			if (oe.wheelDelta) {
				delta = -oe.wheelDelta;
			}
			if (oe.detail) {
				delta = oe.detail * 40;
			}

			value -= delta / 8;
			if (value > 100) {
				value = 100;
			}
			if (value < 0) {
				value = 0;
			}

			result = element.slider('option', 'slide').call(element, e, {value: value});
			if (result !== false) {
				element.slider('value', value);
			}
			return false;
		});
		
		// Bind mouse wheel to the horizontal slider
		$(ft_sh).bind('mousewheel DOMMouseScroll', function (e) {
			var delta = 0, element = $(this), value, result, oe;
			oe = e.originalEvent; // for jQuery >=1.7
			value = element.slider('value');

			if (oe.wheelDelta) {
				delta = -oe.wheelDelta;
			}
			if (oe.detail) {
				delta = oe.detail * 40;
			}

			value -= delta / 8;
			if (value > 100) {
				value = 100;
			}
			if (value < 0) {
				value = 0;
			}

			result = element.slider('option', 'slide').call(element, e, {value: value});
			if (result !== false) {
				element.slider('value', value);
			}
			return false;
		});

         //size scrollbar and handle proportionally to scroll distance
        function sizeScrollbar() {
			var remainder = scrollContentData.width() - scrollPaneX.width();

			if ( remainder <= 0 ) {
				scrollbar.find(".ui-slider-handle").css("display", "none");
			} else {
				var proportion = remainder / scrollContentData.width();
				var handleSize = scrollPaneX.width() - (proportion * scrollPaneX.width());
				scrollbar.find(".ui-slider-handle").css({
					width: handleSize,
					"margin-left": -handleSize / 2,
					display: "block"
				});
				handleHelper.width("").width(scrollPaneX.width() - handleSize);
			}
        }

        //reset slider value based on scroll content position
        function resetValue() {
				scrollbar.slider("value", 0);
				scrollContentX.css("margin-left", 0);
            var remainder = scrollPaneX.width() - scrollContentData.width();
            var leftVal = scrollContentX.css("margin-left") === "auto" ? 0 : parseInt(scrollContentX.css("margin-left"));
            var percentage = Math.round(leftVal / remainder * 100);
              scrollbar.slider("value", percentage);
        }
       
		// Sets up the size and positioning of the vertical slider
        function sizeScrollbarY() {
			if (viewportSize < scrollContentData.height()) {
				var remainderY = scrollContentData.height() - viewportSize;
				var proportionY = remainderY / scrollContentData.height();
				var handleSizeY = viewportSize - (proportionY * viewportSize);

				scrollbarY.find(".ui-slider-handle").css({
					height: handleSizeY,
					"margin-bottom": -handleSizeY / 2,
					display: "block"
				});
				$(ft_sv).css({
					height: viewportSize / 2,
					"margin-top": colHeadsH + 5 + (handleSizeY / 2)
				});

				handleHelperY.height("").height(viewportSize - handleSizeY);
				scrollbarY.height(handleHelperY.height());
			} else {
				scrollbarY.find(".ui-slider-handle").css("display", "none");
			}
        }

        // Supporting functions for carving up the source table
        function findWidestCell(t1, t2, colID, useTD) {           
            var widest = 0, counting = 0;
            
            if (useTD) {
                var haystack = '#' + t1 + ' th:nth-child(' + colID + '), #' 
						+ t1 + ' td:nth-child(' + colID + '), #' 
						+ t2 + ' th:nth-child(' + colID + '), #' 
						+ t2 + ' td:nth-child(' + colID + ')';
            } else {
                var haystack = '#' + t1 + ' th:nth-child(' + colID + '), #' 
						+ t2 + ' th:nth-child(' + colID + ')';
            }
            $(haystack).each(function () {
                counting++;
                if ($(this).innerWidth() > widest) {
                    widest = $(this).innerWidth();
                }
            });
            return widest;
        }

        function findTallestCell(t1, t2, rowID) {
            var tallest = 0;
            var haystack = '#' + t1 + ' tr:nth-child(' + rowID + ') td, #' 
					+ t1 + ' tr:nth-child(' + rowID + ') th, #' 
					+ t2 + ' tr:nth-child(' + rowID + ') th, #' 
					+ t2 + ' tr:nth-child(' + rowID + ') td';

            $(haystack).each(function () {
                if ($(this).innerHeight() > tallest) {
                    tallest = $(this).innerHeight();
                }
            });
            return tallest;
        }

        function fixCellWidths(tblHead, tblData, useTD) {          
            // Passed the table of column headers and the table of data
            // both will have the same number of columns. Find out how many columns
            // we're dealing with, then go through each and fix their widths to 
            // the widest cell

            // Padding css should be the same for TH and TD, else this may go wrong.
            useTD = typeof useTD !== 'undefined' ? useTD : false;

            var colCount = 0, cellCount = 0;
            $('#' + tblData + ' tr:nth-child(1) tr').each(function () {
                colCount++;
            });
            $('#' + tblData + ' tr:nth-child(1) td').each(function () {
                cellCount++;
            });
            
            if (cellCount > colCount) {
                colCount = cellCount;
            }
            
            for (var i = 1; i <= colCount; i++) {
                var w = findWidestCell(tblHead, tblData, i, useTD);

                $('#' + tblHead + ' th:nth-child(' + i + ')').css({
                    width: w,
                        'min-width': w,
                        'max-width': w
                });
                $('#' + tblData + ' td:nth-child(' + i + ')').css({
                    width: w,
                        'min-width': w,
                        'max-width': w
                });
            }

            $('#' + tblHead + ' th, #' + tblHead + ' td').css({
                overflow: 'hidden'
            });
            $('#' + tblData + ' th, #' + tblData + ' td').css({
                overflow: 'hidden'
            });
        }

        function fixCellHeights(t1, t2) {
            // Designed for use of two tables sat side by side pretending to be
            // of the same rows.
            // Go through each row and find the height of the first cell 
            // of both tables.  Set the height of all cells in that row.

            // How many rows?

            rowCount = 0;
            $('#' + t1 + ' tr').each(function () {
                rowCount++;
            });

            for (var i = 1; i <= rowCount; i++) {

                var h = findTallestCell(t1, t2, i);

                // Iterate through the whole row, setting the heights on all cells
                $('#' + t1 + ' tr:nth-child(' + i + ') td, #' + t1 + ' tr:nth-child(' + i + ') th').each(function () {
                    $(this).css({
                        height: h,
                            'max-height': h,
                            'min-height': h,
                        overflow: 'hidden'
                    });
                });
                $('#' + t2 + ' tr:nth-child(' + i + ') td, #' + t2 + ' tr:nth-child(' + i + ') th').each(function () {
                    $(this).css({
                        height: h,
                            'max-height': h,
                            'min-height': h,
                        overflow: 'hidden'
                    });
                });
            }
        }
    }
}(jQuery));

/*
 * Example of use
 *
$(function () {
    $('#StudentAttendanceWeekGrid').fixRowsCols({
        r: 1,
        c: 6,
		h: 400
    });
});*/