// vim: et:ts=4:sw=4:sts=4:ft=javascript
// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new)

/*@
Ox.Element <function:Ox.JQueryElement> Basic UI element object
    # Usage --------------------------------------------------------------------
    ([options[, self]]) -> <object> UI element
    # Arguments ----------------------------------------------------------------
    options <object> the options of the element
        # Properties
        element <string> tagname or CSS selector
        tooltip <string|function> tooltip title or function that returns one
            (e) -> <string> tooltip title
            e <object> mouse event
    options <string> tagname or CSS selector
    self    <object> shared private variable
    # Events -------------------------------------------------------------------
    anyclick    <event> anyclick
        fires on mouseup, but not on any subsequent mouseup within 250 ms
        *           <*> original event properties
    doubleclick <event> doubleclick
        fires on the second mousedown within 250 ms
        * <*> original event properties
    drag        <event> drag
        fires on mousemove after dragstart, stops firing on mouseup
        clientDX    <number> horizontal drag delta in px
        clientDY    <number> vertical drag delta in px
        *           <*> original event properties
    dragend     <event> dragpause
        Fires on mouseup after dragstart
        clientDX    <number> horizontal drag delta in px
        clientDY    <number> vertical drag delta in px
        *           <*> original event properties
    dragpause   <event> dragpause
        Fires once when the mouse doesn't move for 250 ms after drag
        clientDX    <number> horizontal drag delta in px
        clientDY    <number> vertical drag delta in px
        *           <*> original event properties
    dragstart   <event> dragstart
        fires when the mouse is down for 250 ms
        *           <*> original event properties
    mouserepeat <event> mouserepeat
        fires every 50 ms after the mouse was down for 250 ms, stops firing on
        mouseleave or mouseup
        *           <*> original event properties
    singleclick <event> singleclick
        fires 250 ms after mouseup, if there was no subsequent mousedown
        *           <*> original event properties
@*/

Ox.Element = function(options, self) {

        /*
        // allow for 'Ox.Element()' instead of 'Ox.Element()'
        if (!(this instanceof arguments.callee)) {
            return new arguments.callee(options, self);
        }
        */

        // create private object
        self = self || {};
        // create defaults and options objects
        self.defaults = {};
        self.options = options || {};
        // allow for Ox.TestElement('<tagname>')
        // or Ox.TestElement('cssSelector')
        if (Ox.isString(self.options)) {
            self.options = {
                element: self.options
            };
        }
        // create event handler
        if (!self.$eventHandler) {
            self.$eventHandler = $('<div>');
        }

        // create public object
        var that = new Ox.JQueryElement(
                $(self.options.element || '<div>')
            )
            .mousedown(mousedown);

        if (self.options.tooltip) {
            if (Ox.isString(self.options.tooltip)) {
                that.$tooltip = Ox.Tooltip({
                    title: self.options.tooltip,
                });
                that.bind({
                    mouseenter: mouseenter
                });
            } else {
                that.$tooltip = Ox.Tooltip({
                    animate: false
                });
                that.bind({
                    mousemove: mousemove
                });
            }
            that.bind({
                mouseleave: mouseleave
            });
        }

        function bind(action, event, fn) {
            self.$eventHandler[action]('ox_' + event, function(event, data) {
                // fixme: remove second parameter
                fn(Ox.extend({
                    _element: that.$element,
                    _event: event
                }, data), data);
            });
        }

        function mousedown(e) {
            /*
                better mouse events
                mousedown:
                    trigger mousedown
                within 250 msec:
                    mouseup: trigger anyclick ("click" would collide with click events of certain widgets)
                    mouseup + mousedown: trigger doubleclick
                after 250 msec:
                    mouseup + no mousedown within 250 msec: trigger singleclick
                    no mouseup within 250 msec:
                        trigger mouserepeat every 50 msec
                        trigger dragstart
                            mousemove: trigger drag
                            no mousemove within 250 msec:
                                trigger dragpause
                            mouseup: trigger dragend
            */
            var clientX, clientY,
                dragTimeout = 0,
                mouseInterval = 0;
            if (!self.mouseTimeout) {
                // first mousedown
                that.triggerEvent('mousedown', e);
                self.mouseup = false;
                self.mouseTimeout = setTimeout(function() {
                    self.mouseTimeout = 0;
                    if (self.mouseup) {
                        // singleclick
                        that.triggerEvent('singleclick', e);
                    } else {
                        // mouserepeat, drag
                        clientX = e.clientX;
                        clientY = e.clientY;
                        that.triggerEvent('dragstart', e);
                        mouserepeat();
                        mouseInterval = setInterval(mouserepeat, 50);
                        Ox.UI.$window.unbind('mouseup', mouseup)
                            .mousemove(mousemove)
                            .one('mouseup', function(e) {
                                clearInterval(mouseInterval);
                                clearTimeout(dragTimeout);  
                                Ox.UI.$window.unbind('mousemove', mousemove);
                                that.triggerEvent('dragend', extend(e));
                            });
                        that.one('mouseleave', function() {
                            clearInterval(mouseInterval);
                        });
                    }
                }, 250);
            } else {
                // second mousedown
                clearTimeout(self.mouseTimeout);
                self.mouseTimeout = 0;
                that.triggerEvent('doubleclick', e);
            }
            Ox.UI.$window.one('mouseup', mouseup);
            function extend(e) {
                return Ox.extend({
                    clientDX: e.clientX - clientX,
                    clientDY: e.clientY - clientY
                }, e);
            }
            function mousemove(e) {
                e = extend(e);
                clearTimeout(dragTimeout);  
                dragTimeout = setTimeout(function() {
                    that.triggerEvent('dragpause', e);
                }, 250);
                that.triggerEvent('drag', e);
            }
            function mouserepeat() {
                that.triggerEvent('mouserepeat');
            }
            function mouseup(e) {
                // only trigger on first mouseup
                if (!self.mouseup) {
                    that.triggerEvent('anyclick', e);
                    self.mouseup = true;
                }
            }
        }

        function mouseenter(e) {
            that.$tooltip.show(e);
        }

        function mouseleave(e) {
            that.$tooltip.hide();
        }

        function mousemove(e) {
            Ox.print('mousemove!!')
            that.$tooltip.options({
                title: self.options.tooltip(e)
            }).show(e);
        }

        self.setOption = function() {
            // self.setOptions(key, value)
            // is called when an option changes
            // (to be implemented by widget)
        };

        that._self = self; // fixme: remove

        /*@
        bindEvent <function> Binds a function to an event
            (event, callback)        -> <o> This element
            ({event: callback, ...}) -> <o> This element
            callback <f> Callback function
                data <o> event data (key/value pairs)
            event <s> Event name
                Event names can be namespaced, like <code>'click.foo'</code>
            
        @*/
        that.bindEvent = function() {
            Ox.forEach(Ox.makeObject(arguments), function(fn, event) {
                bind('bind', event, fn);
            });
            return that;
        }

        /*@
        bindEventOnce <function> Binds a function to an event, once
            (event, callback)        -> <obj> This element object
            ({event: callback, ...}) -> <obj> This element object
            callback <f> Callback function
                data <o> event data (key/value pairs)
            event <s> Event name
                Event names can be namespaced, like <code>'click.foo'</code>
        @*/
        that.bindEventOnce = function() {
            Ox.forEach(Ox.makeObject(arguments), function(fn, event) {
                bind('one', event, fn);
            });
            return that;
        };

        /*@
        defaults <function> Sets the default options for an element object
        ({key: value, ...}) -> <obj> This element object
        key     <str> The name of the default option
        value   <val> The value of the default option
        @*/
        that.defaults = function(defaults) {
            // sets the default options
            self.defaults = defaults;
            self.options = defaults;
            return that;
        };

        /*@
        gainFocus <function> Makes an element object gain focus
        () -> <obj> This element object
        @*/
        that.gainFocus = function() {
            Ox.Focus.focus(that.id);
            return that;
        };

        /*@
        hasFocus <function> Returns true if an element object has focus
        () -> <boolean> True if the element has focus
        @*/
        that.hasFocus = function() {
            return Ox.Focus.focused() == that.id;
        };

        /*@
        loseFocus <function> Makes an element object lose focus
        () -> <object> This element object
        @*/

        that.loseFocus = function() {
            Ox.Focus.blur(that.id);
            return that;
        };

        /*@
        options <function> Gets or sets the options of an element object
            # Usage
            ()                  -> <obj> all options
            (key)               -> <any> the value of option[key]
            (key, value)        -> <obj> this element
                Sets options[key] to value and calls self.setOption(key, value)
                if the key/value pair was added or modified
            ({key: value, ...}) -> <obj> this element
                Sets multiple options and calls self.setOption(key, value)
                for every key/value pair that was added or modified
            # Arguments
            key     <str> the name of the option
            value   <val> the value of the option
        @*/

        that.options = function() {
            return Ox.getset(self.options, arguments, self.setOption, that);
        };

        /*@
        removeElement <function> Removes an element object and its event handler
            () -> <obj> This element
        @*/
        that.removeElement = function() {
            that.loseFocus();
            delete self.$eventHandler;
            that.remove();
            delete Ox.UI.elements[that.id];
            return that;
        };

        /*@
        triggerEvent <function> Triggers an event
            (event) -> <object> This element object
            (event, data) -> <object> This element object
            ({event: data, ...}) -> <object> This element object
            event <string> Event name
            data <object> Event data (key/value pairs)
        @*/
        that.triggerEvent = function() {
            Ox.forEach(Ox.makeObject(arguments), function(data, event) {
                if ([
                    'mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick',
                    'dragstart', 'drag', 'dragpause', 'dragend', 'playing', 'position', 'progress'
                ].indexOf(event) == -1) {
                    Ox.print(that.id, self.options.id, 'trigger', event, data);
                }
                // it is necessary to check if self.$eventHandler exists,
                // since, for example, when removing the element on click,
                // singleclick will fire after the removal of the event handler
                self.$eventHandler && self.$eventHandler.trigger('ox_' + event, data);
            });
            return that;
        };

        /*@
        unbindEvent <function> Unbinds all callbacks from an event
            To unbind a specific handler, use namespaced events, like
            <code>bindEvent('click.foo', callback)</code>, and then
            <code>unbindEvent('click.foo')</code>.
            () -> <object> This element object
                Unbinds all events
            (event) -> <object> This element object
                Unbinds one event
            (event, event, ...) -> <object> This element object
                Unbinds multiple events
            ([event, event, ...]) -> <object> This element object
                Unbinds multiple events
            event <string> Event name
        @*/
        that.unbindEvent = function() {
            if (arguments.length == 0) {
                self.$eventHandler.unbind();
            } else {
                Ox.makeArray(arguments).forEach(function(event) {
                    self.$eventHandler.unbind('ox_' + event);
                });
            }
            return that;
        };

        return that;

};