/*
Calendar component, written for pagerduty.com.

Copyright (c) 2009-2010 PagerDuty, Inc.

Version 0.1
*/

var PdUtils = {
  parseIntFromElementId: function(/* String*/ elementId, /* String */ elementPrefix) {
    return parseInt(elementId.sub(elementPrefix, ""));
  },

  // Calculate the next month (take into account rolling over to next year if month is 12).
  // Returns the month & year as a simple obj, ex: {month: 4, year: 2009}
  getNextMonth: function(month, year) {
    var nextMonth = {month: month+1, year: year};
    if (nextMonth.month > 12) {
      nextMonth.month = 1;
      nextMonth.year++;
    }
    return nextMonth;
  },

  // Calculate the previous month (take into account rolling back to previous year if month is 1).
  // Returns the month & year as a simple obj, ex: {month: 4, year: 2009}
  getPreviousMonth: function(month, year) {
    var prevMonth = {month: month-1, year: year};
    if (prevMonth.month < 1) {
      prevMonth.month = 12;
      prevMonth.year--;
    }
    return prevMonth;
  }
};

var ExceptionEntry = Class.create({
  initialize: function(userName, start, end) {
    this.userName = userName;
    this.start = start;
    this.end = end;
  }
});

var OnCallCalendar = Class.create({
  initialize: function(calElement) {
    this.calendarId = $(calElement).id;
  },

  currentMonth: function() {
    return parseInt($(this.calendarId + "_month").value);
  },
  currentYear: function() {
    return parseInt($(this.calendarId + "_year").value);
  },

  calendarElement: function() {
    return $(this.calendarId);
  },

  showNextCalendarMonth: function() {
    var nextMonth = PdUtils.getNextMonth(this.currentMonth(), this.currentYear());
    this.showCalendarMonth(nextMonth.month, nextMonth.year);
  },

  showPrevCalendarMonth: function() {
    var prevMonth = PdUtils.getPreviousMonth(this.currentMonth(), this.currentYear());
    this.showCalendarMonth(prevMonth.month, prevMonth.year);
  }
});

var RotationCalendar = Class.create(OnCallCalendar, {
  initialize: function($super, calendarElement) {
    $super(calendarElement);   // initialize superclass

    //this.rotationNum = PdUtils.parseIntFromElementId(this.calendarId, "calendar_");   //NAMEDCAL
    this.rotationId = PdUtils.parseIntFromElementId(this.calendarId, "calendar_");
    this.exceptionEntries = new Hash();
  },

  showCalendarMonth: function(month, year) {
    var params = {
      month: month.toString(),
      year: year.toString(),
      //rotation_number: this.rotationNum.toString()
      rotation_id: this.rotationId
    };
    new Ajax.Request("/schedule/rotation_cal", {
      method: 'post',
      parameters: params,
      onFailure: function(transport) {alert('Failure');}    // TODO: handle failure in a better way
    });
  },

  setExceptionData: function(exceptionData) {
    var exceptionsHsh = $H(exceptionData.evalJSON());
    var exEntries = new Hash();      // needs to be done b/c this keyword won't work below
    exceptionsHsh.each(function(pair) {
      exEntries.set(pair.key, new ExceptionEntry(pair.value.user, pair.value.start, pair.value.end));
    });
    this.exceptionEntries = exEntries;
  },

  /*
  selectExceptionEntry: function(entryElement) {
    this.unselectAllExceptionEntries();

    // the element has a classname of the form "schedule_entry_#{id}"; we need the id portion
    var classNames = $w($(entryElement).className);
    var schedClass = classNames.find(function(className) {
      return className.startsWith("exception_entry_");
    });
    var exceptionId = PdUtils.parseIntFromElementId(schedClass, "exception_entry_");

    // mark all the exception entry elements with a class of schedClass as selected
    // TODO: make sure the styles are correct!
    $$("div." + schedClass).each(function(entry, index) {
      var style = {
        borderTop: "3px solid rgb(115, 130, 153)", 
        borderBottom: "3px solid rgb(115, 130, 153)", 
        marginLeft: "-3px", 
        marginTop: "-2px",        zIndex: "2002"
      };      if (entry.hasClassName("left")) {
        style.borderLeft = "3px solid rgb(115, 130, 153)";
      } else {
        style.borderLeft = "none";
      }      if (entry.hasClassName("right")) {
        style.borderRight = "3px solid rgb(115, 130, 153)";
      } else {
        style.borderRight = "none";
      }

      entry.setStyle(style);
    });

    this._createInfoBubbleForException(entryElement, exceptionId);
  },*/

  unselectAllExceptionEntries: function() {
    $$("#" + this.calendarId + " div.on_call_exception_entry").each(function(entry, index) {
      // TODO: the border color is not correct
      entry.setStyle({border: '1px solid #000', marginLeft: "-1px", marginTop: "0", zIndex: "2000"});
    });
  },

  displayNewExceptionForm: function() {
    // first, reset the form
    this.resetNewExceptionForm();
    
    //var rotationNum = this.rotationNum;
    //$j("#new_exception_rotation_number").val(rotationNum);
    $j("#new_exception").dialog('open');
  },

  hideNewExceptionForm: function() {
    $j("#new_exception").dialog('close');
  },

  resetNewExceptionForm: function() {
    // reset the form
    Form.reset("new_exception_frm");
    // empty out any errors and hide the error box
    $('errorExplanation').innerHTML = "";
    $('errorExplanation').hide();
  },

  deleteExceptionEntry: function(exceptionEntryId) {
    if (!confirm("Are you sure you want to delete this on-call exception?")) {
      return;
    }

    //SimileAjax.WindowManager.cancelPopups();

    var params = { exception_entry_id: exceptionEntryId };
    params.month = this.currentMonth();
    params.year = this.currentYear();
    new Ajax.Request("/schedule/rotations/" + this.rotationId + "/delete_exception", {
      method: 'post',
      parameters: params,
      onFailure: function(transport) {alert('Failure');}    // TODO: handle failure in a better way
    });
  }

  /*
  _createInfoBubbleForException: function(exceptionElement, exceptionId) {
    var x = $(exceptionElement).cumulativeOffset()[0] + $(exceptionElement).getWidth() / 2;
    var y = $(exceptionElement).cumulativeOffset()[1];

    var exceptionEntry = this.exceptionEntries.get(exceptionId);
    //var exData = exceptionData[exceptionId.toString()];
    //console.log("exData = " + exData);

    var table = new Element("table");
    var row1 = new Element("tr");
    row1.appendChild(new Element("th").update("Start:"));
    row1.appendChild(new Element("td").update(exceptionEntry.start));
    var row2 = new Element("tr");
    row2.appendChild(new Element("th").update("End:"));
    row2.appendChild(new Element("td").update(exceptionEntry.end));
    table.appendChild(row1);
    table.appendChild(row2);

    var rotationNum = this.rotationNum;
    var delDiv = new Element('div', {style: "position: absolute; right: 5px;"});
    var delLnk = new Element('a', {href: "#", onclick: "calendar_"+rotationNum+".deleteExceptionEntry("+exceptionId+");"}).update("delete");
    //delLnk.appendChild(new Element('img', {src: "/images/trash.png"}));
    delDiv.appendChild(delLnk);

    var exceptionDiv = new Element('div', {'class': "exception_bubble"});
    exceptionDiv.appendChild(delDiv);
    exceptionDiv.appendChild(new Element('div', {style: "font-size: 13px;"}).update(exceptionEntry.userName));
    exceptionDiv.appendChild(table);

    //SimileAjax.WindowManager.cancelPopups();

    //SimileAjax.Graphics.createBubbleForContentAndPoint(exceptionDiv, x, y, 200, null, 300)
  }*/
});

var PreviewCalendar = Class.create(OnCallCalendar, {
  // mode can be either "new" or "edit"
  initialize: function($super, calendarElement, mode) {
    $super(calendarElement);   // initialize superclass

    this.mode = mode;
    this.calendarPreviewUrl = "/schedule/" + mode + "_cal_preview";

    this._makeUserTilesSortable();
    this._enableOrDisableAddUser(true);

    this._buttonEnabledText = (mode == "new" ? "Create Schedule" : "Save Changes");
    this._buttonDisabledText = (mode == "new" ? "Creating..." : "Saving...");
    this._enableSubmitButton();
  },

  // If month or year are null, they are defaulted to currentMonth() and currentYear() respectively.
  showCalendarMonth: function(month, year) {
    var params = this._constructRotationParams();
    var requestUrl = this.calendarPreviewUrl;
    if (month != null) { params.month = month; }
    if (year != null) { params.year = year; }

    new Ajax.Request(requestUrl, {
      method: 'post',
      parameters: params,
      onFailure: function(transport) {alert('Failure');}    // TODO: handle failure in a better way
    });
  },

  updateCurrentMonth: function() {
    this.showCalendarMonth(null, null);
  },

  removeAllOnCallEntries: function() {
    $$("#" + this.calendarId + " div.on_call_entry").each(function(entryDiv, index) {
      entryDiv.remove();
    });
  },

  // add a new user to the preview rotation
  addUser: function() {
    var userId = $('user').value;

    // remove the selected user from the users dropdown (store it in the users_in_rotation hidden dropdown)
    this._removeUserOptionFromUserDropdown(userId);

    // if this is the first user being added to the rotation, hide the empty_rotation div, and
    // show the rotation_users div
    if ($('empty_rotation').visible()) {
      $('empty_rotation').hide();
      $('rotation_users').show();
    }

    // add the user tile to the visible rotation_users div
    this._addUserTileToRotation(userId);

    //rotationOrderChanged();
    //_updateUserHiddenInputWithCurrentColor(userId);   // update hidden input with user color

    //updateCalendarMonth();  // update the calendar
    this.updateCurrentMonth();
  },

  removeUser: function(userId) {
    // enable hidden field for this user (so it gets picked up when form is submitted)
    //$('add_user_' + userId).disable();

    // add the user back into the users dropdown (from the users_in_rotation hidden dropdown)
    this._addUserOptionToUserDropdown(userId);

    // if the rotation is empty, display the empty_rotation div and hide the rotation_users div
    if ($('users_in_rotation').childElements().size() == 0) {
      $('rotation_users').hide();
      $('empty_rotation').show();
    }

    // remove the user tile from the visible rotation_users div
    this._removeUserTileFromRotation(userId);

    //rotationOrderChanged();

    //updateCalendarMonth();  // update the calendar
    this.updateCurrentMonth();
  },

  showHideColorPickerForUser: function(userId, show) {
    var colorPicker = $("color_picker_" + userId);
    if (show) {
      if (!colorPicker.visible()) {
        colorPicker.show();
        /* Effect.SlideDown('color_picker_' + userId, {duration: 0.3}); */
      }
    } else {
      colorPicker.hide();
      /* Effect.SlideUp('color_picker_' + userId, {duration: 1}); */
    }
  },

  changeUserColor: function(userId, newColorIndex) {
    var colorPickerId = "color_picker_" + userId;

    // unselect the old color from the picker
    var oldSelected = $(colorPickerId).select("div.selected").first();
    oldSelected.removeClassName("selected");
    oldSelected.setStyle({borderColor: '#CCC'});
    // select the newly selected color from the picker
    var newSelected = $(colorPickerId).select("div.color_choice")[newColorIndex];
    newSelected.addClassName("selected");

    // change the color of the user
    $(colorPickerId).up().select("div.user_color").first().setStyle({backgroundColor: newSelected.getStyle("backgroundColor")});

    // hide the color picker
    this.showHideColorPickerForUser(userId, false);

    // update the hidden input element (for the user) with the new color
    //_updateUserHiddenInputWithCurrentColor(userId);

    //updateCalendarMonth();  // update the calendar
    this.updateCurrentMonth();
  },

  showHideUserTileModLinks: function(userTile, show) {
    if (show) {       
      userTile.select("div.user_modify_links").first().show();
    } else {
      userTile.select("div.user_modify_links").first().hide();
    }
  },

  createRotation: function() {
    var params = this._constructRotationParams();
    var selfObj = this;

    this._createRotationInProgress();

    $j.ajax({
      type: 'POST',
      url: "/schedule/rotations/create",
      data: params,
      dataType: 'script',
      timeout: 30000,   // 30 sec
      error: function(xhr, error, exception) {
        alert('Schedule creation failed.');
        selfObj.createRotationFailed();
      }
    });
  },

  // The rotation creation process Ajax call takes a long time. During that time, we disable the submit
  // button and display a Loading graphic on the page.
  _createRotationInProgress: function() {
    this._disableSubmitButton();
    this._showLoadingMask();
  },
  // The create failed, so re-enable the submit button and hide the Loading graphic.
  createRotationFailed: function() {
    this._enableSubmitButton();
    this._hideLoadingMask();
  },

  updateRotation: function() {
    var rotationId = $('rotation_id').value;
    var params = this._constructRotationParams();
    var selfObj = this;

    this._updateRotationInProgress();

    $j.ajax({
      type: 'POST',
      url: "/schedule/rotations/" + rotationId + "/update",
      data: params,
      dataType: 'script',
      timeout: 30000,   // 30 sec
      error: function(xhr, error, exception) {
        alert('Schedule update failed.');
        selfObj.updateRotationFailed();
      }
    });
  },

  // The rotation update process Ajax call takes a long time. During that time, we disable the submit
  // button and display a Loading graphic on the page.
  _updateRotationInProgress: function() {
    this._disableSubmitButton();
    this._showLoadingMask();
  },
  // The update failed, so re-enable the submit button and hide the Loading graphic.
  updateRotationFailed: function() {
    this._enableSubmitButton();
    this._hideLoadingMask();
  },

  //// BEGIN methods for selecting Schedule Starting Point

  rotationStartsNowOrLaterChanged: function() {
    if ($('schedule_delay_no').checked) {
      $('starts_on_params').hide();
      this.updateCurrentMonth();
    } else if ($('schedule_delay_yes').checked) {
      $('starts_on_params').show();
      this.rotationStartsLaterParamsChanged();
    }
  },

  rotationStartsLaterParamsChanged: function() {
    this.updateCurrentMonth();
  },

  //// END methods for selecting Schedule Starting Point


  //// BEGIN methods for controlling Selecting the Rotation Type
  // TODO - possibly refactor these into its own class

  rotationTypeChanged: function() {
    var rotationType = $('rotation_type').value;

    if (rotationType == "daily") {
      $('rlen').innerHTML = "one day";
      $('rotation_len_msg').show();

      $('daily_rotation').show();
      $('weekly_rotation').hide();

      this.dailyRotationParamsChanged();
    } else if (rotationType == "weekly") {
      $('rlen').innerHTML = "one week";
      $('rotation_len_msg').show();

      $('daily_rotation').hide();
      $('weekly_rotation').show();

      this.weeklyRotationParamsChanged();
    } else if (rotationType == "custom") {
      // TODO
    } else if (rotationType == "") {
      $('rotation_len_msg').hide();

      $('daily_rotation').hide();
      $('weekly_rotation').hide();
    }
  },

  DAY_NAMES: [
    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
  ],

  dailyRotationParamsChanged: function() {
    var hour = parseInt($('daily_handoff_hour').value);
    var minute = parseInt($('daily_handoff_minute').value);
    $('dh_time').innerHTML = "" + hour + ":" + minute.toPaddedString(2);

    this.updateCurrentMonth();
  },

  weeklyRotationParamsChanged: function() {
    // get the weekly rotation params -- day of week, hour, minute
    var dayOfWeek = this._getWeeklyRotationHandoffDayOfWeek();
    var hour = parseInt($('weekly_handoff_hour').value);
    var minute = parseInt($('weekly_handoff_minute').value);

    if (dayOfWeek > 0) {
      $('wh_day').innerHTML = this.DAY_NAMES[dayOfWeek-1] + "s";
      $('wh_time').innerHTML = "" + hour + ":" + minute.toPaddedString(2);
      $('weekly_handoff_msg').show();
    } else {
      $('weekly_handoff_msg').hide();
    }

    this.updateCurrentMonth();
  },

  _getWeeklyRotationHandoffDayOfWeek: function() {
    var dayOfWeek = 0;    // 1-based: Sun => 1, Mon => 2, etc. 0 => none are selected
    var radioBtns = $$('input[name="weekday"]');
    for (var i = 0; i < radioBtns.length; i++) {
      if (radioBtns[i].checked) {
        dayOfWeek = i + 1;
        break;
      }
    }
    return dayOfWeek;
  },

  //// END methods for controlling Selecting the Rotation Type

  _disableSubmitButton: function() {
    $j("input.rotation_btn").attr("disabled", true).val(this._buttonDisabledText);
  },
  _enableSubmitButton: function() {
    $j("input.rotation_btn").removeAttr("disabled").val(this._buttonEnabledText);
  },

  // Show a Loading... graphic in the middle of the page on an overlay. Useful during time-intensive
  // Ajax calls.
  _showLoadingMask: function() {
    $j("#schedule_section_content").mask("Loading...");
  },
  _hideLoadingMask: function() {
    $j("#schedule_section_content").unmask();
  },

  // TODO - REFACTOR ALL THE STUFF IN THIS SECTION
  _addUserTileToRotation: function(userId) {
    var userTile = $('all_users').select("div#user_" + userId).first().remove();
    $('rotation_users').insert(userTile, {position: "bottom"});
    this._makeUserTilesSortable();
  },
  _removeUserTileFromRotation: function(userId) {
    var userTile = $('rotation_users').select("div#user_" + userId).first().remove();
    $('all_users').insert(userTile, {position: "bottom"});
    this._makeUserTilesSortable();    // TODO: may not need to do this if div is empty
  },
  _addUserOptionToUserDropdown: function(userId) {
    var insertOption = $('users_in_rotation').select('option[value="' + userId + '"]').first().remove();
    var insertName = insertOption.innerHTML;
    var userOptions = $('user').select('option');
    
    for (var i = 0; i < userOptions.length; i++) {
      if (userOptions[i].innerHTML > insertName) {
        userOptions[i].insert({before: insertOption});
        this._enableOrDisableAddUser(true);
        return;
      }
    }
    
    $('user').insert({bottom: insertOption});
    this._enableOrDisableAddUser(true);
  },
  _removeUserOptionFromUserDropdown: function(userId) {
    var userOption = $('user').select('option[value="' + userId + '"]').first().remove();
    $('users_in_rotation').insert(userOption, {position: "bottom"});
    if ($('user').childElements().size() == 0) {
      this._enableOrDisableAddUser(false);
    }
  },
  _enableOrDisableAddUser: function(enabled) {
    $('user').disabled = !enabled;
    $('add_user_btn').disabled = !enabled;
  },
  _makeUserTilesSortable: function() {
    var boundUpdateCurrentMonth = this.updateCurrentMonth.bind(this);
    Sortable.create(
      'rotation_users', 
      {
        tag: 'div',
        onUpdate: function() {      // when the rotation order changes, update the calendar
          boundUpdateCurrentMonth();
        }
      }
    );
  },
  // TODO - END REFACTOR SECTION

  _constructRotationParams: function() {
    // construct a params object with all the rotation information
    var params = {
      rotation_id: $('rotation_id').value,
      //rotation_number: $('rotation_number').value,  // NAMEDCAL
      rotation_name: $('rotation_name').value,
      rotation_type: $('rotation_type').value,
      now: $('now').value
    };
    params.rotation_length = this._getRotationLength();
    params.month = this.currentMonth();
    params.year = this.currentYear();

    // add on parameters for the on-call handoff point
    if (params.rotation_type == "daily") {
      params["daily_handoff[hour]"] = $('daily_handoff_hour').value;
      params["daily_handoff[minute]"] = $('daily_handoff_minute').value;
    } else if (params.rotation_type == "weekly") {
      params["weekly_handoff[hour]"] = $('weekly_handoff_hour').value;
      params["weekly_handoff[minute]"] = $('weekly_handoff_minute').value;

      var dayOfWeek = this._getWeeklyRotationHandoffDayOfWeek();
      if (dayOfWeek != 0) {         // ensure a day-of-week is selected
        params["weekly_handoff[day_of_week]"] = dayOfWeek;
      }
    } else if (params.rotation_type == "custom") {
      // TODO
    }

    // add on parameters for the schedule starting point (if provided)
    if ($('schedule_delay_yes').checked) {
      params["starts_on[date]"] = $('starts_on_date').value;
      params["starts_on[hour]"] = $('starts_on_hour').value;
      params["starts_on[minute]"] = $('starts_on_minute').value;
    }

    $('rotation_users').select("div.user_tile").each(function(userTile, index) {
      var userId = PdUtils.parseIntFromElementId(userTile.id, "user_");
      var userColor = userTile.select("div.user_color").first().getStyle("backgroundColor");
      var rCol, gCol, bCol;

      // HACK for Opera
      if (userColor.startsWith("#") && userColor.length == 7) {
        rCol = parseInt( userColor.substr(1, 2), 16 ).toString();  // the parseInt converts hex to decimal
        gCol = parseInt( userColor.substr(3, 2), 16 ).toString();
        bCol = parseInt( userColor.substr(5, 2), 16 ).toString();
        userColor = "rgb(" + rCol + "," + gCol + "," + bCol + ")";
      }

      params["user[" + userId + "][order]"] = index;
      params["user[" + userId + "][color]"] = userColor;
    });
    return params;
  },

  _getRotationLength: function() {
    var rotationType = $('rotation_type').value;
    var rotationLen = 0;    // measured in seconds
    if (rotationType == "daily") {
      rotationLen = 24 * 60 * 60;
    } else if (rotationType == "weekly") {
      rotationLen = 7 * 24 * 60 * 60;
    } else if (rotationType == "custom") {
      // TODO
    }
    return rotationLen;
  }
});

// Class method to set the day-of-week for a rotation.
// To unset, pass in a null dayOfWeek.
PreviewCalendar.setWeeklyRotationHandoffDayOfWeek = function(dayOfWeek) {
  var radioBtns = $$('input[name="weekday"]');
  for (var i = 0; i < radioBtns.length; i++) {
    radioBtns[i].checked = (i + 1 == dayOfWeek);
  }
};

