<?php

/*
 * This file is part of the Access to Memory (AtoM) software.
 *
 * Access to Memory (AtoM) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Access to Memory (AtoM) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Access to Memory (AtoM).  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * arElasticSearchPluginUtil
 *
 * @package     AccesstoMemory
 * @subpackage  arElasticSearchPlugin
 */
class arElasticSearchPluginUtil
{
  public static function convertDate($date)
  {
    if (is_null($date))
    {
      return;
    }

    if ($date instanceof DateTime)
    {
      $value = $date->format('Y-m-d\TH:i:s\Z');
    }
    else
    {
      $value = \Elastica\Util::convertDate($date);
    }

    return $value;
  }

  public static function parseYear($dateString)
  {
    // Do not use date_parse if we already have a year
    if (strlen($dateString) === 4 && preg_match("/\d{4}/", $dateString) === 1)
    {
      return $dateString;
    }

    // Use date_parse but do not return empty year
    $dateComponents = date_parse($dateString);
    if ($dateComponents['year'])
    {
      return $dateComponents['year'];
    }
  }

  /**
   * Set all fields for a QueryString, removing those hidden for public users
   */
  public static function setAllFields(\Elastica\Query\QueryString $query, $options = array())
  {
    // Set _all for authenticated users
    if (sfContext::getInstance()->user->isAuthenticated())
    {
      $query->setDefaultField('_all');
    }
    else
    {
      // Get available cultures
      $cultures = array();
      foreach (QubitSetting::getByScope('i18n_languages') as $setting)
      {
        $cultures[] = $setting->getValue(array('sourceCulture' => true));
      }

      $allFields = $filteredFields = $hiddenFields = $relations = array();

      if (!isset($options['type']))
      {
        $options['type'] = 'informationObject';
      }

      // Load ES mappings
      $mappings = arElasticSearchPlugin::loadMappings();

      // Get all string fields included in _all for the type and actual culture
      if (isset($mappings[$options['type']]))
      {
        $allFields = self::getAllObjectStringFields($mappings[$options['type']], $prefix = '', $cultures);
      }

      // Get actual template tor the type
      switch ($options['type'])
      {
        case 'informationObject':

          $infoObjectTemplate = QubitSetting::getByNameAndScope('informationobject', 'default_template');
          if (isset($infoObjectTemplate))
          {
            $template = $infoObjectTemplate->getValue(array('sourceCulture'=>true));
          }

          break;

        // TODO: Other types (actor, term, etc)
      }

      // Create array with relations (hidden field => ES mapping field) for the actual template and cultures
      if (isset($template))
      {
        switch ($template)
        {
          case 'isad':

            $relations = array(
              'isad_archival_history' => self::getI18nFieldNames('i18n.%s.archivalHistory', $cultures),
              'isad_immediate_source' => self::getI18nFieldNames('i18n.%s.acquisition', $cultures),
              'isad_appraisal_destruction' => self::getI18nFieldNames('i18n.%s.appraisal', $cultures),
              'isad_notes' => '',
              'isad_physical_condition' => self::getI18nFieldNames('i18n.%s.physicalCharacteristics', $cultures),
              'isad_control_description_identifier' => '',
              'isad_control_institution_identifier' => self::getI18nFieldNames('i18n.%s.institutionResponsibleIdentifier', $cultures),
              'isad_control_rules_conventions' => self::getI18nFieldNames('i18n.%s.rules', $cultures),
              'isad_control_status' => '',
              'isad_control_level_of_detail' => '',
              'isad_control_dates' => self::getI18nFieldNames('i18n.%s.revisionHistory', $cultures),
              'isad_control_languages' => '',
              'isad_control_scripts' => '',
              'isad_control_sources' => self::getI18nFieldNames('i18n.%s.sources', $cultures),
              'isad_control_archivists_notes' => '');

            break;

          case 'rad':

            $relations = array(
              'rad_archival_history' => self::getI18nFieldNames('i18n.%s.archivalHistory', $cultures),
              'rad_physical_condition' => self::getI18nFieldNames('i18n.%s.physicalCharacteristics', $cultures),
              'rad_immediate_source' => self::getI18nFieldNames('i18n.%s.acquisition', $cultures),
              'rad_general_notes' => '',
              'rad_conservation_notes' => '',
              'rad_control_description_identifier' => '',
              'rad_control_institution_identifier' => self::getI18nFieldNames('i18n.%s.institutionResponsibleIdentifier', $cultures),
              'rad_control_rules_conventions' => self::getI18nFieldNames('i18n.%s.rules', $cultures),
              'rad_control_status' => '',
              'rad_control_level_of_detail' => '',
              'rad_control_dates' => self::getI18nFieldNames('i18n.%s.revisionHistory', $cultures),
              'rad_control_language' => '',
              'rad_control_script' => '',
              'rad_control_sources' => self::getI18nFieldNames('i18n.%s.sources', $cultures));

            break;

          // TODO: Other templates (dacs, dc, isaar, etc)
        }
      }

      // Obtain hidden fields
      foreach (QubitSetting::getByScope('element_visibility') as $setting)
      {
        if(!(bool) $setting->getValue(array('sourceCulture' => true))
          && isset($relations[$setting->name])
          && $relations[$setting->name] != '')
        {
          foreach ($relations[$setting->name] as $fieldName)
          {
            $hiddenFields[] = $fieldName;
          }
        }
      }

      // Remove hidden fields from ES mapping fields
      $filteredFields = array_diff($allFields, $hiddenFields);

      // Set filtered fields for the query (use array_values() because array_diff() adds keys)
      $query->setFields(array_values($filteredFields));
    }
  }

  /**
   * Gets all string fields included in _all from a mapping type array and cultures
   */
  protected static function getAllObjectStringFields($object, $prefix, $cultures)
  {
    $fields = array();
    if (isset($object['properties']))
    {
      foreach ($object['properties'] as $propertyName => $propertyProperties)
      {
        // Get i18n fields for selected cultures, they're always included in _all
        if ($propertyName == 'i18n')
        {
          foreach ($cultures as $culture)
          {
            if (!isset($propertyProperties['properties'][$culture]['properties']))
            {
              continue;
            }

            foreach ($propertyProperties['properties'][$culture]['properties'] as $fieldName => $fieldProperties)
            {
              // Concatenate object name ($prefix) and field name
              $fields[] = $prefix.'i18n.'.$culture.'.'.$fieldName;
            }
          }
        }
        // Get nested objects fields
        else if (isset($propertyProperties['type']) && $propertyProperties['type'] == 'object')
        {
          $fields = array_merge($fields, self::getAllObjectStringFields($object['properties'][$propertyName], $prefix.$propertyName.'.', $cultures));
        }
        // Get foreing objects fields (couldn't find a better why that checking the dynamic property)
        else if (isset($propertyProperties['dynamic']))
        {
          $fields = array_merge($fields, self::getAllObjectStringFields($object['properties'][$propertyName], $prefix.$propertyName.'.', $cultures));
        }
        // Get string fields included in _all
        else if ((!isset($propertyProperties['include_in_all'])
          || $propertyProperties['include_in_all'])
          && (isset($propertyProperties['type'])
          && ($propertyProperties['type'] == 'text'
          || $propertyProperties['type'] == 'keyword')))
        {
          // Concatenate object name ($prefix) and field name
          $fields[] = $prefix.$propertyName;
        }
      }
    }

    return $fields;
  }

  public static function getI18nFieldNames($fields, $cultures = null, $boost = array())
  {
    // Get all available cultures if $cultures isn't set
    if (empty($cultures))
    {
      $cultures = array();
      foreach (QubitSetting::getByScope('i18n_languages') as $setting)
      {
        $cultures[] = $setting->getValue(array('sourceCulture' => true));
      }
    }

    // Make sure cultures is an array
    if (!is_array($cultures))
    {
      $cultures = array($cultures);
    }

    // Make sure fields is an array
    if (!is_array($fields))
    {
      $fields = array($fields);
    }

    // Format fields
    $i18nFieldNames = array();
    foreach ($cultures as $culture)
    {
      foreach ($fields as $field)
      {
        $formattedField = sprintf($field, $culture);

        if (isset($boost[$field]))
        {
          $formattedField .= '^'.$boost[$field];
        }

        $i18nFieldNames[] = $formattedField;
      }
    }

    return $i18nFieldNames;
  }

  /**
   * Gets all premis data related to an information object
   */
  public static function getPremisData($ioId, $conn)
  {
    $premisData = array();

    $sql  = 'SELECT *
      FROM '.QubitPremisObject::TABLE_NAME.' premis
      WHERE premis.information_object_id = ?';

    $statement = $conn->prepare($sql);
    $statement->execute(array($ioId));

    foreach ($statement->fetch() as $field => $value)
    {
      if (empty($value))
      {
        continue;
      }

      switch ($field)
      {
        case 'last_modified':
          $premisData['lastModified'] =  arElasticSearchPluginUtil::convertDate($value);

          break;

        case 'date_ingested':
          $premisData['dateIngested'] =  arElasticSearchPluginUtil::convertDate($value);

          break;

        case 'mime_type':
          $premisData['mimeType'] = $value;

          break;

        case 'size':
          $premisData['size'] = $value;

          break;

        case 'filename':
          $premisData['filename'] = $value;

          break;

        case 'puid':
          $premisData['puid'] = $value;

          break;
      }
    }

    $sql  = 'SELECT property.name, i18n.value
      FROM '.QubitProperty::TABLE_NAME.' property
      JOIN '.QubitPropertyI18n::TABLE_NAME.' i18n
        ON property.id = i18n.id
      WHERE property.scope = "premisData"
        AND property.source_culture = i18n.culture
        AND property.object_id = ?';

    $statement = $conn->prepare($sql);
    $statement->execute(array($ioId));

    foreach ($statement->fetchAll(PDO::FETCH_OBJ) as $property)
    {
      $value = unserialize($property->value);

      switch ($property->name)
      {
        case 'fitsAudio':
          $premisData['audio'] = $value;

          break;

        case 'fitsDocument':
          $premisData['document'] = $value;

          break;

        case 'fitsText':
          $premisData['text'] = $value;

          break;

        case 'mediainfoGeneralTrack':
          $premisData['mediainfo']['generalTracks'][] = $value;

          break;

        case 'mediainfoVideoTrack':
          $premisData['mediainfo']['videoTracks'][] = $value;

          break;

        case 'mediainfoAudioTrack':
          $premisData['mediainfo']['audioTracks'][] = $value;

          break;

        case 'format':
          $premisData['format'] = $value;

          break;

        case 'formatIdentificationEvent':
          $premisData['formatIdentificationEvent'] = $value;

          break;

        case 'otherEvent':
          $premisData['otherEvents'][] = $value;

          break;

        case 'agent':
          $premisData['agents'][] = $value;

          break;
      }
    }

    if (!empty($premisData))
    {
      return $premisData;
    }
  }
}
