<?php

/**
 * @file
 * LingotekSync
 */

/**
 * A utility class for Lingotek Syncing.
 */
class LingotekSync {

  const STATUS_CURRENT = 'CURRENT';  // The node or target translation is current
  const STATUS_EDITED = 'EDITED';    // The node has been edited, but has not been uploaded to Lingotek
  const STATUS_FAILED = 'FAILED';    // The node or target translation has failed during processing
  const STATUS_PENDING = 'PENDING';  // The target translation is awaiting to receive updated content from Lingotek
  const STATUS_READY = 'READY';      // The target translation is complete and ready for download
  const STATUS_TARGET = 'TARGET';    // A target node is being used to store a translation (ignored for upload by Lingotek)
  const STATUS_UNTRACKED = 'UNTRACKED'; // A translation was discovered that is not currently managed by Lingotek
  const STATUS_TARGET_LOCALIZE = 'TARGET_LOCALIZE'; // A localization must be made of the source before uploading to Lingotek
  const STATUS_TARGET_EDITED = 'TARGET_EDITED'; // A localized version is ready for uploading to Lingotek
  const STATUS_NON_LINGOTEK = 'NON_LINGOTEK'; // Translations exist but the translation_agent_id != 3 (Not owned by Lingotek)

  const PROFILE_CUSTOM = 'CUSTOM';
  const PROFILE_DISABLED = 'DISABLED';
  const PROFILE_CONFIG = 'CONFIG';
  const PROFILE_AUTOMATIC = 0;
  const PROFILE_MANUAL = 1;

  public static function getTargetStatus($doc_id, $lingotek_locale) {
    $key = 'target_sync_status_' . $lingotek_locale;
    if ($chunk_id = LingotekConfigSet::getIdByDocId($doc_id)) {
      return LingotekConfigSet::getTargetStatusById($chunk_id, $lingotek_locale);
    }
    else {
      list($entity_id, $entity_type) = self::getEntityIdFromDocId($doc_id);
      return lingotek_keystore($entity_type, $entity_id, $key);
    }
    LingotekLog::error('Did not find any local info for Lingotek Doc ID "@id"', array('@id' => $doc_id));
    return FALSE;
  }

  public static function getTargetStatusOptions() {
    return array(
      'STATUS_CURRENT' => self::STATUS_CURRENT,
      'STATUS_EDITED' => self::STATUS_EDITED,
      'STATUS_FAILED' => self::STATUS_FAILED,
      'STATUS_PENDING' => self::STATUS_PENDING,
      'STATUS_READY' => self::STATUS_READY,
      'STATUS_TARGET' => self::STATUS_TARGET,
      'STATUS_UNTRACKED' => self::STATUS_UNTRACKED,
      'STATUS_TARGET_LOCALIZE' => self::STATUS_TARGET_LOCALIZE,
      'STATUS_TARGET_EDITED' => self::STATUS_TARGET_EDITED,
    );
  }

  public static function getAllTargetStatusNotCurrent($nid) {
    $query = db_select('lingotek_entity_metadata', 'l')
        ->fields('l', array('entity_key', 'value'))
      ->condition('entity_type', 'node')
      ->condition('entity_key', 'target_sync_status_%', 'LIKE')
      ->condition('value', 'CURRENT', '!=')
      ->condition('entity_id', $nid);
    $result = $query->execute()->fetchAll();
    return $result;
  }

  public static function setTargetStatus($entity_type, $entity_id, $lingotek_locale, $status, $update_on_dup = TRUE) {
    $key = 'target_sync_status_' . $lingotek_locale;
    return lingotek_keystore($entity_type, $entity_id, $key, $status, $update_on_dup);
  }
  
  public static function setAllTargetStatus($entity_type, $entity_id, $status) {
    $query = db_update('lingotek_entity_metadata')
        ->condition('entity_type', $entity_type)
      ->condition('entity_id', $entity_id)
      ->condition('entity_key', 'target_sync_status%', 'LIKE')
      ->fields(array('value' => $status, 'modified' => time()))
        ->execute();
  }

  public static function setNodeStatus($node_id, $status) {
    return lingotek_keystore('node', $node_id, 'upload_status', $status);
  }

  public static function getSyncProjects() {
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->fields('l', array('value'));
    $query->condition('entity_type', 'node');
    $query->condition('entity_key', 'project_id');
    $query->distinct();
    $result = $query->execute();
    $projects = $result->fetchCol();
    $default_project_id = variable_get('lingotek_project', NULL);
    if (!is_null($default_project_id) && !in_array($default_project_id, $projects)) {
      $projects[] = $default_project_id;
    }
    return $projects;
  }

  public static function insertTargetEntriesForAllSets($lingotek_locale) {
    // insert/update a target language for all chunks
    $query = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta', array('id'))
        ->groupBy('id');
    $ids = $query->execute()->fetchCol();

    foreach ($ids as $i) {
      $set = LingotekConfigSet::loadById($i);
      if (is_object($set)) {
        $set->setTargetsStatus(self::STATUS_PENDING, $lingotek_locale);
      }
    }
  }

  public static function insertTargetEntriesForAllEntities($lingotek_locale) {
    // insert/update a target language for all entities
    $query = db_select('lingotek_entity_metadata', 'meta')
        ->fields('meta', array('entity_id', 'entity_type'))
        ->condition('meta.entity_key', 'document_id');
    $entities = $query->execute()->fetchAll();

    foreach ($entities as $e) {
      self::setTargetStatus($e->entity_type, $e->entity_id, $lingotek_locale, self::STATUS_PENDING);
    }
  }

  // Remove the node sync target language entries from the lingotek table lingotek_delete_target_sync_status_for_all_nodes
  public static function deleteTargetEntriesForAllEntities($lingotek_locale) {
    $keys = array(
      'target_sync_status_' . $lingotek_locale,
    );
    db_delete('lingotek_entity_metadata')->condition('entity_key', $keys, 'IN')->execute();
  }

  public static function deleteTargetEntriesForAllChunks($lingotek_locale) {
    $key = 'target_sync_status_' . $lingotek_locale;
    db_delete('lingotek_config_metadata')->condition('config_key', $key)->execute();
  }

  public static function deleteTargetEntriesForAllDocs($lingotek_locale) {
    self::deleteTargetEntriesForAllEntities($lingotek_locale);
    self::deleteTargetEntriesForAllChunks($lingotek_locale);
  }
  
  
  /**
   * getDocIdTargetsByStatus
   * 
   * @param status (e.g., LingotekSync::READY)
   * 
   * @return an array of associate arrays.  Each associate array will have a 'nid' (e.g., 5), 'locale' (e.g., 'de_DE'), and optionally 'doc_id' (e.g., 46677222-b5ec-47d5-880e-24632feffaf5)
   */
  public static function getTargetsByStatus($entity_type, $status, $include_doc_ids = FALSE) {
    $target_language_search = '%';
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->fields('l', array('entity_id', 'entity_key', 'value'));
    $query->condition('entity_type', $entity_type);
    $query->condition('entity_key', 'target_sync_status_' . $target_language_search, 'LIKE');
    $query->condition('value', $status);
    $result = $query->execute();
    $records = $result->fetchAll(); //$result->fetchAllAssoc('nid');

     // build nid_doc_map (if needed)
    if ($include_doc_ids) {
      $nid_doc_map = array();
      foreach ($records as $record) {
        if (!key_exists($record->entity_id, $nid_doc_map)) {
          $doc_id = self::getDocIdFrom($record->entity_id);
          $nid_doc_map[$record->entity_id] = $doc_id;
        }
      }
    }
    $targets = array();
    foreach ($records as $record) {
      $doc_target = array(
        'id' => $record->entity_id,
        'doc_id' => $include_doc_ids ? $nid_doc_map[$record->entity_id] : NULL,
        'locale' => str_replace('target_sync_status_', '', $record->entity_key)
      );
      $targets[] = $doc_target;
    }

    return $targets;
  }

  public static function getDownloadableReport() {
    $document_ids = array_unique(array_merge(self::getConfigDocIdsByStatus(self::STATUS_PENDING), self::getConfigDocIdsByStatus(self::STATUS_READY)));

    $report = array(
      'download_targets_workflow_complete' => array(), // workflow complete and ready for download
      'download_targets_workflow_complete_count' => 0,
      'download_workflow_complete_targets' => array(),
      'download_targets_workflow_incomplete' => array(), // not workflow complete (but download if wanted)
      'download_targets_workflow_incomplete_count' => 0,
      'download_workflow_incomplete_targets' => array(),
    );
    if (empty($document_ids))
      return $report; // if no documents are PENDING, then no need to make the API call.
    $response = lingotek_update_config_progress($document_ids);

    $locales = lingotek_get_target_locales();
    foreach ($document_ids as $document_id) {
      if (!$document_id) {
        continue;
      }
      foreach ($locales as $locale) {
        if (isset($response->byDocumentIdAndTargetLocale->$document_id->$locale)) {
          $target_status = self::getTargetStatus($document_id, $locale);
          $doc_target = array(
            'document_id' => $document_id,
            'locale' => $locale,
            'percent_complete' => $response->byDocumentIdAndTargetLocale->$document_id->$locale,
          );
          if ($target_status == self::STATUS_READY) {
            $report['download_targets_workflow_complete'][] = $doc_target;
            $report['download_targets_workflow_complete_count']++;
          }
          elseif ($target_status == self::STATUS_PENDING) {
            $report['download_targets_workflow_incomplete'][] = $doc_target;
            $report['download_targets_workflow_incomplete_count']++;
          }
        }
      }
    }
    return $report;
  }

   /**
   * Sums the values of the arrays be there keys (PHP 4, PHP 5)
   * array array_sum_values ( array array1 [, array array2 [, array ...]] )
   */
  public static function arraySumValues() {
    $return = array();
    $intArgs = func_num_args();
    $arrArgs = func_get_args();
    if ($intArgs < 1) {
      trigger_error('Warning: Wrong parameter count for arraySumValues()', E_USER_WARNING);
    }

    foreach ($arrArgs as $arrItem) {
      if (!is_array($arrItem)) {
        trigger_error('Warning: Wrong parameter values for arraySumValues()', E_USER_WARNING);
      }
      foreach ($arrItem as $k => $v) {
        if (!key_exists($k, $return)) {
          $return[$k] = 0;
        }
        $return[$k] += $v;
      }
    }
    return $return;

    $sumArray = array();
    foreach ($myArray as $k => $subArray) {
      foreach ($subArray as $id => $value) {
        $sumArray[$id]+=$value;
      }
    }
    return $sumArray;
  }

  public static function getSourceCounts($lingotek_locale) {
    $total = 0;
    $managed_entities = lingotek_managed_entity_types();
    $response = array();
    $response['types'] = array();
    foreach(array_keys($managed_entities) as $entity_type){
      $entity_type_count = self::getEntitySourceCount($lingotek_locale, $entity_type);
      $response['types'][$entity_type] = $entity_type_count;
      $total += $entity_type_count;
    }
    $response['total'] = $total;
    return $response;
  }

  public static function getEntitySourceCount($lingotek_locale, $entity_type = NULL) {
    $managed_entities = lingotek_managed_entity_types();
    $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale);
    $q = array();
    $total_count = 0;
    foreach ($managed_entities as $m_entity_type => $properties) {
      if (!is_null($entity_type) && $entity_type != $m_entity_type) {
        continue;
      }
      $entity_base_table = $properties['base table'];
      $query = db_select($entity_base_table, 't')->condition('t.language', $drupal_language_code);

      // exclude translation sets (only for nodes)
      if ($entity_base_table == 'node') {
        $tnid_query = db_or();
        $tnid_query->condition('t.tnid', 0);
        $tnid_query->where('t.tnid = t.nid');
        $query->condition($tnid_query);
      }

      // exclude disabled entities (including those that have disabled bundles)
      $disabled_entities = lingotek_get_entities_by_profile_and_entity_type(LingotekSync::PROFILE_DISABLED, $entity_type);
      if (count($disabled_entities)) {
        $disabled_entity_ids = array();
        array_walk($disabled_entities, function($a) use (&$disabled_entity_ids) {
          $disabled_entity_ids[] = $a['id'];
        });
        $enabled_entity_ids = lingotek_get_enabled_entities_by_type($entity_type);
        if (count($disabled_entity_ids) < count($enabled_entity_ids)) {
          $query->condition($properties['entity keys']['id'], array_merge(array(-1), $disabled_entity_ids), "NOT IN"); //exclude disabled entities
        }
        else {
          $query->condition($properties['entity keys']['id'], array_merge(array(-1), $enabled_entity_ids), "IN"); //include only eabled entities
        }
      }

      $count = $query->countQuery()->execute()->fetchField();
      $total_count += $count;
    }
    return $total_count;
  }

  public static function getCountsByStatus($status, $lingotek_locale) {
    $total = 0;
    $managed_entities = lingotek_managed_entity_types();
    $response = array();
    $response['types'] = array();
    foreach(array_keys($managed_entities) as $entity_type){
      $entity_type_count = self::getEntityTargetCountByStatus($status, $lingotek_locale, $entity_type);
      $response['types'][$entity_type] = $entity_type_count;
      $total += $entity_type_count;
    }
    $response['total'] = $total;
    return $response;
  }

  /**
   * Get a count of translation targets by entity and status.
   *
   * @param mixed $status
   *   a string or array of strings containing the desired status(es) to count
   * @param string $lingotek_locale
   *   the desired locale to count
   * @param string $entity_type
   *   the desired entity type to count
   * @return array
   */
  public static function getEntityTargetCountByStatus($status, $lingotek_locale, $entity_type = NULL) {
    if (!is_array($status)) {
      $status = array(-1, $status);
    }
    $managed_entities = lingotek_managed_entity_types();
    $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale);
    $target_prefix = 'target_sync_status_';
    $target_key = $target_prefix . $lingotek_locale;
    $q = array();
    $total_count = 0;
    foreach($managed_entities as $m_entity_type => $properties){
      if(!is_null($entity_type) && $entity_type != $m_entity_type){
        continue;
      }
      $entity_base_table = $properties['base table'];
      $query = db_select($entity_base_table, 't');
      $query->leftJoin('lingotek_entity_metadata', 'l', 'l.entity_id = '.$properties['entity keys']['id'].
       ' AND l.entity_type = \''.$m_entity_type.'\''.
       ' AND l.entity_key = \''.$target_key.'\' '
      );

      // exclude translation sets (only for nodes)
      if ($entity_base_table == 'node') {
        $tnid_query = db_or();
        $tnid_query->condition('t.tnid', 0);
        $tnid_query->where('t.tnid = t.nid');
        $query->condition($tnid_query);
      }

      // exclude disabled nodes (including those that have disabled bundles)
      $disabled_entities = lingotek_get_entities_by_profile_and_entity_type(LingotekSync::PROFILE_DISABLED, $entity_base_table);
      if (!empty($disabled_entities)) {
        $disabled_entity_ids = array();
        array_walk($disabled_entities, function($a) use (&$disabled_entity_ids) {
          $disabled_entity_ids[] = $a['id'];
        });
        $query->condition("t.".$properties['entity keys']['id'], $disabled_entity_ids, "NOT IN"); //exclude disabled entities
      }

      $query->condition('l.value', $status, 'IN');
      $count = $query->countQuery()->execute()->fetchField();
      $total_count += $count;
    }
    return $total_count;
  }

  public static function getTargetNodeCountByStatus($status, $lingotek_locale) {
    $target_prefix = 'target_sync_status_';
    $target_key = $target_prefix . $lingotek_locale;

    $query = db_select('lingotek_entity_metadata', 'l')->fields('l');
    $query->condition('entity_type', 'node');
    $query->condition('entity_key', $target_key);
    $query->condition('value', $status);
    $result = $query->countQuery()->execute()->fetchAssoc();

    $count = 0;
    if (is_array($result)) {
      $count = array_shift($result);
    }

    // count nodes having this language as the source as current
    if ($status == LingotekSync::STATUS_CURRENT) {
      $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale, TRUE);
      $query = db_select('node', 'n');
      $query->leftJoin('lingotek_entity_metadata', 'l', 'l.entity_id = n.nid
        AND l.entity_type = \'node\'
        AND l.entity_key = \'profile\'
           AND l.value != \'DISABLED\'');
      $query->condition('n.language', $drupal_language_code);
      $query->addExpression('COUNT(*)', 'cnt');
      $result = $query->execute()->fetchField();
      $count += $result;
    }
    return $count;
  }

  public static function getTargetChunkCountByStatus($status, $lingotek_locale) {
    $target_prefix = 'target_sync_status_';
    $target_key = $target_prefix . $lingotek_locale;

    $query = db_select('lingotek_config_metadata', 'l')->fields('l');
    $query->condition('value', $status);
    $query->condition('config_key', $target_key);

    $count = 0;
    $result = $query->countQuery()->execute()->fetchAssoc();
    if (is_array($result)) {
      $count = array_shift($result);
    }
    return $count;
  }

  //lingotek_count_all_targets
  public static function getTargetCountByStatus($status, $lingotek_locale) {

    $count = 0;

    // get the count of nodes
    $count += self::getTargetNodeCountByStatus($status, $lingotek_locale);

    // get the count of config chunks (turned off for now)
    /*
    if (variable_get('lingotek_translate_config', 0)) {
      $count += self::getTargetChunkCountByStatus($status, $lingotek_locale);
    }
     */
    return $count;
  }

  public static function getTargetCountByDocumentIds($document_ids) {
    if (empty($document_ids)) {
      return;
    }
    if (!is_array($document_ids)) {
      $document_ids = array($document_ids);
    }
    $subquery = db_select('lingotek_entity_metadata', 'l1')
        ->fields('l1', array('entity_id'))
      ->condition('l1.entity_type', 'node')
      ->condition('l1.entity_key', 'document_id')
      ->condition('l1.value', $document_ids, 'IN');
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->addField('l', 'entity_id', 'nid');
    $query->condition('l.entity_type', 'node');
    $query->condition('l.entity_key', 'target_sync_status_%', 'LIKE');
    $query->condition('l.entity_id', $subquery, 'IN');
    $query->addExpression('COUNT(l.entity_key)', 'targets');

    $query->groupBy('l.entity_id');
    $result = $query->execute()->fetchAllAssoc('nid');
    return $result;
  }

  public static function getETNodeIds() { // get nids for entity_translation nodes that are not lingotek pushed
    $types = lingotek_translatable_node_types(); // get all translatable node types 
    $et_content_types = array();
    foreach ($types as $type) {
      if (lingotek_managed_by_entity_translation($type)) { // test if lingotek_managed_by_entity_translation
        $et_content_types[] = $type;
      }
    }
    if (empty($et_content_types))
      return array();

    $nodes = entity_load('node', FALSE, array('type' => $et_content_types)); // select nodes with et types
    $et_node_ids = array();
    foreach ($nodes as $node) {
      if (!lingotek_node_pushed($node)) {
        $et_node_ids[] = $node->nid;
      }
    }
    return $et_node_ids;
  }

  protected static function getQueryCompletedConfigTranslations($drupal_codes) {
    // return a query object that contains all fully-translated/current strings
    // or ones that were not translated by Lingotek.
    // use the first addtl language as the query's base.
    $first_lang = array_shift($drupal_codes);
    $lingotek_id = LingotekConfigSet::getLingotekTranslationAgentId();
    $primary_or = db_or()
        ->condition('lt0.i18n_status', 0)
        ->condition('lt0.translation_agent_id', $lingotek_id, '!=');
    $query = db_select('locales_target', "lt0")
        ->fields('lt0', array('lid'))
        ->condition('lt0.language', $first_lang)
        ->condition($primary_or);
    $addtl_joins = 0;
    foreach ($drupal_codes as $new_join) {
      // join a new instance of locales_target for each target language
      // where an entry for the language exists for the given lid and
      // it is "current" (ie. i18n_status field is set to 0)
      $addtl_joins++;
      $ja = "lt$addtl_joins"; // join alias
      $join_str = "$ja.lid = lt0.lid and $ja.language = '$new_join' and ($ja.i18n_status = 0 or $ja.translation_agent_id != $lingotek_id)";
      $query->join('locales_target', $ja, $join_str);
    }
    return $query;
  }

  public static function getConfigSet($set_id) {
    // return LingotekConfigSet object containing all segments
    // for the given set id.
    return LingotekConfigSet::loadById($set_id);
  }

  public static function getDirtySetLids() {
    // return the list of all lids from the locale_source table *not* fully translated
    $source_language = language_default();
    if (!isset($source_language->lingotek_locale)) {
      $source_language->lingotek_locale = Lingotek::convertDrupal2Lingotek($source_language->language);
    }
    $lingotek_codes = Lingotek::getLanguagesWithoutSource($source_language->lingotek_locale);
    if (!count($lingotek_codes)) {
      LingotekLog::error('No languages configured for this Lingotek account.', array());
      return array();
    }
    // get the drupal language for each associated lingotek locale
    $drupal_codes = array();
    foreach ($lingotek_codes as $lc) {
      $drupal_codes[] = Lingotek::convertLingotek2Drupal($lc);
    }
    // get the list of all segments that need updating
    // that belong to the textgroups the user wants translated
    $textgroups = array_merge(array(-1), LingotekConfigSet::getTextgroupsForTranslation());
    $max_length = variable_get('lingotek_config_max_source_length', LINGOTEK_CONFIG_MAX_SOURCE_LENGTH);
    $query = db_select('locales_source', 'ls');
    $query->fields('ls', array('lid'))
        ->condition('ls.source', '', '!=')
        ->condition('ls.lid', self::getQueryCompletedConfigTranslations($drupal_codes), 'NOT IN')
        ->where('length(ls.source) < ' . (int) $max_length);
    if (in_array('misc', $textgroups)) {
      $or = db_or();
      $or->condition('ls.textgroup', $textgroups, 'IN');
      $or->where("ls.textgroup NOT IN ('default','menu','taxonomy','views','blocks','field')");
      $query->condition($or);
    }
    else {
      $query->condition('ls.textgroup', $textgroups, 'IN');
    }
    return $query->execute()->fetchCol();
  }

  public static function getDirtyConfigChunks() {
    // return the set of chunk IDs, which are the chunks that contain
    // lids that are in need of some translation.  These IDs are calculated
    // as the segment ID of the first segment in the chunk, divided by
    // the configured chunk size.  So, segments 1 through [chunk size] would
    // be in chunk #1, etc.
    $lids = self::getDirtySetLids();
    $chunk_ids = array();
    foreach ($lids as $lid) {
      $id = LingotekConfigSet::getSetId($lid);
      if (array_key_exists($id, $chunk_ids)) {
        $chunk_ids[$id]++;
      }
      else {
        $chunk_ids[$id] = 1;
      }
    }
    $chunk_ids = self::pruneChunksWithPendingTranslations($chunk_ids);

    return $chunk_ids;
  }

  protected static function getChunksWithPendingTranslations() {
    // get the list of chunks with pending translations
    $result = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta', array('id', 'id'))
        ->condition('config_key', 'target_sync_status_%', 'LIKE')
        ->condition('value', array(self::STATUS_PENDING, self::STATUS_READY), 'IN')
        ->distinct()
        ->execute();
    return $result->fetchAllKeyed();
  }

  protected static function pruneChunksWithPendingTranslations($chunk_ids) {
    // return the chunk_ids not in the pending set
    $final_chunks = array_diff_key($chunk_ids, self::getChunksWithPendingTranslations());
    return $final_chunks;
  }

  public static function getUploadableReport() {
    // Handle configuration chunks
    $report = array();
    if (variable_get('lingotek_translate_config')) {
      $config_chunks_to_update = self::getDirtyConfigChunks();
      $num_updates = count($config_chunks_to_update);
      $report = array(
        'upload_config' => (array_keys($config_chunks_to_update)),
        'upload_config_count' => $num_updates,
        );
    }
    return $report;
  }

  public static function getReport() {
    $report = array_merge(
        self::getUploadableReport(), self::getDownloadableReport()
    );
    return $report;
  }

  public static function getNodeIdsByStatus($status, $source) {
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->condition('entity_type', 'node');
    $query->addField('l', 'entity_id', 'nid');
    if($source) {
      $query->condition('entity_key', 'upload_status');
    } else {
      $query->condition('entity_key', 'target_sync_status_%', 'LIKE');
    }
    $query->condition('value', $status);
    $query->distinct();
    $result = $query->execute();
    $nids = $result->fetchCol();
    return $nids;
  }

  public static function getEntityIdsToUpload($entity_type) {
    $info = entity_get_info($entity_type);
    $id_key = $info['entity keys']['id'];
    $query = db_select($info['base table'], 'base');
    $query->addField('base', $id_key);
    $query->leftJoin('lingotek_entity_metadata', 'upload', 'upload.entity_id = base.' . $id_key . ' and upload.entity_type =\'' . $entity_type . '\' and upload.entity_key = \'upload_status\'');

    if ($entity_type == 'node') {
      // Exclude any target nodes created using node-based translation.
      $tnid_query = db_or();
      $tnid_query->condition('base.tnid', 0);
      $tnid_query->where('base.tnid = base.nid');
      $query->condition($tnid_query);
    }

    $or = db_or();
    $or->condition('upload.value', LingotekSync::STATUS_EDITED);
    $or->isNull('upload.value');
    $query->condition($or);

    $result = $query->execute()->fetchCol();
    return $result;
  }

  public static function getConfigSetIdsToUpload() {
    $query = db_select('lingotek_config_metadata', 'lcm')
        ->fields('lcm', array('id'))
        ->condition('config_key', 'upload_status')
        ->condition('value', LingotekSync::STATUS_EDITED);
    $set_ids = $query->execute()->fetchCol();
    return $set_ids;
  }

  /*
   * Get the entities, by ID, by target status
   * @param $entity_type
   *   a string of the entity type
   * @param $status
   *   a string containing the desired status
   * @param $locales
   *   (optional) an array containing the locales to check
   * 
   * @return an array of IDs
   */
  public static function getEntityIdsByTargetStatus($entity_type, $status, $locales = NULL) {
    if ($locales === NULL) {
      $verb = 'LIKE';
      $target_locales = 'target_sync_status_%';
    }
    else {
      $verb = 'IN';
      $target_locales = array();
      foreach ($locales as $l) {
        $target_locales[] = 'target_sync_status_' . $l;
      }
    }
    $query = db_select('lingotek_entity_metadata', 'l')
        ->distinct()
      ->condition('entity_type', $entity_type)
      ->condition('entity_key', $target_locales, $verb)
      ->condition('value', $status);
    $query->addField('l', 'entity_id', 'nid');
    $result = $query->execute()->fetchCol();
    return $result;
  }

  public static function getEntityIdsByProfileStatus($entity_type, $status) {
    $query = db_select('lingotek_entity_metadata', 'l')
        ->distinct()
      ->condition('entity_type', $entity_type)
      ->condition('entity_key', 'profile')
      ->condition('value', $status);
    $query->addField('l', 'entity_id', 'nid');
    $result = $query->execute()->fetchCol();
    return $result;
  }

  public static function getConfigDocIdsByStatus($status) {
    $doc_ids = array();

    if (variable_get('lingotek_translate_config', 0)) {
      // retrieve document IDs from config chunks
      $cids = self::getChunkIdsByStatus($status);
      if (!empty($cids)) {
        $query = db_select('lingotek_config_metadata', 'meta');
        $query->fields('meta', array('value'));
        $query->condition('config_key', 'document_id');
        $query->condition('id', $cids);
        $result = $query->execute();
        $doc_ids = array_merge($doc_ids, $result->fetchCol());
      }
    }

    return $doc_ids;
  }

  public static function getSetIdsFromLids($lids) {
    if (empty($lids)) {
      return $lids;
    }
    $query = db_select('lingotek_config_map', 'l');
    $query->addField('l', 'set_id');
    $query->condition('lid', $lids, 'IN');
    $sids = $query->execute()->fetchCol();

    return $sids;
  }

  public static function getConfigDocIdsFromSetIds($sids) {
    $query = db_select('lingotek_config_metadata', 'l');
    $query->addField('l', 'value');
    $query->condition('id', $sids, 'IN');
    $query->condition('config_key', 'document_id');
    $doc_ids = $query->execute()->fetchCol();

    return $doc_ids;
  }

  public static function getChunkIdsByStatus($status) {
    $query = db_select('lingotek_config_metadata', 'meta');
    $query->fields('meta', array('id'));
    $query->condition('config_key', 'target_sync_status_%', 'LIKE');
    $query->condition('value', $status);
    $query->distinct();
    $result = $query->execute();
    $cids = $result->fetchCol();
    return $cids;
  }

  public static function disassociateAllEntities() {
    db_truncate('lingotek_entity_metadata')->execute();
  }

  public static function disassociateAllSets() {
    db_truncate('lingotek_config_metadata')->execute();
  }

  public static function disassociateEntities($document_ids = array()) {
    $eids = self::getNodeIdsFromDocIds($document_ids);
    db_delete('lingotek_entity_metadata')
        ->condition('entity_type', 'node')
      ->condition('entity_id', $eids, 'IN')
      ->execute();
  }

  public static function getAllLocalDocIds() {
    // entity-related doc IDs
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->fields('l', array('value'));
    $query->condition('entity_key', 'document_id');
    $query->distinct();
    $result = $query->execute();
    $doc_ids = $result->fetchCol();

    // config-related doc IDs
    $query = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('value'))
        ->condition('config_key', 'document_id')
        ->distinct();
    $result = $query->execute();
    $doc_ids = array_merge($doc_ids, $result->fetchCol());

    return $doc_ids;
  }

  public static function getAllNodeIds() { // This query is broken - it also gets things without doc ids
    // all node ids having document_ids in lingotek table
    $query = db_select('lingotek_entity_metadata', 'l');
    $query->addField('l', 'entity_id');
    //$query->condition('lingokey', 'document_id');
    $query->distinct('entity_id');
    $result = $query->execute();
    $nids = $result->fetchCol();
    return $nids;
  }

  public static function getEntityIdFromDocId($lingotek_document_id, $entity_type = NULL) {
    $key = 'document_id';

    $query = db_select('lingotek_entity_metadata', 'l')->fields('l');
    if ($entity_type) {
      $query->condition('entity_type', $entity_type);
    }
    $query->condition('entity_key', $key);
    $query->condition('value', $lingotek_document_id);
    $result = $query->execute();
    
    $found = FALSE;
    $type = FALSE;
    
    if ($record = $result->fetchAssoc()) {
      $found = $record['entity_id'];
      $type = $record['entity_type'];
    }

    return array($found, $type);
  }
  
  public static function getNodeIdFromDocId($lingotek_document_id) {
    list($id, $type) = LingotekSync::getEntityIdFromDocId($lingotek_document_id);
    return array($id, $type);
  }

  public static function getNodeIdsFromDocIds($lingotek_document_ids) {
    $nids = array();
    $query = db_select('lingotek_entity_metadata', 'l')
        ->fields('l', array('nid'))
        ->condition('entity_type', 'node')
        ->condition('entity_key', $key)
        ->condition('value', $lingotek_document_ids, 'IN');
    $result = $query->execute()->fetchCol();
    return $result;
  }

  public static function getDocIdFromNodeId($drupal_node_id) {
    return getDocIdFromEntityId('node', $drupal_node_id);
  }
  
  public static function getDocIdFromEntityId($entity_type, $entity_id) {
    $found = FALSE;

    $query = db_select('lingotek_entity_metadata', 'l')->fields('l');
    $query->condition('entity_type', $entity_type);
    $query->condition('entity_id', $entity_id);
    $query->condition('entity_key', 'document_id');
    $result = $query->execute();

    if ($record = $result->fetchAssoc()) {
      $found = $record['value'];
    }

    return $found;
  }

  public static function getDocIdsFromEntityIds($entity_type, $entity_ids, $associate = FALSE) {

    $query = db_select('lingotek_entity_metadata', 'l');
    $query->addField('l', 'value', 'doc_id');
    $query->condition('entity_type', $entity_type);
    $query->condition('entity_id', $entity_ids, 'IN');
    $query->condition('entity_key', 'document_id');

    if ($associate) {
      $query->addField('l', 'entity_id', 'nid');
      $result = $query->execute()->fetchAllAssoc('nid');
    }
    else {
      $result = $query->execute()->fetchCol();
    }

    return $result;
  }

  public static function getEntityIdSubsetByTargetStatusReady($entity_type, $nids, $lingotek_locale) {
    $query = db_select('lingotek_entity_metadata', 'l')
        ->fields('l', array('entity_id'))
        ->condition('entity_type', $entity_type)
        ->condition('entity_id', $nids, 'IN')
        ->condition('entity_key', 'target_sync_status_' . $lingotek_locale)
        ->condition('value', LingotekSync::STATUS_READY);
    $result = $query->execute()->fetchCol();

    return $result;
  }

  public static function updateNotifyUrl() {
    $security_token = md5(time());
    $new_url = lingotek_notify_url_generate($security_token);
    $api = LingotekApi::instance();
    $integration_method_id = variable_get('lingotek_integration_method', '');

    if (!strlen($integration_method_id)) { // request integration id when not already set, attempt to detect
      $params = array(
        'regex' => ".*"
      );
      $response = $api->request('searchOutboundIntegrationUrls', $params);
      if (isset($response->results) && $response->results) {
        global $base_url;
        $integration_methods = $response->integrationMethods;
        foreach ($integration_methods as $integration_method) {
          if (strpos($integration_method->url, $base_url) !== FALSE) {
            $integration_method_id = $integration_method->id; // prefer integration with matching base_url
          }
        }
        if (!strlen($integration_method_id)) {
          reset($integration_methods); // just in case the internal pointer is not pointing to the first element
          $integration_method = current($integration_methods); // grab the first element in the list
          $integration_method_id = $integration_method->id; // use the first url found (if no matching url was found previously)
        }
        variable_set('lingotek_integration_method', $integration_method_id);
      }
    }
    $parameters = array(
      'id' => $integration_method_id,
      'url' => $new_url
    );
    $response = $api->request('updateOutboundIntegrationUrl', $parameters);
    $success = isset($response->results) ? $response->results : FALSE;
    if ($success) {
      variable_set('lingotek_notify_url', $new_url);
      variable_set('lingotek_notify_security_token', $security_token);
    }
    return $success;
  }

}

?>
