/*
  dragtable v1.0
  June 26, 2008
  Dan Vanderkam, http://danvk.org/dragtable/
                 http://code.google.com/p/dragtable/

  Instructions:
    - Download this file
    - Add <script src="dragtable.js"></script> to your HTML.
    - Add class="draggable" to any table you might like to reorder.
    - Drag the headers around to reorder them.

  This is code was based on:
    - Stuart Langridge's SortTable (kryogenix.org/code/browser/sorttable)
    - Mike Hall's draggable class (http://www.brainjar.com/dhtml/drag/)
    - A discussion of permuting table columns on comp.lang.javascript

  Licensed under the MIT license.
 */

// Here's the notice from Mike Hall's draggable script:
//*****************************************************************************
// Do not remove this notice.
//
// Copyright 2001 by Mike Hall.
// See http://www.brainjar.com for terms of use.
//*****************************************************************************
dragtable = {
    // How far should the mouse move before it's considered a drag, not a click?
    dragRadius2: 100,
    setMinDragDistance: function (x) {
        dragtable.dragRadius2 = x * x;
    },

    // How long should cookies persist? (in days)
    cookieDays: 365,
    setCookieDays: function (x) {
        dragtable.cookieDays = x;
    },

    // Determine browser and version.
    // TODO: eliminate browser sniffing except where it's really necessary.
    Browser: function () {
        var ua, s, i;

        this.isIE = false;
        this.isNS = false;
        isSafari = false;
        this.version = null;
        ua = navigator.userAgent;
        s = "MSIE";
        if ((i = ua.indexOf(s)) >= 0) {
            this.isIE = true;
            this.version = parseFloat(ua.substr(i + s.length));
            return;
        }

        s = "Netscape6/";
        if ((i = ua.indexOf(s)) >= 0) {
            this.isNS = true;
            this.version = parseFloat(ua.substr(i + s.length));
            return;
        }
        s = "Safari";
        if ((i = ua.indexOf(s)) >= 0) {
            isSafari = true;
            return;
        }
        // Treat any other "Gecko" browser as NS 6.1.
        s = "Gecko";
        if ((i = ua.indexOf(s)) >= 0) {
            this.isNS = true;
            this.version = 6.1;
            return;
        }

    },
    browser: null,

    // Detect all draggable tables and attach handlers to their headers.
    init: function () {
        // Don't initialize twice
        if (arguments.callee.done) return;
        arguments.callee.done = true;
        if (_dgtimer) clearInterval(_dgtimer);
        if (!document.createElement || !document.getElementsByTagName) return;

        dragtable.dragObj.zIndex = 0;
        dragtable.browser = new dragtable.Browser();
        forEach(document.getElementsByTagName('table'), function (table) {
            if (table.className.search(/\bdraggable\b/) != -1) {
                dragtable.makeDraggable(table);
            }
        });
    },

    // The thead business is taken straight from sorttable.
    makeDraggable: function (table) {
        if (table.getElementsByTagName('thead').length == 0) {
            the = document.createElement('thead');
            the.appendChild(table.rows[0]);
            table.insertBefore(the, table.firstChild);
        }

        // Safari doesn't support table.tHead, sigh
        if (table.tHead == null) {
            table.tHead = table.getElementsByTagName('thead')[0];
        }

        var headers = table.tHead.rows[0].cells;
        for (var i = 1; i < headers.length; i++) {
            if (isSafari) {
                headers[i].firstChild.onmousedown = dragtable.dragStart;
            } else if (dragtable.browser.isIE) {
                headers[i].firstChild.onmousedown = dragtable.dragStart;
            } else {
                headers[i].onmousedown = dragtable.dragStart;
            }
        }

        // Replay reorderings from cookies if there are any.
        if (dragtable.cookiesEnabled() && table.id &&
				table.className.search(/\bforget-ordering\b/) == -1) {
            dragtable.replayDrags(table);
        }
    },

    // Global object to hold drag information.
    dragObj: new Object(),

    // Climb up the DOM until there's a tag that matches.
    findUp: function (elt, tag) {
        do {
            if (elt.nodeName && elt.nodeName.search(tag) != -1)
                return elt;
        } while (elt = elt.parentNode);
        return null;
    },

    // clone an element, copying its style and class.
    fullCopy: function (elt, deep) {
        var new_elt = elt.cloneNode(deep);
        new_elt.className = elt.className;
        forEach(elt.style,
        function (value, key, object) {
            if (value == null) return;
            if (typeof (value) == "string" && value.length == 0) return;

            new_elt.style[key] = elt.style[key];
        });
        return new_elt;
    },

    eventPosition: function (event) {
        var x, y;
        if (dragtable.browser.isIE) {
            x = window.event.clientX + document.documentElement.scrollLeft
        + document.body.scrollLeft;
            y = window.event.clientY + document.documentElement.scrollTop
        + document.body.scrollTop;
            return { x: x, y: y };
        }
        return { x: event.pageX, y: event.pageY };
    },

    // Determine the position of this element on the page. Many thanks to Magnus
    // Kristiansen for help making this work with "position: fixed" elements.
    absolutePosition: function (elt, stopAtRelative) {
        var ex = 0, ey = 0;
        do {
            var curStyle = dragtable.browser.isIE ? elt.currentStyle
                                           : window.getComputedStyle(elt, '');
            var supportFixed = !(dragtable.browser.isIE &&
                          dragtable.browser.version < 7);
            if (stopAtRelative && curStyle.position == 'relative') {
                break;
            } else if (supportFixed && curStyle.position == 'fixed') {
                // Get the fixed el's offset
                ex += parseInt(curStyle.left, 10);
                ey += parseInt(curStyle.top, 10);
                // Compensate for scrolling
                ex += document.body.scrollLeft;
                ey += document.body.scrollTop;
                // End the loop
                break;
            } else {
                ex += elt.offsetLeft;
                ey += elt.offsetTop;
            }
        } while (elt = elt.offsetParent);
        return { x: ex, y: ey };
    },

    // MouseDown handler -- sets up the appropriate mousemove/mouseup handlers
    // and fills in the global dragtable.dragObj object.
    dragStart: function (event, id) {
        var el;
        var x, y;
        var dragObj = dragtable.dragObj;

        var browser = dragtable.browser;
        if (browser.isIE)
            dragObj.origNode = window.event.srcElement;
        else
            dragObj.origNode = event.target;
        var pos = dragtable.eventPosition(event);

        // Drag the entire table cell, not just the element that was clicked.
        dragObj.origNode = dragtable.findUp(dragObj.origNode, /T[DH]/);

        // Since a column header can't be dragged directly, duplicate its contents
        // in a div and drag that instead.
        // TODO: I can assume a tHead...
        var table = dragtable.findUp(dragObj.origNode, "TABLE");
        dragObj.table = table;
        dragObj.startCol = dragtable.findColumn(table, pos.x);
        if (dragObj.startCol == -1) return;

        var new_elt = dragtable.fullCopy(table, false);
        new_elt.style.margin = '0';

        // Copy the entire column
        var copySectionColumn = function (sec, col) {
            var new_sec = dragtable.fullCopy(sec, false);
            forEach(sec.rows, function (row) {
                var cell = row.cells[col];
                var new_tr = dragtable.fullCopy(row, false);
                if (row.offsetHeight) new_tr.style.height = row.offsetHeight + "px";
                var new_td = dragtable.fullCopy(cell, true);
                if (cell.offsetWidth) new_td.style.width = cell.offsetWidth + "px";
                new_tr.appendChild(new_td);
                new_sec.appendChild(new_tr);
            });
            return new_sec;
        };

        // First the heading
        if (table.tHead) {
            new_elt.appendChild(copySectionColumn(table.tHead, dragObj.startCol));
        }
        forEach(table.tBodies, function (tb) {
            new_elt.appendChild(copySectionColumn(tb, dragObj.startCol));
        });
        if (table.tFoot) {
            new_elt.appendChild(copySectionColumn(table.tFoot, dragObj.startCol));
        }

        var obj_pos = dragtable.absolutePosition(dragObj.origNode, true);
        new_elt.style.position = "absolute";
        new_elt.style.left = obj_pos.x + "px";
        new_elt.style.top = obj_pos.y + "px";
        new_elt.style.width = dragObj.origNode.offsetWidth + "px";
        new_elt.style.height = dragObj.origNode.offsetHeight + "px";
        new_elt.style.opacity = 0.7;

        // Hold off adding the element until this is clearly a drag.
        dragObj.addedNode = false;
        dragObj.tableContainer = dragObj.table.parentNode || document.body;
        dragObj.elNode = new_elt;

        // Save starting positions of cursor and element.
        dragObj.cursorStartX = pos.x;
        dragObj.cursorStartY = pos.y;
        dragObj.elStartLeft = parseInt(dragObj.elNode.style.left, 10);
        dragObj.elStartTop = parseInt(dragObj.elNode.style.top, 10);

        if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
        if (isNaN(dragObj.elStartTop)) dragObj.elStartTop = 0;

        // Update element's z-index.
        dragObj.elNode.style.zIndex = ++dragObj.zIndex;

        // Capture mousemove and mouseup events on the page.
        if (browser.isIE) {
            document.attachEvent("onmousemove", dragtable.dragMove);
            document.attachEvent("onmouseup", dragtable.dragEnd);
            window.event.cancelBubble = true;
            window.event.returnValue = false;
        } else {
            document.addEventListener("mousemove", dragtable.dragMove, true);
            document.addEventListener("mouseup", dragtable.dragEnd, true);
            event.preventDefault();
        }
    },

    // Move the floating column header with the mouse
    // TODO: Reorder columns as the mouse moves for a more interactive feel.
    dragMove: function (event) {
        var x, y;
        var dragObj = dragtable.dragObj;

        // Get cursor position with respect to the page.
        var pos = dragtable.eventPosition(event);

        var dx = dragObj.cursorStartX - pos.x;
        var dy = dragObj.cursorStartY - pos.y;
        if (!dragObj.addedNode && dx * dx + dy * dy > dragtable.dragRadius2) {
            dragObj.tableContainer.insertBefore(dragObj.elNode, dragObj.table);
            dragObj.addedNode = true;
        }

        // Move drag element by the same amount the cursor has moved.
        var style = dragObj.elNode.style;
        style.left = (dragObj.elStartLeft + pos.x - dragObj.cursorStartX) + "px";
        style.top = (dragObj.elStartTop + pos.y - dragObj.cursorStartY) + "px";

        if (dragtable.browser.isIE) {
            window.event.cancelBubble = true;
            window.event.returnValue = false;
        } else {
            event.preventDefault();
        }
    },

    // Stop capturing mousemove and mouseup events.
    // Determine which (if any) column we're over and shuffle the table.
    dragEnd: function (event) {
        if (dragtable.browser.isIE) {
            document.detachEvent("onmousemove", dragtable.dragMove);
            document.detachEvent("onmouseup", dragtable.dragEnd);
        } else {
            document.removeEventListener("mousemove", dragtable.dragMove, true);
            document.removeEventListener("mouseup", dragtable.dragEnd, true);
        }

        // If the floating header wasn't added, the mouse didn't move far enough.
        var dragObj = dragtable.dragObj;
        if (!dragObj.addedNode) {
            return;
        }
        dragObj.tableContainer.removeChild(dragObj.elNode);

        // Determine whether the drag ended over the table, and over which column.
        var pos = dragtable.eventPosition(event);
        var table_pos = dragtable.absolutePosition(dragObj.table);
        if (pos.y < table_pos.y ||
        pos.y > table_pos.y + dragObj.table.offsetHeight) {
            return;
        }
        var targetCol = dragtable.findColumn(dragObj.table, pos.x);
        //if (targetCol != -1 && targetCol != dragObj.startCol) {
        if (targetCol > 0 && targetCol != dragObj.startCol) {
            dragtable.moveColumn(dragObj.table, dragObj.startCol, targetCol);
            if (dragObj.table.id && dragtable.cookiesEnabled() &&
					dragObj.table.className.search(/\bforget-ordering\b/) == -1) {
                dragtable.rememberDrag(dragObj.table.id, dragObj.startCol, targetCol);
            }
        }
        $('#techspec thead th').removeClass('thlast');
        $('#techspec thead th').last().addClass('thlast');
    },

    // Which column does the x value fall inside of? x should include scrollLeft.
    findColumn: function (table, x) {
        var header = table.tHead.rows[0].cells;
        for (var i = 0; i < header.length; i++) {
            //var left = header[i].offsetLeft;
            var pos = dragtable.absolutePosition(header[i]);
            //if (left <= x && x <= left + header[i].offsetWidth) {
            if (pos.x <= x && x <= pos.x + header[i].offsetWidth) {
                return i;
            }
        }
        return -1;
    },

    // Move a column of table from start index to finish index.
    // Based on the "Swapping table columns" discussion on comp.lang.javascript.
    // Assumes there are columns at sIdx and fIdx
    moveColumn: function (table, sIdx, fIdx) {
        var row, cA;
        var i = table.rows.length;
        while (i--) {
            row = table.rows[i]
            var x = row.removeChild(row.cells[sIdx]);
            if (fIdx < row.cells.length) {
                row.insertBefore(x, row.cells[fIdx]);
            } else {
                row.appendChild(x);
            }
        }

        // For whatever reason, sorttable tracks column indices this way.
        // Without a manual update, clicking one column will sort on another.
        var headrow = table.tHead.rows[0].cells;
        for (var i = 0; i < headrow.length; i++) {
            headrow[i].sorttable_columnindex = i;
        }
    },

    // Are cookies enabled? We should not attempt to set cookies on a local file.
    cookiesEnabled: function () {
        return (window.location.protocol != 'file:') && navigator.cookieEnabled;
    },

    // Store a column swap in a cookie for posterity.
    rememberDrag: function (id, a, b) {
        var cookieName = "dragtable-" + id;
        var prev = dragtable.readCookie(cookieName);
        var new_val = "";
        if (prev) new_val = prev + ",";
        new_val += a + "/" + b;
        dragtable.createCookie(cookieName, new_val, dragtable.cookieDays);
    },

    // Replay all column swaps for a table.
    replayDrags: function (table) {
        if (!dragtable.cookiesEnabled()) return;
        var dragstr = dragtable.readCookie("dragtable-" + table.id);
        if (!dragstr) return;
        var drags = dragstr.split(',');
        for (var i = 0; i < drags.length; i++) {
            var pair = drags[i].split("/");
            if (pair.length != 2) continue;
            var a = parseInt(pair[0]);
            var b = parseInt(pair[1]);
            if (isNaN(a) || isNaN(b)) continue;
            dragtable.moveColumn(table, a, b);
        }
    },

    // Cookie functions based on http://www.quirksmode.org/js/cookies.html
    // Cookies won't work for local files.
    cookiesEnabled: function () {
        return false; // (window.location.protocol != 'file:') && navigator.cookieEnabled;
    },

    createCookie: function (name, value, days) {
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            var expires = "; expires=" + date.toGMTString();
        }
        else var expires = "";

        var path = document.location.pathname;
        document.cookie = name + "=" + value + expires + "; path=" + path
    },

    readCookie: function (name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
        }
        return null;
    },

    eraseCookie: function (name) {
        dragtable.createCookie(name, "", -1);
    }

}

/* ******************************************************************
   Supporting functions: bundled here to avoid depending on a library
   ****************************************************************** */

// Dean Edwards/Matthias Miller/John Resig
// has a hook for dragtable.init already been added? (see below)
var dgListenOnLoad = false;

/* for Mozilla/Opera9 */
if (document.addEventListener) {
  dgListenOnLoad = true;
  document.addEventListener("DOMContentLoaded", dragtable.init, false);
}

/* for Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
  dgListenOnLoad = true;
  document.write("<script id=__dt_onload defer src=//0)><\/script>");
  var script = document.getElementById("__dt_onload");
  script.onreadystatechange = function() {
    if (this.readyState == "complete") {
      dragtable.init(); // call the onload handler
    }
  };
/*@end @*/

/* for Safari */
if (/WebKit/i.test(navigator.userAgent)) { // sniff
  dgListenOnLoad = true;
  var _dgtimer = setInterval(function() {
    if (/loaded|complete/.test(document.readyState)) {
      dragtable.init(); // call the onload handler
    }
  }, 10);
}

/* for other browsers */
/* Avoid this unless it's absolutely necessary (it breaks sorttable) */
if (!dgListenOnLoad) {
  window.onload = dragtable.init;
}

// Dean's forEach: http://dean.edwards.name/base/forEach.js
/*
  forEach, version 1.0
  Copyright 2006, Dean Edwards
  License: http://www.opensource.org/licenses/mit-license.php
*/

// array-like enumeration
if (!Array.forEach) { // mozilla already supports this
  Array.forEach = function(array, block, context) {
    for (var i = 0; i < array.length; i++) {
      block.call(context, array[i], i, array);
    }
  };
}

// generic enumeration
Function.prototype.forEach = function(object, block, context) {
  for (var key in object) {
    if (typeof this.prototype[key] == "undefined") {
      block.call(context, object[key], key, object);
    }
  }
};

// character enumeration
String.forEach = function(string, block, context) {
  Array.forEach(string.split(""), function(chr, index) {
    block.call(context, chr, index, string);
  });
};

// globally resolve forEach enumeration
var forEach = function(object, block, context) {
  if (object) {
    var resolve = Object; // default
    if (object instanceof Function) {
      // functions have a "length" property
      resolve = Function;
    } else if (object.forEach instanceof Function) {
      // the object implements a custom forEach method so use that
      object.forEach(block, context);
      return;
    } else if (typeof object == "string") {
      // the object is a string
      resolve = String;
    } else if (typeof object.length == "number") {
      // the object is array-like
      resolve = Array;
    }
    resolve.forEach(object, block, context);
  }
};

