<?php

/**
 * @file
 * Defines LingotekConfigSet.
 */

/**
 * A class wrapper for Lingotek-specific behavior on ConfigSets.
 */
class LingotekConfigSet implements LingotekTranslatableEntity {
  /**
   * The Drupal entity type associated with this class
   */

  const DRUPAL_ENTITY_TYPE = 'config_set';
  const TAG_PREFIX = 'config_';
  const TAG_PREFIX_LENGTH = 7; // length of 'config_'

  /**
   * The title of the document
   */
  protected $title = NULL;

  /**
   * A Drupal config_set.
   *
   * @var object
   */

  protected $sid;

  /**
   * Array for storing source and target translation strings
   */
  protected $source_data;
  protected $target_data;

  /**
   * A reference to the Lingotek API.
   *
   * @var LingotekApi
   */
  protected $api = NULL;

  /**
   * A static flag for content updates.
   */
  protected static $content_update_in_progress = FALSE;

  /**
   * Constructor.
   *
   * This is private since we want consumers to instantiate via the factory methods.
   *
   * @param $set_id
   *   A Config Set ID.
   */
  private function __construct($set_id = NULL) {
    $this->sid = $set_id;
    $this->set_size = LINGOTEK_CONFIG_SET_SIZE;
    $this->source_data = self::getAllSegments($this->sid);
    $this->source_meta = self::getSetMeta($this->sid);
    $this->language = language_default();
    if (!isset($this->language->lingotek_locale)) { // if Drupal variable 'language_default' does not exist
      $this->language->lingotek_locale = Lingotek::convertDrupal2Lingotek($this->language->language);
    }
    $this->language_targets = Lingotek::getLanguagesWithoutSource($this->language->lingotek_locale);
  }

  /**
   * Injects reference to an API object.
   *
   * @param LingotekApi $api
   *   An instantiated Lingotek API object.
   */
  public function setApi(LingotekApi $api) {
    $this->api = $api;
  }

  /**
   * Return the status of the target locale for the given set_id
   *
   * @param int $set_id
   *   The id of the set to search for
   * @param string $target_locale
   *   The lingotek locale to search for
   *
   * @return string
   *   The status of the target locale for the given set_id
   */
  public static function getTargetStatusById($set_id, $target_locale) {
    $result = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta', array('value'))
        ->condition('id', $set_id)
        ->condition('config_key', 'target_sync_status_' . $target_locale)
        ->execute();
    if ($result && $value = $result->fetchField()) {
      return $value;
    }
    LingotekLog::error('Did not find a target status for set ID "@id"', array('@id' => $set_id));
    return FALSE;
  }

  /**
   * Return the set ID for the current set
   *
   * @return int
   *   The ID of the current set, if it exists, otherwise NULL
   */
  public function getId() {
    return $this->sid;
  }

  /**
   * Return the title for the current set
   *
   * @return string
   *   The title of the current set
   */
  public function getTitle() {
    if ($this->title) {
      return $this->title;
    }
    $this->title = self::getTitleBySetId($this->sid);
    return $this->title;
  }

  /**
   * Set the display name for the document in the TMS
   */
  public function setTitle($title) {
    $this->title = $title;
  }

  /**
   * Return the title for the given set id
   *
   * @return string
   *   The title of the current set
   */
  public static function getTitleBySetId($set_id) {
    $textgroup = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('value'))
        ->condition('id', $set_id)
        ->condition('config_key', 'textgroup')
        ->execute()
        ->fetchField();

    $all_from_group = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('id'))
        ->condition('config_key', 'textgroup')
        ->condition('value', $textgroup)
        ->orderBy('id')
        ->execute()
        ->fetchCol();
    $num_in_group = array_search($set_id, $all_from_group);
    $textgroup = ($textgroup == 'default') ? 'Built-in Interface' : $textgroup;
    return ucfirst($textgroup) . ' ' . (1 + $num_in_group);
  }

  /**
   * Return the description for the current set
   *
   * @return string
   *   The description of the current set
   */
  public function getDescription() {
    return $this->getTitle();
  }

  /**
   * Return the set ID for a given segment from the locales source
   *
   * @param int
   *   the lid of a segment from the locales source
   *
   * @return int
   *   the ID of a set of configuration segments
   */
  public static function getSetId($lid, $assign = TRUE) {
    // Check if the lid already has a set:
    $existing_sid = db_select('lingotek_config_map', 'l')
        ->fields('l', array('set_id'))
        ->condition('lid', $lid)
        ->execute()
        ->fetchField();
    // If not, assign one to it
    if (!$existing_sid && $assign) {
      $new_sid = self::assignSetId($lid);
      return $new_sid;
    }
    return $existing_sid;
  }

  protected static function assignSetId($lid) {
    // get the $lid's textgroup
    $textgroup = db_select('locales_source', 'l')
        ->fields('l', array('textgroup'))
        ->condition('lid', $lid)
        ->execute()
        ->fetchField();

    $open_set_id = self::getOpenSet($textgroup);
    if ($open_set_id === FALSE) {
      $open_set_id = self::createSet($textgroup);
    }
    // assign lid to that set
    db_merge('lingotek_config_map')
        ->key(array('lid' => $lid))
        ->fields(array(
          'lid' => $lid,
          'set_id' => $open_set_id,
        ))
        ->execute();
    return $open_set_id;
  }

  protected static function getOpenSet($textgroup) {
    $full_sets = self::getFullSets();

    $query = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('id'))
        ->condition('config_key', 'textgroup')
        ->condition('value', $textgroup);
    if (!empty($full_sets)) {
      $query->condition('id', $full_sets, 'NOT IN');
    }
    $query->orderBy('id');
    $result = $query->execute();
    $set_ids = $result->fetchCol();
    $open_set_id = FALSE;
    foreach ($set_ids as $key => $set_id) {
      if (!self::hasMaxChars($set_id)) {
        $open_set_id = $set_ids[$key];
        break;
      }
    }
    return $open_set_id;
  }

  protected static function getFullSets() {
    $query = db_query('SELECT set_id, COUNT(*) c FROM {lingotek_config_map} GROUP BY set_id HAVING c >= :max_size', array(':max_size' => LINGOTEK_CONFIG_SET_SIZE));

    $full_sets = $query->fetchCol();
    return $full_sets;
  }

  protected static function hasMaxChars($set_id) {
    $lids = self::getLidsFromSets($set_id);
    if (!empty($lids)) {
      $query = db_select('locales_source', 'ls')
          ->fields('ls', array('source'))
          ->condition('lid', $lids, 'IN')
          ->execute();
      $strings_array = $query->fetchCol();
      $strings = implode('', $strings_array);
      if (strlen($strings) > LINGOTEK_CONFIG_MAX_SOURCE_LENGTH) {
        return TRUE;
      }
    }
    return FALSE;
  }

  protected static function createSet($textgroup) {
    $timestamp = time();
    $next_id = self::getNextSetId();
    db_insert('lingotek_config_metadata')
        ->fields(array(
          'id' => $next_id,
          'config_key' => 'textgroup',
          'value' => $textgroup,
          'created' => $timestamp,
          'modified' => $timestamp
        ))
        ->execute();

    return $next_id;
  }

  protected static function getNextSetId() {
    $query = db_select('lingotek_config_metadata', 'lcm');
    $query->addExpression('MAX(id)');
    $max_set_id = $query->execute()->fetchField();
    if ($max_set_id) {
      return (int) $max_set_id + 1;
    }
    return 1;
  }

  public static function getDocId($set_id) {
    $doc_id = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('value'))
        ->condition('id', $set_id)
        ->condition('config_key', 'document_id')
        ->execute()
        ->fetchField();
    return $doc_id;
  }

  public static function getAllConfigDocIds() {
    $doc_ids = db_select('lingotek_config_metadata', 'l')
        ->fields('l', array('value'))
        ->condition('config_key', 'document_id')
        ->execute()
        ->fetchCol();
    return $doc_ids;
  }

  public function getSourceLocale() {
    return $this->language->lingotek_locale;
  }

  /**
   * Set all segments for a given set ID to CURRENT status
   *
   * @param int
   *   the ID of a set of configuration segments
   */
  public static function setSegmentStatusToCurrentById($set_id) {
    $lids = self::getLidsFromSets($set_id);
    $result = db_update('locales_target')
        ->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
        ->condition('lid', $lids, 'IN')
        ->execute();
  }

  /**
   * Save segment's translation for the given target
   *
   * @param int
   *   the lid of the segment being translated
   * @param string
   *   the 2-digit language code for the target language
   * @param string
   *   the translated content to be saved
   */
  public static function saveSegmentTranslation($lid, $target_language, $content) {
    // insert/update translations, overwriting everything that is there
    // except for the i18n_status field, which should preserve its
    // currently-set flags, and the plid and plural fields which just
    // take default values for now.
    db_merge('locales_target')
        ->key(array('lid' => $lid, 'language' => $target_language,))
        ->fields(array(
          'lid' => $lid,
          'translation' => $content,
          'language' => $target_language,
        ))
        ->execute();
  }

  /**
    /**
   * Return the set ID for a given Lingotek document ID, if it exists
   *
   * @param int
   *   the id of a lingotek document
   *
   * @return int
   *   the ID of a set of configuration segments
   */
  public static function getIdByDocId($doc_id) {
    $query = db_select('lingotek_config_metadata', 'meta');
    $query->fields('meta', array('id'));
    $query->condition('config_key', 'document_id');
    $query->condition('value', $doc_id);
    $set_id = $query->execute()->fetchField();
    if ($set_id !== FALSE) {
      return $set_id;
    }
    return FALSE;
  }

  /**
   * Factory method for getting a loaded LingotekConfigSet object.
   *
   * @param object $config_set
   *   A Drupal config_set.
   *
   * @return LingotekConfigSet
   *   A loaded LingotekConfigSet object.
   */
  public static function load($config_set) {
    // WTD: not sure how to build this yet, so just raise NYI for now...
    throw new Exception('Not yet implemented');
  }

  /**
   * Factory method for getting a loaded LingotekConfigSet object.
   *
   * @param int $set_id
   *   A Drupal config set ID.
   *
   * @return mixed
   *   A loaded LingotekConfigSet object if found, FALSE if the set could not be loaded.
   */
  public static function loadById($set_id) {
    $set = FALSE;
    // get any segments that should be associated with this set
    // if segments exist, return a LingotekConfigSet instance
    // otherwise, return FALSE
    $set_segments = self::getLidsFromSets($set_id);
    if ($set_segments) {
      $set = new LingotekConfigSet($set_id);
      $set->setApi(LingotekApi::instance());
    }

    return $set;
  }

  /**
   * Return all segments from the database that belong to a given set ID
   *
   * @param int $set_id
   *
   * @return array
   *   An array containing the translation sources from the locales_source table
   */
  protected static function getAllSegments($set_id) {
    $max_length = variable_get('lingotek_config_max_source_length', LINGOTEK_CONFIG_MAX_SOURCE_LENGTH); //is this just to make sure there are no enormous config items or is there another reason? How often does this come into play?

    $lids = self::getLidsFromSets($set_id);
    if (empty($lids)) {
      return $lids;
    }

    $results = db_select('locales_source', 'ls')
        ->fields('ls', array('lid', 'source'))
        ->condition('lid', $lids, 'IN')
        ->orderBy('lid')
        ->execute();

    $response = array();
    while ($r = $results->fetchAssoc()) {
      if(strlen($r['source']) < $max_length) {
        $response[$r['lid']] = $r['source'];
      }
      else {
        LingotekLog::warning("Config item @id was not sent to Lingotek for translation because it exceeds the max length of 4096 characters.", array('@id' => $r['lid']));
        // Remove it from the set in the config_map table so it doesn't get marked as uploaded or translated.
        self::disassociateSegments($r['lid']);
      }
    }

    return $response;
  }

  public static function getLidsFromSets($set_ids) {
    $set_ids = is_array($set_ids) ? $set_ids : array($set_ids);
    if (empty($set_ids)) {
      return array();
    }
    $lids = db_select('lingotek_config_map', 'lcm')
        ->fields('lcm', array('lid'))
        ->condition('set_id', $set_ids, 'IN')
        ->execute()
        ->fetchCol();
    return $lids;
  }

  protected static function getLidsForTextgroup($textgroup) {
    $query = db_select('locales_source', 'ls')
        ->fields('ls', array('lid'))
        ->condition('textgroup', $textgroup)
        ->execute();

    $lids = $query->fetchCol();
    return $lids;
  }

  public static function getLidsByStatus($status) {
    $target_language_search = '%';
    $query = db_select('lingotek_config_metadata', 'l');
    $query->fields('l', array('id'));
    $query->condition('config_key', 'target_sync_status_' . $target_language_search, 'LIKE');
    $query->condition('value', $status);
    $result = $query->execute();
    $set_ids = $result->fetchCol(); //$result->fetchAllAssoc('nid');

    $lids = self::getLidsFromSets($set_ids);
    return $lids;
  }

  /**
   * Return any metadata for the given set ID, if it exists
   *
   * @param int $set_id
   *
   * @return array
   *   An array containing anything for the set_id from table lingotek_config_metadata
   */
  protected static function getSetMeta($set_id) {
    $query = db_select('lingotek_config_metadata', 'l');
    $query->fields('l', array('id', 'config_key', 'value'));
    $query->condition('l.id', $set_id);
    $result = $query->execute();
    $response = array();
    while ($record = $result->fetch()) {
      $response[$record->config_key] = $record->value;
    }
    return $response;
  }

  /**
   * Loads a LingotekConfigSet by Lingotek Document ID.
   *
   * @param string $lingotek_document_id
   *   The Document ID whose corresponding set should be loaded.
   * @param string $lingotek_language_code
   *   The language code associated with the Lingotek Document ID.
   * @param int $lingotek_project_id
   *   The Lingotek project ID associated with the Lingotek Document ID.
   *
   * @return mixed
   *   A LingotekConfigSet object on success, FALSE on failure.
   */
  public static function loadByLingotekDocumentId($lingotek_document_id) {
    $set = FALSE;

    // Get the set entries in the system associated with the document ID.
    $query = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta', array('id'))
        ->condition('config_key', 'document_id')
        ->condition('value', $lingotek_document_id)
        ->execute();
    $set_id = $query->fetchField();

    // this returns a 0 for the first id then the if shows false
    if (isset($set_id)) {
      $set = self::loadById($set_id);
    }
    
    return $set;
  }

  /**
   * Event handler for updates to the config set's data.
   */
  public function contentUpdated() {

    $metadata = $this->metadata();
    if (empty($metadata['document_id'])) {
      $this->createLingotekDocument();
    }
    else {
      $update_result = $this->updateLingotekDocument();
    }

    // Synchronize the local content with the translations from Lingotek.
    // We instruct the users to configure config set translation with a
    // single-phase machine translation-only Workflow, so the updated content
    // should be available right after our create/update document calls from above.
    // If it isn't, Lingotek will call us back via LINGOTEK_NOTIFICATIONS_URL
    // when machine translation for the item has finished.
    if (!self::$content_update_in_progress) {
      // Only update the local content if the Document is 100% complete
      // according to Lingotek.

      $document_id = $this->getMetadataValue('document_id');
      if ($document_id) {
        $document = $this->api->getDocument($document_id);
        if (!empty($document->percentComplete) && $document->percentComplete == 100) {
          $this->updateLocalContent();
        }
      }
      else {
        LingotekLog::error('Unable to retrieve Lingotek Document ID for config set @id', array('@id' => $this->sid));
      }
    }
  }

  /**
   * Creates a Lingotek Document for this config set.
   *
   * @return bool
   *   TRUE if the document create operation was successful, FALSE on error.
   */
  protected function createLingotekDocument() {
    return ($this->api->addContentDocumentWithTargets($this)) ? TRUE : FALSE;
  }

  /**
   * Updates the existing Lingotek Documemnt for this config set.
   *
   * @return bool
   *   TRUE if the document create operation was successful, FALSE on error.
   */
  protected function updateLingotekDocument() {
    $result = $this->api->updateContentDocument($this);
    return ($result) ? TRUE : FALSE;
  }

  /**
   * Gets the local Lingotek metadata for this config set.
   *
   * @return array
   *   An array of key/value data for the current config set.
   */
  protected function metadata() {
    $metadata = array();

    $results = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta')
        ->condition('id', $this->sid)
        ->execute();

    foreach ($results as $result) {
      $metadata[$result->config_key] = $result->value;
    }

    return $metadata;
  }

  /**
   * Return true if current set already has an assigned Lingotek Document ID
   *
   * @return boolean TRUE/FALSE
   */
  public function hasLingotekDocId() {
    $has_id = array_key_exists('document_id', $this->source_meta);
    if ($has_id && (strlen($this->source_meta['document_id']) > 0)) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Get the set's target translation status for the given locale
   */
  public function getTargetStatus($lingotek_locale) {
    $result = db_select('lingotek_config_metadata', 'meta')
        ->fields('meta', array('value'))
        ->condition('id', $this->sid)
        ->condition('config_key', 'target_sync_status_' . $lingotek_locale)
        ->execute();
    return $result->value;
  }

  /**
   * Assign the set's document ID in the config metadata table
   */
  public function setDocumentId($doc_id) {
    $this->setMetadataValue('document_id', $doc_id);
    return $this;
  }

  /**
   * Assign the set's project ID in the config metadata table
   */
  public function setProjectId($project_id) {
    $this->setMetadataValue('project_id', $project_id);
    return $this;
  }

  /**
   * Assign the set's status in the config metadata table
   */
  public function setStatus($status) {
    $this->setMetadataValue('upload_status', $status);
    return $this;
  }

  /**
   * Assign the set's last error in the config metadata table
   */
  public function setLastError($errors) {
    $this->setMetadataValue('last_sync_error', substr($errors, 0, 255));
    return $this;
  }

  /**
   * Assign the set's target status(es) in the config metadata table
   */
  public function setTargetsStatus($status, $lingotek_locale = 'all') {
    if ($lingotek_locale != 'all') {
      $this->setMetadataValue('target_sync_status_' . $lingotek_locale, $status);
    }
    else { // set status for all available targets
      foreach ($this->language_targets as $lt) {
        $this->setMetadataValue('target_sync_status_' . $lt, $status);
      }
    }
    return $this;
  }

  /**
   * Gets the contents of this item formatted as XML to be sent to Lingotek.
   *
   * @return string
   *   The XML document representing the entity's translatable content.
   */
  public function documentLingotekXML() {
    $translatable = array();

    // for now, assume all strings in locales_source table for the given set are translatable
    if (TRUE) {
      $translatable = array_keys($this->source_data);
    }

    $content = '';
    foreach ($translatable as $field) {
      $field_data = $this->source_data[$field];
      if (variable_get('lingotek_config_encode_ampersand', FALSE)) {
        $field_data = str_replace("&", "&amp;", $field_data);
      }
      $text = lingotek_filter_placeholders($field_data, TRUE);

      if ($text) {
        $current_field = '<' . self::TAG_PREFIX . $field . '>';
        $current_field .= '<element><![CDATA[' . $text . ']]></element>' . "\n";
        $current_field .= '</' . self::TAG_PREFIX . $field . '>';
        $content .= $current_field . "\n";
      }
    }

    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><contents>$content</contents>";
  }

  /**
   * Gets a Lingotek metadata value for this item.
   *
   * @param string $key
   *   The key whose value should be returned.
   *
   * @return string
   *   The value for the specified key, if it exists.
   */
  public function getMetadataValue($key) {
    return db_select('lingotek_config_metadata', 'meta')
            ->fields('meta', array('value'))
            ->condition('config_key', $key)
            ->condition('id', $this->sid)
            ->execute()
            ->fetchField();
  }

  /**
   * Sets a Lingotek metadata value for this item.
   *
   * @param string $key
   *   The key for a name/value pair.
   * @param string $value
   *   The value for a name/value pair.
   */
  public function setMetadataValue($key, $value) {
    $metadata = $this->metadata();
    $timestamp = time();
    if (!isset($metadata[$key])) {
      db_insert('lingotek_config_metadata')
          ->fields(array(
            'id' => $this->sid,
            'config_key' => $key,
            'value' => $value,
            'created' => $timestamp,
            'modified' => $timestamp
          ))
          ->execute();
    }
    else {
      db_update('lingotek_config_metadata')
          ->fields(array(
            'value' => $value,
            'modified' => $timestamp
          ))
          ->condition('id', $this->sid)
          ->condition('config_key', $key)
          ->execute();
    }
  }
  
  /**
   * Deletes a Lingotek metadata value for this item
   * 
   * @param string $key
   *  The key for a name/value pair
   */
  public function deleteMetadataValue($key) {
    $metadata = $this->metadata();
    if (isset($metadata[$key])) {
      db_delete('lingotek_config_metadata')
          ->condition('id', $this->sid)
        ->condition('config_key', $key, 'LIKE')
        ->execute();
    }
  }

  /**
   * Updates the local content with data from a Lingotek Document.
   *
   * @return bool
   *   TRUE if the content updates succeeded, FALSE otherwise.
   */
  public function updateLocalContent() {
    $metadata = $this->metadata();
    $document_id = $metadata['document_id'];

    if (empty($document_id)) {
      LingotekLog::error('Unable to refresh local contents for config set @sid. Could not find Lingotek Document ID.', array('@sid' => $this->sid));
      return FALSE;
    }

    $api = LingotekApi::instance();
    $document = $api->getDocument($document_id);

    foreach ($document->translationTargets as $target) {
      $this->downloadTriggered($target->lingotek_locale);
    }
  }

  /**
   * Updates the local content of $target_code with data from a Lingotek Document
   *
   * @param string $lingotek_locale
   *   The code for the language that needs to be updated.
   * @return bool
   *   TRUE if the content updates succeeded, FALSE otherwise.
   */
  public function downloadTriggered($lingotek_locale) {
    $metadata = $this->metadata();
    $document_id = $metadata['document_id'];

    if (empty($document_id)) {
      LingotekLog::error('Unable to refresh local contents for config set @sid. Could not find Lingotek Document ID.', array('@sid' => $this->sid));
      return FALSE;
    }

    $api = LingotekApi::instance();
    $document_xml = $api->downloadDocument($document_id, $lingotek_locale);
    $target_language = Lingotek::convertLingotek2Drupal($lingotek_locale);

    /* FAST VERSION (git history for slow version) */
    // 1. save the dirty targets associated with given language
    $dirty_lids = self::getDirtyLidsBySetIdAndLanguage($this->sid, $target_language);
    // 2. delete all segment targets associated with given language
    self::deleteSegmentTranslationsBySetIdAndLanguage($this->sid, $target_language);
    // 3. insert all segments for the given language
    self::saveSegmentTranslations($document_xml, $target_language);
    // 4. return the dirty targets' statuses
    self::restoreDirtyLids($dirty_lids);
    /* END FAST */

    // assign set status to current
    $this->setStatus(LingotekSync::STATUS_CURRENT);
    $this->setTargetsStatus(LingotekSync::STATUS_CURRENT, $lingotek_locale);
    self::markSetsCurrent($this->sid);

    return TRUE;
  }

  /**
   * Return all target segments by ID marked to be updated
   *
   * This is a preparatory step before resetting the locales targets for a given
   * set.
   *
   * @param int
   *    the ID of the set under which to search
   * @param string
   *    the language code for which to get the segments that need updating
   */
  public static function getDirtyLidsBySetIdAndLanguage($set_id, $language) {
    $lids = self::getLidsFromSets($set_id);
    $result = db_select('locales_target', 'lt')
        ->fields('lt', array('lid'))
        ->condition('lid', $lids, 'IN')
        ->condition('language', $language)
        ->condition('i18n_status', I18N_STRING_STATUS_CURRENT, '!=')
        ->condition('translation_agent_id', self::getLingotekTranslationAgentId())
        ->execute();
    return $result->fetchCol();
  }

  public static function getEditedLidsInSets($set_ids) {
    $set_ids = is_array($set_ids) ? $set_ids : array($set_ids);
    $query = db_select('lingotek_config_map', 'lcm')
        ->fields('lcm', array('lid'))
        ->condition('set_id', $set_ids, 'IN')
        ->condition('current', 1);
    $query->join('locales_target', 'lt', 'lt.lid = lcm.lid');
    $query->condition('i18n_status', 1);
    $result = $query->execute()->fetchCol();
    $edited_lids = array_unique($result);
    return $edited_lids;
  }

  /**
   * Mark as dirty all target segments passed, in the locales targets
   *
   * @param array $dirty_lids
   *    the list of segments that need updating
   */
  public static function restoreDirtyLids($dirty_lids) {
    if ($dirty_lids) {
      db_update('locales_target')
          ->fields(array('i18n_status' => 1))
          ->condition('lid', $dirty_lids, 'IN')
          ->execute();
    }
  }

  /**
   * Return bool if a specific lid is current
   *
   * @param int $lid
   *    a single lid
   */
  public static function isLidCurrent($lid) {
    $query = db_select('lingotek_config_map', 'lcm')
        ->fields('lcm', array('current'))
        ->condition('lid', $lid)
        ->execute();
    $current = $query->fetchField();
    return $current;
  }

  /**
   * Mark all lids passed as current or not current, in the lingotek_config_map table
   *
   * @param array $lids
   *    the list of lids that are current
   */
  public static function markLidsNotCurrent($lids) {
    $query = db_update('lingotek_config_map')
        ->fields(array('current' => 0));
    if ($lids != 'all') {
      $query->condition('lid', $lids, 'IN');
    }
    $query->execute();
  }

  /**
   * Mark all sets passed as current, in the lingotek_config_map table
   *
   * @param array $set_ids
   *    the list of lids that are current
   */
  protected static function markSetsCurrent($set_ids) {
    $query = db_update('lingotek_config_map')
        ->fields(array('current' => 1));
    if ($set_ids != 'all') {
      $set_ids = is_array($set_ids) ? $set_ids : array($set_ids);
      $query->condition('set_id', $set_ids, 'IN');
    }
    $query->execute();
  }

  /**
   * Get all lids marked as current or not, in the lingotek_config_map table
   *
   * @param int $current
   *    1 to get lids of all current segments, 0 to get lids for segments that are not current
   * @param array $lids
   *    a subset of lids to check, defaults to look for all current segments
   */
  public static function getLidsToUpdate($current = 0, $lids = 'all') {
    $textgroups = array_merge(array(-1), LingotekConfigSet::getTextgroupsForTranslation());

    $query = db_select('lingotek_config_map', 'lcm')
        ->fields('lcm', array('lid'));
    if ($lids !== 'all') {
      $query->condition('lcm.lid', $lids, 'IN');
    }
    $query->join('locales_source', 'ls', "lcm.lid = ls.lid");
    $query->condition('ls.textgroup', $textgroups, 'IN');

    $query->join('locales_target', 'lt', "lcm.lid = lt.lid");
    $or = db_or();
    $or->condition('lcm.current', $current);
    $or->condition('lt.i18n_status', 1);
    $query->condition($or);

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

  /**
   * Delete all target segments for a given set
   *
   * @param int
   *    the ID of the set for which to delete target segments
   * @param string
   *    the language code for which to delete target segments
   */
  public static function deleteSegmentTranslationsBySetIdAndLanguage($set_id, $target_language) {
    $lids = self::getLidsFromSets($set_id);
    db_delete('locales_target')
        ->condition('language', $target_language)
        ->condition('lid', $lids, 'IN')
        ->condition('translation_agent_id', self::getLingotekTranslationAgentId())
        ->execute();
  }

  public static function deleteSegmentTranslations($lids) {
    $lids = is_array($lids) ? $lids : array($lids);
    db_delete('locales_target')
        ->condition('lid', $lids, 'IN')
        ->execute();

    db_delete('lingotek_config_map')
        ->condition('lid', $lids, 'IN')
        ->execute();
  }

  public static function disassociateSegments($lids) {
    $lids = is_array($lids) ? $lids : array($lids);
    db_delete('lingotek_config_map')
        ->condition('lid', $lids, 'IN')
        ->execute();
  }

  /**
   * Get lingotek translation agent ID
   */
  public static function getLingotekTranslationAgentId() {
    $result = db_select('lingotek_translation_agent', 'lta')
        ->fields('lta', array('id'))
        ->condition('name', 'Lingotek')
        ->execute();
    return $result->fetchField();
  }

  /**
   * Get all locales target entries that were not created by Lingotek
   */
  protected static function getNonLingotekLocalesTargets($document_xml, $target_language) {
    $lids = array(-1); // seed lids for proper query handling on empty case
    foreach ($document_xml as $drupal_field_name => $xml_obj) {
      $lids[] = self::getLidFromTag($drupal_field_name);
    }
    $result = db_select('locales_target', 'lt')
        ->fields('lt', array('lid'))
        ->condition('lid', $lids, 'IN')
        ->condition('language', $target_language)
        ->condition('translation_agent_id', self::getLingotekTranslationAgentId(), '!=')
        ->execute();
    return $result->fetchCol();
  }

  /**
   * Save segment target translations for the given language
   *
   * @param obj
   *    the SimpleXMLElement object containing the translations to be saved
   * @param string
   *    the language code under which to save the translations
   */
  public static function saveSegmentTranslations($document_xml, $target_language) {
    $non_lingotek_locales_targets = self::getNonLingotekLocalesTargets($document_xml, $target_language);
    $plural_mapping = variable_get('lingotek_config_plural_mapping', array());
    $rows = array();
    $sql = 'INSERT INTO {locales_target} (lid, translation, language, plid, plural, translation_agent_id) VALUES ';
    $subsql = '';
    $icount = 0;
    $lingotek_agent = self::getLingotekTranslationAgentId();
    foreach ($document_xml as $drupal_field_name => $xml_obj) {
      $lid = self::getLidFromTag($drupal_field_name);
      if (!in_array($lid, $non_lingotek_locales_targets)) {
        $content = (string) $xml_obj->element;
        $content = lingotek_unfilter_placeholders(decode_entities($content));
        $plural_lid = array_key_exists($lid, $plural_mapping);
        $rows += array(
          ":l_$icount" => $lid,
          ":c_$icount" => $content,
          ":lang_$icount" => $target_language,
          ":plid_$icount" => ($plural_lid ? $plural_mapping[$lid]['plid'] : 0),
          ":plural_$icount" => ($plural_lid ? $plural_mapping[$lid]['plural'] : 0),
          ":agent_$icount" => $lingotek_agent,
          );
        $subsql .= "( :l_$icount, :c_$icount, :lang_$icount, :plid_$icount, :plural_$icount, :agent_$icount),";
        $icount++;
      }
    }
    if (!empty($rows)) {
      $subsql = rtrim($subsql, ',');
      db_query($sql . $subsql, $rows);
    }
  }

  /**
   * Get the Lingotek document ID for this entity.
   *
   * @return mixed
   *   The integer document ID if the entity is associated with a
   *   Lingotek document. FALSE otherwise.
   */
  public function lingotekDocumentId() {
    return $this->getMetadataValue('document_id');
  }

  /**
   * Return the Drupal Entity type
   *
   * @return string
   *   The entity type associated with this object
   */
  public function getEntityType() {
    return self::DRUPAL_ENTITY_TYPE;
  }
  
  /**
   * Magic get for access to set and set properties.
   */
  public function __get($property_name) {
    $property = NULL;

    if ($property === self::DRUPAL_ENTITY_TYPE) {
      $property = $this;
    }
    elseif (isset($this->$property_name)) {
      $property = $this->$property_name;
    }

    return $property;
  }

  /**
   * Return the lid for locales source/target tables from the XML tag name
   */
  protected static function getLidFromTag($tag) {
    // for now, remove the 'config_' as quickly as possible
    return substr($tag, self::TAG_PREFIX_LENGTH);
  }

  public function getWorkflowId() {
    $profiles = variable_get('lingotek_profiles');
    $config_profile = $profiles[LingotekSync::PROFILE_CONFIG];
    $workflow_id = array_key_exists('workflow_id', $config_profile) ? $config_profile['workflow_id'] : variable_get('lingotek_translate_config_workflow_id', '');
    return $workflow_id;
  }

  public function getProjectId() {
    $profiles = variable_get('lingotek_profiles');
    $config_profile = $profiles[LingotekSync::PROFILE_CONFIG];
    $project_id = array_key_exists('project_id', $config_profile) ? $config_profile['project_id'] : variable_get('lingotek_project', '');
    return $project_id;
  }

   public function getVaultId() {
    $profiles = variable_get('lingotek_profiles');
    $config_profile = $profiles[LingotekSync::PROFILE_CONFIG];
    $vault_id = array_key_exists('vault_id', $config_profile) ? $config_profile['vault_id'] : variable_get('lingotek_vault', '');
    return $vault_id;
  }

  /**
   * Return all textgroups from locales_source for which translation is desired
   */
  public static function getTextgroupsForTranslation() {
    $textgroups = array();
    if (variable_get('lingotek_translate_config_builtins', 0)) {
      $textgroups[] = 'default';
    }
    if (variable_get('lingotek_translate_config_menus', 0)) {
      $textgroups[] = 'menu';
    }
    if (variable_get('lingotek_translate_config_taxonomies', 0)) {
      $textgroups[] = 'taxonomy';
    }
    if (variable_get('lingotek_translate_config_blocks', 0)) {
      $textgroups[] = 'blocks';
    }
    if (variable_get('lingotek_translate_config_views', 0)) {
      $textgroups[] = 'views';
    }
    if (variable_get('lingotek_translate_config_fields', 0)) {
      $textgroups[] = 'field';
    }
    if (variable_get('lingotek_translate_config_misc', 0)) {
      $textgroups[] = 'misc';
    }
    return $textgroups;
  }
  
  public static function getLidBySource($source_string) {
    return db_select('locales_source', 's')
            ->fields('s', array('lid'))
        ->condition('s.source', $source_string)
        ->execute()
        ->fetchField();
  }

/*
 * Return all document IDs related to config translation
 */

  public static function getAllDocumentIds() {
  $result = db_select('lingotek_config_metadata', 'c')
        ->fields('c', array('value'))
      ->condition('c.config_key', 'document_id')
      ->execute();
  return $result->fetchCol();
}


  public function getDocumentName() {
    return $this->getTitle();
  }

  public function getUrl() {
    return '';
  }
  
  public function getNote() {
    return '';
  }

  public function preDownload($lingotek_locale, $completed) {
    // If auto download is turned off, you need to uncomment these lines and set status to READY.
    /* if ($completed) {
      $this->setTargetsStatus(LingotekSync::STATUS_READY, $lingotek_locale);
      // The following lines mark the whole set as ready rather than just the changed items.
      $lids = array_keys(self::getAllSegments($set_id));
      self::markLidsNotCurrent($lids)
    } */
  }

  public function postDownload($lingotek_locale, $completed) {
  }
}
