YAHOO.namespace("atlassian.jira.widget");

/**
 * A global map of all widgets
 */
atlassian.jira.widget.WidgetMap = {};

atlassian.jira.widget.log = function(msg, cat, src)
{
    // set this to true to stop all logging
    if (true) return;

    var logE = document.getElementById('log');
    if (! logE)
    {
        // create it
        logE = document.createElement("div");
        logE.id = 'log';
        logE.style.position = "absolute";
        logE.style.zIndex = 999;
        logE.style.backgroundColor = "#000000";
        logE.style.color = "white";
        logE.style.right = "0px";
        logE.style.borderWidth = "1px";
        logE.style.borderStyle = "solid";
        logE.style.borderColor = "#000000";
        logE.style.overflow = "scroll";
        logE.style.width = "40%";
        logE.style.height = "70%";

        var headingE = document.createElement("div");
        headingE.innerHTML = "Logger...";
        headingE.style.backgroundColor = "#FFFFE5";
        headingE.style.color = "black";
        logE.appendChild(headingE);

        var preE = document.createElement("pre");
        logE.appendChild(preE);
        document.body.insertBefore(logE, document.body.firstChild);
    }
    if (logE)
    {
        var now = new Date();
        var s = now.getMinutes() + ":" + now.getSeconds() + "." + now.getMilliseconds() + " : " + msg;
        s += (document.all) ? "<br/>" : "\n";

        var preE = logE.firstChild.nextSibling;
        s = s + preE.innerHTML;
        preE.innerHTML = s;
    }
    YAHOO.log(msg, cat, src);
};


/**
 * Implementation of YAHOO.widget.DataSource that uses DWR as is live data source. This can be used
 * to allow data objects returned from DWR to be plugged into the Yahoo auto complete client side framework.
 *
 * The major difference between the 2 is that Yahoo expects arrays of objects to be returned while
 * DWR does not impose any specific return type (albeit it will probably be an array) so you may need
 * to convert a returned DWR object into an array of objects, even if its only an array of one.
 *
 * As both yui and DWR require a callback, there is a specific contract that the dwrInvokerFunc must follow.
 *
 *  The oConfigs parameter is the same as the base YAHOO.widget.DataSource ones plus these extensions :

 oConfigs.timeout - the DWR timeout, defaults to 3000;
 oConfigs.maxXhrCount - the maximum number of outstanding XHR requests allowed, defaults to 10000;
 oConfigs.httpMethod - the type of http interaction, defaults to "GET";
 oConfigs.maxXhrErrorMessage - a message to show when the maximum XHR count is reached.  I18n please.

 *
 * @class DS_DWR
 * @constructor
 * @extends YAHOO.widget.DataSource
 * @param dwrInvokerFunc {HTMLFunction} The dwrInvokerFunc will be called
 * @param oConfigs {Object} (optional) Object literal of config params.

 */

atlassian.jira.widget.DS_DWR = function(dwrInvokerFunc, oConfigs)
{

    this.timeout = 6000;
    this.maxXhrCount = 10000;
    this.maxXhrErrorMessage = "Server maybe busy.  Please use popup for searching."
    this.httpMethod = "POST";
    if (atlassian.jira.widget.BrowserDetect.browser == 'Safari')
    {
        // Safari doesnt support POST as a XHR connection mechanism so GET it is
        this.httpMethod = "GET";
    }
    // Set any config params passed in to override defaults
    if (typeof oConfigs == "object")
    {
        if (oConfigs.cacheResults == null)
        {
            oConfigs.cacheResults = true;
        }
        for (var sConfig in oConfigs)
        {
            this[sConfig] = oConfigs[sConfig];
        }
    }
    this.dwrInvokerFunc = dwrInvokerFunc;
    this.xhrCount = 0;
    this._init();
    atlassian.jira.widget.log('DWR DataSource initialized', 'info', this.toString());
};
atlassian.jira.widget.DS_DWR.prototype = new YAHOO.widget.DataSource();

/**
 * This is just an example of the JS object returned via DWR and Java.  This is also keeps the
 * IDEA JavaScript syntax checking happy.  There is no need to instatiate this object in JS but
 * you must use it in the JAVA.
 */
atlassian.jira.widget.DS_DWR.AutoCompleteResults = {
    errorMessage : null,
    headerMessage : null,
    footerMessage : null,
    results : []
};


// returns the scrolling offset.  This was taken from Quirksmode.
function getScrollingOffset()
{
    var x,y;
    if (self.pageYOffset) // all except Explorer
    {
        x = self.pageXOffset;
        y = self.pageYOffset;
    }
    else if (document.documentElement && document.documentElement.scrollTop)
    // Explorer 6 Strict
    {
        x = document.documentElement.scrollLeft;
        y = document.documentElement.scrollTop;
    }
    else if (document.body) // all other Explorers
    {
        x = document.body.scrollLeft;
        y = document.body.scrollTop;
    }
    return [x,y];
}

/**
 * Our doQuery() implementation does a query use DWR.
 */
atlassian.jira.widget.DS_DWR.prototype.doQuery = function(oCallbackFn, sQuery, oParent)
{
    var self = this;
    oParent.ajaxAutoCompleteResults = null;

    var dwrCallContext = {
        callback : function(ajaxAutoCompleteResults)
        {
            self.xhrCount--;
            self.ajaxLastSearchTime = new Date().getTime() - self.dwrStartTime;
            atlassian.jira.widget.log("DWR has returned in " + self.ajaxLastSearchTime + " ms", "info", self.toString());

            self.postAjaxHook();

            // now do the yui stuff with the results
            if (ajaxAutoCompleteResults == null || ajaxAutoCompleteResults.results == null)
            {
                self.dataErrorEvent.fire(self, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
                atlassian.jira.widget.log(YAHOO.widget.DataSource.ERROR_DATANULL, "error", self.toString());
                return;
            }


            oParent.ajaxAutoCompleteResults = ajaxAutoCompleteResults;

            var aResults = [];
            for (var i = 0; i < ajaxAutoCompleteResults.results.length; i++)
            {
                var resultItem = [];
                var j = 0;
                for (; j < ajaxAutoCompleteResults.results[i].length; j++)
                {
                    resultItem[j] = ajaxAutoCompleteResults.results[i][j];
                }
                resultItem[j + 1] = ajaxAutoCompleteResults;
                // point back to ourselves
                aResults[aResults.length] = resultItem;
            }
            ;

            // should we cache results
            if (self.cacheResults)
            {
                var resultCacheObj = {};
                resultCacheObj.query = decodeURIComponent(sQuery);
                resultCacheObj.results = aResults;
                self._addCacheElem(resultCacheObj);
            }
            self.getResultsEvent.fire(self, oParent, sQuery, aResults);

            atlassian.jira.widget.log("Making callback into YUI autocomplete", "info", self.toString());
            oCallbackFn(sQuery, aResults, oParent);
        },

        timeout : self.timeout,

        httpMethod : self.httpMethod,

        errorHandler : function(errMsg)
        {
            atlassian.jira.widget.log("DWR err : " + errMsg);
            self.xhrCount--;
            self.dataErrorEvent.fire(self, oParent, sQuery, errMsg);
            atlassian.jira.widget.log(errMsg, "error", self.toString());
        },

        warningHandler : function(warnMsg)
        {
            atlassian.jira.widget.log("DWR warn : " + warnMsg);
            self.xhrCount--;
            self.dataErrorEvent.fire(self, oParent, sQuery, warnMsg);
            atlassian.jira.widget.log(warnMsg, "error", self.toString());
        }

    }


    // can they invoke a XHR right now
    if (self.xhrCount >= self.maxXhrCount)
    {
        atlassian.jira.widget.log(self.maxXhrErrorMessage);

        oParent.ajaxAutoCompleteResults = {
            errorMessage : self.maxXhrErrorMessage
        };
        var aResults = [];
        oCallbackFn(sQuery, aResults, oParent);
        return;
    }

    // invoke the DWR via the invoker function, giving it the current query and some context
    atlassian.jira.widget.log("Invoking DWR invoker function", "info", self.toString());
    //
    // yiu uses encoded query strings on input but we want them to go to DWR unencoded because it does encoding itself
    // so off with its head
    var decodedQuery = decodeURIComponent(sQuery);
    self.preAjaxHook();
    self.dwrStartTime = new Date().getTime();

    self.xhrCount = (self.xhrCount < 0) ? 0 : self.xhrCount + 1;
    //
    // invoke DWR function and pass its the meta data object to use
    self.dwrInvokerFunc(decodedQuery, dwrCallContext);
};

/**
 * The preAjaxHook method is called just before the DataSource does a AJAX call
 * via DWR to the server.    By default nothing is done here.
 */
atlassian.jira.widget.DS_DWR.prototype.preAjaxHook = function(sQuery)
{

};

/**
 * The postAjaxHook method is called just after AJAX call via DWR to the server has completed.
 * By default nothing is done here.
 */
atlassian.jira.widget.DS_DWR.prototype.postAjaxHook = function(sQuery)
{

};
/**


 config.inputId -
 config.resultsId -
 config.waiticonId -
 config.dwrInvokerFunc -


 */
atlassian.jira.widget.AutoComplete = function(config, yuiConfig)
{
    var self = this;
    this.inputId = config.inputId;
    this.resultsId = config.resultsId;
    this.waiticonId = config.waiticonId;
    this.dwrInvokerFunc = config.dwrInvokerFunc
    this.enabled = true;

    // put ourselves in the global map keyed by the input id
    atlassian.jira.widget.WidgetMap[this.inputId] = this;

    // setup DS_DWR  data source
    var dwrDS = new atlassian.jira.widget.DS_DWR(this.dwrInvokerFunc, yuiConfig);
    dwrDS.preAjaxHook = function()
    {
        var waitIconE = document.getElementById(self.waiticonId) ;
        if (waitIconE)
        {
            waitIconE.style.display = '';
        }
    }
    dwrDS.postAjaxHook = function()
    {
        var waitIconE = document.getElementById(self.waiticonId) ;
        if (waitIconE)
        {
            waitIconE.style.display = 'none';
        }
    }

    // setup the auto complete defaults we want
    if (yuiConfig && yuiConfig.maxResultsDisplayed == null)
    {
        // let the server limit this not us but be warned it pre-populates the results div
        // with maxResultsDisplayed number of <li> elements.
        yuiConfig.maxResultsDisplayed = 100;
    }
    if (yuiConfig && yuiConfig.minQueryLength == null)
    {
        yuiConfig.minQueryLength = 3;
    }
    if (yuiConfig && yuiConfig.animVert == null)
    {
        yuiConfig.animVert = false;
    }
    if (yuiConfig && yuiConfig.animHoriz == null)
    {
        yuiConfig.animHoriz = false;
    }
    var BD = atlassian.jira.widget.BrowserDetect;
    if (yuiConfig && yuiConfig.useIFrame == null && BD.browser == "IE" && BD.version < 7)
    {
        yuiConfig.useIFrame = true;
    }
    // instantiate our YUI component
    var autoComplete = new YAHOO.widget.AutoComplete(this.inputId, this.resultsId, dwrDS, yuiConfig);


    autoComplete._oldSendQuery = autoComplete._sendQuery;
    autoComplete._sendQuery = function(sQuery)
    {
        if (! self.enabled)
        {
            return;
        }
        autoComplete._oldSendQuery(sQuery);
    }

    // called when the container is about to expand
    autoComplete.doBeforeExpandContainer = function(textBox, container, sQuery, aResults)
    {
        // find result that has a key
        var index = -1;
        for (var x in aResults)
        {
            index++;
            var key = aResults[x][0];
            if (key && key.length > 0)
            {
                break;
            }
        }
        if (index != -1)
        {
            // find a li that is at index and hilight it
            var liItems = autoComplete.getListItems();

            // private call but there you go!  Again now way to do it other wise
            autoComplete._toggleHighlight(liItems[index], "to");
        }
        return true;
    };

    // called when some data has been returned
    autoComplete.dataReturnEvent.subscribe(function(sType, aArgs)
    {

        var oAutoComp = aArgs[0];
        var sQuery = aArgs[1];
        var ajaxAutoCompleteResults = oAutoComp.ajaxAutoCompleteResults;

        // clear any previous data from the header and footer first
        oAutoComp.setHeader(null);
        oAutoComp.setFooter(null);
        if (ajaxAutoCompleteResults.errorMessage)
        {
            oAutoComp.setHeader(ajaxAutoCompleteResults.errorMessage);
            oAutoComp.setBody("<div></div>");
        }
        else
        {
            if (ajaxAutoCompleteResults.headerMessage)
            {
                oAutoComp.setHeader(ajaxAutoCompleteResults.headerMessage);
            }
            if (ajaxAutoCompleteResults.footerMessage)
            {
                oAutoComp.setFooter(ajaxAutoCompleteResults.footerMessage);
            }
        }

    });

    // called when the container appears
    autoComplete.containerExpandEvent.subscribe(function(sType, aArgs)
    {
        //increase the z-index on show to make it appear above other containers
        var containerE = document.getElementById(config.containerId);
        if (containerE && containerE.style)
        {
            containerE.style.zIndex = 10;
        }

        // size the absolute yui-content bit if its too small.  This can happen when we are embedded
        // in small spaces.
        var contentDivE = YAHOO.util.Dom.getElementsByClassName('yui-ac-content', 'div', containerE)[0];
        if (contentDivE)
        {
            if (contentDivE.offsetWidth < 450)
            {
                contentDivE.style.width = "450px";
            }
        }

        var targetE = YAHOO.util.Dom.getElementsByClassName('yui-ac-ft', 'div', containerE)[0];
        if (targetE && targetE.scrollIntoView && contentDivE)
        {
            // Note that this only semi-works for Opera and Safari.  Both opera and safari don't increase the
            // scroll Y size, if the autocomplete dropdown drops below the page.
            var scrollAutoCompleteIntoView = function()
            {
                if (BD.browser == "Firefox")
                {
                    //firefox is smart. it only scrolls an element into view if it has to.
                    targetE.scrollIntoView(false);
                    contentDivE.scrollIntoView(false);
                }
                else
                {
                    //other browsers are a little dumber.  We need to explicitly check here if the bottom of the
                    //autocomplete pops off the viewport.
                    var viewPortHeight = YAHOO.util.Dom.getViewportHeight();
                    var scrollingOffset = getScrollingOffset();
                    var viewPortBottom = viewPortHeight + scrollingOffset[1];

                    var contentRegionHeight = YAHOO.util.Dom.getRegion(contentDivE).bottom - YAHOO.util.Dom.getRegion(contentDivE).top;
                    var contentRegionBottom = YAHOO.util.Dom.getY(contentDivE) + contentRegionHeight;

                    //opera's y coordinates seem to be off completely.  Hence the magic number 130 offset. (and different Y element (footer)
                    if (BD.browser == "Opera")
                    {
                        contentRegionBottom = YAHOO.util.Dom.getY(targetE) + contentRegionHeight + 130;
                    }

                    //if the content is below the bottom of the viewport, scrollinto view.
                    if (viewPortBottom < contentRegionBottom)
                    {
                        targetE.scrollIntoView(false);
                        contentDivE.scrollIntoView(false);
                    }
                }
            }
            window.setTimeout(scrollAutoCompleteIntoView, 100);
        }
    });


    // called when the container disappears
    autoComplete.containerCollapseEvent.subscribe(function(sType, aArgs)
    {
        //reset the z-index on hide so on show we can increase the z-index for one container
        var container = document.getElementById(config.containerId);
        container.style.zIndex = 0;
    });

    // called to format the results of the data
    autoComplete.formatResult = function(sResult, sQuery)
    {
        //HTML type
        if (sResult && sResult[1] == 'h')
        {
            return sResult[2];
        }
        //issue type
        else if (sResult && sResult[1] == 'i')
        {
            var key = sResult[0];
            var fieldName = sResult[2];
            var id = sResult[3];
            var imageUrl = sResult[4];
            var htmlKey = sResult[5];
            var description = sResult[6];

            var aMarkup = ["<div id=\"", fieldName, "_i_", id, "_", key,
                    "\" class=\"yad\" ><table class=\"yat\" cellpadding=\"0\" cellspacing=\"0\"><tr><td><img src=\"",
                    imageUrl,
                    "\"/></td><td><div class=\"yak\">",
                    htmlKey,
                    "</div></td><td style=\"width:100%;\">",
                    description,
                    "</td></tr></table></div>"];

            return (aMarkup.join(""));
        }
        //message type
        else if (sResult && sResult[1] == 'm')
        {
            var fieldName = sResult[2];
            var id = sResult[3];
            var message = sResult[4];

            var aMarkup = ["<div id=\"", fieldName, "_i_", id, "_n\" class=\"yad\" onclick=\"YAHOO.util.Event.stopEvent(event);\">", message, "</div>"];

            return (aMarkup.join(""));
        }
        //section type with subheader
        else if (sResult && sResult[1] == 'b')
        {
            var fieldName = sResult[2];
            var field = sResult[3];
            var label = sResult[4];
            var subHeader = sResult[5];

            var aMarkup = ["<div id=\"", fieldName, "_s_", field, "\" class=\"yag\" onclick=\"YAHOO.util.Event.stopEvent(event);\">",
                    label,
                    "<span class=\"yagt\">(",
                    subHeader,
                    ")</span></div>"];

            return (aMarkup.join(""));
        }
        //subsection type
        else if (sResult && sResult[1] == 's')
        {
            var fieldName = sResult[2];
            var field = sResult[3];
            var label = sResult[4];


            var aMarkup = ["<div id=\"", fieldName, "_s_", field, "\" class=\"yag\" onclick=\"YAHOO.util.Event.stopEvent(event);\">",
                    label,
                    "</div>"];

            return (aMarkup.join(""));
        }
        else
        {
            return "";
        }
    }

    // save away the old function and we will quasi inject a new one
    autoComplete.oldMoveSelection = autoComplete._moveSelection;

    // a new onkey handler for selection to jump headers, eg things with empty keys
    autoComplete._moveSelection = function(nKeyCode)
    {
        // these two variables are private and hence pose a risk to compatibility in the future
        // but we cant do it any other way I am afraid.
        var displayedItemCount = autoComplete._nDisplayedItems;
        var oCurItem = autoComplete._oCurItem;

        var direction = (nKeyCode == 40) ? 1 : - 1;
        var nCurItemIndex = -1;
        if (oCurItem)
        {
            nCurItemIndex = oCurItem._nItemIndex;
        }
        var jumpCount = 0;
        nCurItemIndex = nCurItemIndex + direction;
        while (true)
        {
            if (nCurItemIndex < 0 || nCurItemIndex >= displayedItemCount)
            {
                return;
            }
            var li = autoComplete.getListItems()[nCurItemIndex];
            var result = autoComplete.getListItemData(li);
            var key = result[0];
            jumpCount++;
            if (key && key.length > 0)
            {
                break;
            }
            nCurItemIndex = nCurItemIndex + direction;
        }


        // Found possible target, lets move there
        for (var i = 0; i < jumpCount; i++)
        {
            autoComplete.oldMoveSelection(nKeyCode);
        }
    }

    autoComplete.oldonTextboxKeyDown = autoComplete._onTextboxKeyDown;
    autoComplete._onTextboxKeyDown = function(v, oSelf)
    {
        var nKeyCode = v.keyCode;
        if (nKeyCode == 13)
        {
            autoComplete.cancelSubmit = false;
            if (BD.browser == "Opera")
            {
                /**
                 * OPERA is buggy and its event model is broken and you cant cancel keydowns but you can cancel keypress
                 * so we set this up here and then cancel it later.  As a backup we also have a form submit cancel if this fails.
                 */
                var resultsE = document.getElementById(config.resultsId);
                var yuiResultsE = resultsE.firstChild;
                if (yuiResultsE.style.display != 'none')
                {
                    // the drop down box is open and they have pressed ENTER on Opera.
                    autoComplete.cancelSubmit = true;
                }
            }
        }

        switch (nKeyCode)
                {
            case 9: // tab
            case 13 : // enter
                if (oSelf._oCurItem)
                {
                    var result = autoComplete.getListItemData(oSelf._oCurItem);
                    var key = result[0];
                    if (key == null || key.length == 0)
                    {
                        YAHOO.util.Event.stopEvent(v);
                        return;
                    }
                }

        }
        autoComplete.oldonTextboxKeyDown(v, oSelf);
    };
    // remove the old text box listener and stick ours in
    var inputE = document.getElementById(config.inputId);
    YAHOO.util.Event.removeListener(inputE, "keydown", autoComplete.oldonTextboxKeyDown);
    YAHOO.util.Event.addListener(inputE, "keydown", autoComplete._onTextboxKeyDown, autoComplete);

    //
    // Opera has bad event handling and cancelling and it will submit the form
    // if you press enter, even if you are selecting an item from the list.  We want to control this.
    // On other browsers we cancel the keydown event, here we jump in on the submit event.
    //
    if (BD.browser == "Opera")
    {
        var onFormSubmitOperaCancel = function(v, oSelf)
        {
            var cancelTheSubmission = oSelf.cancelSubmit;
            oSelf.cancelSubmit = false;
            if (cancelTheSubmission)
            {
                YAHOO.util.Event.stopEvent(v);
                return false;
            }
            else
            {
                inputE.form.submit();
            }
        }
        YAHOO.util.Event.addListener(inputE.form, "submit", onFormSubmitOperaCancel, autoComplete);

        var keyPressFunc = function(v, olSelf)
        {
            var nKeyCode = v.keyCode;
            if (nKeyCode == 13)
            {
                if (autoComplete.cancelSubmit)
                {
                    autoComplete.cancelSubmit = false;
                    YAHOO.util.Event.stopEvent(v);
                }
            }
        };
        YAHOO.util.Event.addListener(inputE, "keypress", keyPressFunc, autoComplete);
    }
    this.autoComplete = autoComplete;
};