<?php

namespace Drupal\languagewire_translation_provider\api\Libraries\Project;

use Drupal\languagewire_translation_provider\api\Libraries\Document\DocumentReferenceFiles;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\UnrecognizedLanguageException;
use Drupal\languagewire_translation_provider\api\Models\ConfigurationModel;
use Drupal\languagewire_translation_provider\api\Services\LanguageService;

/**
 * Project Specification.
 *
 * Project Specification is necessary for creating
 * a project on the server. Since project creation
 * requires a lot of data, this class helps to manage
 * them all.
 *
 * Project requires following data to be provided:
 * <ul>
 * <li>
 * list of source document IDs
 * (uploaded and validated)
 * </li>
 * <li>
 * list of language codes in LanguageWire format
 * (Look at {@link \Drupal\languagewire_translation_provider\api\Language}).
 * </li>
 * <li>
 * correlation ID - unique ID provided by a client
 * </li>
 * <li>
 * title - Human readable project title
 * </li>
 * <li>
 * purchase order number - an arbitrary number that
 * will be printed on an invoice for translating the project
 * </li>
 * <li>
 * invoicing account - an ID of an account created in
 * LanguageWire platform beforehand
 * </li>
 * <li>
 * translation memory - an ID of a translation memory created in
 * LanguageWire platform beforehand
 * </li>
 * <li>
 * product - an ID of LanguageWire product
 * </li>
 * <li>
 * work area - an ID of LanguageWire work area
 * </li>
 * <li>
 * deadline - when project should finish. Timestamp in UTC timezone
 * </li>
 * <li>
 * briefing - an optional short instructions for a translator
 * </li>
 * <li>
 * reference files - a list of reference file IDs (previously uploaded)
 * </li>
 * </ul>
 *
 * Translation Memory, invoicing accounts, products
 * and work areas can be fetched from the server using
 * {@link ConfigurationService}.
 *
 * Example usage:
 * ```
 * $deadline = new \DateTimeImmutable(
 *          '2017-01-01 12:00:00',
 *          new \DateTimeZone('UTC')
 * ); //a future date
 *
 * $projectSpecification = new ProjectSpecification(
 *     [$documentId1, $documentId2],
 *     [Language::DANISH, Language::POLISH],
 *     'unique_correlation_id_1',
 *     'Translation of a page X',
 *     $deadline,
 *     $configurationModel,
 *     'Make sure to translate a "car" into "auto" in Polish',
 *     '20161201/0001',
 *     false,
 *     [$referenceFile1, $referenceFile2]
 * );
 *
 * $projectService->create($projectSpecification);
 * ```
 */
class ProjectSpecification extends ProjectSpecificationBase implements ProjectSpecificationInterface {

  /**
   * List of source document IDs.
   *
   * @var int[]
   */
  protected array $sourceDocuments;

  /**
   * List of target languages.
   *
   * @var string[]
   */
  private array $targetLanguages;

  /**
   * Configuration model.
   *
   * @var \Drupal\languagewire_translation_provider\api\Models\ConfigurationModel
   */
  private ConfigurationModel $configurationModel;

  /**
   * Constructs a new ProjectSpecification object.
   *
   * @param array $sourceDocuments
   *   Source documents.
   * @param array $targetLanguages
   *   Target languages.
   * @param string $correlationId
   *   Correlation ID.
   * @param string $title
   *   Project title.
   * @param \Drupal\languagewire_translation_provider\api\Models\ConfigurationModel $configuration
   *   Configuration model.
   * @param \DateTimeInterface $deadline
   *   Project deadline.
   * @param string $briefing
   *   Project briefing.
   * @param string $purchaseOrderNumber
   *   Purchase order number.
   * @param int[] $projectReferenceFileIds
   *   Project reference file IDs.
   * @param \Drupal\languagewire_translation_provider\api\Libraries\Document\DocumentReferenceFiles|null $documentReferenceFiles
   *   Document reference files.
   *
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException
   *   If any of the required parameters is empty.
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\UnrecognizedLanguageException
   * @throws \Exception
   *   If any of the target languages is not recognized.
   */
  public function __construct(
    array $sourceDocuments,
    array $targetLanguages,
    string $correlationId,
    string $title,
    ConfigurationModel $configuration,
    \DateTimeInterface $deadline,
    string $briefing = '',
    string $purchaseOrderNumber = '',
    array $projectReferenceFileIds = [],
    DocumentReferenceFiles $documentReferenceFiles = NULL
  ) {
    parent::__construct(
            $correlationId,
            $title,
            $deadline,
            $briefing,
            $purchaseOrderNumber,
            $projectReferenceFileIds,
            $documentReferenceFiles
        );

    $this->sourceDocuments = array_values(array_unique($sourceDocuments));
    $this->targetLanguages = array_values(array_unique($targetLanguages));
    $this->configurationModel = $configuration;

    $this->validateLanguages($targetLanguages);
    $this->validateRequiredParametersAreNotEmpty();
  }

  /**
   * {@inheritdoc}
   */
  public function getCreateEndpoint(): string {
    return '/projects';
  }

  /**
   * {@inheritdoc}
   */
  public function getSourceDocuments(): array {
    return $this->sourceDocuments;
  }

  /**
   * {@inheritdoc}
   */
  public function getTargetLanguages(): array {
    return $this->targetLanguages;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguration(): ConfigurationModel {
    return $this->configurationModel;
  }

  /**
   * Dehydrates object data into a serializable format.
   *
   * @return array
   *   Dehydrated data.
   */
  public function dehydrate(): array {
    $configuration = $this->getConfiguration();

    return [
      'sourceDocuments' => $this->sourceDocuments,
      'targetLanguages' => $this->targetLanguages,
      'correlationId' => $this->correlationId,
      'title' => $this->title,
      'briefing' => $this->briefing,
      'purchaseOrderNumber' => $this->purchaseOrderNumber,
      'deadline' => $this->getFormattedDeadline(),
      'user' => $configuration->getUserId(),
      'workArea' => $configuration->getWorkAreaId(),
      'product' => $configuration->getProductId(),
      'invoicingAccount' => $configuration->getInvoicingAccountId(),
      'translationMemory' => $configuration->getTranslationMemoryId(),
      'termBase' => $configuration->getTermBaseId(),
      'referenceFiles' => $this->getUniqueReferenceFileIds(),
      'documentReferenceFiles' => $this->documentReferenceFiles->dehydrate(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function validateRequiredParametersAreNotEmpty(): void {
    parent::validateRequiredParametersAreNotEmpty();

    if (count($this->sourceDocuments) == 0) {
      throw EmptyParameterException::fromEmptyParameter("sourceDocuments");
    }

    if (count($this->targetLanguages) == 0) {
      throw EmptyParameterException::fromEmptyParameter("targetLanguages");
    }

    if (empty($this->configurationModel->getInvoicingAccountId())) {
      throw EmptyParameterException::fromEmptyParameter("invoicingAccount");
    }

    if (empty($this->configurationModel->getTranslationMemoryId())) {
      throw EmptyParameterException::fromEmptyParameter("translationMemory");
    }
  }

  /**
   * Validates language code.
   *
   * @param array $targetLanguages
   *   List of target languages.
   *
   * @SuppressWarnings(PHPMD.StaticAccess)
   */
  private function validateLanguages(array $targetLanguages): void {
    foreach ($targetLanguages as $language) {
      if (!LanguageService::isSupported($language)) {
        throw UnrecognizedLanguageException::fromUnrecognizedLanguage($language);
      }
    }
  }

}
