<?php

require_once  "api/tripal_ws.api.inc";
require_once  "includes/TripalWebService.inc";
require_once  "includes/TripalWebServiceResource.inc";
require_once  "includes/TripalWebServiceCollection.inc";


/**
 * Implements hook_init()
 */
function tripal_ws_init() {
  global $base_url;

  $version = 'v0.1';
  $api_url = $base_url . '/ws/' . $version;

  // Following the WC3 Hydra documentation, we want to add  LINK to the header
  // of the site that indicates where the API documentation can be found.
  // This allows a hydra-enabled client to discover the API and use it.
  $attributes = array(
    'rel' => 'http://www.w3.org/ns/hydra/core#apiDocumentation',
    'href' => $api_url . '/ws-doc/',
  );
  drupal_add_html_head_link($attributes, $header = FALSE);
}

/**
 * Implements hook_menu().
 * Defines all menu items needed by Tripal Core
 *
 * @ingroup tripal_ws
 */
function tripal_ws_menu() {

  // Web Services API callbacks.
  $items['ws'] = array(
    'title' => 'Tripal Web Services API',
    'page callback' => 'tripal_ws_services',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  // Web Services API callbacks.
  $items['web-services'] = array(
    'title' => 'Tripal Web Services API',
    'page callback' => 'tripal_ws_get_services',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

  $items['remote/%/%/%/%'] = array(
    'page callback' => 'tripal_ws_load_remote_entity',
    'page arguments' => array(1, 2, 3, 4),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

  // Tripal Web Services setting groups
  $items['admin/tripal/storage/ws'] = array(
    'title' => 'Remote Tripal Sites',
    'description' => t("Create mashups of content using data from this site and remote Tripal sites."),
    'weight' => 20,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer tripal'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/tripal/storage/ws/tripal_sites'] = array(
    'title' => 'Configuration',
    'description' => t('Provides information about other Tripal sites.
        This allows data exchange and communication betwen Tripal
        enabled sites through the web services.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('tripal_ws_tripal_sites_form'),
    'access arguments' => array('administer tripal'),
    'type' => MENU_NORMAL_ITEM,
    'weight' => 0,
    'file' => 'includes/tripal_ws.admin.inc',
    'file path' => drupal_get_path('module', 'tripal_ws'),
  );
  $items['admin/tripal/storage/ws/tripal_sites/edit'] = array(
    'title' => 'Add Tripal Site',
    'description' => 'Add a Tripal site',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('tripal_ws_tripal_sites_edit_form'),
    'access arguments' => array('administer tripal'),
    'file' =>  'includes/tripal_ws.admin.inc',
    'file path' => drupal_get_path('module', 'tripal_ws'),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 2
  );
  $items['admin/tripal/storage/ws/tripal_sites/remove/%'] = array(
    'title' => 'Remove Tripal Site',
    'description' => 'Remove a Tripal site',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('tripal_ws_tripal_sites_remove_form', 6),
    'access arguments' => array('administer tripal'),
    'file' =>  'includes/tripal_ws.admin.inc',
    'file path' => drupal_get_path('module', 'tripal_ws'),
    'type' => MENU_CALLBACK,
    'weight' => 2
  );
  return $items;
}

/**
 * The callback function for all RESTful web services.
 *
 */
function tripal_ws_get_services() {
  global $base_url;
  $service_path = $base_url . '/web-services';

  drupal_add_http_header('Content-Type', 'application/json');

  try {
    $ws_path = func_get_args();
    $args = $_GET;
    unset($args['q']);

    // The web services should never be cached.
    drupal_page_is_cacheable(FALSE);

    // The Tripal web services bath will be:
    // [base_path]/web-services/[service name]/v[major_version].[minor_version]
    $matches = array();
    $service = '';
    $major_version = '';
    $minor_version = '';
    $list_services = FALSE;

    // If there is no path then we should list all of the services available.
    if (empty($ws_path)) {
      tripal_ws_list_services();
      return;
    }
    // A service path will have the service name in $ws_path[0] and the
    // version in $ws_path[1].  If we check that the version is correctly
    // formatted then we can look for the service class and invoke it.
    else if (preg_match("/^v(\d+)\.(\d+)$/", $ws_path[1], $matches)) {
      $service_type = $ws_path[0];
      $major_version = $matches[1];
      $minor_version = $matches[2];
      $service_version = 'v' . $major_version . '.' . $minor_version;
    }
    // If the URL doesn't match then return not found.
    else {
      throw new Exception("Unsupported service URL.  Web service URLs must be of the following format:  ");
    }

    // Get the service that matches the service_name
    $service = NULL;
    $services = tripal_get_web_services();
    foreach ($services as $service_class) {
      tripal_load_include_web_service_class($service_class);
      if ($service_class::$type == $service_type) {
        $service = new $service_class($service_path);
        if ($service->getVersion() == $service_version) {
          break;
        }
        $service = NULL;
      }
    }
    // If a service was not provided then return an error.
    if (!$service) {
      throw new Exception('The service type, "' . $service_type . '", is not available');
    }

    // Adjust the path to remove the service type and the version.
    $adj_path = $ws_path;
    array_shift($adj_path);
    array_shift($adj_path);

    // Now call the service to handle the request.
    $service->setPath($adj_path);
    $service->setParams($args);
    $service->handleRequest();
    $response = $service->getResponse();
    print drupal_json_encode($response);

  }
  catch (Exception $e) {
    $service = new TripalWebService($service_path);
    $service->setError($e->getMessage());
    $response = $service->getResponse();
    print drupal_json_encode($response);
  }
}

/**
 * Generates the list of services as the "home page" for Tripal web services.
 */
function tripal_ws_list_services() {
  global $base_url;
  $base_path = $base_url . '/web-services';

  // Create an instance of the TriaplWebService class and use it to build
  // the entry point for the web serivces.
  $service = new TripalWebService($base_path);

  // Get the list of web service classes.
  $services = tripal_get_web_services();

  // Create the parent resource which is a collection.
  $resource = new TripalWebServiceResource($base_path);
  $resource->addContextItem('entrypoint', 'hydra:entrypoint');
  $resource->setType('entrypoint');

  // Now add the member to the collection
  foreach ($services as $service_class) {
    tripal_load_include_web_service_class($service_class);
    $service = new $service_class($base_path);
    $version = $service->getVersion();
    $resource->addContextItem($service_class::$type, '');
    $resource->addProperty($service_class::$type, $service->getServicePath());
  }

  // For discoverability add the document webservice.
  $service->setResource($resource);
  $response = $service->getResponse();
  print drupal_json_encode($response);



}
/**
 * The callback function for all RESTful web services.
 *
 */
function tripal_ws_services() {
  $ws_path = func_get_args();
  $params = $_GET;
  unset($params['q']);

  // The web services should never be cached.
  drupal_page_is_cacheable(FALSE);

  // Using the provided version number, determine which web services
  // verion to call.
  $version = array_shift($ws_path);
  if ($version and preg_match('/v\d+\.\d+/', $version)) {

    $api_url = 'ws/' . $version;

    // Add the file with the appropriate web services.
    module_load_include('inc', 'tripal_ws', 'includes/tripal_ws.rest_' . $version);
    $version = preg_replace('/\./', '_', $version);
    $function = 'tripal_ws_services_' . $version;
    $response = array();
    if (function_exists($function)) {
      $response = $function($api_url, $ws_path, $params);
    }
  }
  else {
    // TODO: What do we do if no version is provided?
  }

  drupal_add_http_header('Content-Type', 'application/json');
  print drupal_json_encode($response);
}

/**
 *
 * @param $entities
 * @param $type
 */
function tripal_ws_entity_load($entities, $type) {
  foreach ($entities as $entity) {

  }
}

/**
 *
 * @param $site_id
 * @param $api_version
 * @param $ctype
 * @param $id
 *
 * @return
 */
function tripal_ws_load_remote_entity($site_id, $api_version, $ctype, $id) {

  // Get the content type on this site
  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
  $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
  $term = reset($term);
  $vocab = $term->vocab;

  $query = db_select('tripal_sites', 'ts');
  $query->fields('ts');
  $query->condition('id', $site_id);
  $site = $query->execute()->fetchObject();

  if (!$site) {
    return 'Could not find specified site.';
  }

  // Get the content from the web services of the remote site.
  $url = $site->url . "/ws/v0.1/content/" . $ctype . "/" . $id;
  $json = file_get_contents($url);
  $response = json_decode($json, TRUE);

  // Set the title for this page to match the title provided.
  drupal_set_title($response['label']);

  // Attribute this data to the proper source.
  $source_url = l($response['label'], $response['ItemPage'], array('attributes' => array('target' => '_blank')));
  $content = '<div><strong>Source:</strong> ' . $site->name . ': ' . $source_url . '</div>';

  // Fake an entity so we can display this content using the same
  // entity type on this site.
  $entity = new TripalEntity(array(), 'TripalEntity');
  $entity->id = 807;
  $entity->type = 'TripalEntity';
  $entity->bundle = $bundle->name;
  $entity->term_id = $term->id;
  $entity->title = $response['label'];
  $entity->uid = 1;
  $entity->status = 1;

  // Get the fields and create a list of those that are attached to the bundle.
  $fields = field_info_fields();
  $my_fields = array();
  foreach ($fields as $field) {
    if (isset($field['bundles']['TripalEntity'])) {
      foreach ($field['bundles']['TripalEntity'] as $bundle_name) {
        if ($bundle_name == $bundle->name) {
          $my_fields[] = $field;
        }
      }
    }
  }

  // Add in the value for the 'content_type' field.
  $entity->content_type = array();
  $entity->content_type['und'][0]['value'] = $bundle->label;

  // For each field we know about that should be attached to our bundle,
  // see if we can find a corresponding entry in the results returned from
  // the web service call. If so, then add the field to our fake entity.
  foreach ($my_fields as $field) {
    // Get the semantic web term for this field.
    $field_name = $field['field_name'];
    $settings = $field['settings'];

    // If the field does not have a semantic web mapping, then skip it.
    if (!isset($settings['semantic_web'])) {
      continue;
    }

    // Convert the term into it's db and accession elements and look it up
    // for more details.
    list($vocabulary, $accession) = explode(':', $settings['semantic_web']);
    $term = tripal_get_term_details($vocabulary, $accession);

    // Convert the term to lowercase and remove spaces so we can compare
    // correctly.
    $term_name = strtolower(preg_replace('/ /', '_', $term['name']));

    // TODO: check for the term in the response makes the assumption
    // that the term is the same on both sides. This may not be true. The
    // acutal vocab and accession for both terms should be compared.
    if (isset($response[$term_name])) {

      // If this field is of type '@id' then this links out to another
      // URL where that information can be retrieved. We'll have to
      // handle that separately.
      if (isset($response['@context'][$term_name]['@type']) and
          $response['@context'][$term_name]['@type'] == '@id') {
        $subquery = json_decode(file_get_contents($response[$term_name]), TRUE);

        // If the result is a collection then we want to add each value with
        // it's own delta value.
        if (array_key_exists('@type', $subquery) and $subquery['@type'] == 'Collection') {
          $i = 0;
          $f = array();
          foreach ($subquery['member'] as $member) {
            $f['und'][$i]['value'] = $member;
            $i++;
          }
          $entity->$field_name = $f;
        }
        // If the result is not a collection then just add it.
        else {
          unset($subquery['@context']);
          unset($subquery['@id']);
          $f = array();
          $f['und'][0]['value'] = $subquery;
          $entity->$field_name = $f;
        }
      }
      // For all fields that are currently attached, add the field and
      // value to the entity.
      else {
        $f = array();
        $f['und'][0]['value'] = $response[$term_name];
        $entity->$field_name = $f;
      }
    }
  }

  // Generate the View for this entity
  $entities = array();
  $entities[] = $entity;
  $view = entity_view('TripalEntity', $entities);
  $content .= drupal_render($view['TripalEntity'][807]);

  return $content;

}