<?php

/**
 * @file
 * Drush commands for Archibald.
 */

/**
 * Implements hook_drush_command().
 */
function archibald_drush_command() {
  return array(
    'archibald-create-vocabulary' => array(
      'description' => dt("Create the vocabularies required for Archibald to work, namely the Languages and the Terms of use vocabularies."),
      'aliases' => array('acv'),
      'arguments' => array(
        'vocabulary' => dt("The vocabulary to create. Can be either 'licenses' or 'languages'. If not provided, the command will create both."),
      ),
      'options' => array(
        'force' => dt("Whether to force the creation of the vocabularies. This will merge the existing terms with the new ones, based on their archibald_license_id field for licenses, and archibald_lang_iso_code field for languages."),
      ),
      'callback' => 'archibald_drush_create_vocabularies',
    ),
    'archibald-libraries-setup' => array(
      'description' => dt("Helper function to download and setup the different required libraries."),
      'aliases' => array('als'),
      'arguments' => array(
        'location' => dt("The location to which the libraries should be downloaded. Defaults to sites/all/libraries."),
      ),
      'options' => array(
        'force' => dt("Whether to force the download of the libraries. This removes existing libraries and replace them with fresh downloads."),
      ),
      'callback' => 'archibald_drush_libraries_setup',
    ),
    'archibald-quick-setup' => array(
      'description' => dt("Helper function to quickly setup Archibald. Used for development! Not recommended for production."),
      'aliases' => array('aqs'),
      'options' => array(
        'default-partner-private-key' => dt("The location of the private key for the default partner."),
        'default-partner-passphrase' => dt("The passphrase for the private key of the default partner."),
        'default-partner-username' => dt("The default partner username."),
        'default-partner-display-name' => dt("The default partner display name."),
        'private-file-system' => dt("The location of the private file system. Defaults to sites/default/files/.private_files. If a private file system already exists, this command will not override it, unless this option is explicitly specified."),
      ),
      'callback' => 'archibald_drush_quick_setup',
    ),
  );
}

/**
 * Create vocabularies necessary for Archibald to function correctly.
 *
 * @param string $vocabulary
 *    (optional) The type of vocabulary to create. Can be either "licenses" or
 *    "languages". If no type is provided, it will create both.
 * @param bool $force
 *    (optional) Whether to force the creation of the vocabulary items, which
 *    means overriding existing ones of necessary. Defaults to false.
 */
function archibald_drush_create_vocabularies($type = NULL, $force = FALSE) {
  // This function can be called outside of Drush. Check if we are in context
  // of Drush or not.
  $is_drush = function_exists('drush_get_option');

  // Already fetch the translation function based on this knowledge.
  $t = $is_drush ? 'dt' : get_t();

  // Check the type argument.
  if (isset($type) && !in_array($type, array('licenses', 'languages'))) {
    $message = $t('"@type" is not a valid vocabulary type. Please choose between "licenses" and "languages".', array('@type' => $type));

    if ($is_drush) {
      return drush_set_error($message);
    }
    else {
      drupal_set_message($message, 'error');
    }
  }

  // Do we have to force the creation of vocabularies?
  $force = $is_drush ? !!drush_get_option('force', $force) : $force;

  if (isset($type)) {
    $type = array($type);
  }
  else {
    $type = array('licenses', 'languages');
  }

  // Check if these vocabularies were already created.
  foreach ($type as $i => $vocabulary) {
    if (variable_get("archibald_{$vocabulary}_vocabulary_created", FALSE)) {
      // Do we have to force? If so, put up a warning message. If not, remove it
      // and put up a message as well.
      if ($force) {
        drupal_set_message($t("The !vocabulary vocabulary already exists. The new data will be merged with the existing.", array(
          '!vocabulary' => $vocabulary,
        )), 'warning');
      }
      else {
        unset($type[$i]);

        $message = $t("The !vocabulary vocabulary already exists, and will be skipped.", array(
          '!vocabulary' => $vocabulary,
        ));

        if ($is_drush) {
          $message .= ' ' . $t("You can use the --force option to force the creation of this vocabulary.");
        }

        drupal_set_message($message, 'warning');
      }
    }
  }

  // First check the languages. Do we have to create them?
  if (in_array('languages', $type)) {
    $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_description_languages');

    // We need to create a set of vocabulary terms. We base this on the
    // language list that Drupal provides out-of-the-box in includes/iso.inc.
    include_once DRUPAL_ROOT . '/includes/iso.inc';

    // The following languages are active by default. All other are not.
    $active_languages = array(
      'none',
      'fr',
      'de',
      'en',
      'it',
      'rm',
    );

    // Load the full list Drupal provides, and add our own, LOM-CH specific
    // ones.
    foreach (
      // Make the None language the first.
      array('none' => array('None')) +
      // Add the standards.
      _locale_get_predefined_list() +
      // Add the Rhaeto-Romance ones.
      array(
        'rm2907' => array('Rhaeto-Romance Puter'),
        'rm2908' => array('Rhaeto-Romance Rumantsch grischun'),
        'rm16068' => array('Rhaeto-Romance Surmiran'),
        'rm16069' => array('Rhaeto-Romance Sursilvan'),
        'rm13312' => array('Rhaeto-Romance Sutsilvan'),
        'rm2906' => array('Rhaeto-Romance Vallader'),
      )
    as $code => $names) {
      // We need check if a term already exists. If so, update it. If not,
      // create one.
      $query = new EntityFieldQuery();
      $result = $query->entityCondition('entity_type', 'taxonomy_term')
                      ->entityCondition('bundle', 'archibald_lomch_description_languages')
                      ->fieldCondition('archibald_lang_iso_code', 'value', $code, '=')
                      ->execute();

      if (!empty($result['taxonomy_term'])) {
        // There should only be one, but if by some crazy coincidence, there
        // happen to be more...
        foreach ($result['taxonomy_term'] as $term) {
          // Fully load the term.
          $term = taxonomy_term_load($term->tid);
          $term->name = $names[0];
          $term->archibald_term_status[LANGUAGE_NONE][0]['value'] = (int) in_array($code, $active_languages);
          taxonomy_term_save($term);
        }
      }
      else {
        taxonomy_term_save((object) array(
          'vid' => $vocabulary->vid,
          // We only want the English name, which is the first key.
          'name' => $names[0],
          'archibald_lang_iso_code' => array(
            'und' => array(
              array(
                'value' => $code,
              ),
            ),
          ),
          'archibald_term_status' => array(
            'und' => array(
              array(
                'value' => (int) in_array($code, $active_languages),
              ),
            ),
          ),
        ));
      }
    }

    variable_set("archibald_languages_vocabulary_created", time());
  }

  // Now check the licenses. Do we have to create them?
  if (in_array('licenses', $type)) {
    $vocabulary = taxonomy_vocabulary_machine_name_load('archibald_lomch_description_licenses');

    // We need to create a set of vocabulary terms. We base this on the
    // licenses list that the Ontology server provides. Notice we *cannot* turn
    // this into a "archibald_lomch_ontology_vocabulary" field, as the
    // rights_licenses Ontology vocabulary does not follow the same standard as
    // all other vocabularies. It is not parsable in the same way. So, we do it
    // like this instead (for now?)
    // @todo This should use the new API, and bypass the Ontology server!! But
    //       the API doesn't have this vocabulary yet. Check it out.
    $ontology_url = 'http://ontology.biblio.educa.ch/json/rights_licenses';
    $json = drupal_http_request($ontology_url);

    if (!empty($json->status_message) && $json->status_message == 'OK' && !empty($json->data)) {
      $data = json_decode($json->data);
      foreach ($data->terms as $term_id => $term_data) {
        // We need check if a term already exists. If so, update it. If not,
        // create one.
        $query = new EntityFieldQuery();
        $result = $query->entityCondition('entity_type', 'taxonomy_term')
                        ->entityCondition('bundle', 'archibald_lomch_description_licenses')
                        ->fieldCondition('archibald_license_id', 'value', $term_id, '=')
                        ->execute();

        if (!empty($result['taxonomy_term'])) {
          // There should only be one, but if by some crazy coincidence, there
          // happen to be more...
          foreach ($result['taxonomy_term'] as $term) {
            // Fully load the term.
            $term = taxonomy_term_load($term->tid);

            // Set the term status.
            $term->archibald_term_status[LANGUAGE_NONE][0]['value'] = (int) empty($term_data->deprecated);

            // Set the term URL.
            $term->archibald_license_url[LANGUAGE_NONE][0]['value'] = empty($term_data->url) ? '' : trim($term_data->url);

            // Localize and update.
            _archibald_drush_localize_taxonomy_term($term, $term_data, $term_id);
          }
        }
        else {
          $term = (object) array(
            'vid' => $vocabulary->vid,
            'translations' => (object) array(
              'original' => language_default('language'),
            ),
            'archibald_license_id' => array(
              'und' => array(
                array(
                  'value' => $term_id,
                ),
              ),
            ),
            'archibald_term_status' => array(
              'und' => array(
                array(
                  'value' => (int) empty($term_data->deprecated),
                ),
              ),
            ),
            'archibald_license_url' => array(
              'und' => array(
                array(
                  'value' => empty($term_data->url) ? '' : trim($term_data->url),
                ),
              ),
            ),
          );

          // Localize and save.
          _archibald_drush_localize_taxonomy_term($term, $term_data, $term_id);
        }
      }

      variable_set("archibald_licenses_vocabulary_created", time());
    }
    else {
      drupal_set_message(
        $t("Couldn't create the Terms of use vocabulary terms. Fetching the data from the Ontology server failed. Please check the terms under !link and add them by hand if needed.", array(
          '!link' => l("admin/structure/taxonomy/archibald_lomch_description_licenses", "admin/structure/taxonomy/archibald_lomch_description_licenses"),
        )),
        'error'
      );
    }
  }

  if ($is_drush) {
    drupal_set_message($t("Finished importing vocabularies."));
  }
}

/**
 * Download required libraries.
 *
 * Helper function to download the required libraries in the correct locations.
 * This code is heavily inspired by the Cloud Zoom module.
 *
 * @param string $location
 *    (optional) Where to download the libraries. Defaults to
 *    'sites/all/libraries'.
 */
function archibald_drush_libraries_setup($location = 'sites/all/libraries') {
  $force = !!drush_get_option('force', FALSE);
  return _archibald_drush_libraries_setup(archibald_libraries_info(), $location, $force);
}

/**
 * Real implementation of the download function.
 *
 * We split the function from the Drush command, so we can re-use it in other
 * contexts (see modules/curricula/drush/archibald_curricula.drush.inc).
 *
 * @param array $libraries
 *    A list of libraries to download. It uses a format similar to what the
 *    Libraries API provides. Each library requires a "drush download url" key,
 *    which allows the function to download a specific version. It is different
 *    from the standard Libraries "download url", which is a page where a user
 *    can navigate to to download a package.
 * @param string $location
 *    Where to download the libraries.
 * @param bool $force
 *    (optional) Whether to replace existing libraries or not. Defaults to
 *    false.
 */
function _archibald_drush_libraries_setup($libraries, $location, $force = FALSE) {
  $path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/' . $location;

  drush_log(dt("Download destination path: @path", array('@path' => $path)), 'ok');

  if (!is_dir($path)) {
    if (!drush_shell_exec('mkdir -p ' . $path)) {
      return drush_set_error(dt("Directory @path does not exist, but could not be created.", array('@path' => $path)));
    }
    else {
      drush_log(dt("Directory @path was created", array('@path' => $path)), 'ok');
    }
  }

  // Store the old working directory.
  $old_cwd = getcwd();
  chdir($path);

  // Iterate over every library, and trigger the download.
  foreach ($libraries as $dir => $library_info) {
    if (is_dir("$path/$dir") && !$force) {
      drush_log(dt("The directory @dir already exists. Skipping the download of @library_name. Try using --force if you wish to overwrite the existing one.", array(
        '@dir' => "$path/$dir",
        '@library_name' => $library_info['name'],
      )), 'ok');
    }
    else {
      // Download the library.
      $filename = @end(explode('/', $library_info['drush download url']));
      if (!drush_shell_exec('wget ' . $library_info['drush download url'])) {
        drush_shell_exec('curl -O ' . $library_info['drush download url']);
      }

      if (is_file($filename)) {
        // Decompress the zip archive.
        drush_shell_exec('unzip -qq -o ' . $filename);

        // Remove the zip archive.
        drush_op('unlink', $filename);

        // Remove the previous folder.
        if ($force) {
          drush_shell_exec("rm -r $dir");
        }

        // Rename the unzipped archive. The format when downloading from Github
        // is [repo-name]-[version]. Remove the extension, and prefix with the
        // library name (dir, in our case).
        $folder_name = $dir . '-' . preg_replace('/\.(zip|tar\.gz)$/', '', preg_replace('/^v?/', '', $filename));
        if (!drush_shell_exec("mv $folder_name $dir")) {
          drush_log(dt("Drush was unable to find the unzipped library code for @library_name. Skipping.", array('@library_name' => $library_info['name'])), 'error');
        }
        elseif (!is_dir($dir)) {
          drush_log(dt("Drush was unable to unzip the library code for @library_name. Skipping.", array('@library_name' => $library_info['name'])), 'error');
        }
        else {
          drush_log(dt("@zip was extracted. @library_name has been successfully installed in @dir", array(
            '@zip' => $filename,
            '@dir' => "$path/$dir",
            '@library_name' => $library_info['name'],
          )), 'ok');
        }
      }
      else {
        drush_log(dt("Drush was unable to download or unzip the library code for @library_name. Skipping.", array('@library_name' => $library_info['name'])), 'error');
      }
    }
  }

  // Go back to the original working directory.
  chdir($old_cwd);
}

/**
 * Helper command for quickly setting up Archibald.
 *
 * This command is NOT recommended to run on a production site!
 */
function archibald_drush_quick_setup() {
  // Put up a warning.
  if (!drush_confirm(dt("Warning! This command is intended for development installations, and is not recommended on production sites! Are you sure you want to continue?"))) {
    return;
  }

  // Start with the private file system.
  $private_filesystem = drush_get_option('private-file-system', FALSE);
  if (!$private_filesystem) {
    // Do we need to pass in the default value? If we already have a private
    // file system, this is not necessary.
    if (variable_get('file_private_path', 0)) {
      drush_log(dt("Private file system exists. Skipping."), 'ok');
    }
    else {
      // Set the default.
      $private_filesystem = 'sites/default/files/.private_files';
    }
  }

  if ($private_filesystem) {
    module_load_include('inc', 'system', 'system.admin');

    $private_filesystem = drush_get_context('DRUSH_DRUPAL_ROOT') . '/' . $private_filesystem;
    if (!is_dir($private_filesystem)) {
      if (!drush_shell_exec('mkdir -p ' . $private_filesystem)) {
        drush_log(dt("Couldn't create @path!", array('@path' => $private_filesystem)), 'error');
      }
      else {
        // Give public access. It will be adapted by Drupal later, so
        // we can safely do this.
        drush_shell_exec('chmod 2777 ' . $private_filesystem);
      }
    }

    $form_state = array(
      'values' => array(
        'file_private_path' => $private_filesystem,
      ),
    );
    drupal_form_submit('system_file_system_settings', $form_state);
    drush_log(dt("Private file system set to @path.", array('@path' => $private_filesystem)), 'ok');
  }

  // Do we need to create a new default partner?
  $private_key = drush_get_option('default-partner-private-key', FALSE);
  $partner_username = drush_get_option('default-partner-username', FALSE);
  $partner_display_name = drush_get_option('default-partner-display-name', FALSE);

  if (
    $private_key && file_exists($private_key) &&
    $partner_username &&
    $partner_display_name
  ) {
    if (!variable_get('file_private_path', 0)) {
      drush_log(dt("Couldn't create a partner, because there's no private file system. Try using the --private-file-system option."), 'error');
    }
    else {
      // If we just set the private filesystem, Drupal won't know of it yet.
      // Refresh the stream wrappers.
      $cache = &drupal_static('file_get_stream_wrappers');
      $cache = NULL;
      file_get_stream_wrappers();

      // "Upload" the private key.
      $filename = trim(drupal_basename($private_key), '.');
      $real_filename = uniqid() . '-' . $filename;
      copy($private_key, variable_get('file_private_path', 0) . '/' . $real_filename);

      // Store it in the system.
      $file = new stdClass();
      $file->uid = 1;
      $file->status = 1;
      $file->filename = $filename;
      $file->filemime = file_get_mimetype($filename);
      $file->uri = 'private://' . $real_filename;
      $file = file_save($file);

      // If there already is a default partner, disable it.
      $partner = archibald_load_default_partner();
      if ($partner) {
        // Update it.
        $partner->is_default = 0;

        // Save it.
        $wrapper = entity_metadata_wrapper('archibald_partner', $partner);
        $wrapper->save();
      }

      // Create the new partner.
      $partner = entity_create('archibald_partner', array());
      $partner->name = "{$partner_display_name} ({$partner_username})";
      $partner->is_default = 1;
      $partner->passphrase = drush_get_option('default-partner-passphrase', '');

      // Save it.
      $wrapper = entity_metadata_wrapper('archibald_partner', $partner);
      $wrapper->partner_display_name->set($partner_display_name);
      $wrapper->partner_username->set($partner_username);
      $wrapper->partner_privatekey->file->set($file);
      $wrapper->save();

      drush_log(dt("Created default partner @name.", array('@name' => $partner_username)), 'ok');
    }
  }
  else {
    drush_log(dt("There's no sufficient information for a default partner. Skipping."), 'warning');
  }

  if (drush_confirm(dt("Do you wish to import the vocabularies while you're at it?"))) {
    archibald_drush_create_vocabularies();
  }
}

/**
 * Helper function for localizing taxonomy terms via Entity Translation.
 *
 * @param object $term
 *    The taxonomy term to localize.
 * @param object $term_data
 *    The taxonomy term data from the Ontology server.
 * @param string $term_id
 *    The taxonomy term ID from the Ontology server.
 */
function _archibald_drush_localize_taxonomy_term($term, $term_data, $term_id) {
  // Fetch all enabled languages. If we try to set a translation for a language
  // that doesn't exist, we will get an error.
  $languages = language_list();

  // Make sure we get the correct source language. If the default language
  // changed since the last import, we will mess things up.
  $source_language = isset($term->translations->original) ?
    $term->translations->original :
    language_default('language');

  // Did the source language get disabled? Could happen, and has happened.
  if (!isset($languages[$source_language])) {
    unset($term->translations->data[$source_language]);
    $term->translations->original = $source_language = language_default('language');

    // Does the term already exist (should, in our case; but you never know)?
    if (!empty($term->tid)) {
      taxonomy_term_save($term);
      // Reload it fully.
      $term = taxonomy_term_load($term->tid);
    }
  }
  elseif (empty($term->translations->original)) {
    // This one was seriously messed up. Set the source again.
    $term->translations->original = $source_language;

    // Does the term already exist (should, in our case; but you never know)?
    if (!empty($term->tid)) {
      taxonomy_term_save($term);
      // Reload it fully.
      $term = taxonomy_term_load($term->tid);
    }
  }

  // First, get the "base" name. We preferably want the current language one,
  // otherwise Title gets confused. For that we use the Utils class. If it's not
  // available, we fallback to a hacky method, or to the English name, or term
  // Ontology ID, as a last resort.
  if (class_exists('\Educa\DSB\Client\Utils')) {
    $name = \Educa\DSB\Client\Utils::getLSValue(
      $term_data->name,
      archibald_language_fallback_list()
    );
    $term->name_field[$source_language][0]['value'] = \Educa\DSB\Client\Utils::getLSValue(
      $term_data->name,
      array_merge(array($source_language), archibald_language_fallback_list())
    );
  }
  elseif (!$name = @$term_data->name->{$source_language}) {
    if (!$name = @$term_data->name->en) {
      $name = $term_id;
    }
  }
  $term->name = $name;

  // If we don't have a term ID, it means this is a new term. We cannot
  // translate a term that doesn't have an ID, because we use
  // Entity Translation.
  if (empty($term->tid)) {
    taxonomy_term_save($term);
    // Reload it fully.
    $term = taxonomy_term_load($term->tid);
  }

  $handler = entity_translation_get_handler('taxonomy_term', $term, TRUE);
  foreach ($term_data->name as $langcode => $value) {
    // Skip langcodes we don't know about.
    if (
      empty($langcode) ||
      !isset($languages[$langcode]) ||
      $langcode == $source_language
    ) {
      continue;
    }

    $values = array();
    $translation = array(
      'translate' => 0,
      'status' => 1,
      'language' => $langcode,
      'source' => $source_language,
    );
    $values['name_field'][$langcode][0]['value'] = $value;
    $handler->setTranslation($translation, $values);
  }
  taxonomy_term_save($term);
}
