/**
* Check whether an element is visible. Also checks for parent visibility and
* form inputs of type "hidden".
*/
function isVisible (e) {
    if (e.style) {
        if (e.style.visibility && e.style.visibility == 'hidden')
            return false ; 
        if (e.style.display && e.style.display == 'none' )
            return false ; 
    }
    if (e.getAttribute && e.getAttribute('type') && e.getAttribute('type') == 'hidden')
        return false;
    if (e.parentNode)
        return isVisible(e.parentNode);
    return true; 
}

/**
* Create string representation from the given element node.
* FIXME: remove, just for debugging
*/
function node2String (n) {
    var s = '<' + n.nodeName.toLowerCase();
    for (var i=0; i<n.attributes.length; i++) {
        s += '\n    ' + n.attributes[i].name + '="' + n.attributes[i].value.replace(/\n/, '').replace(/ /, '') + '"';
    }
    return s + '\n>';
}

/**
* This is a custom tab-handler that is able to focus any element in the DOM,
* not only those which are "allowed" to have focus. The focus is indicated by
* a special CSS-class "focused" which allows for easily handling focus on any
* type of element (even in dumb and stupid browsers like IE).
*
* One remaining problem is how we can handle manual calls to HTMLElement.focus,
* because they are not interceptable in any way. My proposal is to replace all
* calls to HTMLElement.focus with calls to TabHandler.focusElement so we can
* safely intercept all focusing using our own functions.
*
* FIXME: maybe FocusHandler would be a better name??
*/
_TabHandler = Class.create({

    /**
    * Create new _TabHandler. You should only have one instance of this.
    */
    initialize : function () {
        // init properties
        this.curElement = null;         // currently selected element
        // hook ourselves into the document's global events
        Event.observe(document, 'keydown', this._onKeyDown.bind(this));
        Event.observe(document, 'mousedown', this._onMouseDown.bind(this));
    },

    /**
    * Return all elements in the whole document that have tab support.
    */
    findAll : function () {
        return $$('[istabtarget="true"]');
    },

    /**
    * Return the first element in the whole document that has tab support.
    */
    findFirst : function () {
        return this.findAll()[0];
    },

    /**
    * Return the last element in the whole document that has tab support.
    */
    findLast : function () {
        var all = this.findAll();
        return all[all.length-1];
    },
    
    /**
    * Return the next element that has tab support (searching from the
    * current element or from the first if curElement is null).
    */
    findNext : function () {
        if (!this.curElement)
            return this.findFirst();
        // find curr. element in all elements
        var all = this.findAll();
        if (!all)
            return null;
        var p = all.indexOf(this.curElement);
        // if next element exceeds bounds (or is unset), start from first element
        if (p+1 >= all.length || !all[p+1]) {
            p = -1;
        }
        // find next visible element
        for (var i=p+1; i<all.length; i++) {
            if (isVisible(all[i]))
                return all[i];
        }
        // else return first element
        return this.findFirst();
    },

    /**
    * Return the previous element that has tab support (searching from the
    * current element or from the first if curElement is null).
    */
    findPrevious : function () {
        if (!this.curElement)
            return this.findFirst();
        // find curr. element in all elements
        var all = this.findAll();
        if (!all)
            return null;
        var p = all.indexOf(this.curElement);
        // if next element exceeds bounds, start from last element
        if (p == 0 || !all[p-1]) {
            p = all.length;
        }
        // find previous visible element
        for (var i=p-1; i>=0; i--) {
            if (isVisible(all[i]))
                return all[i];
        }
        // else return last element
        return this.findLast();
    },

    /**
    * Move the focus to the given element (and remove it from current element).
    */    
    focusElement : function (element) {
        // unfocus current element
        this.unfocusCurrent();
        // set element as current
        this.curElement = element; 
        if (isVisible(this.curElement) && this.curElement.focus)
            this.curElement.focus();
        this.curElement.addClassName('focused');
        // observe blur event on currently focused element
        /*this.curElement.observe('blur', (function(e) {
            this.unfocusCurrent();
        }).bind(this));*/
         this.curElement.observe('blur', this.unfocusCurrent);
    },
    
    /**
    * Remove the focus from the currently focused element.
    */    
    unfocusCurrent : function () {
        if (!this.curElement)
            return;
        this.curElement.removeClassName('focused');
        this.curElement.stopObserving('blur', this.unfocusCurrent);
        this.curElement = null;
    },
    
    _onKeyDown : function (e) {
        var key = e.which ? e.which : e.keyCode;
        var shift = e.modifiers & Event.SHIFT_MASK || e.shiftKey;
        if (key == Event.KEY_TAB) {
            // TAB?
            // if we have no current element, use event target
            if (!this.curElement)
                this.curElement = e.element();
            // find next (or previous) visible element with tab-support
            var next = shift ? this.findPrevious() : this.findNext();
            // and move focus to it
            if (next) {
                this.focusElement(next);
            }
            // TODO: not further propagate event in Opera (Opera seems to fail stopping event propagation yet)
            e.preventDefault();
            e.stop();
            return false;
            
        } else if (key == 32 || key == 13) {
            // SPACE or ENTER?
            var el = e.element();
            if (el && el.getAttribute && el.getAttribute('istabtarget') && el.onClick) {
                el.onClick();
            }
            
        }
    },

    _onMouseDown : function (e) {
        // check if our event target is an element with tabsupport
        var el = e.element();
        if (el && el != this.curElement && el.getAttribute && el.getAttribute('istabtarget')) {
            // if it is, set focus on that element
            this.focusElement(el); 
        }
    }
    
});
TabHandler = new _TabHandler();

// stubs (required for now to avoid errors related to old tab handler)
g_keyDown = function () {}
g_keyUp = function () {}
g_allKeyDown = function () {}
g_allKeyUp = function () {}

