/**
 * Extends the autocompleter to support disabled menu items that 
 * can serve as separators in the list.
 *
 * Overrides the functions that require different behavior to support
 * disabled items.
 */
var MyAutocompleter = Class.create(Ajax.Autocompleter, {


  baseInitialize: function(element, update, options) {
    element          = $(element)
    this.element     = element; 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
      function(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);
    
    // SEE: patch.diff on http://dev.rubyonrails.org/ticket/4782
    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    // BLN: Change to keydown for Safari/IE compatibility.
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  // SEE: http://dev.rubyonrails.org/attachment/ticket/4782/controls.js.2.patch
  // SCRIPTACULOUS TICKET #33 
  onBlur: function(event) {
      // Dont hide the div on "blur" if the user clicks scrollbar 
      if(Element.getStyle(this.update, 'height') != ''){ 
                window.status=Position.within(this.update,Event.pointerX(event),Event.pointerY(event)) 
        if( Position.within( this.update , Event.pointerX(event) , Event.pointerY(event) ) ){  
                        Event.observe(this.update, "blur", this.onBlur.bindAsEventListener(this),true); // make sure blur is still around on 
                        return;  
                } 
    } 
    // needed to make click events working 
    setTimeout(this.hide.bind(this), 250); 
    this.hasFocus = false; 
    this.active = false; 
  },


    initialize: function($super, element, update, url, options) {
        // check for an onBlur overload
        if (options.onBlur) {
            this._onBlur = this.onBlur;
            this.onBlur = function(event) {
                var wasActive = this.active;
                this._onBlur(event);
                // check if this blur is caused by the completion box before calling
                // the child blur 
                if (!wasActive) {
                    options.onBlur(event);
                }
            }.bind(this);
        }
        $super(element, update, url, options);
    },

    onHover: function(event) {
        // overrides onHover to prevent disabled elements
        // from being selected
        var element = Event.findElement(event, 'LI');
        if (element.hasClassName('disabled')) return;

        if (this.index != element.autocompleteIndex) {
            this.index = element.autocompleteIndex;
            this.render();
        }
        Event.stop(event);
    },

    onClick: function(event) {
        // overrides onClick to prevent disabled elements
        // from being selected
        var element = Event.findElement(event, 'LI');
        if (element.hasClassName('disabled')) return;

        this.index = element.autocompleteIndex;
        this.selectEntry();
        this.hide();
    },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         // stop events to prevent the keypress from affecting cursor state
         // in the input field
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         // stop events to prevent the keypress from affecting cursor state
         // in the input field
         Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },


    markPrevious: function() {
        if (this.index > 0) 
            this.index--
        else 
            this.index = this.entryCount-1;
 
        // get the previous entry
        var entry = this.getEntry(this.index);
        // check if its disabled - skip to the previous entry
        // if it is
        if (entry.hasClassName('disabled')) {
            this.markPrevious();
        } else {
            //entry.scrollIntoView(true);
            // FIX to the scroll hopping
            // see https://prototype.lighthouseapp.com/projects/8887/tickets/44-autocompleter-scroll-problem
	    var layer = entry.up(1); // "update" element (suggestions list)
	    var position = entry.positionedOffset();
	    var overlap = {
		top: layer.scrollTop - position[1],
		bottom: (position[1] + entry.getHeight()) - (layer.getHeight() + layer.scrollTop)
	    }
	    var threshold = (0.1 * entry.getHeight());
	    if(overlap.top > threshold) {
		layer.scrollTop = position[1];
	    }
	    else if(overlap.bottom > threshold) {
		layer.scrollTop += overlap.bottom;
	    }
        }
    },

    markNext: function() {
        if (this.index < this.entryCount - 1) 
            this.index++
        else 
            this.index = 0;

        var entry = this.getEntry(this.index);
        if (entry.hasClassName('disabled')) {
            this.markNext();
        } else {
            //entry.scrollIntoView(false);
            // FIX to the scroll hopping
            // see https://prototype.lighthouseapp.com/projects/8887/tickets/44-autocompleter-scroll-problem
	    var layer = entry.up(1); // "update" element (suggestions list)
	    var position = entry.positionedOffset();
	    var overlap = {
		top: layer.scrollTop - position[1],
		bottom: (position[1] + entry.getHeight()) - (layer.getHeight() + layer.scrollTop)
	    }
	    var threshold = (0.1 * entry.getHeight());
	    if(overlap.top > threshold) {
		layer.scrollTop = position[1];
	    }
	    else if(overlap.bottom > threshold) {
		layer.scrollTop += overlap.bottom;
	    }

        }
    },

    updateChoices: function(choices) {
        if(!this.changed && this.hasFocus) {
            this.update.innerHTML = choices;
            Element.cleanWhitespace(this.update);
            Element.cleanWhitespace(this.update.down());

            if(this.update.firstChild && this.update.down().childNodes) {
                this.entryCount = this.update.down().childNodes.length;
                for (var i = 0; i < this.entryCount; i++) {
                    var entry = this.getEntry(i);
                    entry.autocompleteIndex = i;
                    this.addObservers(entry);
                }
            } else { 
                this.entryCount = 0;
            }
 
            this.stopIndicator();

            // set the index to -1 so the user has to take action to enter
            // the auto completion
            this.index = -1;
            //var entry = this.getEntry(this.index);
            //while (entry.hasClassName('disabled') 
            //        && this.index < this.entryCount - 1) {
            //    this.index++;
            //    entry = this.getEntry(this.index);
            //}

            // handle the case that all entries are disabled
            if (entry && entry.hasClassName('disabled')) this.index = 0;
      
            if(this.entryCount==1 && this.options.autoSelect) {
                this.selectEntry();
                this.hide();
            } else {
                this.render();
            }
        }
    }

});







/**
 * Extend the in place editor to provide a cancel on blur option.
 */
Ajax.InPlaceEditor.prototype._createEditField = Ajax.InPlaceEditor.prototype.createEditField;
Ajax.InPlaceEditor.prototype = Object.extend(Ajax.InPlaceEditor.prototype, {

    createEditField: function() {
        this._createEditField();
        // provide the cancelOnBlur option
        if (this.options.cancelOnBlur) {
          this._controls.editor.onblur = this._boundCancelHandler;
        }
    }
});


/**
 * Extends the Scriptaculous' Ajax.InPlaceEditor to create an autocompleting
 * text box for the input field.
 */
var AutocompletingEditor = Class.create(Ajax.InPlaceEditor, {

    initialize: function($super, element, submitUrl, completionUrl, options) {
        // create a div for the inline editor
        this.editorElem = new Element('div', {'class': 'new_target'});
        this.editorElem.hide();
        element.insert({ after: this.editorElem });

        // create a div for the autocompletion        
        this.completionElem = new Element('div', {'class': 'complete_target'});
        this.completionElem.hide();
        element.insert({ after: this.completionElem });

        this.completionUrl = completionUrl;

        options = Object.extend(options,
                                {'externalControl': element,
                                 'externalControlOnly': true});

        $super(this.editorElem, submitUrl, options);
    },

    createEditField: function($super) {
        $super();
        // create a completer on the edit field
        this.completer = new MyAutocompleter(this._controls.editor,
                                             this.completionElem, 
                                             this.completionUrl, 
                                             {minChars: 3});

    },

    leaveEditMode: function($super) {
        // hide the autocompletion div if it isn't hidden already
        if (this.completer) {
            // inform the completer that the input field no longer has the focus
            this.completer.hasFocus = false;
            this.completer.hide();
        }
        $super();
        // hide the editor div. this needs to be after the super call
        // since our element is hidden - the super call will invoke
        // show() on this.element, which is an alias for this.editorElem
        this.editorElem.hide();
        this.editorElem.update('');
    }

});


var Dialog = Class.create({

    initialize: function(element) {
        this.element = $(element);
        this.options = Object.extend({ 
            showHeader: false,
            onShow:   Prototype.emptyFunction,
            onClose:  Prototype.emptyFunction
        }, arguments[1] || { });

        this.element.setStyle({position: 'absolute'});

        // add the close header
        if (this.options.closeHeader) {
            this.headerElem = new Element("div", {'class': "header"});
            this.closeElem = new Element("a", {title: "Close", href: "#"}).update("X");
            this.headerElem.update(this.closeElem);
            this.element.insert({top: this.headerElem});

            this.hideObserver = this._hide.bindAsEventListener(this);
            this.closeElem.observe("click", this.hideObserver);
        }
    },

    show: function(position) {
        if (position) {
            this.position = position;
        } else {
            // center the dialog
            // show the dialog in the center of the screen
            this.position = document.viewport.getScrollOffsets(); 
            // use the arrowDialog element to calculate height/width - dialog var has no height/width???
            this.position['top'] = this.position['top'] + document.viewport.getHeight()/2 - this.element.getHeight()/2;
            this.position['left'] = this.position['left'] + document.viewport.getWidth()/2 - this.element.getWidth()/2;
        }
        this.element.setStyle({
            top: this.position['top'] + 'px',
            left: this.position['left'] + 'px'
        });

        this.element.show();

        $('overlay').show();
    },
 
    hide: function() {
        if (this.element.visible()) {
            this.element.hide();
            $('overlay').hide();
            this.options.onClose();
        }
    },

    /* Internal hide method for event handling. */
    _hide: function(event) {
        event.stop();
        this.hide();
    }
});

var Popup = Class.create({

    initialize: function(element) {
        this.element = $(element);
        //this.element.hover(this.mouseEnter.bind(this),
        //                   this.mouseOut.bind(this),
        //                   {leaveDelay: 0});
        this.element.observe('mouseenter', this.mouseEnter.bind(this));
        this.element.observe('mouseleave', this.mouseOut.bind(this));
        this.hasFocus = false;
    },

    mouseEnter: function() {
        this.hasFocus = true;
    },

    mouseOut: function() {
        if (this.hasFocus) {
            this.hasFocus = false;
            this.hide();
        }
    },

    isHover: function() {
        return this.hasFocus;
    },

    show: function(position) {
        this.position = position;
        this.element.setStyle({
                            position: 'absolute',
                            top: position['top'] + 'px',
                            left: position['left'] + 'px'
                             });
        this.element.show();
    },

    hide: function() {
        this.element.hide();
    }
});

var HoverPopup = Class.create({

    initialize: function(element, popup) {
        this.element = $(element);
        this.popup = popup;
        this.displayed = false;
        this.options = Object.extend({ 
            delay:         500,
            onPopup:       Prototype.emptyFunction,
            afterPopup:    Prototype.emptyFunction,
            onClosePopup:  Prototype.emptyFunction
        }, arguments[2] || { });
        // NOTE: leaveDelay is 100 to allow time for the mouse to
        // move from the element to the popup
        //this.element.hover(this.mouseEnter.bind(this),
        //                   this.mouseOut.bind(this), 
        //                   {enterDelay: 0, leaveDelay: 100});

        var mouseOut = function(){window.setTimeout(this.mouseOut.bind(this), 100)}.bind(this);
        this.element.observe('mouseenter', this.mouseEnter.bind(this));
        this.element.observe('mouseleave', mouseOut);
        this.element.observe('mousemove', this.mouseMove.bind(this));
    },

    mouseEnter: function(event) {
        this.event = event;
        this.mouseIn = true;
        window.setTimeout(this.checkHover.bind(this), this.options.delay);
    },

    mouseOut: function(event) {
        if (this.mouseIn) {
            this.mouseIn = false;
            if (this.displayed && !this.popup.isHover()) {
                this.hidePopup();
            }
        }
    },

    mouseMove: function(event) {
        this.event = event;
    },

    checkHover: function() {
        if (this.mouseIn) {
            this.options.onPopup(this.element);
            this.showPopup();
            this.options.afterPopup();
        }
    },

    showPopup: function() {
        var offset = this.element.cumulativeOffset();
        var viewportTopOffset = this.element.viewportOffset()['top'];
        if ((this.popup.element.getHeight()) < viewportTopOffset) {
            var top = this.event.pointerY() - this.popup.element.getHeight(); // for the margin at the bottom of the image
            // substract the element height
            top = top - (this.event.pointerY() - this.element.cumulativeOffset()['top']);
            var left = this.event.pointerX() - 18;
            this.popup.element.addClassName('above');
            this.popup.element.removeClassName('side');
        } else {
            // display popup on the side
            var top = this.event.pointerY() - 12; // for the margin at the bottom of the image
            var left = this.event.pointerX() + 8;
            this.popup.element.addClassName('side');
            this.popup.element.removeClassName('above');
        }
        // will need a callback for app logic
        var position = {top: top, left: left};
        this.popup.show(position);
        this.displayed = true;
    },

    hidePopup: function() {
        this.popup.hide();
        this.displayed = false;
    }
});


var TopicInput = Class.create({

    initialize: function(input) {
        this.input = input;
        this.completeDiv = new Element('div', {'class': 'completions'});
        this.completeDiv.hide();
        this.input.insert({'after': this.completeDiv});

        this.options = Object.extend({ 
            booleanMode: false,
            url: '/icresource/complete_target',
            onSelectCompletion: Prototype.emptyFunction
        }, arguments[1] || { });

        new MyAutocompleter(this.input, this.completeDiv, this.options.url, {
            minChars: 3,
            paramName: 'value',
            select: 'primary',
            callback: this.beforeCompletion.bind(this),
            afterUpdateElement: this.onSelect.bind(this)
        });
    },

    beforeCompletion: function(input, querystring) {
        if (!this.options.booleanMode) return querystring;

        var value = input.value;
        if (value.startsWith('+')) {
            this.require = true;
            this.exclude = false;
            value = value.substring(1);
        } else if (value.startsWith('-')) {
            this.require = false;
            this.exclude = true;
            value = value.substring(1);
        } else {
            this.require = false;
            this.exclude = false;
            value = value;
        }
        var index = querystring.indexOf('=');
        return querystring.substring(0, index + 1) + value;
    },

    // afterUpdateElement implementation to store the selected
    // value
    onSelect: function(input, selected) {
        var id = selected.id;
        var fields = id.split(':');
        var selectedInput = new Element('input', {'name': fields[0], 'type': 'hidden', 'value': fields[1]});
        this.input.insert({'after': selectedInput});

        if (this.options.booleanMode) {
            if (this.require) {
                input.value = '+"' + input.value + '"';
            } else if (this.exclude) {
                input.value = '-"' + input.value + '"';
            }
        }

        this.options.onSelectCompletion(this.input);
    }

});
