<?php
/**
 * @file
 * Defines an interval field
 * @copyright Copyright(c) 2011 Rowlands Group
 * @license GPL v2+ http://www.fsf.org/licensing/licenses/gpl.html
 * @author Lee Rowlands leerowlands at rowlandsgroup dot com
 *
 */


/***************************************************************
 * Field Type API hooks
 ***************************************************************/

/**
 * Implements hook_field_info().
 *
 * Provides the description of the field.
 */
function interval_field_info() {
  return array(
    // We name our field as the associative name of the array.
    'interval' => array(
      'label' => t('Interval'),
      'description' => t('Provides an interval field allowing you to enter a number and select a period.'),
      'default_widget' => 'interval_default',
      'default_formatter' => 'interval_default',
      // Add entity property metadata for the entity api module.
      'property_type' => 'field_item_interval',
      'property_callbacks' => array('interval_field_entity_property_info_alter'),
    ),
  );
}

/**
 * Defines the data structure used for interval field items.
 */
function interval_entity_property_field_item_interval_info() {
  return array(
    'interval' => array(
      'type' => 'integer',
      'label' => t('Interval number'),
      'description' => t('The number of multiples of the period.'),
    ),
    'period' => array(
      'type' => 'token',
      'label' => t('Interval period'),
      'options list' => 'interval_period_options_list',
    ),
  );
}

/**
 * Callback to alter the property info of interval fields.
 *
 * @see interval_field_info()
 */
function interval_field_entity_property_info_alter(&$info, $entity_type, $field, $instance, $field_type) {
  $name = $field['field_name'];
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];

  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  $property['property info'] = interval_entity_property_field_item_interval_info();
  unset($property['query callback']);
}

/**
 * Implements hook_field_validate().
 *
 * @see interval_field_widget_error()
 */
function interval_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if (!empty($item['interval'])) {
      if (!is_numeric($item['interval'])) {
        $errors[$field['field_name']][$langcode][$delta][] = array(
          'error' => 'interval_non_numeric',
          'message' => t('You must enter a numeric value.'),
        );
      }
    }
  }
}


/**
 * Implements hook_field_is_empty().
 */
function interval_field_is_empty($item, $field) {
  return empty($item['interval']);
}

/**
 * Implements hook_field_formatter_info().
 *
 * We need to tell Drupal that we have two different types of formatters
 * for this field. One will change the text color, and the other will
 * change the background color.
 *
 * @see interval_field_formatter_view()
 */
function interval_field_formatter_info() {
  return array(
    // This formatter just displays the interval and period wrapped in a div.
    'interval_default' => array(
      'label' => t('Plain'),
      'field types' => array('interval'),
    ),
    // Same as default formatter but without the wrapper div.
    'interval_raw' => array(
      'label' => t('Raw'),
      'field types' => array('interval'),
    )
  );
}

/**
 * Implements hook_field_formatter_view().
 *
 * @see interval_field_formatter_info()
 */
function interval_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();

  switch ($display['type']) {
    // This formatter simply outputs the interval/period wrapped in a div.
    case 'interval_default':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#type' => 'html_tag',
          '#attributes' => array('class' => array('interval-value')),
          '#tag' => 'div',
          '#value' => interval_format_interval($item)
        );
      }
      break;

    // This formatter outputs the interval/period.
    case 'interval_raw':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => check_plain(interval_format_interval($item))
        );
      }
      break;

  }

  return $element;
}

/**
 * Implements hook_field_instance_settings_form().
 *
 * Allow the user to choose from available periods
 */
function interval_field_instance_settings_form($field, $instance) {
  $form = array();
  $settings = $instance['settings'];

  $widget = $instance['widget']['type'];
  if ($widget == 'interval_default') {
    $options = array();
    $intervals = interval_get_intervals();
    foreach ($intervals as $key => $detail) {
      $options[$key] = $detail['plural'];
    }
    $form['allowed_periods'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Allowed periods'),
      '#options' => $options,
      '#description' => t('Select the periods you wish to be available in the dropdown. Selecting none will make all of them available.'),
      '#default_value' => isset($settings['allowed_periods']) ? $settings['allowed_periods'] : array()
    );
  }
  return $form;
}

/**
 * Implements hook_field_widget_info().
 *
 * @see interval_field_widget_form()
 */
function interval_field_widget_info() {
  return array(
    'interval_default' => array(
      'label' => t('Interval and Period'),
      'field types' => array('interval'),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 *
 */
function interval_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  // Fetch list of intervals.
  $intervals = interval_get_intervals();

  // Convenience variables.
  $value = isset($items[$delta]) ? $items[$delta] : array('interval' => NULL, 'period' => NULL);
  $field_name = $instance['field_name'];

  // We need to check our form_state values for ajax completion.
  if (isset($form_state['values']) &&
      !empty($form_state['values'][$field_name][$langcode][$delta])) {
    // We use the current form state values instead of those from the db.
    $value = $form_state['values'][$field_name][$langcode][$delta];
  }

  $settings = $instance['settings'];
  $required = $element['#required'];
  $widget = $instance['widget'];

  // Available periods.
  $periods = array();
  if (isset($settings['allowed_periods'])) {
    $periods = array_keys(array_filter($settings['allowed_periods']));
  }
  $period_options = array();
  foreach ($intervals as $key => $detail) {
    if (in_array($key, $periods) && isset($detail['plural'])) {
      $period_options[$key] = $detail['plural'];
    }
  }

  switch ($widget['type']) {

    case 'interval_default':
      if ($field['cardinality'] == 1) {
        $element += array(
          '#type' => 'fieldset',
          '#collapsible' => FALSE
        );
      }
      else {
        $element += array(
          '#type' => 'container'
        );
      }
      $element += array(
        '#attributes' => array(
          'class' => array(
            'clearfix'
          )
        ),
        '#attached' => array(
          'css' => array(drupal_get_path('module', 'interval') . '/interval.css')
        )
      );
      $element['interval'] = array(
        '#type' => 'textfield',
        '#size' => 8,
        '#required' => $required,
        '#attributes' => array('class' => array('interval-interval')),
        '#default_value' => $value['interval']
      );
      $element['period'] = array(
        '#type' => 'select',
        '#options' => $period_options,
        '#attributes' => array('class' => array('interval-period')),
        '#default_value' => $value['period']
      );
      break;

  }
  return $element;
}

/**
 * Implements hook_field_widget_error().
 *
 * @see interval_field_validate()
 * @see form_error()
 */
function interval_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'interval_non_numeric':
      form_error($element['interval'], $error['message']);
      break;
  }
}

/***************************************************************
 * Non core hooks
 ***************************************************************/

/**
 * Implements hook_interval_intervals
 */
function interval_interval_intervals() {
  return array(
    'month' => array(
      'plural' => t('Months'),
      'singular' => t('Month'),
      'php' => 'months',
      'multiplier' => 1
    ),
    'day' => array(
      'plural' => t('Days'),
      'singular' => t('Day'),
      'php' => 'days',
      'multiplier' => 1
    ),
    'year' => array(
      'plural' => t('Years'),
      'singular' => t('Year'),
      'php' => 'years',
      'multiplier' => 1
    ),
    'week' => array(
      'plural' => t('Weeks'),
      'singular' => t('Week'),
      'php' => 'days',
      'multiplier' => 7
    ),
    'fortnight' => array(
      'plural' => t('Fortnights'),
      'singular' => t('Fortnight'),
      'php' => 'days',
      'multiplier' => 14
    ),
    'quarter' => array(
      'plural' => t('Quarters'),
      'singular' => t('Quarter'),
      'php' => 'months',
      'multiplier' => 3
    )
  );
}

/***************************************************************
 * Module functions
 ***************************************************************/
/**
 * Util function to fetch defined intervals
 */
function interval_get_intervals() {
  $intervals = &drupal_static(__FUNCTION__);
  if (!isset($intervals)) {
    // First prime of the static - try cache.
    $cached = cache_get('interval_intervals');
    if ($cached && $cached->data) {
      $intervals = $cached->data;
    }
    else {
      // Non-primed cache too - initialize with module_invoke_all.
      $intervals = module_invoke_all('interval_intervals');
      // Cache them.
      cache_set('interval_intervals', $intervals);
    }
  }
  return $intervals;
}

/**
 * Applies the interval values to a given date
 *
 * @param object $date
 *   A DateTime object to which the interval needs to be applied
 * @param array $item
 *   An array of values for the interval item consisting of:
 *   - interval: the interval (int)
 *   - period: the interval period
 *
 * @return bool
 *   TRUE/FALSE
 *
 * @see interval_get_intervals
 */
function interval_apply_interval($date, $item) {
  $intervals = interval_get_intervals();
  $interval = $intervals[$item['period']];
  if (is_object($date)) {
    $value = $item['interval'] * $interval['multiplier'];
    $date->modify("$value {$interval['php']}");
    return TRUE;
  }
  return FALSE;
}

/**
 * Formats an interval
 *
 * Takes the given interval values and formats them as a string
 *
 * @param array $item
 *   An array of values for the interval item consisting of:
 *   - interval: the interval (int)
 *   - period: the interval period
 *
 * @return string
 *   The formatted interval
 */
function interval_format_interval($item) {
  $intervals = interval_get_intervals();
  $interval = $intervals[$item['period']];
  return format_plural(
    $item['interval'], '1 @singular', '@count @plural',
    array(
      '@singular' => $interval['singular'],
      '@plural' => $interval['plural']
    )
  );
}

/**
 * Returns an options list of all supported interval periods.
 */
function interval_period_options_list() {
  $options = array();
  foreach (interval_get_intervals() as $interval => $info) {
    $options[$interval] = $info['plural'];
  }
  return $options;
}
