<?php

/**
 * @file
 * Module file for Views Gantt
 */

/**
 * Implements hook_views_api().
 */
function views_gantt_views_api() {
  return array(
    'api' => 3,
  );
}

/**
 * Implements hook_libraries_info().
 */
function views_gantt_libraries_info() {
  $libraries['dhtmlxgantt'] = array(
    'name' => 'dhtmlxgantt',
    'vendor url' => 'http://dhtmlx.com/docs/products/dhtmlxGantt/index.shtml',
    'download url' => 'http://dhtmlx.com/docs/products/dhtmlxGantt/index.shtml',
    'version' => '2.0',
    'files' => array(
      'js' => array(
        'codebase/dhtmlxgantt.js',
      ),
      'css' => array(
        'codebase/dhtmlxgantt.css',
      ),
    ),
  );

  return $libraries;
}


/**
 * Implements hook_menu().
 */
function views_gantt_menu() {
  $items = array();
  $items['views_gantt/data.json'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'views_gantt_data_json',
    'access arguments' => array('access content'),
  );  
  // @todo Add permission to update tasks from Gantt chart?
  $items['views_gantt/update'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'views_gantt_update',
    'access arguments' => array('access content'),
  );

  return $items;
}

function views_gantt_data_json() {
  $data = array();
  $links = array();

  if (!isset($_SESSION['views_gantt']) && isset($_GET['view'], $_GET['display'], $_GET['project'])) {
    _views_gantt_load_view($_GET['view'], $_GET['display'], $_GET['project']);
  }

  if (isset($_SESSION['views_gantt'])) {
    $project_id = $_SESSION['views_gantt']['project']['id'];
    foreach ($_SESSION['views_gantt']['tasks'] as $id => $task) {
      if (!$task['parent_id'] && $id != $project_id) {
        $task['parent_id'] = $project_id;
      }
      $data[] = array(
        'id' => $id,
        'text' => $task['name'],
        'start_date' => $task['est'],
        'duration' => $task['duration'] / 8,
        'progress' => $task['percentcompleted'] / 100,
        'parent' => $task['parent_id'],
        'open' => TRUE,
      );
      if ($task['predecessortasks']) {
        $links[] = array(
          'id' => $task['predecessortasks'] . '_' . $id,
          'source' => $task['predecessortasks'],
          'target' => $id,
          'type' => 0,
        );
      }
    }
  }
  drupal_json_output(array('data' => $data, 'links' => $links));
}

function _views_gantt_load_view($view_name, $display, $project_id) {
  $view = views_get_view($view_name);
  $exposed_filters = array();
  foreach ($_GET as $key => $value) {
    if (isset($view->exposed_data[$key])) {
      $exposed_filters[$key] = $value;
    }
  }
  
  if (isset($view->exposed_input)) {
    $view->exposed_input = array_merge($exposed_filters, (array)$view->exposed_input);
  }
  if (isset($view->exposed_raw_input)) {
    $view->exposed_raw_input = array_merge($exposed_filters, (array)$view->exposed_raw_input);
  }
  if (isset($view->exposed_data)) {
    $view->exposed_data = array_merge($exposed_filters, (array)$view->exposed_data);
  }

  $view->preview($display, $project_id); 
}

/**
 * Callback for task/project update when we change it in Gantt chart.
 */
function views_gantt_update() {
  $view_name = variable_get('views_gantt_view_name', NULL);

  // Show empty page in browser if request incorrect.
  if (!$view_name || empty($_POST['ids'])) return NULL;

  $data = $_POST;
  $id = $data['ids'];
  $style_options = views_gantt_get_style_options($view_name);

  switch ($_GET['gantt_mode']) {
    case 'tasks':
      $fields = array(
        'date_field' => $data[$id . '_start_date'],
        'end_date_field' => $data[$id . '_end_date'],
        'progress_field' => $data[$id . '_progress'] * 100,
      );
      $node = node_load($id);
      break;
    
    case 'links':
      if ($data[$id . '_!nativeeditor_status'] != 'deleted') {
        $source = $data[$id . '_source'];
        $target = $data[$id . '_target'];
      } else {
        list($source, $target) = explode('_', $id);
        $source = NULL;
      }
      
      $fields = array(
        'predecessor_id_field' => $source,
      );
      $node = node_load($target);
      break;
  }

  if (!empty($node)) {
    views_gantt_update_node($node, $fields, $style_options);
    $response_type = 'updated';
  } else {
    $response_type = 'error';
  }
  $xml = new SimpleXMLElement("<data><action type='$response_type' sid='$id' tid='$id' /></data>");
  drupal_add_http_header('Content-Type', 'text/xml');
  print $xml->asXML();
  drupal_exit();
}

/**
 * Updates node if there are changes in it's fields.
 * 
 * @param object $node
 *   Node object
 * @param array $data
 *   Array of option names (from style plugin options) => values to update
 * @param array $style_options
 *   Array of all style plugin option names
 */
function views_gantt_update_node($node, $data, $style_options) {
  $changed = FALSE;
  $lang = LANGUAGE_NONE;

  // For now we support only simple fields that
  // storing their values in array with 'value' key,
  // and datetime fields (form Date module).
  // Maybe we don't need to add another field types,
  // because we update only dates and duration.
  foreach ($data as $option_name => $option_value) {
    if (isset($style_options[$option_name])) {
      $field_name = $style_options[$option_name];
      if (isset($node->$field_name)) {
        if (is_array($node->$field_name)) {
          // Get field info.
          $field_info = field_info_field($field_name);
          
          // If field is translatable, we check if it has
          // an index equals to node language.
          $is_translatable = field_is_translatable('node', $field_info);
          if ($is_translatable && isset($node->{$field_name}[$node->language])) {
            $lang = $node->language;
          }

          $index = key($field_info['columns']);
          if ($option_name == 'end_date_field' && isset($field_info['columns']['value2'])) {
            $index = 'value2';
          }

          // If field type = datetime, modify new value before storing.
          if (isset($field_info['columns'][$index]) && $field_info['columns'][$index]['type'] == 'datetime') {
            views_gantt_normalize_date_field($option_value, 'Y-m-d H:i:s');
          }

          if ($option_value) {
            $node->{$field_name}[$lang][0][$index] = $option_value;        
          } else {
            $node->$field_name = array();
          }
        }
        else {
          $node->$field_name = $option_value;
        }
        $changed = TRUE;
      }
    }
  }

  // If we have some changes, we need to save node.
  // Also if node saved, we remove cached gantt data from session.
  if ($changed) {
    node_save($node);
    unset($_SESSION['views_gantt']);
  }
}

/**
 * Formats date for Gantt Chart.
 */
function views_gantt_normalize_date_field(&$field, $format = 'Y-m-d') {
  if (!is_numeric($field)) {
    $field = strtotime($field);
  }
  $field = format_date($field, 'custom', $format);
}

/**
 * Get style plugin options.
 */
function views_gantt_get_style_options($view_name) {
  $view = views_get_view($view_name);
  // Get style plugin options.
  // Some fields in $this->options can have incorrect names (e.g. field_date_1),
  // so we should check the real field name.
  $style_options = array();
  foreach ($view->display as $display) {
    $display_options = $display->display_options;
    if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'gantt') {
      $style_options = $display->display_options['style_options'];
      foreach ($style_options as $key => &$value) {
        if (isset($display->display_options['fields'][$value])) {
          $value = $display->display_options['fields'][$value]['field'];
        }
      }
      break;
    }
  }

  return $style_options;
}