<?php

/**
 * @file
 * 
 * Simple module that allows a user to select when entities/fields should be
 * used to autotag an entity.  This currently supports:
 * 
 * - node/all fields
 * ...
 * 
 * The module allows a user to specify the search module to use, and where the
 * resulting terms should be saved.  The following entities are supported out of
 * the box:
 * - comment
 * - node
 * - user
 */
/**
 * Implementation of hook_form_FORM_ID_alter().
 *
 * This function basically allows us to setup each of the Tag modules for a
 * specific content type. A site administrator will be required to select the
 * search service to use with this content type, the fields to search, the field
 * to populate, and the user interface for selecting tags.
 */
function tagtag_form_node_type_form_alter(&$form, &$form_state, $form_id){
  // Get the list of search services for the form.
  $search_services = module_invoke_all('tag_suggestion_info');
  $search_options = array();
  $search_options_forms = array();
  $settings = variable_get('tagtag_node_' . $form['#node_type']->type, array());
  foreach($search_services as $key => $search_service){
    // Add the search service option for checkboxes later.
    $search_options[$key] = $search_service['label'];
    // Add each search service's options form if available.
    if(isset($search_service['options']['callback']) && function_exists($search_service['options']['callback'])){
      $search_options_forms[$key] = $search_service['options']['callback']($form['#node_type']->type, $settings);
    }
  }
  // Get a list of front end services that will enable us to interact with
  // the data.
  $ui_services = module_invoke_all('tag_ui_info');
  $ui_options = array();
  $ui_options_forms = array();
  foreach($ui_services as $key => $ui_service){
    // UI services are options in a drop down.
    $ui_options[$key] = $ui_service['label'];
    // Add the UI service's options form if required
    if(isset($ui_service['options']['callback']) && function_exists($ui_service['options']['callback'])){
      $ui_options_forms[$key] = $ui_service['options']['callback']($form['#node_type']->type);
    }
  }
  // Get a list of fields that this data can be saved to
  $search_fields = _tagtag_fields_list($form['#node_type']->type);
  $populate_fields = _tagtag_fields_list($form['#node_type']->type, 'populate');
  // Set the default value to the search fields to be ALL text fields.
  $search_fields_default = array();
  if(!count($search_fields_default)){
    $search_fields_default = array_keys($search_fields);
  }
  // Form
  $keys_array = array_keys($ui_options);
  $form['tagtag'] = array(
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#title' => t('Tag options'),
    '#group' => 'additional_settings',
    'tagtag_search_fields' => array(
      '#weight' => -10,
      '#default_value' => isset($settings['tagtag_search_fields']) ? $settings['tagtag_search_fields'] : $search_fields_default,
      '#title' => t('Fields to search for tags'),
      '#type' => 'checkboxes',
      '#options' => $search_fields,
      '#description' => t('Select the fields that the Tag search modules should search within when suggesting appropriate tags.  You MUST select at least one field if you have selected a search service below.')
    ),
    'tagtag_search' => array(
      '#weight' => 0,
      '#default_value' => isset($settings['tagtag_search']) ? $settings['tagtag_search'] : array(),
      '#title' => t('Search services'),
      '#type' => 'checkboxes',
      '#options' => $search_options,
      '#description' => t('Select the search services you would like to use to provide tags to this content type.  Additional options for each search service will be displayed below.')
    ),
    'tagtag_ui' => array(
      '#weight' => 10,
      '#default_value' => (count($ui_options) === 1) ? array_pop($keys_array) : (isset($settings['tagtag_ui']) ? $settings['tagtag_ui'] : ''),
      '#title' => t('Interface'),
      '#type' => 'radios',
      '#options' => $ui_options,
      '#description' => t('Select the user interface that you would like to use to select suggested tags.')
    ),
    'tagtag_field' => array(
      '#weight' => 20,
      '#required' => TRUE,
      '#title' => t('Field to populate'),
      '#default_value' => isset($settings['tagtag_field']) ? $settings['tagtag_field'] : '',
      '#type' => 'select',
      '#options' => array_merge(array(
        0 => '-- Select --'
      ), $populate_fields),
      '#description' => t('Please select the field you would like tags saving to.  Note, all content in the field will be replaced by tag data.')
    ),
    'tagtag_append' => array(
      '#weight' => 30,
      '#title' => t('Replace or Append'),
      '#default_value' => isset($settings['tagtag_append']) ? $settings['tagtag_append'] : 1,
      '#type' => 'radios',
      '#options' => array(
        0 => t('Replace'),
        1 => t('Append')
      ),
      '#description' => t('Replace will delete all values already set in the selected field, while append will add to the current values')
    )
  );
  // Additional settings for each module - hopefully hidden if the search
  // isn't selected.
  foreach($search_options_forms as $key => $options_form){
    $form['tagtag'][$key] = $options_form;
    $form['tagtag'][$key]['#weight'] = 5;
    $form['tagtag'][$key]['#type'] = 'fieldset';
    $form['tagtag'][$key]['#title'] = $search_options[$key];
    $form['tagtag'][$key]['#states'] = array(
      'visible' => array(
        ':input[name="tagtag_search[' . $key . ']"]' => array(
          'checked' => TRUE
        )
      )
    );
  }
  // Additional settings for each module - hopefully hidden if the interface
  // isn't selected.
  foreach($ui_options_forms as $key => $options_form){
    $form['tagtag'][$key] = $options_form;
    $form['tagtag'][$key]['#weight'] = 15;
    $form['tagtag'][$key]['#type'] = 'fieldset';
    $form['tagtag'][$key]['#title'] = $ui_options[$key];
    $form['tagtag'][$key]['#states'] = array(
      'visible' => array(
        ':input[value="' . $key . '"]' => array(
          'checked' => TRUE
        )
      )
    );
  }
  $form['#validate'][] = 'tagtag_node_type_form_validate';
  $form['#submit'][] = 'tagtag_node_type_form_submit';
}

/**
 * Callback for the above to validate the form
 */
function tagtag_node_type_form_validate($form, &$form_state){
  // Simply check that a field has been selected ONLY if a search method has
  // been specified.
  foreach($form_state['values']['tagtag']['tagtag_search'] as $key => $value){
    if($value && !$form_state['values']['tagtag']['tagtag_field']){
      // We've selected a search, but not a field.
      form_set_error('tagtag_field', t('Please select a field to save tags to'));
    }
  }
}

/**
 * Submit function for the above - this saves all of the variables defined by
 * the tag module and their helpers.
 */
function tagtag_node_type_form_submit($form, &$form_state){
  variable_set('tagtag_node_' . $form['#node_type']->type, $form_state['values']['tagtag']);
  // The node form automatically saves a variable that's utterly useless to us,
  // so we delete it just to keep things tidy!
  variable_del('tagtag_' . $form['#node_type']->type);
}

/**
 * Implementation of hook_form_alter().
 */
function tagtag_form_alter(&$form, &$form_state, $form_id){
  if(isset($form['#node_edit_form']) && $form['#node_edit_form']){
    // We're here, we should call our form_alter functions, and add a submit
    // handler.
    // First get what search services we're using.
    $settings = variable_get('tagtag_node_' . $form['type']['#value'], array());
    $implementation = isset($settings['tagtag_ui']) ? $settings['tagtag_ui'] : '';
    $search_services = module_invoke_all('tag_ui_info');
    if(isset($search_services[$implementation]['form_alter_callback']) && function_exists($search_services[$implementation]['form_alter_callback'])){
      $search_services[$implementation]['form_alter_callback']($form, $form_state);
    }
    // Finally, we add a submit function to ensure that we can update the data
    // when we're finished.
    $additional_submit = array(
      'tagtag_submit_form'
    );
    $form['#submit'] = is_array($form['#submit']) ? array_merge($form['#submit'], $additional_submit) : $additional_submit;
  }
}

/**
 * Implementation of hook_node_type_delete().
 */
function tagtag_node_type_delete($info){
  if(is_object($info) && isset($info->type)){
    variable_del("tagtag_node_{$info->type}");
  }
}

/**
 * Helper function to get a list of fields that can be used!
 */
function _tagtag_fields_list($node_type, $list_type=null){
  $instances = field_info_instances('node', $node_type);
  $field_types = array();
  switch ($list_type) {
    case 'populate':
      drupal_alter('tag_populate_field_types', $field_types);
      break;
    default:
      $field_types = array(
        'taxonomy',
        'text',
        'options',
        'tree_widget'
      );
      drupal_alter('tag_field_types', $field_types);
  }

  $field_list = array();
  foreach($instances as $key => $instance){
    if(in_array($instance['widget']['module'], $field_types)){
      $field_options[$key] = $instance['label'];
      if(substr($instance['widget']['type'], 0, 5) == 'text_'){
        $field_list[$key] = $instance['label'];
      }else{
        $field_info = field_info_field($instance['field_name']);
        if(substr($field_info['type'], 0, 9) == 'taxonomy_'){
          $field_list[$key] = $instance['label'];
        }
      }
    }
  }
  return $field_list;
}

/**
 * Callback for the submit
 *
 * This does a lot of the heavy lifting, ensuring that tags are saved to the
 * correct fields.
 */
function tagtag_submit_form($form, &$form_state){
  // Do the searches on each of the fields that we want to search. The
  // searches should return an array of tags which simply get added to the
  // tagtag element of the $node (entity???)
  // First we get the list of fields that we're searching.
  $settings = variable_get('tagtag_node_' . $form['type']['#value'], array());
  $implementations = isset($settings['tagtag_search']) ? array_filter($settings['tagtag_search']) : array();
  $search_services = module_invoke_all('tag_suggestion_info');
  $tags = array();
  // Loop through each field
  $fields_to_search = isset($settings['tagtag_search_fields']) ? array_filter($settings['tagtag_search_fields']) : FALSE;
  if(!$fields_to_search){
    // We haven't set the fields to search - we probably don't have
    // implementations anyway, but in case we do, we'll get the fields to search
    if(count($implementations)){
      $fields_to_search = array_keys(_tagtag_fields_list($form['type']['#value']));
    }
  }
  foreach($implementations as $implementation){
    if(isset($search_services[$implementation]['request_callback']) && function_exists($search_services[$implementation]['request_callback'])){
      // Get the options if they're required.
      $options = array();
      if(isset($search_services[$implementation]['options']['keys'])){
        foreach($search_services[$implementation]['options']['keys'] as $key){
          $options[$key] = isset($settings[$implementation][$key]) ? $settings[$implementation][$key] : array();
        }
      }
      foreach($fields_to_search as $field){
        $text = tagtag_get_values($form_state['values'][$field]);
        if($text){
          $tags = array_merge($tags, $search_services[$implementation]['request_callback']($text, $options));
        }
      }
    }
  }
  // We're here, we should have an arary of tags (possibly empty) - we now
  // simply add this to the $form_state array under tagtag
  $form_state['values']['tagtag'] = $tags;
  // Finally execute the submit_callback for the UI module we're using
  // The UI module should save data here, rather than saving it on a
  // hook_node_update/insert
  $implementation = isset($settings['tagtag_ui']) ? $settings['tagtag_ui'] : '';
  $ui_service = module_invoke_all('tag_ui_info');
  if(isset($ui_service[$implementation]['submit_callback']) && function_exists($ui_service[$implementation]['submit_callback'])){
    $ui_service[$implementation]['submit_callback']($form, $form_state);
  }
}

/**
 * Get value from $form_state['values'] array
 */
function tagtag_get_values($array){
  // We're sent an array, we need to get the first instance of "value" from it.
  // Note, if we're not sent an array, we simply return $array.
  if(isset($array['value'])){
    return $array['value'];
  }else{
    if(is_array($array)){
      foreach($array as $array_part){
        $value = tagtag_get_values($array_part);
        if($value){
          return $value;
        }
      }
    }
  }
  return FALSE;
}

/**
 * Helper function for UI modules.
 * This adds the tags specified to a node.
 * This function will do checks to ensure that tags can be added to a specific
 * field.
 */
function tagtag_add_tags(&$form_state){
  // First we need to get the field we're adding the data to.
  $settings = variable_get('tagtag_node_' . $form_state['values']['type'], array());
  $field = isset($settings['tagtag_field']) ? $settings['tagtag_field'] : FALSE;
  if($field){
    // Get the field
    $field_language = field_language('node', $form_state['node'], $field);
    $field_instance = field_read_instance('node', $field, $form_state['values']['type']);
    // kprint_r($field_instance);
    // Get whether or not this field is set to replace or append
    $append = isset($settings['tagtag_append']) ? $settings['tagtag_append'] : 1;
    $map = array(
      'taxonomy' => 'taxonomy',
      'options' => 'taxonomy',
      'tree_widget' => 'taxonomy',
      'text' => 'text'
    );
    drupal_alter('tag_widget_map', $map);
    if(isset($map[$field_instance['widget']['module']])){
      switch($map[$field_instance['widget']['module']]){
        case 'taxonomy':
          if(!$append){
            $form_state['values'][$field][$field_language] = array();
          }
          // We have a taxonomy field, here we'll add the values/
          foreach($form_state['values']['tagtag'] as $tag){
            // Check that the tag hasn't already been saved to the field (yes, the
            // same tag can be saved multiple times to a taxonomy field).
            $already_set = FALSE;
            foreach($form_state['values'][$field][$field_language] as $value){
              if($value['tid'] == $tag->tid){
                $already_set = TRUE;
              }
            }
            if(!$already_set){
              $form_state['values'][$field][$field_language][] = (array)$tag;
            }
          }
          break;
        case 'text':
          // Text field, add the values as ";" delimitted text.
          $tag_text = array();
          foreach($form_state['values']['tagtag'] as $tag){
            if(strpos($tag->name, ';')){
              $tag_text[] = '"' . str_replace('"', '\"', $tag->name) . '"';
            }else{
              $tag_text[] = $tag->name;
            }
          }
          if($append){
            $form_state['values'][$field][$field_language][0]['value'] .= ' ' . implode("; ", $tag_text);
          }else{
            $form_state['values'][$field][$field_language][0]['value'] = implode("; ", $tag_text);
          }
          break;
        default:
          // We've defined a map, but are unable to handle it - we should give
          // an error!
          drupal_set_message(t('Unable to handle widget of type !type', array(
            '!type' => $map[$field_instance['widget']['module']]
          )), 'error');
          break;
      }
    }
  }
}