/**
 * @provides javelin-behavior-fancy-datepicker
 * @requires javelin-behavior
 *           javelin-util
 *           javelin-dom
 *           javelin-stratcom
 *           javelin-vector
 */

JX.behavior('fancy-datepicker', function(config, statics) {
  if (statics.initialized) {
    return;
  }
  statics.initialized = true;

  var picker;
  var anchor_node;
  var root;

  var value_y;
  var value_m;
  var value_d;

  var get_format_separator = function() {
    var format = get_format();
    switch (format.toLowerCase()) {
      case 'n/j/y':
        return '/';
      default:
        return '-';
    }
  };

  var get_key_maps = function() {
    var format = get_format();
    var regex = new RegExp('[./ -]');
    return format.split(regex);
  };

  var get_format = function() {
    var format = config.format;

    if (format === null) {
      format = 'Y-m-d';
    }

    return format;
  };

  var get_week_start = function() {
    var week_start = config.weekStart;

    if (week_start === null) {
      week_start = 0;
    }

    return week_start;
  };

  var onopen = function(e) {
    e.kill();

    // If you click the calendar icon while the date picker is open, close it
    // without writing the change.

    if (picker) {
      if (root == e.getNode('phabricator-date-control')) {
        // If the user clicked the same control, just close it.
        onclose(e);
        return;
      } else {
        // If the user clicked a different control, close the old one but then
        // open the new one.
        onclose(e);
      }
    }


    root = e.getNode('phabricator-date-control');

    picker = JX.$N(
      'div',
      {
        className: 'fancy-datepicker',
        sigil: 'phabricator-datepicker'
      },
      JX.$N(
        'div',
        {
          className: 'fancy-datepicker-core',
          sigil: 'fancy-datepicker-core'
        }));
    document.body.appendChild(picker);

    anchor_node = e.getNode('calendar-button');
    JX.DOM.alterClass(root, 'picker-open', true);

    JX.Mask.show('jx-date-mask');

    read_date();
    render();
  };

  var onclose = function(e) {
    if (!picker) {
      return;
    }

    JX.Mask.hide('jx-date-mask');

    JX.DOM.remove(picker);
    picker = null;
    JX.DOM.alterClass(root, 'picker-open', false);
    if (e) {
      e.kill();
    }

    root = null;
  };

  var ontoggle = function(e) {
    var box = e.getTarget();
    root = e.getNode('phabricator-date-control');
    JX.Stratcom.getData(root).disabled = !box.checked;
    redraw_inputs();
  };

  var get_inputs = function() {
    return {
      d: JX.DOM.find(root, 'input', 'date-input'),
      t: JX.DOM.find(root, 'input', 'time-input')
    };
  };

  var read_date = function(){
    var inputs = get_inputs();
    var date = inputs.d.value;
    var regex = new RegExp('[./ -]');
    var date_parts = date.split(regex);
    var map = get_key_maps();

    for (var i=0; i < date_parts.length; i++) {
      var key = map[i].toLowerCase();

      switch (key) {
        case 'y':
          value_y = date_parts[i];
          break;
        case 'm':
          value_m = date_parts[i];
          break;
        case 'd':
          value_d = date_parts[i];
          break;
      }
    }
  };

  var write_date = function() {
    var inputs = get_inputs();
    var map = get_key_maps();
    var arr_values = [];

    for(var i=0; i < map.length; i++) {
      switch (map[i].toLowerCase()) {
        case 'y':
          arr_values[i] = value_y;
          break;
        case 'm':
          arr_values[i] = value_m;
          break;
        case 'n':
          arr_values[i] = value_m;
          break;
        case 'd':
          arr_values[i] = value_d;
          break;
        case 'j':
          arr_values[i] = value_d;
          break;
      }
    }

    var text_value = '';
    var separator = get_format_separator();

    for(var j=0; j < arr_values.length; j++) {
      var element = arr_values[j];
      var format = get_format();

      if ((format.toLowerCase() === 'd-m-y' ||
        format.toLowerCase() === 'y-m-d') &&
        element < 10) {
        element = '0' + element;
      }

      if (text_value.length === 0) {
        text_value += element;
      } else {
        text_value = text_value + separator + element;
      }
    }

    inputs.d.value = text_value;
  };

  var render = function() {
    if (!picker) {
      return;
    }

    var button = anchor_node;
    var p = JX.$V(button);
    var d = JX.Vector.getDim(picker);
    var b = JX.Vector.getDim(button);

    if (JX.Device.isDesktop()) {
      picker.style.top = (p.y) + 'px';
      picker.style.left = (p.x - d.x - 2) + 'px';
    } else {
      picker.style.top = (p.y + b.y) + 'px';
      picker.style.left = '';
    }

    JX.DOM.setContent(
      picker.firstChild,
      [
        render_month(),
        render_day()
      ]);
  };

  var redraw_inputs = function() {
    var disabled = JX.Stratcom.getData(root).disabled;
    JX.DOM.alterClass(root, 'datepicker-disabled', disabled);

    var box = JX.DOM.scry(root, 'input', 'calendar-enable');
    if (box.length) {
      box[0].checked = !disabled;
    }
  };

  // Render a cell for the date picker.
  var cell = function(label, value, selected, class_name) {

    class_name = class_name || '';

    if (selected) {
      class_name += ' datepicker-selected';
    }
    if (!value) {
      class_name += ' novalue';
    }

    return JX.$N('td', {meta: {value: value}, className: class_name}, label);
  };

  // Render the top bar which allows you to pick a month and year.
  var render_month = function() {
    var valid_date = getValidDate();
    var month = valid_date.getMonth();
    var year = valid_date.getYear() + 1900;

    var months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December'];

    var buttons = [
      cell('\u25C0', 'm:-1', false, 'lrbutton'),
      cell(months[month] + ' ' + year, null),
      cell('\u25B6', 'm:1', false, 'lrbutton')];

    return JX.$N(
      'table',
      {className: 'month-table'},
      JX.$N('tr', {}, buttons));
  };

  function getValidDate() {
    var year_int = parseInt(value_y, 10);

    if (isNaN(year_int) || year_int < 0) {
      return new Date();
    }

    // If the user enters "11" for the year, interpret it as "2011" (which
    // is almost certainly what they mean) not "1911" (which is the default
    // behavior of Javascript).
    if (year_int < 70) {
      year_int += 2000;
    }

    var month_int = parseInt(value_m, 10);
    if (isNaN(month_int) || month_int < 1 || month_int > 12) {
      return new Date();
    }

    // In Javascript, January is "0", not "1", so adjust the value down.
    month_int = month_int - 1;

    var day_int = parseInt(value_d, 10);
    if (isNaN(day_int) || day_int < 1 || day_int > 31) {
      return new Date();
    }

    var written_date = new Date(year_int, month_int, day_int);
    if (isNaN(written_date.getTime())) {
      return new Date();
    }

    return written_date;
  }


  // Render the day-of-week and calendar views.
  var render_day = function() {
    var today = new Date();
    var valid_date = getValidDate();

    var weeks = [];

    // First, render the weekday names.
    var weekdays = 'SMTWTFS';
    var weekday_names = [];
    var week_start = parseInt(get_week_start(), 10);
    var week_end = weekdays.length + week_start;

    for (var ii = week_start; ii < week_end; ii++) {
      var index = ii%7;
      weekday_names.push(cell(weekdays.charAt(index), null, false, 'day-name'));
    }
    weeks.push(JX.$N('tr', {}, weekday_names));


    // Render the calendar itself. NOTE: Javascript uses 0-based month indexes
    // while we use 1-based month indexes, so we have to adjust for that.
    var days = [];
    var start = (new Date(
      valid_date.getYear() + 1900,
      valid_date.getMonth(),
      1).getDay() - week_start + 7) % 7;

    while (start--) {
      days.push(cell('', null, false, 'day-placeholder'));
    }

    for (ii = 1; ii <= 31; ii++) {
      var date = new Date(
        valid_date.getYear() + 1900,
        valid_date.getMonth(),
        ii);
      if (date.getMonth() != (valid_date.getMonth())) {
        // We've spilled over into the next month, so stop rendering.
        break;
      }

      var is_today = (today.getYear() == date.getYear() &&
                      today.getMonth() == date.getMonth() &&
                      today.getDate() == date.getDate());

      var classes = [];
      classes.push('day');
      if (is_today) {
        classes.push('today');
      }
      if (date.getDay() === 0 || date.getDay() == 6) {
        classes.push('weekend');
      }

      days.push(cell(
        ii,
        'd:'+ii,
        valid_date.getDate() == ii,
        classes.join(' ')));
    }

    // Slice the days into weeks.
    for (ii = 0; ii < days.length; ii += 7) {
      weeks.push(JX.$N('tr', {}, days.slice(ii, ii + 7)));
    }

    return JX.$N('table', {className: 'day-table'}, weeks);
  };


  JX.Stratcom.listen('click', 'calendar-button', onopen);
  JX.Stratcom.listen('change', 'calendar-enable', ontoggle);

  JX.Stratcom.listen(
    'click',
    ['phabricator-datepicker', 'tag:td'],
    function(e) {
      e.kill();

      var data = e.getNodeData('tag:td');
      if (!data.value) {
        return;
      }

      var valid_date = getValidDate();
      value_y = valid_date.getYear() + 1900;
      value_m = valid_date.getMonth() + 1;
      value_d = valid_date.getDate();

      var p = data.value.split(':');
      switch (p[0]) {
        case 'm':
          // User clicked left or right month selection buttons.
          value_m = value_m + parseInt(p[1], 10);
          if (value_m > 12) {
            value_m -= 12;
            value_y++;
          } else if (value_m <= 0) {
            value_m += 12;
            value_y--;
          }
          // This relies on months greater than 11 rolling over into the next
          // year and days less than 1 rolling back into the previous month.
          var last_date = new Date(value_y, value_m, 0);
          if (value_d > last_date.getDate()) {
            // The date falls outside the new month, so stuff it back in.
            value_d = last_date.getDate();
          }
          break;
        case 'd':
          // User clicked a day.
          value_d = parseInt(p[1], 10);
          write_date();

          // Wait a moment to close the selector so they can see the effect
          // of their action.
          setTimeout(JX.bind(null, onclose, e), 150);
          break;
      }

      // Enable the control.
      JX.Stratcom.getData(root).disabled = false;
      redraw_inputs();

      render();
    });

  JX.Stratcom.listen('click', null, function(e){
    if (e.getNode('phabricator-datepicker-core')) {
      return;
    }
    onclose();
  });

  JX.Stratcom.listen('resize', null, render);

});
