<?php
/**
 * @file
 * Implementation of Date Repeat API calculations for the CCK Date field.
 *
 * Consolidated here so the code isn't parsed if repeats aren't being used
 * or processed, and to make it easier to maintain.
 *
 * The current implementation adds a repeat form to the date field so the user
 * can select the repeat rules. That selection is built into an RRULE
 * which is stored in the zero position of the field values. During widget
 * validation, the rule is parsed to see what dates it will create,
 * and multiple values are added to the field, one for each repeat date.
 * That update only happens when the rule, the from date, or the to date
 * change, no need to waste processing cycles for other changes to the node
 * values.
 *
 * Lots of possible TODOs, the biggest one is figuring out the best
 * way to handle dates with no UNTIL date since we can't add an infinite
 * number of values to the field. For now, we require the UNTIL date.
 */

/**
 * Widget processing for date repeat form element.
 *
 * Create the RRULE as a top-level element rather than a delta level
 * element, we'll compute the repeat sequence in the widget validation
 * to create the element delta values.
 */
function _date_repeat_widget(&$element, $field, $items, $delta) {
  $element['rrule'] = array(
    '#type' => 'date_repeat_rrule',
    '#default_value' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
    '#date_timezone' => $element['#date_timezone'],
    '#date_format'      => date_limit_format(date_input_format($element, $field), $field['granularity']),
    '#date_text_parts'  => (array) $field['widget']['text_parts'],
    '#date_increment'   => $field['widget']['increment'],
    '#date_year_range'  => $field['widget']['year_range'],
    '#date_label_position' => $field['widget']['label_position'],
    '#prev_value' => isset($items[0]['value']) ? $items[0]['value'] : '',
    '#prev_value2' => isset($items[0]['value2']) ? $items[0]['value2'] : '',
    '#prev_rrule' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
    '#date_repeat_widget' => str_replace('_repeat', '', $field['widget']['type']),
    '#date_repeat_collapsed' => $field['repeat_collapsed'],
    );

  return $element;
}
/**
 * Validation for date repeat form element.
 *
 * Create multiple values from the RRULE results.
 * Lots more work needed here.
 */
function _date_repeat_widget_validate($element, &$form_state) {
  require_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_form.inc');
  $form_values = $form_state['values'];
  $field_name = $element['#field_name'];
  $fields = content_fields();
  $field = $fields[$field_name];
  $item = $form_values[$field_name];
  $values = date_repeat_merge($element['#post'][$field_name]['rrule'], $element['rrule']);

  // If no start date was set, clean up the form and return.
  // If no repeats are set, clean up the form and return.
  if (empty($form_values[$field_name]['value']) || $values['FREQ'] == 'NONE') {
    $form_values[$field_name]['rrule'] = NULL;
    form_set_value($element, array($form_values[$field_name]), $form_state);
    return;
  }

  // Require the UNTIL date for now.
  // The RRULE has already been created by this point, so go back
  // to the posted values to see if this was filled out.
  $error_field = implode('][', $element['#parents']) .'][rrule][UNTIL][datetime][date';
  if (empty($values['UNTIL']['datetime'])) {
    form_set_error($error_field, t('The UNTIL value is required for repeating dates.'));
  }
  if (form_get_errors()) {
    return;
  }

  // If the rule, the start date, or the end date have changed, re-calculate
  // the repeating dates, wipe out the previous values, and populate the
  // field with the new values.

  // TODO
  // Is it right to not do anything unless there are changes? Will that
  // confuse anyone? Commenting that out for now...
  $rrule = $form_values[$field_name]['rrule'];
  if (!empty($rrule)
    //&& ($rrule != $element['rrule']['#prev_rrule']
    //|| $form_values[$field_name][0]['value'] != $element['rrule']['#prev_value']
    //|| $form_values[$field_name][0]['value2'] != $element['rrule']['#prev_value2'])
    ) {

    $item = $form_values[$field_name];

    // Avoid undefined index problems on dates that don't have all parts.
    $possible_items = array('value', 'value2', 'timezone', 'offset', 'offset2');
    foreach ($possible_items as $key) {
      if (empty($item[$key])) {
        $item[$key] = '';
      }
    }
    $value = date_repeat_build_dates($rrule, $values, $field, $item);
    form_set_value($element, $value, $form_state);
  }
  else {
    // If no changes are needed, move the RRULE back to the zero value
    // item of the field.
    form_set_value(array('#parents' => array($field_name, 0, 'rrule')), $rrule, $form_state);
    form_set_value($element['rrule'], NULL, $form_state);
  }
}

/**
 * Helper function to build repeating dates from a $node_field.
 *
 * Pass in either the RRULE or the $form_values array for the RRULE,
 * whichever is missing will be created when needed.
 */
function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) {
  module_load_include('inc', 'date_api', 'date_api_ical');
  $field_name = $field['field_name'];

  if (empty($rrule)) {
    $rrule = date_api_ical_build_rrule($rrule_values);
  }
  elseif (empty($rrule_values)) {
    $rrule_values = date_ical_parse_rrule(NULL, $rrule);
  }

  // By the time we get here, the start and end dates have been
  // adjusted back to UTC, but we want localtime dates to do
  // things like '+1 Tuesday', so adjust back to localtime.
  $timezone = date_get_timezone($field['tz_handling'], $item['timezone']);
  $timezone_db = date_get_timezone_db($field['tz_handling']);
  $start = date_make_date($item['value'], $timezone_db, $field['type'], $field['granularity']);
  if ($timezone != $timezone_db) {
    date_timezone_set($start, timezone_open($timezone));
  }
  if (!empty($item['value2']) && $item['value2'] != $item['value']) {
    $end = date_make_date($item['value2'], date_get_timezone_db($field['tz_handling']), $field['type'], $field['granularity']);
    date_timezone_set($end, timezone_open($timezone));
  }
  else {
    $end = $start;
  }
  $duration = date_difference($start, $end);
  $start_datetime = date_format($start, DATE_FORMAT_DATETIME);
  if (!empty($rrule_values['UNTIL']['datetime'])) {
    if (strlen($rrule_values['UNTIL']['datetime']) < 11) {
      $rrule_values['UNTIL']['datetime'] .= ' 23:59:59';
    }
    $end = date_ical_date($rrule_values['UNTIL'], $timezone);
    $end_datetime = date_format($end, DATE_FORMAT_DATETIME);
  }
  elseif (!empty($rrule_values['COUNT'])) {
    $end_datetime = NULL;
  }
  else {
    // No UNTIL and no COUNT?
    return array();
  }

  // Split the RRULE into RRULE, EXDATE, and RDATE parts.
  $parts = date_repeat_split_rrule($rrule);
  $parsed_exceptions = (array) $parts[1];
  $exceptions = array();
  foreach ($parsed_exceptions as $exception) {
    if (strlen($exception['datetime']) < 11) {
      $exception['datetime'] .= ' 00:00:00';
    }
    $date = date_ical_date($exception, $timezone);
    $exceptions[] = date_format($date, 'Y-m-d');
  }

  $parsed_rdates = (array) $parts[2];
  $additions = array();
  foreach ($parsed_rdates as $rdate) {
    if (strlen($rdate['datetime']) < 11) {
      $rdate['datetime'] .= ' 00:00:00';
    }
    $date = date_ical_date($rdate, $timezone);
    $additions[] = date_format($date, 'Y-m-d');
  }

  $dates = date_repeat_calc($rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions);
  $value = array();
  foreach ($dates as $delta => $date) {
    // date_repeat_calc always returns DATE_DATETIME dates, which is
    // not necessarily $field['type'] dates.
    // Convert returned dates back to db timezone before storing.
    $date_start = date_make_date($date, $timezone, DATE_DATETIME, $field['granularity']);
    date_timezone_set($date_start, timezone_open($timezone_db));
    $date_end = drupal_clone($date_start);
    date_modify($date_end, '+' . $duration . ' seconds');
    $value[$delta] = array(
      'value' => date_format($date_start, date_type_format($field['type'])),
      'value2' => date_format($date_end, date_type_format($field['type'])),
      'offset' => date_offset_get($date_start),
      'offset2' => date_offset_get($date_end),
      'timezone' => $timezone,
      'rrule' => $rrule,
      );
  }
  return $value;
}