/**
 * Creates a new object.
 *
 * @param prototype
 *   Optional (or null), Object, the prototype for the new object.
 * @param existing
 *   Optional, a default object.
 * @return
 *   If default is missing, null or the global object a new object; otherwise existing.
 */
function createObject(prototype,existing)
{
    if(existing!==undefined && existing!==null && existing!==global)
        return existing;
    if(prototype===undefined || prototype===null)
        return ({});
    function constructor(){};
    constructor.prototype=prototype;
    return new constructor();
};

/////////////
// TinyMCE //
/////////////

var TINYMCE_SETTINGS=
{
    // General options
    dialog_type: "modal",
    language: "de",
    object_resizing: false,
    plugins: "autoresize,inlinepopups,paste,safari,tabfocus",
    skin: "default",
    // Cleanup options
    entity_encoding: "raw",
    fix_nesting: true,
    formats:
    {
        bold: {inline: "b"},
        italic: {inline: "i"}
    },
    valid_elements: "-a[!href],-b,-i,img[!src|!alt|!class],-p,-ul,li",
    // URL options
    relative_urls: false,
    // Layout options
    content_css: "/media/css/tinymce.css",
    // Undo options
    custom_undo_redo_levels: 20,
    // Theme options
    theme: "advanced",
    theme_advanced_toolbar_location: "top",
    theme_advanced_toolbar_align: "left",
    theme_advanced_statusbar_location: "none",
    theme_advanced_buttons1: "",
    theme_advanced_buttons2: "",
    theme_advanced_buttons3: "",
    // Emoticon buttons
    setup: function(ed)
    {
        addEmoticonButton(ed,"smile","L\u00e4cheln");
        addEmoticonButton(ed,"wink","Zwinkern");
        addEmoticonButton(ed,"redface","Rot werden");
        addEmoticonButton(ed,"cry","Traurig");
        addEmoticonButton(ed,"cool","Cool");
        addEmoticonButton(ed,"eek","Staunen");
        addEmoticonButton(ed,"mrgreen","Grinsen");
        addEmoticonButton(ed,"idea","Idee");
        addEmoticonButton(ed,"razz","Zunge rausstrecken");
        addEmoticonButton(ed,"rolleyes","Verwirrt");
        addEmoticonButton(ed,"easypeasy","Pille Palle");
        addEmoticonButton(ed,"bang","Kopf an Wand schlagen");
    }
};
var TINYMCE_DISABLED=(window.tinymce===undefined);

/**
 * Adds an emoticon to the toolbar.
 *
 * @param ed
 *   Editor, the TinyMCE editor
 * @param id
 *   String, the id of the emoticon.
 * @param text
 *   String, the name of the emoticon.
 */
function addEmoticonButton(ed,id,text)
{
    ed.addButton("emoticon_"+id,
    {
        title: text,
        image: "/media/icon/emoticon/"+id+".gif",
        onclick: function()
        {
            ed.focus();
            ed.selection.setContent('<img src="/media/icon/emoticon/'+id+'.gif" alt="'+text+'" class="emoticon"/>');
        }
    });
}

/**
 * Initializes TinyMCE for a textarea.
 *
 * @param tinymce
 *   jQuery, a single textarea.
 */
function initTinyMCE(tinymce)
{
    var settings=createObject(TINYMCE_SETTINGS);
    settings.theme_advanced_buttons1="undo,redo,separator,bold,italic,removeformat,separator,bullist";
    if(tinymce.hasClass("rteemoticons"))
        settings.theme_advanced_buttons1+=",separator,emoticon_smile,emoticon_wink,emoticon_redface,emoticon_cry,emoticon_cool,emoticon_eek,emoticon_mrgreen,emoticon_idea,emoticon_razz,emoticon_rolleyes,emoticon_easypeasy,emoticon_bang";
    if(tinymce.hasClass("rtelinks"))
        settings.theme_advanced_buttons1+=",separator,link,unlink";
    tinymce.tinymce(settings);
}

////////////
// jQuery //
////////////

jQuery.noConflict();

jQuery.ajaxSetup({dataFilter:function(data,type)
{
    if(type=="json" && typeof data=="string" && data.length>=4 && data.substring(0,2)=="/*" && data.substring(data.length-2)=="*/")
        return data.substring(2,data.length-2);
    return data;
}});

jQuery(function()
{
    // Hover for span, div and tr links
    jQuery("span.link, div.link, tr.link, td.link").hover(
        function()
        {
            var span=jQuery(this);
            var classnames=span.attr("class").split(/\s+/);
            for(var i=0;i<classnames.length;i++)
                if(classnames[i].search(/-hover$/)==-1)
                    span.addClass(classnames[i]+"-hover");
        },
        function()
        {
            var span=jQuery(this);
            var classnames=span.attr("class").split(/\s+/);
            for(var i=0;i<classnames.length;i++)
                if(classnames[i].search(/-hover$/)!=-1)
                    span.removeClass(classnames[i]);
        }
    );
    // Focus first input element
    jQuery(":input[type!='hidden']:first").focus();
    initFragment(document);
});

/**
 * Initializes a fragment.
 *
 * @param element
 *   HTMLElement or jQuery, the element(s) to initialize.
 */
function initFragment(element)
{
    // Sortable tables
    jQuery("table.sortable > tbody",element).sortable(
    {
        axis: 'y',
        tolerance: 'pointer',
        handle: 'div.buttonicon.move',
        start: function(event,ui)
        {
            if(!TINYMCE_DISABLED)
            {
                var tinymce=jQuery("textarea.tinymce",ui.item);
                if(tinymce.length>0)
                {
                    // :-( If another editor has the focus the caret is missing after the sort
                    tinymce.tinymce().focus();
                    // :-( Save content as we have to recreate the editor later
                    tinymce.tinymce().save();
                }
            }
        },
        stop: function(event,ui)
        {
            if(!TINYMCE_DISABLED)
            {
                var tinymce=jQuery("textarea.tinymce",ui.item);
                if(tinymce.length>0)
                {
                    // :-( We need to manually destroy and recreate the editor
                    tinymce.tinymce().destroy(false);
                    jQuery("span.defaultSkin",ui.item).remove();
                    tinymce.removeAttr("id");
                    tinymce.css("display","");
                    initTinyMCE(tinymce);
                }
            }
        },
        update: function(event,ui)
        {
            var table=jQuery(ui.item).closest("table.zebra");
            updateZebraBody(table);
            updateInputNames(table);
        }
    });
    // TinyMCE
    if(!TINYMCE_DISABLED)
    {
        var tinymce=jQuery("textarea.tinymce",element);
        for(var tinymceindex=0;tinymceindex<tinymce.length;tinymceindex++)
            initTinyMCE(jQuery(tinymce[tinymceindex]));
    }
    // Handle enter with invisible submit button
    var forms=jQuery("form",element);
    for(var formindex=0;formindex<forms.length;formindex++)
    {
        var form=forms[formindex];
        if(jQuery("input[type='submit']",form).length==0)
        {
            jQuery(form).append("<input type=\"submit\" tabindex=\"-1\" class=\"hiddensubmit\"/>");
            jQuery(form).submit(function()
            {
                return submitForm.call(jQuery("input[type='submit']",form),undefined,true);
            });
        }
    }
}

/**
 * Updates the odd/even classes of all rows of a zebra table.
 *
 * @param table
 *   jQuery, the zebra table.
 */
function updateZebraBody(table)
{
    var rowclass;
    if(jQuery("> thead > tr.odd",table).length==0)
        rowclass="odd";
    else
        rowclass="even";
    jQuery("> tbody > tr",table).each(function()
    {
        var row=jQuery(this);
        if(!row.hasClass(rowclass))
        {
            row.removeClass(rowclass=="even"?"odd":"even");
            row.addClass(rowclass);
        }
        if(row.hasClass("last"))
            rowclass=(rowclass=="even"?"odd":"even");
    });
}

/**
 * Updatesnames of the inputs in the table and optionally the counter.
 *
 * @param this
 *   HTMLElement, an element in the row.
 * @param event
 *   Event, the event.
 * @param table
 *   jQuery, the table.
 * @param counter
 *   Optional, The name of the row counter input element.
 */
function updateInputNames(table,counter)
{
    var rows=jQuery("> tbody > tr",table);
    // Update control name suffixes
    var nameindex=0;
    for(var rowindex=0;rowindex<rows.length;rowindex++)
    {
        var row=jQuery(rows[rowindex]);
        var inputs=jQuery(":input",row);
        inputs.each(function()
        {
            var input=jQuery(this);
            var name=input.attr("name");
            var parts=/^(.*[^0-9])(0|[1-9][0-9]*)$/.exec(name);
            if(parts!=null && parseInt(parts[2])!=nameindex)
                input.attr("name",parts[1]+nameindex);
        });
        if(!table.hasClass("zebra") || row.hasClass("last"))
            nameindex++;
    }
    // Update counter
    if(counter!==undefined)
    {
        var form=table.closest("form");
        var counterinput=jQuery("input[name='"+counter+"']",form);
        counterinput.val(nameindex);
    }
}

/**
 * Adds a row to a table.
 *
 * @param this
 *   HTMLElement, an element in the row.
 * @param event
 *   Event, the event.
 * @param url
 *   The URL of the row.
 * @param id
 *   The id of the process.
 * @param counter
 *   The name of the row counter input element.
 */
function addRow(event,url,id,counter)
{
    var table=jQuery(this).closest("table.zebra");
    postRequest(url,{processid: id, type: counter},function(response)
    {
        if(response=="<submit/>")
            submitForm.call(table[0]);
        else
        {
            // Insert row
            jQuery("> tbody",table).append(response);
            var tr=jQuery("> tbody > tr:last",table);
            // Update table
            updateZebraBody(table);
            updateInputNames(table,counter);
            // Focus first control
            jQuery(":input[type!='hidden']:first",tr).focus();
            initFragment(tr);
        }
    });
}

/**
 * Deletes a row.
 *
 * @param this
 *   HTMLElement, an element in the row.
 * @param event
 *   Event, the event.
 * @param counter
 *   The name of the row counter input element.
 */
function deleteRow(event,counter)
{
    var row=jQuery(this).closest("tr.odd, tr.even");
    var table=row.closest("table.zebra");
    // Delete row
    row.remove();
    // Update table
    updateZebraBody(table);
    updateInputNames(table,counter);
}

/**
 * Adds the rows of favorite to a table.
 *
 * @param this
 *   HTMLElement, an element in the row.
 * @param event
 *   Event, the event.
 * @param url
 *   The URL of the getfavorite function.
 */
function addFavorite(event,url)
{
    var form=jQuery(this).closest("form");
    var table=jQuery("table.zebra",form);
    var processid=jQuery("input[name='processid']",form).val();
    var key=jQuery("select[name='favoritekey']",form).val();
    if(key=="")
        return;
    postRequest(url,{processid: processid, key: key},function(response)
    {
        // Remove last rows if empty
        for(;;)
        {
            var lasttr=jQuery("> tbody > tr:last",table);
            if(lasttr.length==0 ||
               jQuery("input[name^='foodname']",lasttr).val()!="" ||
               jQuery("input[name^='quantity']",lasttr).val()!="")
                break;
            lasttr.remove();
        }
        // Insert rows
        var oldrows=jQuery("> tbody > tr",table).length;
        jQuery("> tbody",table).append(response);
        var trs=jQuery("> tbody > tr:gt("+(oldrows-1)+")",table);
        // Update table
        updateZebraBody(table);
        updateInputNames(table,"foods");
        initFragment(trs);
        // Clear input
        jQuery("select[name='favoritekey']",form).val("");
    });
}

/**
 * Deletes a favorite.
 *
 * @param this
 *   HTMLElement, an element in the row.
 * @param event
 *   Event, the event.
 * @param url
 *   The URL of the deletefavorite function.
 * @param key
 *   The key of favorite.
 */
function deleteFavorite(event,url,key)
{
    var tr=jQuery(this).closest("tr.odd, tr.even");
    var table=tr.closest("table.zebra");
    postRequest(url,{key: key},function(response)
    {
        var newtr=jQuery(response);
        tr.replaceWith(newtr);
        updateZebraBody(table);
        initFragment(newtr);
    });
}

/**
 * Updates a page content area.
 *
 * @param this
 *   HTMLElement, an element in the page content area.
 * @param event
 *   Event, the event.
 * @param url
 *   String, the URL to retrieve the the new parent content area.
 * @param add
 *   Boolean, add a new element?
 * @param parameters
 *   Object, the parameters.
 */
function updatePageContent(event,url,add,parameters)
{
    var oldpagecontent=jQuery(this).closest("div.pagecontent");
    postRequest(url,parameters,function(response)
    {
        if(response=="")
        {
            if(!add)
                oldpagecontent.remove();
        }
        else
        {
            oldpagecontent.before(response);
            var newpagecontent=oldpagecontent.prev();
            if(!add)
                oldpagecontent.remove();
            // TinyMCE
            if(!TINYMCE_DISABLED)
            {
                initFragment(newpagecontent);
                var tinymce=jQuery("textarea.tinymce",newpagecontent);
                tinymce.tinymce().execCommand("mceFocus",false,tinymce.attr("id"));
            }
        }
    });
}

/**
 * Opens an URL.
 *
 * @param url
 *   String, the URL.
 * @param target
 *   Optional, String, the target (can be null).
 */
function openURL(url,target)
{
    if(target==="greybox")
        showGreyBox(url);
    else
        window.open(url,target==null || target==""?"_self":target);
    return false;
}

var InSubmit=false;

/**
 * Submit a form.
 *
 * @param this
 *   HTMLElement, an element in the form.
 * @param processback
 *   Optional, Number, the processback value.
 * @param submitevent
 *   Optional, Boolean, call from submit event handler.
 */
function submitForm(processback,submitevent)
{
    var form=jQuery(this).parents("form");
    if(!InSubmit)
        jQuery("input[name='processback']",form).val(processback===undefined?"":processback);
    if(jQuery(this).parents("div.greyboxcontainer").length>0)
    {
        postRequest(form.attr("action"),jQuery("input, select, textarea",form).serialize()+"&greybox=x",function(response)
        {
            if(response!="")
                updateGreyBoxHtml(response,jQuery(this).parents("div.greyboxupload").length>0);
        });
    }
    else if(form.closest("div.pagecontent").length>0)
        updatePageContent.call(this,null,form.attr("action"),false,jQuery("input, select, textarea",form).serialize());
    else
    {
        if(submitevent===true)
            return true;
        InSubmit=true;
        try
        {
            form.submit();
        }
        finally
        {
            InSubmit=false;
        }
    }
    return false;
}

/**
 * Does a POST request.
 *
 * @param url
 *   String, the URL.
 * @param data
 *   String/Object, the data to post.
 * @param process
 *   Function(response), function to process the response.
 */
function postRequest(url,data,process)
{
    // Use https if necessary
    if(window.location.protocol=="https:" && url.substring(0,5)=="http:")
        url="https:"+url.substring(5);
    jQuery.ajax(
    {
        url: url,
        type: "POST",
        dataType: "html",
        data: typeof data=="string"?data:jQuery.param(data),
        complete: function(res, status)
        {
            if(status=="error" && res.status==401)
                // Session timeout => reload complete page to show login
                window.location.reload();
            else if(status!="success")
                alert("Es ist ein Fehler aufgetreten. Bitte probieren Sie es sp\u00E4ter nochmal.");
        },
        success: function(data, status)
        {
            process(data);
        }
    });
}

/**
 * Remove whitespace (\r, \n, \t, space) from the beginning and end.
 *
 * @param text
 *   String, the string to trim.
 * @return
 *   String, The trimmed string.
 */
function trimWhitespace(text)
{
    return text.replace(/^[\t\n\r ]+|[\t\n\r ]+$/g,"");
}

/**
 * Remove whitespace (\r, \n, \t, space) from the beginning and end and
 * replace whitespace in the middle by a single space.
 *
 * @param text
 *   String, the string to normalize.
 * @return
 *   String, The trimmed string.
 */
function normalizeWhitespace(text)
{
    return trimWhitespace(text).replace(/[\t\n\r ]+/g," ");
}

/**
 * Does a binary search.
 *
 * @param elements
 *   Array, a sorted array.
 * @param value
 *   Object, the value to find.
 * @param startindex
 *   Optional, Number, the start index (incl.) in elements (0 if undefined).
 * @param endindex
 *   Optional, Number, the end index (excl.) in elements (elements.length if undefined).
 * @param comparefn
 *   Optional, Function(a,b), function to compare two elements, s. Array.sort (the values are sorted as strings if undefined).
 * @return
 *   The first index of the value in elements; if value is not in elements ~index where to insert value.
 */
function searchBinary(elements,value,startindex,endindex,comparefn)
{
    // Set default parameter values
    if(startindex===undefined)
        startindex=0;
    if(endindex===undefined)
        endindex=elements.length;
    if(comparefn===undefined)
    {
        comparefn=function(a,b)
        {
            a=String(a);
            b=String(b);
            if(a<b)
                return -1;
            else if(a==b)
                return 0;
            else
                return 1;
        };
    }
    // Range empty?
    if(startindex>=endindex)
        return ~startindex;
    // Binary search
    while(startindex<endindex-1)
    {
        var index=(startindex+endindex)>>>1;
        var result=comparefn(value,elements[index-1]);
        if(result<=0)
            endindex=index;
        else
            startindex=index;
    }
    // Compare value with best element
    var result=comparefn(value,elements[startindex]);
    if(result==0)
        return startindex;
    else if(result<0)
        return ~startindex;
    else
        return ~endindex;
}

/**
 * Returns the selection of an input or textarea control.
 *
 * @param input
 *   Element, the control.
 * @return
 *   {start: Number, end: Number}
 */
function getSelectionRange(input)
{
    var start=0;
    var end=0;
    if(input.selectionStart!==undefined)
    {
        start=input.selectionStart;
        end=input.selectionEnd;
    }
    else if(input.createTextRange!==undefined)
    {
        input.focus();
        var range=document.selection.createRange();
        var fullrange=range.duplicate();
        fullrange.moveToElementText(input);
        if(fullrange.inRange(range))
        {
            // :-( IE7 range.text doesn't include new lines at the end of the textarea
            var length=input.value.length;
            var extra=length-fullrange.text.length;
        
            var range2=fullrange.duplicate();
            range2.setEndPoint("StartToStart",range);
            var length2=range2.text.length;
            if(extra>0)
            {
                for(var i=0;i<extra;i+=2)
                {
                    if(range2.compareEndPoints("StartToEnd",range2)==0)
                        break;
                    range2.moveStart("character",1);
                    length2+=2;
                }
            }
        
            var range3=fullrange.duplicate();
            range3.setEndPoint("StartToEnd",range);
            var length3=range3.text.length;
            if(extra>0)
            {
                for(var i=0;i<extra;i+=2)
                {
                    if(range3.compareEndPoints("StartToEnd",range3)==0)
                        break;
                    range3.moveStart("character",1);
                    length3+=2;
                }
            }
        
            start=length-length2;
            end=length-length3;
        }
    }
    return ({
        start: start,
        end: end
    });
}

/**
 * Sets the selection of an input or textarea control.
 *
 * @param input
 *   Element, the control.
 * @param start
 *   Number, the start (incl.).
 * @param end
 *   Number, the end (excl.).
 */
function setSelectionRange(input,start,end)
{
    if(input.setSelectionRange!==undefined)
    {
        input.setSelectionRange(start,end);
        input.focus();
    }
    else if(input.createTextRange!==undefined)
    {
        var range=input.createTextRange();
        range.collapse(true);
        range.moveStart('character',start);
        range.moveEnd('character',end-start);
        range.select();
        input.focus();
    }
}

/////////
// FAQ //
/////////

/**
 * Returns the matching ids for the keywords.
 * 
 * @param text
 *   String, the text.
 * @return
 *   {id: true}, the matching ids (undefined if text is empty).
 */
function getFAQIdsExact(text)
{
    var ids=undefined;
    text=normalizeWhitespace(text.toLowerCase().replace(/-/g," "));
    if(text=="")
        return ids;
    var words=text.split(" ");
    for(var wordindex=0;wordindex<words.length;wordindex++)
    {
        var word=words[wordindex];
        var oldids=ids;
        ids={};
        var keywordids=FAQKeywords[word];
        if(keywordids!==undefined)
            for(var idindex=0;idindex<keywordids.length;idindex++)
                if(oldids===undefined || oldids[keywordids[idindex]]===true)
                    ids[keywordids[idindex]]=true;
    }
    return ids;
}

/**
 * Returns the matching ids for the keywords.
 * 
 * @param text
 *   String, the text.
 * @return
 *   {id: true}, the matching ids (undefined if text is empty).
 */
function getFAQIds(text)
{
    var ids=undefined;
    text=normalizeWhitespace(text.toLowerCase().replace(/-/g," "));
    if(text=="")
        return ids;
    var words=text.split(" ");
    for(var wordindex=0;wordindex<words.length;wordindex++)
    {
        var word=words[wordindex];
        var oldids=ids;
        ids={};
        for(var keyword in FAQKeywords)
        {
            if(keyword.length>=word.length && keyword.substring(0,word.length)==word)
            {
                var keywordids=FAQKeywords[keyword];
                for(var idindex=0;idindex<keywordids.length;idindex++)
                    if(oldids===undefined || oldids[keywordids[idindex]]===true)
                        ids[keywordids[idindex]]=true;
            }
        }
    }
    return ids;
}

/**
 * Returns the auto extension for a text.
 * 
 * @param text
 *   String, the text.
 * @return
 *   {id: true}, the matching ids.
 */
function getFAQExtension(text)
{
    text=text.toLowerCase().replace(/-/g," ");
    var lastwhitespace=/[\t\n\r ]$/.test(text);
    if(lastwhitespace)
        return "";
    var words=normalizeWhitespace(text).split(" ");
    if(words.length==0)
        return "";
    var word=words[words.length-1];
    var shortest=undefined;
    for(var keyword in FAQKeywords)
        if(keyword.length>=word.length && keyword.substring(0,word.length)==word && (shortest===undefined || keyword.length<shortest.length))
            shortest=keyword;
    if(shortest===undefined)
        return "";
    return shortest.substring(word.length);
}

/**
 * Updates the FAQ input and the FAQ list.
 * 
 * @param this
 *   HTMLElement, the FAQ input.
 * @param event
 *   Event, the event.
 */
function updateFAQ(event)
{
    var input=this;
    // Delay until input value is changed
    window.setTimeout(function()
    {
        var text=input.value;
        /*
        var range=getSelectionRange(input);
        // Extend if nothing is selected and the cursor is at the end
        if(range.start==range.end && range.start==text.length)
        {
            var extension=getFAQExtension(text);
            if(extension!="")
            {
                text+=extension;
                input.value=text;
                setSelectionRange(input,range.start,text.length);
            }
        }
        */
        // Update list
        var ids=getFAQIds(text);
        var notfound=true;
        var groups=jQuery("div.faqgroup");
        for(var groupindex=0;groupindex<groups.length;groupindex++)
        {
            var group=jQuery(groups[groupindex]);
            var empty=true;
            var items=jQuery("div.faqitem",group);
            for(var itemindex=0;itemindex<items.length;itemindex++)
            {
                var item=jQuery(items[itemindex]);
                if(ids===undefined || ids[item.attr("id")]===true)
                {
                    item.show();
                    empty=false;
                    notfound=false;
                }
                else
                    item.hide();
            }
            if(!empty)
                group.show();
            else
                group.hide();
        }
        if(notfound)
            jQuery("div.faqnotfound").show();
        else
            jQuery("div.faqnotfound").hide();
    },1);
}

/////////////////
// MissingFood //
/////////////////

/**
 * Updates the FAQ input and the FAQ list.
 * 
 * @param this
 *   HTMLElement, the FAQ input.
 * @param event
 *   Event, the event.
 */
function updateDetailUnits(event)
{
    var tr=jQuery(this).closest("tr");
    var unitinput=jQuery("select[name^='detailunit']",tr);
    var options='<option value=""></option>';
    var detail=DETAIL_UNITS[jQuery(this).val()];
    if(detail!==undefined)
        for(var unitindex=0;unitindex<detail.values.length;unitindex++)
            options+='<option value="'+detail.values[unitindex]+'"'+(detail.values[unitindex]==detail.selected?' selected="selected"':'')+'>'+detail.texts[unitindex]+'</option>';
    unitinput.empty();
    unitinput.append(options);
}

///////////////
// FoodInput //
///////////////

/**
 * Splits a word into keywords.
 *
 * @param word
 *   String, the word.
 * @param last
 *   Boolean, is this the last entered word?
 * @param keywords
 *   Object, the keywords are added to this object ([key]: key).
 * @return
 *   Boolean, was the word completely split into keywords?
 */
function splitWord(word,last,keywords)
{
    var result=(word.length==0 || (word.length<=2 && last));
    // foodkeywordnames1
    var startindex=0;
    var endindex=foodkeywordnames1.length;
    var minlength=(word.substring(0,2)=="ei"?2:3);
    for(var index=minlength;index<=word.length;index++)
    {
        var keyword=word.substring(0,index);
        // Get range
        var newstartindex=searchBinary(foodkeywordnames1,keyword,startindex,endindex);
        if(newstartindex>=0)
        {
            var key=foodkeywordkeys1[newstartindex];
            keywords[key]=key;
            result|=splitWord(word.substring(index),last,keywords);
            newstartindex++;
        }
        else
            newstartindex=~newstartindex;
        var newendindex=~searchBinary(foodkeywordnames1,keyword+"\uffff",startindex,endindex);
        // Empty range?
        if(newstartindex>=newendindex)
            break;
        startindex=newstartindex;
        endindex=newendindex;
        if(index==word.length && last)
        {
            // Use all in range
            for(var keyindex=newstartindex;keyindex<newendindex;keyindex++)
            {
                var key=foodkeywordkeys1[keyindex];
                keywords[key]=key;
            }
            result=true;
        }
    }
    // foodkeywordnames2
    var startindex=0;
    var endindex=foodkeywordnames2.length;
    for(var index=minlength;index<=word.length;index++)
    {
        var keyword=word.substring(0,index);
        // Get range
        var newstartindex=searchBinary(foodkeywordnames2,keyword,startindex,endindex);
        if(newstartindex>=0)
        {
            var key=foodkeywordkeys2[newstartindex];
            keywords[key]=key;
            result|=splitWord(word.substring(index),last,keywords);
            newstartindex++;
        }
        else
            newstartindex=~newstartindex;
        var newendindex=~searchBinary(foodkeywordnames2,keyword+"\uffff",startindex,endindex);
        // Empty range?
        if(newstartindex>=newendindex)
            break;
        startindex=newstartindex;
        endindex=newendindex;
        if(index==word.length && last)
        {
            // Use all in range
            for(var keyindex=newstartindex;keyindex<newendindex;keyindex++)
            {
                var key=foodkeywordkeys2[keyindex];
                keywords[key]=key;
            }
            result=true;
        }
    }
    return result;
}

/**
 * Splits a word into keywords from right to left.
 *
 * @param word
 *   String, the word.
 * @param keywords
 *   Object, the keywords are added to this object ([key]: key).
 */
function splitWordBackwards(word,keywords)
{
    // foodkeywordnames1
    for(var index=word.length-3;index>=0;index--)
    {
        var keyword=word.substring(index);
        // Get range
        var keyindex=searchBinary(foodkeywordnames1,keyword);
        if(keyindex>=0)
        {
            var key=foodkeywordkeys1[keyindex];
            keywords[key]=key;
            splitWordBackwards(word.substring(0,index),keywords);
        }
    }
    // foodkeywordnames2
    for(var index=word.length-3;index>=0;index--)
    {
        var keyword=word.substring(index);
        // Get range
        var keyindex=searchBinary(foodkeywordnames2,keyword);
        if(keyindex>=0)
        {
            var key=foodkeywordkeys2[keyindex];
            keywords[key]=key;
            splitWordBackwards(word.substring(0,index),keywords);
        }
    }
}

/**
 * Returns the keywords contained in the text.
 *
 * @param text
 *   String, the text.
 * @return
 *   Array of Number, the indices of the keywords.
 */
function getKeywords(text)
{
    text=text.toLowerCase().replace(/-/g," ");
    var lastwhitespace=/[\t\n\r ]$/.test(text);
    var words=normalizeWhitespace(text).split(" ");
    var keywords={};
    for(wordindex=0;wordindex<words.length;wordindex++)
    {
        var word=words[wordindex];
        if(!splitWord(word,wordindex==words.length-1 && !lastwhitespace,keywords))
            splitWordBackwards(word,keywords);
    }
    return ({
        keywords: keywords,
        autoextension: ""
    });
}

/**
 * Highlights the keywords contained in the name.
 *
 * @param name
 *   String, the food name.
 * @param keywords
 *   {keywordkey: {...}}
 * @return
 *   String, the HTML.
 */
function highlightKeywords(name,keywords)
{
    var words=name.split("$");
    var result="";
    for(wordindex=0;wordindex<words.length;wordindex++)
    {
        var word=words[wordindex];
        var highlight=false;
        // foodkeywordnames1
        var keywordindex=searchBinary(foodkeywordnames1,word);
        if(keywordindex>=0 && keywords[foodkeywordkeys1[keywordindex]]!==undefined)
            highlight=true;
        else
        {
            // foodkeywordnames2
            var keywordindex=searchBinary(foodkeywordnames2,word);
            if(keywordindex>=0 && keywords[foodkeywordkeys2[keywordindex]]!==undefined)
                highlight=true;
        }
        if(highlight)
            result+="<b>"+word+"</b>";
        else
            result+=word;
    }
    return result;
}

/**
 * Compares food names ignoring $ characters.
 *
 * @param a
 *   String, the first food name (can contain $).
 * @param b
 *   String, the second food name (can contain $).
 */
function foodNameComparator(a,b)
{
    a=a.replace(/\$/g,"");
    b=b.replace(/\$/g,"");
    if(a<b)
        return -1;
    else if(a==b)
        return 0;
    else
        return 1;
};

/**
 * Compares numbers.
 *
 * @param a
 *   Number, the first number.
 * @param b
 *   Number, the second number.
 */
function numberComparator(a,b)
{
    if(a<b)
        return -1;
    else if(a==b)
        return 0;
    else
        return 1;
};

/**
 * Returns the foods starting with text.
 *
 * @param text
 *   String, the text.
 * @return
 *   Array of Number, the indices of the keywords.
 */
function getFoods(text)
{
    var foods=[];
    var autoextension="";
    text=text.toLowerCase().replace(/-/g,"");
    var lastwhitespace=/[\t\n\r ]$/.test(text);
    text=normalizeWhitespace(text);
    if(lastwhitespace)
        text+=" ";
    if(text.length>=3 || text=="ei")
    {
        // foodnames1
        var startindex=searchBinary(foodnames1,text,0,foodnames1.length,foodNameComparator);
        if(startindex<0)
            startindex=~startindex;
        var endindex=~searchBinary(foodnames1,text+"\uffff",0,foodnames1.length,foodNameComparator);
        for(var index=startindex;index<endindex;index++)
            foods.push(index);
        // Set auto extension
        if(startindex<endindex)
        {
            var name=foodnames1[startindex].replace(/ \$/g," ");
            for(;;)
            {
                var pos=name.indexOf("$");
                if(pos==-1)
                    pos=name.length;
                if(pos>text.length)
                {
                    autoextension=name.substring(text.length,pos);
                    break;
                }
                if(pos==name.length)
                    break;
                name=name.replace("$","");
            }
        }
        // foodnames2
        var startindex=searchBinary(foodnames2,text,0,foodnames2.length,foodNameComparator);
        if(startindex<0)
            startindex=~startindex;
        var endindex=~searchBinary(foodnames2,text+"\uffff",0,foodnames2.length,foodNameComparator);
        for(var index=startindex;index<endindex;index++)
            foods.push(foodnames1.length+index);
        // Set auto extension
        if(startindex<endindex)
        {
            var name=foodnames2[startindex].replace(/ \$/g," ");
            for(;;)
            {
                var pos=name.indexOf("$");
                if(pos==-1)
                    pos=name.length;
                if(pos>text.length)
                {
                    if(autoextension>name.substring(text.length,pos))
                        autoextension=name.substring(text.length,pos);
                    break;
                }
                if(pos==name.length)
                    break;
                name=name.replace("$","");
            }
        }
    }
    if(foods.length==0)
        return undefined;
    return ({
        foods: foods,
        prefixlength: text.length,
        autoextension: autoextension
    });
}

/**
 * Highlights the prefix of the name.
 *
 * @param name
 *   String, the food name.
 * @param prefixlength
 *   Number, the length of the prefix.
 * @return
 *   String, the HTML.
 */
function highlightPrefix(name,prefixlength)
{
    name=name.replace(/\$/g,"");
    return "<b>"+name.substring(0,prefixlength)+"</b>"+name.substring(prefixlength);
}

/**
 * Returns an index for a text.
 *
 * @param text
 *   String, the text.
 * @return
 *   Array of Number, the indices of the keywords.
 */
function getIndex(text)
{
    text=text.toLowerCase().replace(/-/g,"");
    if(!/[\t\n\r ]$/.test(text))
    {
        text=normalizeWhitespace(text);
        if(text.length>=3)
        {
            var keyword=undefined;
            var keywordkey=undefined;
            // foodkeywordnames1
            var startindex=searchBinary(foodkeywordnames1,text);
            if(startindex<0)
                startindex=~startindex;
            var endindex=~searchBinary(foodkeywordnames1,text+"\uffff");
            if(startindex<endindex)
            {
                var key=foodkeywordkeys1[startindex];
                if((foodkeywordindices2[key]!==undefined && foodkeywordindices2[key]!==null) ||
                     (foodkeywordindices2[key]===undefined && foodkeywordindices1[key]!==undefined))
                {
                        keywordkey=key;
                        keyword=foodkeywordnames1[startindex];
                }
            }
            // foodkeywordnames2
            var startindex=searchBinary(foodkeywordnames2,text);
            if(startindex<0)
                startindex=~startindex;
            var endindex=~searchBinary(foodkeywordnames2,text+"\uffff");
            if(startindex<endindex)
            {
                var key=foodkeywordkeys2[startindex];
                if(((foodkeywordindices2[key]!==undefined && foodkeywordindices2[key]!==null) ||
                        (foodkeywordindices2[key]===undefined && foodkeywordindices1[key]!==undefined)) &&
                     (keyword===undefined || foodkeywordnames2[startindex]<keyword))
                {
                        keywordkey=key;
                        keyword=foodkeywordnames2[startindex];
                }
            }
            if(keyword!==undefined)
            {
                if(foodkeywordindices2[keywordkey]!==undefined && foodkeywordindices2[keywordkey]!==null)
                    var foods=foodkeywordindices2[keywordkey];
                else
                    var foods=foodkeywordindices1[keywordkey];
                return ({
                    key: keywordkey,
                    word: keyword,
                    foods: foods,
                    autoextension: keyword.substring(text.length)
                });
            }
        }
    }
    return undefined;
}

/**
 * Highlights the index.
 *
 * @param name
 *   String, the food name.
 * @param index
 *   {key, word, foods, autoextension}, the index data.
 * @return
 *   String, the HTML.
 */
function highlightIndex(name,index)
{
    name=name.replace(/\$/g,"");
    return "<b>"+index.word+": </b>"+name;
}

/**
 * Updates the food input.
 */
function updateFoodInput(event)
{
    var foodinput=jQuery(this).closest("div.foodinput");
    if(event.type=="keydown" && (event.keyCode==38 || event.keyCode==40))
    {
        var foodnameinput=jQuery("input[name^='foodname']",foodinput);
        var table=jQuery("table",foodinput);
        if(jQuery("td",table).length>0)
        {
            var oldtd=jQuery("td.selected",table);
            if(event.keyCode==38)
                var newtd=jQuery("td",oldtd.parent().prev());
            else
                var newtd=jQuery("td",oldtd.parent().next());
            if(newtd.length==0)
            {
                if(event.keyCode==38)
                    var newtd=jQuery("td:last",table);
                else
                    var newtd=jQuery("td:first",table);
            }
            var foodindex=Number(/^(|.* )foodindex-([0-9]+)(| .*)$/.exec(newtd.attr("class"))[2]);
            if(foodindex<foodnames1.length)
                var foodname=foodnames1[foodindex];
            else
                var foodname=foodnames2[foodindex-foodnames1.length];
            foodnameinput.val(foodname.replace(/\$/g,""));
            oldtd.removeClass("selected");
            newtd.addClass("selected");
            updateOtherFoodInputs(foodinput);
        }
    }
    else if(event.type=="keydown" && event.keyCode==13 && jQuery("table:visible td.selected",foodinput).length==1)
    {
        clickFoodInputCell.call(jQuery("table:visible td.selected",foodinput)[0],event);
        return false;
    }
    else if(!(event.type=="keypress" && (event.keyCode==38 || event.keyCode==40)))
    {
        // Delay until input value is changed
        window.setTimeout(function()
        {
            updateFoodInputIntern(foodinput);
            updateOtherFoodInputs(foodinput);
        },1);
    }
    return true;
}

/**
 * Opens the food input.
 */
function openFoodInput(event)
{
    var foodinput=jQuery(this).closest("div.foodinput");
    updateFoodInputIntern(foodinput);
    var foodnameinput=jQuery("input[name^='foodname']",foodinput);
    var table=jQuery("table",foodinput);
    table.css("top",foodnameinput.outerHeight()+"px");
    table.css("visibility","visible");
    return true;
}

/**
 * Closes the food input.
 */
function closeFoodInput(event)
{
    var foodinput=jQuery(this).closest("div.foodinput");
    var table=jQuery("table",foodinput);
    table.css("visibility","");
    return true;
}

/**
 * Click on a food input cell.
 */
function clickFoodInputCell(event)
{
    var foodinput=jQuery(this).closest("div.foodinput");
    var foodnameinput=jQuery("input[name^='foodname']",foodinput);
    var suffix=/foodname([0-9]*)/.exec(foodnameinput.attr("name"))[1];
    var table=jQuery("table",foodinput);
    var td=jQuery(this);
    var foodindex=Number(/^(|.* )foodindex-([0-9]+)(| .*)$/.exec(td.attr("class"))[2]);
    if(foodindex<foodnames1.length)
        var foodname=foodnames1[foodindex];
    else
        var foodname=foodnames2[foodindex-foodnames1.length];
    foodnameinput.val(foodname.replace(/\$/g,""));
    updateOtherFoodInputs(foodinput);
    var form=foodinput.closest("form");
    var foodunitinput=jQuery("select[name='unit"+suffix+"']",form);
    foodunitinput.focus();
    return false;
}

function updateFoodInputIntern(foodinput)
{
    var foodnameinput=jQuery("input[name^='foodname']",foodinput);
    var text=foodnameinput.val();
    var count=0;
    var countmax=20;
    var autoextension=undefined;
    var allfoodindices={};
    // Get index foods
    var index=getIndex(text);
    var foodindexarray=[];
    if(index!==undefined)
    {
        autoextension=index.autoextension;
        for(var foodsindex=0;foodsindex<index.foods.length;foodsindex++)
        {
            var foodindex=index.foods[foodsindex];
            if(allfoodindices[foodindex]===undefined)
            {
                allfoodindices[foodindex]=true;
                count++;
                if(foodindex<foodnames1.length)
                    var foodname=foodnames1[foodindex];
                else
                    var foodname=foodnames2[foodindex-foodnames1.length];
                foodname=foodname.replace(/\$/g,"");
                foodindexarray.push({
                    index: foodindex,
                    name: foodname
                });
            }
        }
        // Sort foods
        foodindexarray.sort(function(food1,food2)
        {
            return food1.name.localeCompare(food2.name);
        });
    }
    // Get prefix foods
    var foodprefixarray=[];
    if(count<countmax)
    {
        var foods=getFoods(text);
        if(foods!==undefined)
        {
            if(autoextension===undefined)
                autoextension=foods.autoextension;
            for(var foodsindex=0;foodsindex<foods.foods.length;foodsindex++)
            {
                var foodindex=foods.foods[foodsindex];
                if(allfoodindices[foodindex]===undefined)
                {
                    allfoodindices[foodindex]=true;
                    count++;
                    if(foodindex<foodnames1.length)
                        var foodname=foodnames1[foodindex];
                    else
                        var foodname=foodnames2[foodindex-foodnames1.length];
                    foodname=foodname.replace(/\$/g,"");
                    foodprefixarray.push({
                        index: foodindex,
                        name: foodname
                    });
                }
            }
        }
        // Sort foods
        foodprefixarray.sort(function(food1,food2)
        {
            return food1.name.localeCompare(food2.name);
        });
    }
    // Get keyword foods
    var foodkeywordarray=[];
    if(count<countmax)
    {
        var keywords=getKeywords(text);
        // Sort by keyword count
        if(autoextension===undefined)
            autoextension=keywords.autoextension;
        // Collect foods
        var foodkeywordmap={};
        for(var key in keywords.keywords)
        {
            var keyfoods=foodkeywordfoods2[key];
            if(keyfoods===undefined)
                var keyfoods=foodkeywordfoods1[key];
            if(keyfoods!==undefined && keyfoods!==null)
            {
                for(var keyfoodindex=0;keyfoodindex<keyfoods.length;keyfoodindex++)
                {
                    var foodindex=keyfoods[keyfoodindex];
                    var food=foodkeywordmap[foodindex];
                    if(food===undefined)
                    {
                        if(allfoodindices[foodindex]===undefined)
                        {
                            allfoodindices[foodindex]=true;
                            if(foodindex<foodnames1.length)
                                var foodname=foodnames1[foodindex];
                            else
                                var foodname=foodnames2[foodindex-foodnames1.length];
                            var length1=foodname.length;
                            foodname=foodname.replace(/\$ \$/g," ");
                            var length2=foodname.length;
                            foodname=foodname.replace(/\$/g,"");
                            var length3=foodname.length;
                            food={
                                index: foodindex,
                                name: foodname,
                                words: 1+(length1-length2)/2+(length2-length3),
                                keywords: 1
                            };
                            foodkeywordmap[foodindex]=food;
                            foodkeywordarray.push(food);
                        }
                    }
                    else
                        food.keywords++;
                }
            }
        }
        // Sort foods
        foodkeywordarray.sort(function(food1,food2)
        {
            if(food1.keywords!=food2.keywords)
                return food2.keywords-food1.keywords;
            if(food1.words!=food2.words)
                return food1.words-food2.words;
            return food1.name.localeCompare(food2.name);
        });
    }
    // Create table rows
    rows="";
    count=0;
    for(var arrayindex=0;arrayindex<foodindexarray.length && count<countmax;arrayindex++)
    {
        var food=foodindexarray[arrayindex];
        if(food.index<foodnames1.length)
        {
            var foodname=foodnames1[food.index];
            var foodkey=foodkeys1[food.index];
        }
        else
        {
            var foodname=foodnames2[food.index-foodnames1.length];
            var foodkey=foodkeys2[food.index-foodnamess1.length];
        }
        rows=rows+'<tr><td class="foodindex-'+food.index+'" onmousedown="return clickFoodInputCell.call(this,event)">'+highlightIndex(foodname,index)+'</td></tr>';
        count++;
    }
    separator="";
    if(count>0)
        separator=" separator";
    for(var arrayindex=0;arrayindex<foodprefixarray.length && count<countmax;arrayindex++)
    {
        var food=foodprefixarray[arrayindex];
        if(food.index<foodnames1.length)
            var foodname=foodnames1[food.index];
        else
            var foodname=foodnames2[food.index-foodnames1.length];
        rows=rows+'<tr><td class="foodindex-'+food.index+separator+'" onmousedown="return clickFoodInputCell.call(this,event)">'+highlightPrefix(foodname,foods.prefixlength)+'</td></tr>';
        separator="";
        count++;
    }
    if(count>0)
        separator=" separator";
    for(var arrayindex=0;arrayindex<foodkeywordarray.length && count<countmax;arrayindex++)
    {
        var food=foodkeywordarray[arrayindex];
        if(food.index<foodnames1.length)
            var foodname=foodnames1[food.index];
        else
            var foodname=foodnames2[food.index-foodnames1.length];
        rows=rows+'<tr><td class="foodindex-'+food.index+separator+'" onmousedown="return clickFoodInputCell.call(this,event)">'+highlightKeywords(foodname,keywords.keywords)+'</td></tr>';
        separator="";
        count++;
    }
    // Update table
    var table=jQuery("table",foodinput);
    jQuery("> tbody",table).replaceWith("<tbody>"+rows+"</tbody>");
    if(count>0)
        table.css("border","1px solid #bbbbbb");
    else
        table.css("border","none");
}

/**
 * Updates the key, unit, preparation and addition.
 *
 * @param foodinput
 *   jQuery, the foodinput element.
 */
function updateOtherFoodInputs(foodinput)
{
    var form=foodinput.closest("form");
    var foodname=jQuery("input[name^='foodname']",foodinput).val();
    var foodkeyinput=jQuery("input[name^='foodkey']",foodinput);
    var suffix=/foodkey([0-9]*)/.exec(foodkeyinput.attr("name"))[1];
    // Key
    var foodquantitygroup=0;
    var foodpreparationgroup=undefined;
    var foodadditiongroup=undefined;
    var foodindex=searchBinary(foodnames1,foodname,0,foodnames1.length,foodNameComparator);
    if(foodindex>=0)
    {
        foodkeyinput.val(foodkeys1[foodindex]);
        foodquantitygroup=foodquantitygroups1[foodindex];
        for(var group in foodpreparations1)
        {
            if(searchBinary(foodpreparations1[group],foodindex,undefined,undefined,numberComparator)>=0)
            {
                foodpreparationgroup=group;
                break
            }
        }
        for(var group in foodadditions1)
        {
            if(searchBinary(foodadditions1[group],foodindex,undefined,undefined,numberComparator)>=0)
            {
                foodadditiongroup=group;
                break
            }
        }
    }
    else
    {
        foodindex=searchBinary(foodnames2,foodname,0,foodnames2.length,foodNameComparator);
        if(foodindex>=0)
        {
            foodkeyinput.val(foodkeys2[foodindex]);
            foodquantitygroup=foodquantitygroups2[foodindex];
            for(var group in foodpreparations2)
            {
                if(searchBinary(foodpreparations2[group],foodindex,undefined,undefined,numberComparator)>=0)
                {
                    foodpreparationgroup=group;
                    break
                }
            }
            for(var group in foodadditions2)
            {
                if(searchBinary(foodadditions2[group],foodindex,undefined,undefined,numberComparator)>=0)
                {
                    foodadditiongroup=group;
                    break
                }
            }
        }
        else
            foodkeyinput.val("");
    }
    // Unit
    var foodunitinput=jQuery("select[name='unit"+suffix+"']",form);
    if(foodquantitygroup==0)
        var options='<option value=""></option>';
    else
    {
        var options="";
        var units=foodunits1[foodquantitygroup];
        var oldunitkey=foodunitinput.val();
        var unitindices={};
        for(var unitindex=0;unitindex<units.length;unitindex++)
        {
            var unit=units[unitindex];
            unitindices["-"+unit.name]=unitindex;
            if(unit.key==oldunitkey)
                unitindices["*"]=unitindex;
        }
        var selectedunitindex=unitindices["*"];
        if(selectedunitindex===undefined)
            selectedunitindex=unitindices["-Scheibe"];
        if(selectedunitindex===undefined)
            selectedunitindex=unitindices["-Becher"];
        if(selectedunitindex===undefined)
            selectedunitindex=unitindices["-Dose"];
        if(selectedunitindex===undefined)
            selectedunitindex=unitindices["-Portion"];
        if(selectedunitindex===undefined)
            selectedunitindex=unitindices["-Stück"];        
        for(var unitindex=0;unitindex<units.length;unitindex++)
        {
            var unit=units[unitindex];
            options+='<option value="'+unit.key+'"'+(unitindex==selectedunitindex?' selected="selected"':'')+'>'+unit.name+'</option>';
        }
    }
    foodunitinput.empty();
    foodunitinput.append(options);
    // Preparation
    var foodpreparationinput=jQuery("select[name='preparationtype"+suffix+"']",form);
    var oldpreparation=foodpreparationinput.val();
    if(foodpreparationgroup===undefined)
        foodpreparationinput.hide();
    else
    {
        var options="";
        var preparations=foodpreparationgroups[foodpreparationgroup];
        for(var preparationindex=0;preparationindex<preparations.length;preparationindex++)
        {
            var preparation=preparations[preparationindex];
            options+='<option value="'+preparation.key+'"'+(oldpreparation==preparation.key?' selected="selected"':'')+'>'+preparation.name+'</option>';
        }
        foodpreparationinput.empty();
        foodpreparationinput.append(options);
        foodpreparationinput.show();
    }
    // Addition
    var foodadditiontext=jQuery("p.addition",foodinput.closest("tr"));
    if(foodadditiongroup===undefined)
        foodadditiontext.hide();
    else
    {
        foodadditiontext.text(foodadditiongroups[foodadditiongroup]);
        foodadditiontext.show();
    }
}

/**
 * Refreshes an element or shows a greybox.
 *
 * @param selector
 *   String, the element selector.
 * @param url
 *   The URL of the function.
 * @param parameters
 *   Optional, Object, {name: value} the parameters.
 */
function updateElement(selector,url,parameters)
{
    postRequest(url,parameters===undefined?{}:parameters,function(response)
    {
        if(response.substring(0,"<div class=\"greyboxcontainer\">".length)=="<div class=\"greyboxcontainer\">")
            showGreyBoxHtml(response);
        else
            jQuery(selector).replaceWith(response);
    });
    return false;
}

/////////////
// GreyBox //
/////////////

/**
 * Special code to show IE6 grey boxes.
 */
function setGreyBoxSize()
{
    var backgrounds=jQuery("div.greyboxbackground");
    backgrounds.css("position","absolute");
    backgrounds.width(jQuery(document).width());
    backgrounds.height(jQuery(document).height());
    jQuery("div.greyboxbody").width(jQuery(document).width());
}

/**
 * Shows a grey box.
 * 
 * @param url
 *   String, the url with the grey box content.
 */
function showGreyBox(url)
{
    postRequest(url,{greybox: "x"},function(response)
    {
        if(response=="")
            return;
        showGreyBoxHtml(response);
    });
    return true;
}

/**
 * Shows a grey box.
 * 
 * @param html
 *   String, the grey box content.
 * @param upload
 *   Optional, Boolean, use upload greybox? 
 */
function showGreyBoxHtml(html,upload)
{
    if(upload===true)
        var parent=jQuery("div.greyboxupload");
    else
        var parent=jQuery("div.greyboxpage");
    if((jQuery.browser.msie && jQuery.browser.version.substr(0,2)=="6.") ||
       /applewebkit.*mobile/i.test(navigator.userAgent))
    {
        setGreyBoxSize();
        window.onresize=setGreyBoxSize;
    }
    jQuery("div.greyboxbackground",parent).show();
    jQuery("div.greyboxbody",parent).css("margin-top","-16000px").show();
    jQuery("div.greyboxcontainer",parent).replaceWith(html);
    var greybox=jQuery("div.greyboxcontainer",parent);
    // Calculate margin-top  
    var gh=greybox.height();
    var ch=jQuery(window).height();
    var cy=jQuery(window).scrollTop();
    var top=Math.round(Math.max(20,(ch-Math.max(400,gh))/2)+cy);
    jQuery("div.greyboxbody",parent).css("margin-top",top+"px");
    // Focus first input element
    jQuery(":input[type!='hidden']:first",greybox).focus();
    initFragment(greybox);
}

/**
 * Updates a grey box.
 * 
 * @param html
 *   String, the new grey box content.
 * @param upload
 *   Optional, Boolean, use upload greybox? 
 */
function updateGreyBoxHtml(html,upload)
{
    if(upload===true)
        var parent=jQuery("div.greyboxupload");
    else
        var parent=jQuery("div.greyboxpage");
    jQuery("div.greyboxcontainer",parent).replaceWith(html);
    var greybox=jQuery("div.greyboxcontainer",parent);
    // Focus first input element
    jQuery(":input[type!='hidden']:first",greybox).focus();
    initFragment(greybox);
}

/**
 * Closes the grey box.
 *
 * @param upload
 *   Optional, Boolean, use upload greybox? 
 */
function closeGreyBox(upload)
{
    if(upload===true)
        var parent=jQuery("div.greyboxupload");
    else
        var parent=jQuery("div.greyboxpage");
    jQuery("div.greyboxbackground",parent).hide();
    jQuery("div.greyboxbody",parent).hide();
    if(upload!==true)
        PAGETRACKER._trackPageview(PAGETRACKERPATH);
}

////////////
// Upload //
////////////

/** String, the id of the button of the current file upload. */
var currentbuttonid;
/** String, the id of the queue of the current file upload. */
var currentqueueid;

/**
 * Creates a file upload button.
 *
 * @param buttonid
 *   String, the id of the file upload button.
 * @param greyboxfileurl
 *   String, the URL of the grey box.
 * @param uploadfileurl
 *   String, the URL of the uploadfile function.
 * @param setfiledata
 *   Function(buttonid,hash,name,errormessage), called to set the result of a file upload.
 */
function createFileUploadButton(buttonid,greyboxurl,uploadfileurl,setfiledata)
{
    jQuery("#"+buttonid).uploadify(
    {
        uploader:   "/media/flash/uploadify.swf",
        althtml:    '<p>Bitte installieren Sie den <a href="http://get.adobe.com/de/flashplayer/">Adobe Flash Player.</a></p>',
        script:     uploadfileurl,
        scriptData: {},
        fileDataName: "file",
        fileDesc:   "GIF, JPG und PNG Dateien",
        fileExt:    "*.gif;*.jpg;*.jpeg;*.png",
        sizeLimit:  16*1024*1024,
        buttonImg:  "/media/icon/button/upload.gif",
        rollover:   false,
        width:      85,
        height:     24,
        wmode:      "transparent",
        onSelect: function(event,queueid,fileobj)
        {
            if(currentbuttonid)
                jQuery("#"+currentbuttonid).uploadifyCancel(currentqueueid);
            if(fileobj.size<=16*1024*1024)
            {
                currentbuttonid=buttonid;
                currentqueueid=queueid;
                setfiledata(buttonid,"","","");
                postRequest(greyboxurl,{greybox: "x"},function(response)
                {
                    if(response=="")
                        return;
                    showGreyBoxHtml(response,true);
                    window.setTimeout("startFileUpload(\'"+buttonid+"\',\'"+queueid+"\')",1);
                });
            }
            else
            {
                jQuery("#"+buttonid).uploadifyClearQueue();
                setfiledata(buttonid,"","","Die Datei ist zu gro\u00DF (maximal 16 MB).");
            }
            return false;
        },
        onCancel: function(event,queueid,fileobj,data)
        {
            currentbuttonid=undefined;
            currentqueueid=undefined;
            setfiledata(buttonid,"","","");
            closeGreyBox(true);
            return false;
        },
        onClearQueue: function(event,data)
        {
            currentbuttonid=undefined;
            currentqueueid=undefined;
            return false;
        },
        onError: function(event,queueid,fileobj,errorobj)
        {
            jQuery("#"+buttonid).uploadifyClearQueue();
            setfiledata(buttonid,"","","Es ist ein Fehler beim Hochladen aufgetreten.");
            closeGreyBox(true);
            return false;
        },
        onProgress: function(event,queueid,fileobj,data)
        {
            var percentage;

            percentage=Math.min(Math.max(0,Math.round(data.percentage*0.95)),95);
            jQuery("div.fileuploadprogressbar").width(Math.round(1+percentage*646/100));
            jQuery("div.greyboxupload div.greyboxcontainer p").text(percentage+"%");
            return false;
        },
        onComplete: function(event,queueid,fileobj,response,data)
        {
            currentbuttonid=undefined;
            currentqueueid=undefined;
            if(/^dynamic-hash-(gif|jpg|png)-[0-9a-f]{64}$/.test(response))
                setfiledata(buttonid,response,fileobj.name,"");
            else
                setfiledata(buttonid,"","","Es ist ein Fehler beim Hochladen aufgetreten.");
            closeGreyBox(true);
            return false;
        }
    });
}

/**
 * Sets the input controls for an uploaded file.
 * 
 * @param buttonid
 *   String, the id of the upload button.
 * @param hash
 *   String, the file hash.
 * @param name
 *   String, the file name.
 * @param errormessage
 *   String, the error message.
 */
function setFormFileData(buttonid,hash,name,errormessage)
{
    var tr=jQuery("#"+buttonid).closest("tr");
    var inputhash=jQuery("input[type='hidden']",tr);
    inputhash.val(hash);
    var inputname=jQuery("input[type='text']",tr);
    inputname.val(name);
    // Set/remove error message
    if(errormessage=="")
    {
        jQuery("td:first",tr).removeClass("invalid");
        jQuery("p",tr).remove();
    }
    else
    {
        jQuery("td:first",tr).addClass("invalid");
        var p=jQuery("p",tr);
        if(p.length==0)
            jQuery("#"+buttonid).closest("td").append('<p>'+errormessage+'</p>')
        else
            p.text(errormessage);
    }
}

/**
 * Internally used function to start a file upload.
 *
 * @param buttonid
 *   String, the id of the file upload button.
 * @param queueid
 *   String, the id of the file upload queue.
 */
function startFileUpload(buttonid,queueid)
{
    if(currentbuttonid==buttonid && currentqueueid==queueid)
        jQuery("#"+buttonid).uploadifyUpload();
}

/**
 * Cancels the current file upload.
 */
function cancelFileUpload()
{
    if(currentbuttonid)
        jQuery("#"+currentbuttonid).uploadifyCancel(currentqueueid);
}


