/*!
 * Fixed Table Header and Left Column
 * 
 * Copyright (c) 2013,2014 nkmrshn
 *
 * Released under the MIT License.
 * http://www.opensource.org/licenses/MIT
 */
(function($) {
  $.fn.extend({
    fixedTblHdrLftCol: function(options) {
      var cfg = $.extend(true, {
        scroll: {
          height: null,
          width: null,
          headRow: {
            className: 'fTHLC-head-row',
            enabled: true,
            overflow: 'auto'
          },
          leftCol : {
            className: 'fTHLC-left-col',
            enabled: true,
            overflow: 'auto',
            fixedSpan: 1
          },
          syncWith: null
        },
        wrapper: {
          outer: {
            idName: null,
            className: 'fTHLC-outer-wrapper'
          },
          inner: {
            idName: null,
            className: 'fTHLC-inner-wrapper'
          }
        },
        corner: {
          append: true,
          deepClone: false,
          outer: {
            idName: null,
            className: 'fTHLC-outer-corner'
          },
          inner: {
            idName: null,
            className: 'fTHLC-inner-corner'
          }
        }
      }, options);

      var scrollWidth = cfg.scroll.width;
      var scrollHeight = cfg.scroll.height;
      var fixedLeftWidth = null;
      var fixedHeadHeight = null;

      function getScrollWidth(table) {
        var width = scrollWidth;
        
        if(!width)
          width = table.outerWidth(true) - getFixedLeftWidth(table);
        
        return width;
      }
      
      function getScrollHeight(table) {
        var height = scrollHeight;
        
        if(!height)
          height = table.outerHeight(true) - getFixedHeadHeight(table);
        
        return height;
      }
      
      function getHeadRowCount(table) {
        return table.find('thead tr').length;
      }
        
      function getFixedHeadRows(table) {
         var rows = $([]);

        for(var i = 0; i < getHeadRowCount(table); i++) {
          var row = $([]);

          for(var j = 0; j < cfg.scroll.leftCol.fixedSpan; j++) {
            row.push(table.find('thead tr:nth-child('+(i + 1)+') th:nth-child('+(j + 1)+')'));
          }

          rows.push(row);
        }

        return rows;
      }

      function getHeadFirstRows(table) {
        var rows = $([]);

        for(var i = 0; i < getHeadRowCount(table); i++) {
          rows.push(table.find('thead tr:nth-child('+(i + 1)+') th:first-child'));
        }

        return rows;
      }
      
      function getHeadCols(table, n) {
        return  table.find('thead tr:nth-child('+(n + 1)+') th');
      }

      function getFixedHeadHeight(table) {
        var height = fixedHeadHeight;

        if(!height) {
          var rows = getHeadFirstRows(table);

          rows.each(function() {
            height += $(this).outerHeight(true);
          });

          fixedHeadHeight = height;
        }

        return height;
      }

      function getFixedLeftCols(table) {
        var cols =$([]);

        for(var i = 0; i < cfg.scroll.leftCol.fixedSpan; i++) {
          cols.push(table.find('tbody tr:first-child td:nth-child('+(i + 1)+')'));
        }

        return cols;
      }

      function getFixedLeftWidth(table) {
        var width = fixedLeftWidth;

        if(!width) {
          var cols = getFixedLeftCols(table);

          cols.each(function() {
            width += $(this).outerWidth(true);
          });

          fixedLeftWidth = width;
        }

        return width;
      }
      
      function getTableWidth(table) {
        var width = 0;
        
        table.find('tbody tr:first-child td').each(function() {
          width += $(this).outerWidth(true);
        });
        
        return width;
      }
      
      function createOuter(table) {
        table
          .wrap($(document.createElement('div'))
          .attr('id', cfg.wrapper.outer.idName)
          .addClass(cfg.wrapper.outer.className)
          .css('width', getScrollWidth(table))
          .css('height', getScrollHeight(table))
          .css('position', 'relative')
          .css('padding-left', getFixedLeftWidth(table)+'px')
          .css('padding-top', getFixedHeadHeight(table)+'px')
          .css('overflow', 'hidden'));
      }
      
      function createInner(table) {
        table
          .wrap($(document.createElement('div'))
          .attr('id', cfg.wrapper.inner.idName)
          .addClass(cfg.wrapper.inner.className)
          .css('overflow-x', cfg.scroll.headRow.overflow)
          .css('overflow-y', cfg.scroll.leftCol.overflow)
          .css('width', getScrollWidth(table))
          .css('height', getScrollHeight(table)));
      }
      
      function setTableCSS(table) {
        var leftCornerWidth = getFixedLeftWidth(table);
        var tableWidth = getTableWidth(table);

        table
          .css('border-collapse', 'collapse')
          .css('width', (tableWidth - leftCornerWidth)+'px');
      }
      
      function setTheadCSS(table) {
        var rows = getHeadFirstRows(table);
        var totalHeight = 0;
        
        rows.each(function(i) {
          var cols = getHeadCols(table, i);
          var totalWidth = 0;
          
          cols.each(function(j) {
            var width = $(this).outerWidth(true);
            var height = $(this).outerHeight(true);

            totalWidth += width;

            if(j == 0)
              totalHeight += height;

            $(this)
              .addClass(cfg.scroll.headRow.className)
              .css('position', 'absolute')
              .css('top', (totalHeight - height)+'px')
              .css('left', (totalWidth - width)+'px');
          });
        });
      }
      
      function setTbodyCSS(table) {
        var leftCornerWidth = getFixedLeftWidth(table);
        var tableWidth = getTableWidth(table);

        table.find('tbody tr').each(function() {
          $(this).css('width', (tableWidth - leftCornerWidth)+'px');
        });
      }

      function setLeftColumnCSS(table) {
        var total = 0;
        var cols = getFixedLeftCols(table);

        cols.each(function(i) {
          var width = $(this).outerWidth(true); 
          
          total += width;
          table
            .find('tbody tr td:nth-child('+(i + 1)+')')
            .addClass(cfg.scroll.leftCol.className)
            .css('position', 'absolute')
            .css('left', (total - width)+'px');
        });
      }
      
      function recalHeight(table) {
        table.find('tbody tr').each(function() {
          var maxHeight = 0;

          for(var i = 0; i < cfg.scroll.leftCol.fixedSpan; i++) {
            var h = $(this).find('td:nth-child('+(i + 1)+')').height();

            if(h > maxHeight)
              maxHeight = h;
          }

          $(this)
            .find('td:nth-child('+(cfg.scroll.leftCol.fixedSpan + 1)+')')
            .height(maxHeight);
          $(this).hide().fadeIn(0);
        });
      }

      function appendCorner(table) {
        var corner = $('<div></div>')
                       .attr('id', cfg.corner.outer.idName)
                       .addClass(cfg.corner.outer.className)
                       .css('position', 'absolute')
                       .css('left', '0px')
                       .css('top',  '0px')
                       .css('margin', '0')
                       .css('padding', '0')
                       .css('width', getFixedLeftWidth(table)+'px')
                       .css('height', getFixedHeadHeight(table)+'px');
        var innerTable = $('<table></table>')
                           .attr('id', cfg.corner.inner.idName)
                           .addClass(cfg.corner.inner.className)
                           .css('border-collapse', 'collapse');
        var thead = $('<thead></thead>');
        var rows = getFixedHeadRows(table);

        rows.each(function() {
          var tr = $('<tr></tr>');

          $(this).each(function() {
            var th = $(this).clone(cfg.corner.deepClone);
            
            $(this).removeAttr('id');
            $(this).unbind();
            th.appendTo(tr);
          });

          tr.appendTo(thead);
        });

        thead.appendTo(innerTable);
        innerTable.appendTo(corner);
        corner.appendTo(table.parent());
      }

      function recalHeaderPosition(table) {
        var leftPosition = [];

        table.find('tbody tr:first').each(function() {
          $(this).find('td').each(function() {
            var position = $(this).position();
            leftPosition.push(position.left);
          });
        });

        table.find('thead tr').each(function() {
          $(this).find('th').each(function(i) {
            $(this).css('left', leftPosition[i]+'px');
          });
        });
      }
      
      function init(table) {
        setLeftColumnCSS(table);
        setTbodyCSS(table);
        setTheadCSS(table);
        setTableCSS(table);
        
        createOuter(table);
        createInner(table);
        
        recalHeight(table);
        
        if(cfg.corner.append)
          appendCorner(table);

        recalHeaderPosition(table);
      }
      
      function scrollCols(table) {
        table.find('tbody tr').each(function() {
          for(var i = 0; i < cfg.scroll.leftCol.fixedSpan; i++) {
            $(this)
             .find('td:nth-child('+(i + 1)+')')
             .css('top', $(this)
                           .find('td:nth-child('+(cfg.scroll.leftCol.fixedSpan + 1)+')')
                           .position()
                           .top+'px');
          }
        });
      }
      
      function scrollRows(table) {
        table.find('thead tr').each(function(i) {
          if(i < getHeadRowCount(table)) {
            $(this).find('th').each(function(j) {
              $(this)
                .css('left', table
                               .find('tbody tr:first-child td:nth-child('+(j + 1)+')')
                               .position()
                               .left+'px');
            });
          }
        });
      }
      
      function scrollOther(table, other) {
        other.scrollTop(table.scrollTop());
        other.scrollLeft(table.scrollLeft());
      }
      
      function syncTables(table) {
        var syncWith = cfg.scroll.syncWith;
        
        if($.isArray(syncWith)) {
          $.each(syncWith, function() {
            scrollOther(table, $(this.toString()).parent());
          });
        } else
          scrollOther(table, $(syncWith).parent());
      }
      
      return this.each(function() {
        if($(this)[0].tagName.toLowerCase() != "table")
          return true;
        
        init($(this)); 
        
        if(cfg.scroll.leftCol.enabled || cfg.scroll.headRow.enabled) {
          $(this).parent().scroll(function() {
            if(cfg.scroll.headRow.enabled)
              scrollRows($(this));

            if(cfg.scroll.leftCol.enabled)
              scrollCols($(this));
            
            if(cfg.scroll.syncWith)
              syncTables($(this));
          });
        }
      });
    }
  });
})(jQuery);
