<?php

// Permission that allows to upload freemind map.
define('GRAPHMIND_SERVICES__PERM__UPLOAD_MAP',  'upload graphmind mindmap');
define('GRAPHMIND_SERVICE_FEATURE_PERM_PREFIX', 'graphmind feature ');
define('GRAPHMIND_SERVICE_PLUGIN_PERM_PREFIX',  'graphmind plugin ');


/**
 * Implementation of hook_service().
 *
 * @return array
 */
function graphmind_service_services_resources() {
  return array(
    'graphmind' => array(
      'getViews' => array(
        'help' => 'Get the list of available views',
        'callback' => 'graphmind_service_get_views',
        'file' => array('type' => 'inc', 'module' => 'graphmind_service'),
        'return' => 'struct',
        'access callback' => 'user_access',
        'access arguments append' => FALSE,
        'access arguments' => array('access content'),
      ),
      
      /**
       * Saving the map content in a field. 
       */
      'saveGraphMind' => array(
        'help' => 'Save the XML output',
        'access arguments' => array(array('update'), 'entity_access'),
        'access callback' => 'graphmind_service_access',
        'access arguments append' => TRUE,
        'callback' => 'graphmind_service_save_graphmind',
        'file' => array('type' => 'inc', 'module' => 'graphmind_service'),
        'args' => array(
          array(
            'name' => 'entity_id',
            'type' => 'int',
            'description' => 'Entity ID',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'entity_vid',
            'type' => 'int',
            'description' => 'Entity VID',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'delta',
            'type' => 'int',
            'description' => 'Delta of the entity instance',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'field_name',
            'type' => 'string',
            'description' => 'Field of the entity',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'entity_type',
            'type' => 'string',
            'description' => 'Type of the entity',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'mm',
            'type' => 'string',
            'description' => t('GraphMind map in FreeMind format.'),
            'optional' => FALSE,
            'source' => 'data',
          ),
        ),
        'return' => 'string',
      ),
      
      'getNodeView' => array(
        'help' => 'Get a brief view of a node',
        'access arguments' => array('access content'),
        'access callback' => 'user_access',
        'access arguments append' => FALSE,
        'callback' => 'graphmind_service_tooltip_get_view',
        'file' => array('type' => 'inc', 'module' => 'graphmind_service'),
        'return' => 'string',
        'args' => array(
          array(
            'name' => 'nid',
            'type' => 'int',
            'description' => 'Node ID.',
            'optional' => FALSE,
            'source' => 'data',
          ),
        ),
      ),

      /**
       * Retrieve map fields content.
       * Returns an XML string.
       */
      'getMap' => array(
        'help' => 'Retrieve map data (XML)',
        'access arguments' => array('access content'),
        'access callback' => 'user_access',
        'access arguments append' => FALSE,
        'callback' => 'graphmind_service_get_map',
        'file' => array('type' => 'inc', 'module' => 'graphmind_service'),
        'return' => 'string',
        'args' => array(
          array(
            'name' => 'entity_vid',
            'type' => 'int',
            'description' => 'Entity VID',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'delta',
            'type' => 'int',
            'description' => 'Delta of the entity instance',
            'optional' => FALSE,
            'source' => 'data',
          ),
          array(
            'name' => 'field_name',
            'type' => 'string',
            'description' => 'Field of the entity',
            'optional' => FALSE,
            'source' => 'data',
          ),
        ),
      ),
      
    ),
  );
}


/**
 * Implementation of hook_menu().
 * @return array
 */
function graphmind_service_menu() {
  return array(
    'admin/config/graphmind' => array(
      'title' => t('Graphmind'),
      'page callback' => 'system_admin_menu_block_page',
      'access arguments' => array('administer site configuration'),
      'file' => 'system.admin.inc',
      'file path' => drupal_get_path('module', 'system'),
      'position' => 'right',
      'description' => 'Graphmind related settings',
    ),
    'admin/config/graphmind/settings' => array(
      'type' => MENU_NORMAL_ITEM,
      'title' => t('Graphmind settings'),
      'page callback' => 'drupal_get_form',
      'page arguments' => array('graphmind_service_admin_form'),
      'access arguments' => array('administer site configuration'),
      'description' => 'Global Graphmind settings',
    ),
  );
}


/**
 * Form for admin settings.
 * @return array
 */
function graphmind_service_admin_form() {
  $form = array(
    'graphmind_default_endpoint' => array(
      '#type' => 'textfield',
      '#title' => t('Default enpoint URL part'),
      '#description' => t('This endpoint has to be defined as a service with AMFServer.'),
      '#weight' => 1,
      '#default_value' => variable_get('graphmind_default_endpoint'),
    ),
  );

  return system_settings_form($form);
}

/**
 * Implementation of hook_theme().
 * @return array
 */
function graphmind_service_theme($existing, $type, $theme, $path) {
  return array(
    'graphmind_service_flash_container' => array(
      'path' => $path . '/template',
      'template' => 'flash_container',
      'variables' => array(
        'entity_id' => NULL, 
        'entity_vid' => NULL, 
        'delta' => NULL, 
        'field_name' => NULL,
        'entity_type' => NULL,
        'item' => NULL,
        'update_access' => NULL,
      ),
    ),
  );
}

/**
 * Custom access handler.
 *
 * @param array $permissions
 * @param string $function
 * @return boolean
 */
function graphmind_service_access($permissions, $function = 'user_access', $args = NULL) {
  if (!function_exists($function)) {
    watchdog(WATCHDOG_CRITICAL, 'Undefined function in graphmind_service_access(): ' . $function, NULL);
    return FALSE;
  }

  if (empty($permissions)) {
    watchdog(WATCHDOG_WARNING, 'Missing graphmind_service_access() variables.', NULL);
    return FALSE;
  }

  if (!is_array($permissions)) {
    $permissions = array($permissions);
  }

  if ($function == 'entity_access' && $permissions[0] == 'update') {
    $entity = entity_load_single($args[4], (int)$args[0]);
    return entity_access($permissions[0], $args[4], $entity);
  }

  foreach ($permissions as $permission) {
    if (!$function($permission)) {
      return FALSE;
    }
  }
  
  return TRUE;
}


/**
 * Implementation of hook_graphmind_flashvars_alter().
 *
 * @param array $flashvars
 */
function graphmind_service_graphmind_flashvars_alter(&$flashvars) {
  global $base_path;
  $path = drupal_get_path('module', 'graphmind_service');

  $flashvars['iconDir']  = $base_path . $path . '/graphmind/icons/';
  $flashvars['basePath'] = url('<front>', array('absolute' => TRUE));
  $flashvars['endPoint'] = variable_get('graphmind_default_endpoint');
}

/**
 * Implementation of hook_menu()
 *
 * @return array
 */
function graphmind_service_permission() {
  $permissions = array();
  
  $features = graphmind_service_get_available_features();
  foreach ((array)$features as $key => $feature) {
    $permissions[GRAPHMIND_SERVICE_FEATURE_PERM_PREFIX . $key] = array(
      'title' => $feature,
    );
  }

  $plugins = graphmind_service_get_available_plugins();
  foreach ((array)$plugins as $key => $plugin) {
    $permissions[GRAPHMIND_SERVICE_PLUGIN_PERM_PREFIX . $key] = array(
      'title' => $plugin['name'],
    );
  }
  
  return $permissions;
}

/**
 * Get available features.
 *
 * @return array
 */
function graphmind_service_get_available_features() {
  $features = array();
  $features = module_invoke_all('graphmind_features');
  
  return $features;
}

/**
 * Implementation of hook_graphmind_features().
 *
 * @return array
 */
function graphmind_service_graphmind_features() {
  return array(
    'nodeInfo' => t('Node information panel'),
    'createMindmapNode' => t('Create mindmap node'),
    'loadDrupalNode' => t('Load Drupal node'),
    'removeNode' => t('Remove mindmap node'),
    'attributes' => t('Using node attributes'),
    'connections' => t('Remote site connections'),
    'tooltips' => t('Show tooltips on Drupal related nodes'),
  );
}


/**
 * Returns the default set of features.
 * 
 * @return array
 */
function graphmind_service_get_default_features() {
  return array(
    'nodeInfo',
    'createMindmapNode',
    'loadDrupalNode',
    'removeNode',
    'attributes'
  );
}

/**
 * Get all the available plugins provided be modules.
 * 
 * @return array
 */
function graphmind_service_get_available_plugins() {
  return module_invoke_all('graphmind_plugin_info');
}

/**
 * Implementation of hook_field_info().
 * 
 * @return array
 */
function graphmind_service_field_info() {
  return array(
    'graphmind' => array(
      'label' => t('Graphmind map field'),
      'description' => t('XML map data for Graphmind'),
      'settings' => array(),
      'instance_settings' => array(
        'plugins' => NULL,
        'features' => NULL,
        // Extarnal modules can hook into.
        'settings' => NULL,
      ),
      'default_widget' => 'graphmind_widget',
      'default_formatter' => 'graphmind_default_formatter',
    ),
  );
}

/**
 * Implements of hook_field_instance_settings_form().
 * 
 * @param array $field
 * @param array $instance
 * @return array 
 */
function graphmind_service_field_instance_settings_form($field, $instance) {
  $form = array();

  $default_sets = array();
  $plugins = graphmind_service_get_available_plugins();
  $default_sets['plugins'] = array();
  if ($plugins) {
    foreach ((array)$plugins as $key => $plugin) {
      if (user_access(GRAPHMIND_SERVICE_PLUGIN_PERM_PREFIX . $key)) {
        $default_sets['plugins'][$plugin['plugin']] = $plugin['name'];
      }
    }
  }
  $default_sets['features'] = graphmind_service_graphmind_features();
  foreach ((array)$default_sets['features'] as $key => $feature) {
    if (!user_access(GRAPHMIND_SERVICE_FEATURE_PERM_PREFIX . $key)) {
      unset($default_sets['features'][$key]);
    }
  }

  // Set up plugins and features form sections.
  foreach ($default_sets as $set => $default_set) {
    if (!empty($default_set)) {
      $form[$set] = array(
        '#title' => t('Available @type', array('@type' => $set)),
        '#type' => 'checkboxes',
        '#options' => $default_set,
      );
      if (!empty($instance['settings'][$set])) {
        if (!is_array($instance['settings'][$set])) {
          $instance['settings'][$set] = unserialize($instance['settings'][$set]);
        }
        $form[$set]['#default_value'] = $instance['settings'][$set];
      }
      else if ($instance['settings'][$set] === NULL) {
        $form[$set]['#default_value'] = array_keys($default_set);
      }
    }
  }

  drupal_alter('graphmind_service_field_instance_settings_form', $form, $instance);

  return $form;
}

/**
 * Implementation of hook_field_is_empty().
 * 
 * @param array $item
 * @param array $field 
 * @return boolean
 */
function graphmind_service_field_is_empty($item, $field) {
  return !isset($item['is_used']) || !$item['is_used'];
}

/**
 * Implementation of hook_field_widget_info().
 * 
 * @return array 
 */
function graphmind_service_field_widget_info() {
  return array(
    'graphmind_widget' => array(
      'label' => t('Graphmind map'),
      'description' => t('Field collection of the Graphmind map'),
      'field types' => array('graphmind'),
      'settings' => array(),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
        'default value' => FIELD_BEHAVIOR_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_field_widget_form().
 * 
 * @param array $form
 * @param array $form_state
 * @param array $field
 * @param object $instance
 * @param string $langcode
 * @param array $items
 * @param integer $delta
 * @param object $element
 * @return string 
 */
function graphmind_service_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  if ($instance['widget']['type'] == 'graphmind_widget') {
    // This checkbox is used to indicate if the field is used -> it can be used
    // even when it's empty.
    $is_used = TRUE;

    if ($field['cardinality'] != 1) {
      $is_used = $delta < count($items);
    }

    $element['is_used'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use this map'),
      '#default_value' => $is_used,
      '#description' => t('Turning off this box will remove all stored property of this map.'),
    );

    $element['map'] = array(
      '#type' => 'textarea',
      '#title' => t('Graphmind map XML'),
      '#description' => t('This field can only contain a valid Freemind XML. You can import your Freemind content by copying the raw XML to this textarea.'),
      '#default_value' => isset($items[$delta]['map']) ? $items[$delta]['map'] : '',
    );
    
    if (!isset($items[$delta])) {
      $items[$delta] = array();
    }

    $default_sets = array();
    $plugins = graphmind_service_get_available_plugins();
    $default_sets['plugins'] = array();
    if ($plugins) {
      foreach ((array)$plugins as $key => $plugin) {
        if (user_access(GRAPHMIND_SERVICE_PLUGIN_PERM_PREFIX . $key) && $instance['settings']['plugins'][$plugin['plugin']]) {
          $default_sets['plugins'][$plugin['plugin']] = $plugin['name'];
        }
      }
    }
    $default_sets['features'] = graphmind_service_graphmind_features();
    foreach ((array)$default_sets['features'] as $key => $feature) {
      if (!user_access(GRAPHMIND_SERVICE_FEATURE_PERM_PREFIX . $key) || !$instance['settings']['features'][$key]) {
        unset($default_sets['features'][$key]);
      }
    }

    // Set up plugins and features form sections.
    foreach ($default_sets as $set => $default_set) {
      if (!empty($default_set)) {
        $element[$set] = array(
          '#title' => t('Default values for @type', array('@type' => $set)),
          '#type' => 'checkboxes',
          '#options' => $default_set,
        );
        if (!empty($items[$delta][$set])) {
          if (!is_array($items[$delta][$set])) {
            $items[$delta][$set] = unserialize($items[$delta][$set]);
          }
          $element[$set]['#default_value'] = $items[$delta][$set];
        }
      }
    }

    // Settings will be the main container for form items provided by external
    // modules.
    $element['settings'] = array(
      '#type' => 'value',
    );

    // Add a chance to other modules to populate the setting forms.
    drupal_alter('graphmind_service_field_widget_form', $element, $items[$delta], $instance);

    return $element;
  }
}

/**
 * Implementation of hook_field_formatter_info(). 
 * 
 * @return array
 */
function graphmind_service_field_formatter_info() {
  return array(
    'graphmind_default_formatter' => array(
      'label' => t('Grapmind map formatter'),
      'description' => t('Formatter for the map field'),
      'field types' => array('graphmind'),
      'settings' => array(),
    ),
  );
}

/**
 * Implementation of hook_field_formatter_view().
 * 
 * @param string $entity_type
 * @param object $entity
 * @param array $field
 * @param object $instance
 * @param string $langcode
 * @param array $items
 * @param array $display
 * @return array
 */
function graphmind_service_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  
  $access = entity_access('update', $entity_type, $entity);
  
  // Extract field entity data.
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  
  if ($display['type'] == 'graphmind_default_formatter') {
    foreach ((array)$items as $delta => $item) {
      $element[$delta] = array(
        '#markup' => theme('graphmind_service_flash_container', array(
          'entity_id' => $id, 
          'entity_vid' => $vid, 
          'delta' => $delta,
          'field_name' => $field['field_name'],
          'entity_type' => $entity_type,
          'item' => $item,
          'update_access' => $access,
        )),
      );
    }
  }
  
  return $element;
}

/**
 * Preprocess hook for the graphmind_service_flash_container theme config.
 * 
 * @param array $vars
 * @param object $entity
 * @param integer $delta 
 */
function graphmind_service_preprocess_graphmind_service_flash_container(&$vars) {
  $flashvars = array(
    'entity_id' => $vars['entity_id'],
    'entity_vid' => $vars['entity_vid'],
    'delta' => $vars['delta'],
    'field_name' => $vars['field_name'],
    'entity_type' => $vars['entity_type'],
    'update_access' => $vars['update_access'] ? 1 : 0,
  );

  foreach (array('plugins', 'features') as $setting) {
    if ($vars['item'][$setting]) {
      $unserialized_setting = unserialize($vars['item'][$setting]);
      $flashvars[$setting] = join(',', array_keys(array_filter($unserialized_setting)));
    }
  }
  
  drupal_alter('graphmind_flashvars', $flashvars, $vars['item']);
  array_walk($flashvars, create_function('&$item, $key', '$item = urlencode($key) . "=" . urlencode($item);'));
  
  $vars['flashvars'] = join('&', $flashvars);
  $vars['swf_source'] = base_path() . drupal_get_path('module', 'graphmind_service') . '/graphmind/GraphMind.swf';
  
  $vars['id'] = "graphmind_map_{$vars['entity_id']}_{$vars['entity_vid']}_{$vars['delta']}";
  $vars['class'] = 'graphmind_map';
}

/**
 * Implementation of hook_field_storage_pre_insert().
 * 
 * @param string $entity_type
 * @param object $entity
 * @param array $skip_fields 
 */
function graphmind_service_field_storage_pre_insert($entity_type, $entity, &$skip_fields) {
  _graphmind_service_flatten_entity_field_data($entity_type, $entity);
}

/**
 * Implementation of hook_field_storage_pre_update().
 * 
 * @param string $entity_type
 * @param object $entity
 * @param array $skip_fields 
 */
function graphmind_service_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
  _graphmind_service_flatten_entity_field_data($entity_type, $entity);
}

/**
 * Helper function to serialize field data.
 * 
 * @param string $entity_type
 * @param object $entity 
 */
function _graphmind_service_flatten_entity_field_data($entity_type, $entity) {
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

  foreach (field_info_instances($entity_type, $bundle) as $instance) {
    if ($instance['widget']['type'] == 'graphmind_widget') {
      foreach ((array)$entity->{$instance['field_name']} as $language => $field_instances) {
        foreach ((array)$entity->{$instance['field_name']}[$language] as $delta => $entity_setting_field) {
          // External plugins can put their settings into the 'settings' section.
          drupal_alter('graphmind_service_field_attach_collect_settings', $entity_setting_field, $entity, $entity_type);

          foreach (array('plugins', 'features', 'settings') as $setting_field) {
            if (isset($entity_setting_field[$setting_field])) {
              if (is_array($entity_setting_field[$setting_field])) {
                $serialized_field = serialize($entity_setting_field[$setting_field]);
                $entity->{$instance['field_name']}[$language][$delta][$setting_field] = $serialized_field;
              }
            }
            else {
              $entity->{$instance['field_name']}[$language][$delta][$setting_field] = '';
            }
          }

          // Override map in case external modules changed it.
          if (isset($entity_setting_field['map'])) {
            $entity->{$instance['field_name']}[$language][$delta]['map'] = $entity_setting_field['map'];
          }

          if (empty($entity->{$instance['field_name']}[$language][$delta]['map'])) {
            $entity->{$instance['field_name']}[$language][$delta]['map'] = '';
          }
        }
      }
    }
  }
}

/**
 * Implements hook_entity_property_info().
 *
 * @return array
 */
function graphmind_service_entity_property_info() {
  $info = array();

  $fields = field_info_fields();
  foreach ((array)$fields as $field_name => $field) {
    if ($field['type'] != 'graphmind') {
      continue;
    }

    foreach ((array)$field['bundles'] as $bundle_name => $bundle) {
      foreach ((array)$bundle as $field_delta => $bundle_type) {
        $info[$bundle_name]['bundles'][$bundle_type]['properties'][$field_name . '__map'] = array(
          'description' => t('GraphMind map'),
          'label' => t('GraphMind map'),
          'type' => 'text',
          'field' => TRUE,
          'required' => FALSE,
          'setter callback' => 'graphmind_service_entity_metadata_property_set',
        );

        $definition = &$info[$bundle_name]['bundles'][$bundle_type]['properties'];
        drupal_alter('graphmind_service_entity_wrapper_definition', $definition, $field_name);
      }
    }
  }
   
  return $info;
}

/**
 * Implements hook_graphmind_service_entity_wrapper_definition_alter().
 * Optional additions to the graphmind field property info declaration.
 *
 * @param array $definition
 * @param string $field_name
 */
function graphmind_service_graphmind_service_entity_wrapper_definition_alter(&$definition, $field_name) {
  foreach ((array)graphmind_service_get_available_plugins() as $plugin) {
    $definition[$field_name . '__plugins__' . $plugin['plugin']] = array(
      'description' => t('GraphMind plugin: @plugin', array('@plugin' => $plugin['name'])),
      'label' => $plugin['name'],
      'type' => 'boolean',
      'field' => TRUE,
      'required' => FALSE,
      'setter callback' => 'graphmind_service_entity_metadata_property_set',
    );
  }
  
  foreach ((array)graphmind_service_graphmind_features() as $feature_id => $feature_name) {
    $definition[$field_name . '__features__' . $feature_id] = array(
      'description' => t('GraphMind feature: @feature', array('@feature' => $feature_name)),
      'label' => $feature_name,
      'type' => 'boolean',
      'field' => TRUE,
      'required' => FALSE,
      'setter callback' => 'graphmind_service_entity_metadata_property_set',
    );
  }
}

/**
 * Setter callback for basic graphmind entity field properties.
 *
 * @param stdClass $entity
 * @param string $name
 * @param string $value
 * @param string $langcode
 * @param string $entity_type
 */
function graphmind_service_entity_metadata_property_set($entity, $name, $value, $langcode, $entity_type) {
  list($field_name, $type, $var_name) = explode('__', $name);

  switch ($type) {
    case 'plugins':
    case 'features':
      if ($value) {
        if (!isset($entity->{$field_name})) {
          $entity->{$field_name} = array(
            $langcode => array(array()),
          );
        }

        foreach ((array)$entity->{$field_name}[$langcode] as $delta => $entity_instance) {
          if (isset($entity->{$field_name}[$langcode][$delta][$type])) {
            $setting = unserialize($entity->{$field_name}[$langcode][$delta][$type]);
          }
          else {
            $setting = array();
          }
          $setting[$var_name] = $var_name;
          $entity->{$field_name}[$langcode][$delta][$type] = serialize($setting);
        }
      }
      break;
    case 'map':
      foreach ((array)$entity->{$field_name}[$langcode] as $delta => $entity_instance) {
        $entity->{$field_name}[$langcode][$delta]['map'] = $value;
      }
      break;
  }
}
