<?php

/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2019  (original work) Open Assessment Technologies SA;
 */

namespace oat\taoDeliveryRdf\model\export;

use oat\taoDeliveryRdf\model\assembly\CompiledTestConverterFactory;
use ZipArchive;
use Exception;
use InvalidArgumentException;
use common_Exception;
use core_kernel_classes_EmptyProperty;
use tao_helpers_Display;
use tao_helpers_Export;
use tao_helpers_File;
use core_kernel_classes_Resource;
use common_ext_ExtensionsManager;
use tao_models_classes_service_ServiceCall;
use tao_models_classes_export_RdfExporter;
use oat\generis\model\OntologyAwareTrait;
use oat\oatbox\log\LoggerAwareTrait;
use oat\oatbox\service\ConfigurableService;
use oat\tao\model\service\ServiceFileStorage;
use oat\taoDeliveryRdf\model\assembly\AssemblyFilesReaderInterface;
use oat\taoDeliveryRdf\model\assembly\UnsupportedCompiledTestFormatException;
use oat\taoDeliveryRdf\model\DeliveryAssemblyService;

class AssemblyExporterService extends ConfigurableService
{
    use LoggerAwareTrait;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          use OntologyAwareTrait;


    const SERVICE_ID = 'taoDeliveryRdf/AssemblyExporterService';
    const OPTION_ASSEMBLY_FILES_READER = 'assembly_files_reader';
    const OPTION_RDF_EXPORTER = 'rdf_exporter';
    const MANIFEST_FILENAME = 'manifest.json';
    const DELIVERY_RDF_FILENAME = 'delivery.rdf';
    /**
         * @var AssemblyFilesReaderInterface
         */
    private $assemblyFilesReader;
    /**
         * @var tao_models_classes_export_RdfExporter
         */
    private $rdfExporter;
    /**
         * AssemblyExporterService constructor.
         * @param array $options
         */
    public function __construct($options = [])
    {
        parent::__construct($options);
        if (!$this->getOption(self::OPTION_ASSEMBLY_FILES_READER) instanceof AssemblyFilesReaderInterface) {
            throw new InvalidArgumentException(sprintf('%s option value must be an instance of %s', self::OPTION_ASSEMBLY_FILES_READER, AssemblyFilesReaderInterface::class));
        }

        $this->rdfExporter = $this->getOption(self::OPTION_RDF_EXPORTER);
        if (!$this->rdfExporter instanceof tao_models_classes_export_RdfExporter) {
            throw new InvalidArgumentException('%s option value must be an instance of %s', self::OPTION_RDF_EXPORTER, tao_models_classes_export_RdfExporter::class);
        }
    }

    /**
     * Export Compiled Delivery
     *
     * Exports a delivery into its compiled form. In case of the $fsExportPath argument is set,
     * the compiled delivery will be stored in the 'taoDelivery' shared file system, at $fsExportPath location.
     *
     * @param core_kernel_classes_Resource $compiledDelivery
     * @param string $outputTestFormat Format compiled test file in output assembly package.
     *
     * @return string The path to the compiled delivery on the local file system OR the 'taoDelivery' shared file system, depending on whether $fsExportPath is set.
     *
     * @throws common_Exception
     * @throws core_kernel_classes_EmptyProperty
     */
    public function exportCompiledDelivery(core_kernel_classes_Resource $compiledDelivery, $outputTestFormat)
    {
        $this->logDebug("Exporting Delivery Assembly '" . $compiledDelivery->getUri() . "'...");
        $fileName = tao_helpers_Display::textCleaner($compiledDelivery->getLabel()) . '.zip';
        $path = tao_helpers_File::concat([tao_helpers_Export::getExportPath(), $fileName]);
        if (!tao_helpers_File::securityCheck($path, true)) {
            throw new Exception('Unauthorized file name');
        }

        // If such a target zip file exists, remove it from local filesystem. It prevents some synchronicity issues
        // to occur while dealing with ZIP Archives (not explained yet).
        if (file_exists($path)) {
            unlink($path);
        }

        $zipArchive = new ZipArchive();
        if ($zipArchive->open($path, ZipArchive::CREATE) !== true) {
            throw new Exception('Unable to create archive at ' . $path);
        }

        $this->setupCompiledTestConverter($outputTestFormat);
        $this->doExportCompiledDelivery($path, $compiledDelivery, $zipArchive);
        $zipArchive->close();
        return $path;
    }

    /**
     * Do Export Compiled Delivery
     *
     * Method containing the main behavior of exporting a compiled delivery into a ZIP archive.
     *
     * For developers wanting to override this method, the following information has to be taken into account:
     *
     * - The value of the $zipArgive argument is an already open ZipArchive object.
     * - The method must keep the archive open after its execution (calling code will take care of it).
     *
     * @param $path
     * @param core_kernel_classes_Resource $compiledDelivery
     * @param ZipArchive $zipArchive
     * @throws common_Exception
     * @throws core_kernel_classes_EmptyProperty
     */
    protected function doExportCompiledDelivery($path, core_kernel_classes_Resource $compiledDelivery, ZipArchive $zipArchive)
    {
        $taoDeliveryVersion = common_ext_ExtensionsManager::singleton()->getInstalledVersion('taoDelivery');
        $data = [
            'dir' => [],
            'label' => $compiledDelivery->getLabel(),
            'version' => $taoDeliveryVersion
        ];
        $directories = $compiledDelivery->getPropertyValues($this->getProperty(DeliveryAssemblyService::PROPERTY_DELIVERY_DIRECTORY));
        foreach ($directories as $id) {
            $directory = $this->getServiceLocator()->get(ServiceFileStorage::SERVICE_ID)->getDirectoryById($id);
            foreach ($this->getAssemblyFilesReader()->getFiles($directory) as $filePath => $fileStream) {
                tao_helpers_File::addFilesToZip($zipArchive, $fileStream, $filePath);
            }
            $data['dir'][$id] = $directory->getPrefix();
        }

        $runtime = $compiledDelivery->getOnePropertyValue(
            $this->getProperty(DeliveryAssemblyService::PROPERTY_DELIVERY_RUNTIME)
        );
        $serviceCall = $runtime instanceof core_kernel_classes_Resource
            ? tao_models_classes_service_ServiceCall::fromResource($runtime)
            : tao_models_classes_service_ServiceCall::fromString((string)$runtime);
        $data['runtime'] = base64_encode(json_encode($serviceCall));
        $rdfData = $this->rdfExporter->getRdfString([$compiledDelivery]);
        if (!$zipArchive->addFromString(self::DELIVERY_RDF_FILENAME, $rdfData)) {
            throw new common_Exception('Unable to add metadata to exported delivery assembly');
        }
        $data['meta'] = self::DELIVERY_RDF_FILENAME;
        $content = json_encode($data);
        if (!$zipArchive->addFromString(self::MANIFEST_FILENAME, $content)) {
            $zipArchive->close();
            unlink($path);
            throw new common_Exception('Unable to add manifest to exported delivery assembly');
        }
    }

    /**
     * @param string $outputTestFormat
     * @return void
     *
     * @throws UnsupportedCompiledTestFormatException
     */
    private function setupCompiledTestConverter($outputTestFormat)
    {
        /** @var CompiledTestConverterFactory $compiledTestConverterFactory */
        $compiledTestConverterFactory = $this->getServiceLocator()->get(CompiledTestConverterFactory::class);
        $converter = $compiledTestConverterFactory->createConverter($outputTestFormat);

        if ($converter) {
            $this->getAssemblyFilesReader()->setCompiledTestConverter($converter);
        }
    }

    /**
     * @return AssemblyFilesReaderInterface
     */
    private function getAssemblyFilesReader()
    {
        if (!$this->assemblyFilesReader instanceof AssemblyFilesReaderInterface) {
            $this->assemblyFilesReader = $this->getOption(self::OPTION_ASSEMBLY_FILES_READER);
        }

        return $this->assemblyFilesReader;
    }
}