var WIDGETHANDLERS = {}

// Taken from <http://happygiraffe.net/blog/articles/2007/09/26/jquery-logging>
jQuery.fn.log = function (msg) {
    //console.log("%s: %o", msg, this);
    return this;
};
function escapeHTML (string) {
    return string.replace(/&/g,'&amp;')
                 .replace(/>/g,'&gt;')
                 .replace(/</g,'&lt;')
                 .replace(/"/g,'&quot;');
};
function unescapeHTML (string) {
    return string.replace(/&amp;/, '&')
                 .replace(/&gt;/, '>')
                 .replace(/&lt;/, '<')
                 .replace(/&quot;/, '"');
};
jQuery.postJSON = function (options) {
    
    options = jQuery.extend( { 'type': 'POST'
                             , 'contentType': 'application/json'
                             , 'processData': false 
                             , 'timeout': 50000
                             , 'dataType': 'json'
                             }
                           , options);
    options.data = JSON.stringify(options.data);
    options._error = options.error
    options.error = function (event) {
        //TODO: Handle other HTTP error codes like redirect.
        //console.log('errorhandler');
        if (event.readyState === 0) {
            // Timeout
            //console.log('Timeout', event);
            if (options.timeoutError)
                options.timeoutError(event);
            else if (options._error)
                options._error(event);
        } else if (event.status === 0) {
            //console.log('No answer', event);
            if (options.noreplyError) 
                options.noreplyError(event);
            else if (options._error)
                options._error(event);
        } else if (event.status === 404) {
            // Topic has been deleted
            //console.log('404', event);
            humanMsg.displayMsg('Error: The topic has been deleted.');
        } else if (event.status >= 500) {
            //console.log('error', event, options._error);
            if (options._error)
                options._error(event);
        } else {
            //console.log('errorhandler  got an unknown error!', event, options._error);
            if (options._error)
                options._error(event);
        }
        return false;
    };
    
    //TODO: Support difference between TIMEOUT, UNREACHABLE and SERVER ERROR
    //TODO: Use object literal as input.
    jQuery.ajax(options);
};


function addWizardInfoIfNeeded(data,widget) {
    // If a widget is being used by the wizard, we must also make sure that we transfer the wizard-specific
    // context. This is stored in two hidden input-fields: "serial" and "step"   
    var form = widget.parents('form');
    var serialElement = $("input",form).filter('[name=serial]');
    if( serialElement ) {
        var stepElement = $("input",form).filter('[name=step]');
        data['serial'] = serialElement.attr("value") ;
        data['step'] = stepElement.attr("value") ;
    }
}
function storeWizardInfoInDOM(widget,serial) {
    // If a widget is being used by the wizard, we might have to update the draft-serial value
    // since the draft is lazily created (i.e not until it is really needed). In those cases the
    // new draft-serial is returned as part of a json-reply, which must be stored in the dom.
    var form = widget.parents('form');
    var serialElement = $("input",form).filter('[name=serial]');
    if( serialElement) {
        // update existing element.
        serialElement.attr("value",serial);
    } else {
        // create new element.
        serialElement = form.append('<input type="hidden" name="serial" value="' + serial + '"/>');
    }
}

function onFilterRadioButtonClicked(event) {
    // The user has clicked a radiobutton, which normally means that the search-filters
    // has been modified, so we must enable the "Search"-button.
    var searchResults = $(event.target).parents('div.results');
    var searchbutton = $(".searchbutton",searchResults);
    searchbutton.removeAttr("disabled");
}

function select(event) {
    var hit = $(this),
        widget = hit.parents('.widget'),
        form = hit.parents('form'),
        data = {},
        operation = { 'operation' : 'createassociation'
                    , 'players': [hit.attr('ztm:serial')]
                    };
    data[widget.attr('ztm:marker')] = operation
    
    // If this widget is being used by the wizard, we must also make sure that we transfer the wizard-specific
    // context. This is stored in two hidden input-fields: "serial" and "step"    
    addWizardInfoIfNeeded(data,widget);
    var loadingimg = widget.find("#loading");
    loadingimg.show();
    
    jQuery.postJSON({ url : form.attr('action')
                    , data: data
                    , timeout: 50000
                    , success: selectSucceded
                    , error: function(event) { loadingimg.hide();selectFailed(event);}
                    });

}
function selectSucceded(result, status) {
    for (name in result) {
        var data = result[name];
        var widget = $('#' + name);
        widget.find('.results').slideUp(200);
        widget.find('p.associationscontrols', widget).show();
        var loadingimg = widget.find("#loading");
        loadingimg.hide();
    }
    rebuildList(result);
}
function selectFailed(event) {
    humanMsg.displayMsg('Select failed');
}



function associationsearch(event, page) {
    //console.log(this, event, event.target);
    var widget = $(event.target).parents('.widget');
    var inner = $(event.target).parents('.inner');
    var form = $(event.target).parents('form').log();

    var areawide = "true";
    if ($('#areasearchoff').attr('checked')){
      areawide = "false";
    }

    var loadingimg = widget.find("#loading");
    loadingimg.show();

    page = page || 0;
    event.stopPropagation();
    event.preventDefault();

    //console.log(widget.find('input.query').log().attr('value'));
    //console.log(form.attr('action'));
    var data = {}, operation = {'operation' : 'search', 'page':page,
                                            'areafilter': areawide, 
                                            'query': widget.find('input.query').attr('value') || '',
                                            'sort': widget.find('input.sort').attr('value') || '',
                                            'reversed': widget.find('input.reversed').attr('value') || ''
                                            };
    data[widget.attr('ztm:marker')] = operation

    
    // If this widget is being used by the wizard, we must also make sure that we transfer the wizard-specific
    // context. This is stored in two hidden input-fields: "serial" and "step"    
    addWizardInfoIfNeeded(data,widget);
    
    jQuery.postJSON({ url : form.attr('action')
                    , data: data
                    , timeout: 50000
                    , success: searchSucceded
                    , error: function (event)  {
                                searchFailed(event);
                                var loadingimg = widget.find("#loading");
                                loadingimg.hide();
                            }
                    });
    return false;
}

function closeSearch(event) { 
    var results = $(this).parents('.results');
    var data = $(this).parents('.inner').log('inner').find('.associationscontrols').log().slideDown(100);
    results.slideUp(200).remove();
;}

function inputQueryKeydown(event) {
    var query = $(event.target);
    var widget = query.parents('.widget');
    var searchbutton = query.siblings("input.searchbutton");
    if ( event.keyCode == 13 ) {
      query.attr("disabled","disabled"); // disable the textfield, so that the user cannot spam the server by hitting the enter-key repeatedly.
      searchbutton.attr('disabled','disabled'); // disable the button to prevent the user from spamming the server by clicking the button multiple times.
      associationsearch(event,0);
      return false;
    } else {
      searchbutton.removeAttr('disabled');
    }
}

function searchSucceded(result, status) {

    var next_text = $('#next_text').attr('value');
    var previous_text = $('#previous_text').attr('value');
    var search_text = $('#search_text').attr('value');
    var title_text = $('#title_text').attr('value');
    var date_text = $('#date_text').attr('value');
    var limit_search = $('#limit_text').attr('value');
    var nolimit_search = $('#nolimit_text').attr('value');
    //var search_text = $('#search_text').attr('value');
    if (!limit_search) {
        limit_search = 'Limit search to area'
        nolimit_search = 'Search all areas'
    }        
    for (name in result) {
        var data = result[name];
        var widget = $('#' + name);
        var inner = widget.find('.inner');
        var query = data['query'];
        if( ! query) {
            query = '';
        }

        var results = '<div class="results">' +
        '<span class="sorters"><span class="titlesort">'+title_text+'</span> <span class="datesort">'+date_text+'</span> <span class="closer">[X]</span></span>' + 
      '<input type="text" class="query" name="query" value="' + query + '">' +
      '<input type="button" class="searchbutton" disabled="disabled" value="'+search_text+'"/>';

        if (data['areawide'] == "true"){
      results = results + '<br/><input id="areasearchon" checked name="areasearch" type="radio" value="on"> '+limit_search+'<br/><input id="areasearchoff" name="areasearch" type="radio" value="off"> '+nolimit_search;
    }else{
      results = results + '<br/><input id="areasearchon" name="areasearch" type="radio" value="on"> '+limit_search+'<br/><input id="areasearchoff" checked name="areasearch" type="radio" value="off"> '+nolimit_search;
    }
    results = results + '<input type="hidden" class="sort" value="' + (data['sort'] || '') + '"/>' +
      '<input type="hidden" class="reversed" value="' + (data['reversed'] || '') + '"/>' +
      '<ul></ul><span class="prev">« '+previous_text+'</span>&nbsp;' +
      '<span class="pager" ztm:page="' + data.current + '">' + (data.current+1) + '/' + data.pages +  '</span>&nbsp;' +
      '<span class="next">'+next_text+' »</span></div>';
                                        

        results = $(results)
            
        results.find('div.control.create').click(createTopic);
        results.find('.closer').click(closeSearch);
        
        var sortholder = results.find('input.sort');
        var reversedholder = results.find('input.reversed');
        var titlesort = results.find('span.titlesort');
        var datesort = results.find('span.datesort');
        
        // Set the classes (selected" and "reversed") on the sortorder links, based on the current sort-settings
        if(sortholder.attr('value') == 'title') {
            titlesort.addClass("selected");
            if( reversedholder.attr('value') ) {
                titlesort.addClass("reversed");
            }
        }
        if(sortholder.attr('value') == 'date') {
            datesort.addClass("selected");
            if( reversedholder.attr('value') ) {
                datesort.addClass("reversed");
            }
        }
        
        // Add eventhandlers for the sortorder links.
        titlesort.click(function(event) {
            titlesort.attr("disabled","disabled"); // disable the link, so that the user cannot spam the server by clicking on it repeatedly
            if(sortholder.attr('value') == 'title') {
                // We are already sorting on this field, so toggle the "reversed" setting.
                if( reversedholder.attr('value') ){
                    reversedholder.removeAttr('value');
                } else {
                    reversedholder.attr('value','true');
                }
            } else {
                // the current sort is on some other field, so we must clear the "reversed" value.
                reversedholder.removeAttr('value');
            }
            sortholder.attr('value','title');
            associationsearch(event,0);
        });
        datesort.click(function(event) {
            datesort.attr("disabled","disabled"); // disable the link, so that the user cannot spam the server by clicking on it repeatedly
            if(sortholder.attr('value') == 'date') {
                // We are already sorting on this field, so toggle the "reversed" setting.
                if( reversedholder.attr('value') ){
                    reversedholder.removeAttr('value');
                } else {
                    reversedholder.attr('value','true');
                }
            } else {
                // the current sort is on some other field, so we must clear the "reversed" value.
                reversedholder.removeAttr('value');
            }
            sortholder.attr('value','date');
            associationsearch(event,0);
        });

        var query = results.find("input.query");
        query.keypress(inputQueryKeydown);
        var searchbutton = query.siblings("input.searchbutton");
        if(query.attr('value')){
            searchbutton.removeAttr('disabled');
        }
        searchbutton.click(function(event) {
            searchbutton.attr('disabled','disabled'); // disable the button to prevent the user from spamming the server by clicking the button multiple times.
            associationsearch(event,0);
        });
        
        if (!data.next) { results.find('span.next').addClass('none');} else { results.find('span.next').click(nextPage);}
        if (!data.prev) { results.find('span.prev').addClass('none');} else { results.find('span.prev').click(prevPage);}

        var filterbuttons = results.find('input[type=radio]');
        filterbuttons.click(onFilterRadioButtonClicked);

        var wizard_serial = data.wizard_serial;
        if( wizard_serial != undefined ) {
            storeWizardInfoInDOM(widget,wizard_serial);            
        }

        var list = results.find('ul');
        for (index in data.topics) {
            var topic = data.topics[index];
            var newline = $('<li><span class="hit" ztm:serial="' + topic.stringserial + '">+ ' + topic.name + ' <span class="type">' + topic.typenames + '</span></span> <span class="open"><a href="' + topic.url + '">open</a></span></li>');
            if( (topic.thumbnail != undefined) ) {
                newline.find(".hit").prepend('<img src="' + topic.thumbnail +'"/>');
            }
            list.append(newline);
        }
        list.find('li span.hit').click(select);
        
        var resultwrapper = inner.find('.results').log();
        if (resultwrapper.length>0) {
            resultwrapper.replaceWith(results);
        } else {
            inner.append(results);
            results.hide().slideDown(200);
        }
        $('.associationscontrols', widget).hide();
        var loadingimg = widget.find("#loading");
        loadingimg.hide();
        
        // Give the query-box the inputfocus
        query[0].focus(); 
    }
}
function searchFailed(event) {
    humanMsg.displayMsg('Search failed');
}

function nextPage(event) { var page = parseInt($(this).prev().attr('ztm:page'), 10); associationsearch(event, page+1); }
function prevPage(event) { var page = parseInt($(this).next().attr('ztm:page'), 10); associationsearch(event, page-1); }
function createTopic(event) {
    var form = $(this).parents('form');
    var widget = $(this).parents('.widget')
    var line = $(this).parents('li');
    
    var data = {}, operation = {'operation' : 'createtopic', 'name': widget.find('input.query').attr('value') || ''};
    data[widget.attr('ztm:marker')] = operation
    
    // If this widget is being used by the wizard, we must also make sure that we transfer the wizard-specific
    // context. This is stored in two hidden input-fields: "serial" and "step"    
    addWizardInfoIfNeeded(data,widget);
    
    jQuery.postJSON({ url : form.attr('action')
                    , data: data
                    , timeout: 50000
                    , success: function (result, status) {rebuildList(result)}
                    , error: function (result, status) {humanMsg.displayMsg('Creation failed');}
                    });    
    
};

function saveDatetime(event) {
    var widget = $(this).parents('.widget'),
        datetime = $(this).attr('value'),
        id = $(this).attr('id'),
        sendname = $(this).attr('name');
        
    hiddenfield = '<input name="'+sendname+'" type="hidden" value="'+datetime+'">';
    $(widget).append(hiddenfield);
};

function rebuildList(result) {
    for (name in result) {
        var data = result[name],
            widget = $('#' + name),
            current = $('.inner > ul li', widget),
            c = {}, r = {};
        // Find the current state of the list.
        for (var index=0; index<current.length; index++) {
            c[$(current[index]).attr('ztm:serial')] = $(current[index])
        }
        // Find the desired state of the list.
        for (var index=0; index<data.status.length; index++) {
            r[data.status[index].stringserial] = data.status[index]
        }
        
        // Remove elements no longer in the desired state.
        for (var s in c) {
            if (!r[s]) {
                c[s].slideUp(200).remove();
            }
        }
        // Add elements missing from the current state
        for (var index=0; index<data.status.length; index++) {
            var hit = data.status[index];
            var lines = $('.inner > ul li', widget);
            var line = $(lines.get(index))
            var serial = line.attr('ztm:serial');
            //console.log(index, line, serial, hit)
            if (serial != hit.stringserial) {
                var newline = $('<li ztm:serial="' + hit.stringserial + '"><a href="' + hit.url + '">' + hit.name + '</a></li>');
                newline.log("hit.thumbnail:"+hit.thumbnail);
                if( (hit.thumbnail != undefined) ) {
                    var newline = $('<li ztm:serial="' + hit.stringserial + '"><img src="' + hit.thumbnail +'"/><div class="imagedetails">' + hit.fullname + '<br><textarea class="imgdesc">' + hit.description +' </textarea></div></li>');
                    newline.find('textarea.imgdesc').blur(saveDescription);
                }
                //newline.click(associationdeleter); // the associationdeleter stuff is handled by associationline()
                newline.hide()
                if(index < lines.length) {
                    newline.insertBefore(line);
                } else {
                    lineparent = $('.inner > ul', widget);
                    newline.appendTo(lineparent);
                }
                newline.fadeIn(200);
                associationline(0, newline);
            }
        }
    }
}

function associationdeleter (event) {
    //console.log('associationdeleter');
    var form = $(this).parents('form');
    var widget = $(this).parents('.widget')
    var line = $(this).parents('li');
    var data = {}, operation = {'operation' : 'removeassociation', 'players': [line.attr('ztm:serial')]};
    data[widget.attr('ztm:marker')] = operation

    // If this widget is being used by the wizard, we must also make sure that we transfer the wizard-specific
    // context. This is stored in two hidden input-fields: "serial" and "step"    
    addWizardInfoIfNeeded(data,widget);

    var loadingimg = widget.find("#loading");
    loadingimg.show();

    jQuery.postJSON({ url : form.attr('action')
                    , data: data
                    , timeout: 50000
                    , success: function (result, status) {rebuildList(result);loadingimg.hide();}
                    , error: function (result, status) {humanMsg.displayMsg('Deletion failed');loadingimg.hide();}
                    });
}
function associationline(index, element) {
    var line = $(element);
    var serial = line.find('input').hide().attr('value');
    var delete_text = $('#delete_text').attr('value');
    line.find('.deleter').remove();
    line.append('<span class="deleter" title="'+delete_text+'">&nbsp;[X]</span>');
    line.attr('ztm:serial', serial);
    line.find('.deleter').click(associationdeleter)
}

function associationwidget(element) {
    // TODO: Animate adding and removal
    // TODO: Disable checking of already associated topics
    // TODO: Disable search when cardinality constraints reached.
    // TODO: Mark as incomplete
    // TODO: Animate waiting for server
    // TODO: Handle error messages from server.
    $('li', element).each(associationline);
    
    $('input.search', element).click(associationsearch);
    //$('input.create', element).click(createassociation).disable();
}
WIDGETHANDLERS['associationwidget'] = associationwidget

function widgethandler(index, item) {
    var widget = $(item);
    var widgettype = widget.attr('ztm:widgettype');

    if (WIDGETHANDLERS[widgettype]) {
        WIDGETHANDLERS[widgettype](widget)
    }
    if (widget.hasClass('collapsed')) {
        widget.find('label').append(' <span class="toggler">[+]</span>');
        widget.find('.inner').hide();
        widget.find('.toggler').click(widgetcollapser);
    }
}
function widgetcollapser(event) {
    var widget = $(this).parents('.widget'), inner = widget.find('.inner');
    if (!inner.is(':visible')) {
        inner.slideDown(200);
        widget.find('.toggler').html('[&ndash:]');
    } else {
        inner.slideUp(200);
        widget.find('.toggler').html('[+]');
    }
}

function initialize() {
              
    $('div.widget').each(widgethandler);    

    /*
    $('.datetime').datepicker(
        {firstDay: 1, 
         dateFormat: 'dd.mm.yy',
         showOn: "both", 
         buttonImage: "@@/images/cal.gif", 
         buttonImageOnly: true        
        });
*/     

 /*   $('.datetime').each(function(i) {
        var datetimeField = $(this);
        var datetimeFieldID = datetimeField.attr("id");
        var buttonID          = datetimeFieldID + "_button";
        Calendar.setup(
            {
                  inputField  : datetimeFieldID         // ID of the input field
                , ifFormat    : "%d.%m.%Y %H:%M"    // the date format 19.08.2008 13:00
                , button      : buttonID       // ID of the button
                , showsTime:true
                //, electric:false //   Set this to ``false'' if you want the calendar to update the field only when closed (by default it updates the field at each date change, even if the calendar is not closed) 
                                    // knutj19aug2008: Note: if we set this to false, there is no intuitive way for the user to modify just the time-field: You first have
                                    //                                  to set the new time, and then you have to click on the current date to get the calendar to close and to update
                                    //                                  the text-field.
            }
        );
        
    });*/

  //initialize partialdatepicker
  $('.extended-date-pick').after(partialDatePicker.generateWidget());
  $('.extended-date-pick')
    .datetimepicker({
      yearRange: '-10:+10',
      showOn:'button', 
      buttonImageOnly: true, 
      buttonImage: '/@@/ztm.wizard/cal.gif',
      dayNames : ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
      monthNames : ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
      dayNamesMin: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr','Lø'],
      nextText : "neste",
      prevText : "forrige",
      closeText: "lukk",
      clearText: "fjern",
      currentText: "I dag",
      dateFormat: 'yy-mm-dd',
      timeFormat: 'Thh:ii',
      //showButtonPanel : true
    });

  $('.date-pick')        
    .datetimepicker({
      yearRange: '-10:+10',
      showOn:'button', 
      buttonImageOnly: true, 
      buttonImage: '/@@/ztm.wizard/cal.gif',
      dayNames : ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
      monthNames : ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
      dayNamesMin: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø'],
      nextText : "neste",
      prevText : "forrige",
      closeText: "lukk",
      clearText: "fjern",
      currentText: "I dag",
      dateFormat: 'dd.mm.yy',
      timeFormat: 'hh:ii',
      //showButtonPanel : true
    })
    //.attr("disabled", true)
    //.bind("change", this.change) 

    $('.extended-date-pick').change(saveDatetime);
    $('.date-pick').change(saveDatetime);


}


/* Run when the DOM is loaded */
$(document).ready(initialize);


// Include the various widget-javascript files.
function IncludeJavaScript(jsFile)
{
  document.write('<script type="text/javascript" src="/@@/' + jsFile + '"></script>'); 
}

IncludeJavaScript('ztm.contenteditor/jquery.compat-1.1.js'); // required by ajaxfileupload.js -->
IncludeJavaScript('ztm.contenteditor/ajaxfileupload.js');  // 
IncludeJavaScript('ztm.contenteditor/filewidget.js'); // Must be included after contentedit.js
IncludeJavaScript('ztm.contenteditor/imagewidget.js'); // filewidget.js 
//IncludeJavaScript('jscalendar/calendar.js');
// TODO: Add i18n support for the calendar. Note that it doesn't work to simply include "calendar-no.js" instead of "calendar-en.js", since
//            calendar-no.js seems to be missing various required values.
//IncludeJavaScript('jscalendar-1.0/lang/calendar-no.js');
//IncludeJavaScript('jscalendar/lang/calendar-en.js');
//IncludeJavaScript('jscalendar/calendar-setup.js');
IncludeJavaScript('ztm.contenteditor/jquery.ui.datetimepicker.js');
IncludeJavaScript('ztm.contenteditor/partialDatePicker.js');

