require(['jquery', 'fullcalendar'], function(jQuery, fullCalendar) {

// Make sure the XWiki 'namespace' and the ModalPopup class exist.
if (typeof(XWiki) == "undefined" || typeof(XWiki.widgets) == "undefined" || typeof(XWiki.widgets.ModalPopup) == "undefined") {
  if (typeof console != "undefined" && typeof console.warn == "function") {
    console.warn("[MessageBox widget] Required class missing: XWiki.widgets.ModalPopup");
  }
} else {
  if (typeof(XWiki.MoccaCalendar) == "undefined") {
    XWiki.MoccaCalendar = {};
  }

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                        

  XWiki.MoccaCalendar.Helper = Class.create({
  initialize: function(calendar, dateFormat, jsonServiceUrl, createEventBaseUrl, updateEventUrl, editEventUrlTemplate, formToken) {
    this.calendar = calendar;
    this.dateFormat = dateFormat;
    this.jsonServiceUrl = jsonServiceUrl;
    this.createEventBaseUrl = createEventBaseUrl;
    this.updateEventUrl = updateEventUrl;
    this.editEventUrlTemplate = editEventUrlTemplate;
    this.formToken = formToken;
    // hack: extract the "day only" and "time only" format, if possible
    // assumes time comes after the day
    var tSep = dateFormat.search(/[hk]/i)
    if (tSep != -1) {
      this.timeFormat = dateFormat.substring(tSep, dateFormat.length);
      this.dayFormat = dateFormat.substring(0, tSep-1);
    } else { // ??
      this.timeFormat = '';
      this.dayFormat = dateFormat;
    }
  },
  displayError: function(errorMessage) {
    if (!errorMessage) {
      errorMessage = "Internal Server Error";
    }
    new XWiki.widgets.Notification("Failed: " + errorMessage, "error");
  },
  showCreateEventFrom: function(start, end) {
    var allDay = !start.hasTime();
    var singleDay = (allDay && end.diff(start,'days') <= 1);

    var mySimpleDateFormatter = new Externals.SimpleDateFormat(this.dateFormat);
    start = mySimpleDateFormatter.format(start.local().toDate());
    end = (singleDay)?'':mySimpleDateFormatter.format(end.local().toDate());
    var paramPrefix = "MoccaCalendar.MoccaCalendarEventClass_0_";
    var params = "&" + paramPrefix + "startDate=" + encodeURIComponent(start);
    params += "&" + paramPrefix + "endDate=" + encodeURIComponent(end);
    params += "&" + paramPrefix + "allDay=" + allDay;
    return this.showCreateEvent(params);
  },
  showCreateEvent: function(extraParams) {
    var params = extraParams || '';
    return new XWiki.MoccaCalendar.MoccaCalendarPopup({pageURL: this.createEventBaseUrl + params}, this);
  },
  updateEvent: function(page, isResize, delta, startDate, endDate, revertFunc) {
    var savingBox = new XWiki.widgets.Notification("Sending request...", "inprogress", {inactive: true});
    var savedBox = new XWiki.widgets.Notification("Done!", "done", {inactive: true});
    var calendar = this.calendar;

    savingBox.show();
    savedBox.hide();

    // let's call the calendar update
    var params = "page=" +  encodeURIComponent(page) + "&isResize=" + (isResize ? "1" : "0") + "&delta=" + delta.asMilliseconds() + "&startDate=" + startDate.format()
    if (endDate) { params += "&endDate=" + endDate.format(); }
    params += "&allDay=" + (startDate.hasTime()?"":"1");
    var url = this.updateEventUrl + params;
    jQuery.ajax(url).fail(function (data) {
      savingBox.hide();
      this.displayError();
      if (revertFunc) revertFunc();
    }.bind(this)).success(function (data) {
      if (data.indexOf("OK ")==0) {
        savingBox.hide();
        savedBox.show();
        calendar.fullCalendar('refetchEvents');
      } else {
        savingBox.hide();
        this.displayError();
        if (revertFunc) revertFunc();
      }
    }.bind(this));
  }
  });

  // 
  // "static" helpers
  //
  XWiki.MoccaCalendar.Helper.getCalendarView = function(defaultVal) {
    var cookieName = 'MoccaCalendar_view=';
    if (document.cookie) {
      var ca = document.cookie.split(';');
      for(var i=0;i<ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cookieName) == 0) return c.substring(cookieName.length,c.length);
      }
    }
    return defaultVal;
  };

  XWiki.MoccaCalendar.Helper.setCalendarView = function(value) {
    var cookieName = 'MoccaCalendar_view=';
    document.cookie = cookieName + value + "; path=/";
  };

  //
  // the dialog to show / edit events
  //
  XWiki.MoccaCalendar.MoccaCalendarPopup = Class.create(XWiki.widgets.ModalPopup, {
    defaultInteractionParameters: {
      editMode : true
    },
    initialize: function($super, interactionParameters, helper) {
      this.interactionParameters = Object.extend(Object.clone(this.defaultInteractionParameters), interactionParameters || {});
      this.interactionParameters.isNew = (undefined == this.interactionParameters.event)
      this.helper = helper;
      this.saving = false;
      // call constructor from ModalPopup with params content, shortcuts, options
      $super(
        // this element will end up as this.content
        new Element('form', {'class' : 'xform'}),
        {
          "show": {method: this.showDialog, keys: []},
          "close": {method: this.closeDialog, keys: ['Esc']}
        },
        {
          verticalPosition: "top",
          backgroundColor: "#FFFFFF",
          title : this.interactionParameters.editMode ? "Create new event" : "Event"
        }
      );
      this.loadEventContent();
      this.showDialog();
      this.setClass("moccacal-modal-popup");

      $$(".xdialog-modal-container").each(function(div) { div.setStyle({position: 'absolute'}) }); // MOCCACAL-32

      this.savingBox = new XWiki.widgets.Notification("Sending request...", "inprogress", {inactive: true});
      this.savedBox = new XWiki.widgets.Notification("Done!", "done", {inactive: true});
      this.savingBox.hide();
      this.savedBox.hide();
    },

    loadEditForm: function(event) {
      event.stop();
      this.interactionParameters.editMode = true;
      this.loadEventContent();
    },

    submitForm: function(event) {
      event.stop();
      var titleField = this.content.down('[name="title"]');

      var title = $(titleField).getValue();
      if (title.length == 0) {
        $$(".val_title_notempty").each(Element.show);
        return false;
      }
      else {
        $$(".val_title_notempty").each(Element.hide);
      }
      this.saving = true; // signal other events we are about to save

      this.savedBox.hide();
      this.savingBox.show();

      if (this.interactionParameters.isNew) {
       // calculate "automatic" document name:
       var name = title.replace(/[\/?]/g,'').replace(/^ +/,'').replace(/ +$/,'');

       // ask the JSONService to create an unused event name for us      
       new Ajax.Request( this.helper.jsonServiceUrl, {
        method: 'get',
        parameters: { 'outputSyntax': 'plain', 'newEvent' : name, 'parent': $F($("calendarParent")) },
        onSuccess: function(response) {
          try {
            var error = (response.responseJSON) ? response.responseJSON['error'] : "Internal Server Error";
            if (error) {
              this.savingBox.hide();
              this.helper.displayError(error);
            } else {
              var eventName = response.responseJSON['name'];
              this.saveEvent(eventName);
            }
          } catch(e) { alert(e); }
        }.bind(this),
        onFailure: function(response) {
          // this should not happen ...
          try {
            this.saving = false;
            this.savingBox.hide();
            this.helper.displayError();
          } catch(e) { alert(e); }
        }.bind(this)
       });
      } else {
        this.saveEvent();
      }
      
      return false;
    },

    saveEvent: function(newEventName) {
      var saveUrl;
      if (this.interactionParameters.isNew) {
        saveUrl = this.helper.editEventUrlTemplate;
        saveUrl = saveUrl.replace("__page__", encodeURIComponent(newEventName));
      } else {
        saveUrl = this.interactionParameters.event.saveUrl;
      }
      this.content.writeAttribute('action', saveUrl + '&xpage=plain&ajax=true');
      this.content.request({
        onSuccess: function() {
          this.saving = false;
          this.savingBox.hide();
          this.savedBox.show();
          this.closeDialog();
          this.helper.calendar.fullCalendar('refetchEvents');
        }.bind(this),
        onFailure: function(e) {
          try {
            this.saving = false;
            var responseText = e.responseText;
            $("forminnercontent").innerHTML = responseText;
            this.savingBox.hide();
            if (e.status > 400) { this.helper.displayError(); } 
            $$('input.datetime').each(function(dateTimeInput) {
              new XWiki.DateTimePicker(dateTimeInput, dateTimeInput.title);
            });
          } catch (e) {
            alert('Failed to save event!');
          }
        }.bind(this)
      })
    },

    showDeleteForm: function(event) {
      event.stop();
      var that = Object.clone(this);
      this.closeDialog();
      var eventName = that.interactionParameters.event.title.escapeHTML();
      new XWiki.widgets.ConfirmationBox({ 
          onYes: function() { that.deleteEvent() }, 
          onNo: function() { new XWiki.MoccaCalendar.MoccaCalendarPopup(that.interactionParameters, that.helper) }
        },
        { confirmationText: "Do you really want to delete the event &#171;@@REPLACE@@&#187; ?".sub("@@REPLACE@@", eventName),
             yesButtonText: "Yes",
              noButtonText: "No"
        });
    },
    deleteEvent: function() {
      var pending = new XWiki.widgets.Notification("Sending request...", "inprogress");
      new Ajax.Request(this.interactionParameters.event.deleteUrl, {
        method: 'post',
        parameters: {'confirm': 1, 'ajax': true, 'form_token': this.helper.formToken},
        onSuccess: function(transport) {
          if (transport.status == 0) {
            return;
          }
          pending.hide();
          new XWiki.widgets.Notification("Done!", "done");
          this.helper.calendar.fullCalendar('refetchEvents');
        }.bind(this),
        onFailure: function(e) {
          pending.hide();
          this.helper.displayError();
        }.bind(this)
      });
    },

    loadEventContent: function() {
      var url;
      if (this.interactionParameters.isNew) {
        url = this.interactionParameters.pageURL;
      } else {
        url = (this.interactionParameters.editMode)?this.interactionParameters.event.editUrl:this.interactionParameters.event.url;
      }
      new Ajax.Request(url, {
        method: 'get',
        parameters: {'xpage': 'plain', 'xhidden': 1},
        onSuccess: function(transport) {
          if (transport.status == 0) {
            return;
          }
          var response = transport.responseText || "no response text";
          var old = $('formcontent');
          if (old) {
            old.purge();
            old.remove();
          }
          this.content.insert('<div id="formcontent"><div id="forminnercontent">' + response + '</div></div>');
          var formcontent = $('formcontent');
          var buttons = new Element('div', {'class':'buttons'});
          var oldSkin = ! $('body').hasClassName('skin-flamingo'); // FIXME: bad b/w compat hack
          formcontent.insert(buttons);
          if (this.interactionParameters.editMode) {
            var that = this;
            buttons.insert(this.createButton("submit","Save","","save-moccacalendar-event","btn"));
            buttons.down('#save-moccacalendar-event').observe('click', this.submitForm.bind(this));
            buttons.insert('<span class="buttonwrapper"><a href="#" id="cancel-moccacalendar-event" class="secondary button">' + "Cancel" + '</a></span>');
            buttons.down('#cancel-moccacalendar-event').observe('click', this.closeDialog.bind(this));
            $$('input.datetime').each(function(dateTimeInput) {
              dateTimeInput.observe('change', function(event) {
                 that.handleDatetimeFieldChange(event, $(this));
              });
            });

            // this is the "all day" box
            this.allDay = $('MoccaCalendar.MoccaCalendarEventClass_0_allDay');
            this.allDay.observe('change', function(event) {
              that.handleAllDayFieldChange(event, $(this));
            });
            // use handler to init date picker, too
            that.handleAllDayFieldChange(null, this.allDay);

            $("MoccaCalendarEvent.MoccaCalendarEventClass_0_title").focus();
          } else {
            var leftGroup = new Element('div', {'class':'btn-group btn-group-left'});
            buttons.insert(leftGroup);
            if (this.interactionParameters.event.canEdit) {
              if (oldSkin) {
                leftGroup.insert(this.createButton("button","Edit","","edit-moccacalendar-event","btn"));
              } else {
                leftGroup.insert('<button class="button btn btn-default" id="edit-moccacalendar-event"><span class="glyphicon glyphicon-pencil"></span> '+"Edit"+'</button>');
              }
              leftGroup.down("#edit-moccacalendar-event").observe('click', this.loadEditForm.bind(this));
            }
            // FIXME: this should be a plain link, but then it has no styles
            if (oldSkin) {
              leftGroup.insert(this.createButton("button","View","","view-moccacalendar-event","btn"));
              leftGroup.insert('<span class="buttonwrapper"><a href="#" id="cancel-moccacalendar-event" class="secondary button">' + "Cancel" + '</a></span>');
            } else {
              leftGroup.insert(' <button class="button btn btn-default" id="view-moccacalendar-event"><span class="glyphicon glyphicon-file"></span> '+"View"+'</button>');
              leftGroup.insert('<button class="button btn btn-default" id="cancel-moccacalendar-event"><span class="glyphicon glyphicon-remove"></span> '+"Cancel"+'</button>');
            }
            leftGroup.down("#view-moccacalendar-event").observe('click', function(e) { e.stop(); window.location.href = this.interactionParameters.event.url; }.bind(this));
            leftGroup.down('#cancel-moccacalendar-event').observe('click', this.closeDialog.bind(this));
            if (this.interactionParameters.event.canDelete) {
              var rightGroup = new Element('div', {'class':'btn-group btn-group-right'});
              buttons.insert(rightGroup);
              if (oldSkin) {
                rightGroup.insert(this.createButton("button","Delete","","delete-moccacalendar-event","btn"));
              } else {
                rightGroup.insert(' <button class="button remove-button btn btn-danger" id="delete-moccacalendar-event"> <span class="glyphicon glyphicon-remove"> </span> '+"Delete"+'</button>');
              }
              rightGroup.down("#delete-moccacalendar-event").observe('click', this.showDeleteForm.bind(this));
            }
            formcontent.insert(new Element('div', {'class': 'clearfloats'}));
          }
        }.bind(this),
        onFailure: function() {
          this.content.insert('Something went wrong...');
        }.bind(this)
      });
    },

    handleDatetimeFieldChange : function(event, element) {
      if (this.saving) { return; }
      var dateStr = element.getValue()
      if (dateStr.match(/^\s*$/)) {
        return;
      }

      // now we would have to find out if the user has typed in something like a date with a time
      // for this we have to parse the date. as we do not have any generic way to do that we send it to the backend ...
      this.checkIfDateWithTime(function(result) {
         var allDay = result.responseJSON.isAllDay;
         if (allDay === '') { return; } // indeterminate
         if (allDay != this.allDay.getValue()) {
           this.allDay.setValue(allDay)
           this.handleAllDayFieldChange(null, this.allDay)
         }
      }.bind(this))
    },
    checkIfDateWithTime : function(callback) {
      new Ajax.Request( this.helper.jsonServiceUrl, {
        method: 'get',
        parameters: {action: 'checkDate', outputSyntax: 'plain', 'startDate' : $F('MoccaCalendar.MoccaCalendarEventClass_0_startDate'), 'endDate' : $F('MoccaCalendar.MoccaCalendarEventClass_0_endDate')},
        onSuccess: callback,
        onFailure: function(response) {
          // this should not happen ...
          try {
            this.helper.displayError();
          } catch(e) { alert(e); }
        }.bind(this)
      })
    },

    handleAllDayFieldChange : function(event, element) {
      if (this.saving) { return; }
      var that = this;
      $$('input.datetime').each(function(d) {
        // clear old observer
        var dateField = $(d);
        dateField.stopObserving('focus');
        dateField.stopObserving('click');

        // update title date format and add a new date picker
        //FIXME: it would be great if we could remove / restore the time values of the date
        // but for this we would really need to be able to parse the date on client side
        var newFormat = element.getValue() ? that.helper.dayFormat : that.helper.dateFormat;
        dateField.writeAttribute('title', newFormat);
        new XWiki.DateTimePicker(dateField, newFormat);
      })
    }
  });


  //
  // the "agenda" view
  //
  // what we do here is mock up a custom fullCalender view by implementing an undocumented interface
  // and we even do this by "duck-typing" as the base "View" class in private to full calendar 
  var PlainList = function(calendar) {
    this.name = 'plainList';
    this.calendar = calendar;
    // Note: this.el is set by the calling side
  }

  PlainList.prototype = {

    incrementDate: function(date, delta) {
      // we intentionally ignore the date given
      // instead we have only two time windows: "past" (delta == -1) and "future" (anything else)
      date = this.calendar.getNow().clone().stripTime();
      if (delta < 0) {
        date = date.subtract(1, 'd')
      }
      return date;
    },

    destroy: function() {
      this.el.empty();
      this.container = null;
    },
  
    render: function(date) {
      // we sometimes get switched into some date of the other views
      // before being called "incrementDate"
      // normalize it to "past" or "future" first:
      this.start = this.calendar.getNow().clone().stripTime();
      var isPast = date.isBefore(this.start)
      if (isPast) {
        this.start = this.start.subtract(10, 'y');
      }
      this.intervalStart = this.start;
      this.end = this.intervalEnd = this.start.clone().add(10, 'y');
     
      if (isPast) {
        this.title = "Past Events";
        this.noEventMsg = "No past events found.";
      } else {
        this.title = "Upcoming Events";
        this.noEventMsg = "No upcoming events found.";
      }
      this.el.append('<div class="fc-timeline-box" style="width:100%"/>');
      this.container = jQuery('.fc-timeline-box',this.el);
      this.container.append('<img src="../../../resources/icons/xwiki/ajax-loader-large.gif" alt="Loading..." />');
      this.trigger('viewRender', this, this, this.el);
    },
  
    destroyEvents: function() {
      if (this.container) { this.container.empty(); }
    },
  
    renderEvents: function(events) {
      if (events.length==0) {
  	    this.container.append('<p></p>');
  	    jQuery('p', this.container).addClass('noevents').text(this.noEventMsg);
  	} else for (var i=0;i<events.length;i++) {
        var ev = events[i];
        this.container.append(ev.html || ev.title)
      }
    },

    // lame: copy from the "View" base class
    trigger: function (name, thisObj) {
      return this.calendar.trigger.apply(
        this.calendar,
        [name, thisObj || this].concat(Array.prototype.slice.call(arguments, 2), [this])
      );
    },

    // resize and drag & drop protocol - no-ops
    updateSize: function(b){
      // console.log('updateSize');
    },
    select: function(start, end) {
      // console.log('select');
    },
    unselect: function() {
      // console.log('unselect');
    },
  
  }; // end PlainList.prototype
  
  jQuery.fullCalendar.views.plainList = PlainList;
  
} // if the XWiki.widgets.ModalPopup  is defined
  
});
