<?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) 2014 (original work) Open Assessment Technologies SA;
 */

namespace oat\taoQtiItem\test\integration;

use common_exception_Error;
use \common_report_Report;
use oat\tao\test\TaoPhpUnitTestRunner;
use oat\taoQtiItem\model\Export\QTIPackedItemExporter;
use oat\taoQtiItem\model\qti\exception\ParsingException;
use oat\taoQtiItem\model\qti\ImportService;
use oat\taoQtiItem\model\QtiItemCompiler;
use \taoItems_models_classes_ItemsService;
use \tao_models_classes_service_FileStorage;
use \ZipArchive;
use oat\taoItems\model\media\LocalItemSource;
use oat\taoQtiItem\model\ItemModel;

include_once dirname(__FILE__) . '/../../includes/raw_start.php';
/**
 * test the item content access
 *
 */
class ItemImportTest extends TaoPhpUnitTestRunner
{
    /**
     * @var ImportService
     */
    protected $importService;
    /**
     * @var taoItems_models_classes_ItemsService
     */
    protected $itemService;
    /**
     * @var array
     */
    protected $exportedZips = [];

    /**
     * tests initialization
     * load qti service
     */
    public function setUp(): void
    {
        TaoPhpUnitTestRunner::initTest();
        $this->importService = ImportService::singleton();
        $this->itemService = taoItems_models_classes_ItemsService::singleton();
    }

    /**
     * @return string
     */
    protected function getSamplePath($relPath)
    {
        return __DIR__ . DIRECTORY_SEPARATOR . 'samples' . str_replace('/', DIRECTORY_SEPARATOR, $relPath);
    }

    public function testWrongPackage()
    {
        $this->expectException(ParsingException::class);
        $itemClass = $this->itemService->getRootClass();
        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/InvalidArchive.zip'),
            $itemClass
        );
    }

    public function testWrongClass()
    {
        $this->expectException(common_exception_Error::class);
        $itemClass = new \core_kernel_classes_Class(taoItems_models_classes_ItemsService::PROPERTY_ITEM_MODEL);
        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/package.zip'),
            $itemClass
        );
    }

    /**
     *
     */
    public function testWrongFormatClass()
    {
        $itemClass = $this->itemService->getRootClass();

        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/MalformedItemXml.zip'),
            $itemClass,
            true,
            true
        );
        $this->assertEquals(\common_report_Report::TYPE_ERROR, $report->getType());
    }


    /**
     *
     */
    public function testWrongFormatXmlClass()
    {
        $itemClass = $this->itemService->getRootClass();

        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/MalformedItemInTheMiddleXml.zip'),
            $itemClass,
            true,
            false,
            true
        );
        $this->assertEquals(\common_report_Report::TYPE_WARNING, $report->getType());
    }

    /**
     *
     */
    public function testWrongManifest()
    {
        $itemClass = $this->itemService->getRootClass();


        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/MalformedManifest.zip'),
            $itemClass,
            true,
            true
        );
        $this->assertEquals(\common_report_Report::TYPE_ERROR, $report->getType());
    }
    /**
     *
     */
    public function testWrongXml()
    {

        $itemClass = $this->itemService->getRootClass();

        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/wrong/WrongManifestFileItemHref.zip'),
            $itemClass,
            true,
            true
        );
        $this->assertEquals(\common_report_Report::TYPE_ERROR, $report->getType());
    }


    public function testImportQti20()
    {
        $itemClass = $this->itemService->getRootClass();
        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/QTI/qti20.zip'),
            $itemClass
        );
        $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $report->getType());

        $items = [];
        foreach ($report as $itemReport) {
            $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $itemReport->getType());
            $data = $itemReport->getData();
            if (!is_null($data)) {
                $items[] = $data;
            }
        }
        $this->assertEquals(2, count($items));
        $this->removeItem($items[1]);

        return $items[0];
    }

    public function testImportApipv1p0Final()
    {
        $itemClass = $this->itemService->getRootClass();
        $report = $this->importService->importQTIPACKFile($this->getSamplePath('/package/APIP/apip_v1p0_final.zip'), $itemClass);
        $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $report->getType());

        $items = [];
        foreach ($report as $itemReport) {
            $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $itemReport->getType());
            $data = $itemReport->getData();
            if ($data !== null) {
                $items[] = $data;
            }
        }
        $this->assertCount(1, $items);
        $this->removeItem($items[0]);
    }

    public function testImportPCI()
    {
        $itemClass = $this->itemService->getRootClass();
        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/PCI/pcisample.zip'),
            $itemClass
        );
        $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $report->getType());

        $items = [];
        foreach ($report as $itemReport) {
            $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $itemReport->getType());
            $data = $itemReport->getData();
            if (!is_null($data)) {
                $items[] = $data;
            }
        }
        $this->assertEquals(1, count($items));
        $item = \oat\taoQtiItem\model\qti\Service::singleton()->getDataItemByRdfItem($items[0], DEFAULT_LANG, false);

        $itemData = $item->toArray();
        $itemDataElemetns = current($itemData['body']['elements']);

        //ensure that path prefixed with interaction identifies was not changed;
        $this->assertEquals($itemDataElemetns['entryPoint'], "adaptiveChoiceInteraction/runtime/adaptiveChoiceInteraction.js");
        //ensure that interaction properties imported properly
        $this->assertTrue(isset($itemDataElemetns['properties']['choices'][0]['label']));

        foreach ($items as $item) {
            $this->itemService->deleteItem($item);
        }
    }

    public function testImport()
    {
        $itemClass = $this->itemService->getRootClass();
        $report = $this->importService->importQTIPACKFile(
            $this->getSamplePath('/package/QTI/package.zip'),
            $itemClass
        );

        $items = [];
        foreach ($report as $itemReport) {
            $data = $itemReport->getData();
            if (!is_null($data)) {
                $items[] = $data;
            }
        }
        $this->assertEquals(1, count($items));

        $item = current($items);
        $this->assertInstanceOf('\core_kernel_classes_Resource', $item);
        $this->assertTrue($item->exists());

        $resourceManager = new LocalItemSource(
            [ 'item' => $item,
                'lang' => DEFAULT_LANG]
        );
        $data = $resourceManager->getDirectory();
        $this->assertTrue(is_array($data));
        $this->assertTrue(isset($data['path']));
        $this->assertEquals('/', $data['path']);

        $this->assertTrue(isset($data['children']));
        $children = $data['children'];
        $this->assertEquals(3, count($children));

        $check = ['/images/','/style/'];

        $file = null;
        $dir = null;
        foreach ($children as $child) {
            if (isset($child['path'])) {
                $this->assertContains($child['path'], $check);
            }
            if (isset($child['name'])) {
                $file = $child;
            }
        }

        $this->assertEquals("qti.xml", $file['name']);
        $this->assertStringContainsString("/xml", $file['mime']);
        $this->assertTrue($file['size'] > 0);


        return $item;
    }

    /**
     * @depends testImport
     * @param $item
     */
    public function testCompile($item)
    {
        $storage = tao_models_classes_service_FileStorage::singleton();
        $compiler = new QtiItemCompiler($item, $storage);
        $compiler->setServiceLocator($this->getServiceManagerProphecy());
        $report = $compiler->compile();
        $this->assertEquals($report->getType(), common_report_Report::TYPE_SUCCESS);
        $serviceCall = $report->getData();
        $this->assertNotNull($serviceCall);
        $this->assertInstanceOf('\tao_models_classes_service_ServiceCall', $serviceCall);
    }

    /**
     * @depends testImport
     * @param $item
     * @throws Exception
     * @throws \oat\taoQtiItem\model\qti\exception\ExtractException
     * @throws \oat\taoQtiItem\model\qti\exception\ParsingException
     * @return mixed
     */
    public function testExport($item)
    {
        $itemClass = $this->itemService->getRootClass();

        list($path, $manifest) = $this->createZipArchive($item);

        $report = $this->importService->importQTIPACKFile($path, $itemClass);
        $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $report->getType());
        $items = [];
        foreach ($report as $itemReport) {
            $data = $itemReport->getData();
            if (!is_null($data)) {
                $items[] = $data;
            }
        }
        $this->assertEquals(1, count($items));
        $item2 = current($items);
        $this->assertInstanceOf('\core_kernel_classes_Resource', $item);
        $this->assertTrue($item->exists());

        $this->assertEquals($item->getLabel(), $item2->getLabel());

        $this->removeItem($item2);

        return $manifest;
    }

    /**
     * @depends testImportQti20
     * @depends testExport
     * @param $item
     * @param $manifest
     * @return mixed
     */
    public function testExportWithManifest($item, $manifest)
    {
        list($path, $manifest2) = $this->createZipArchive($item, $manifest);
        $this->assertSame($manifest, $manifest2);
    }

    /**
     * @depends testImportQti20
     * @depends testImport
     */
    public function testRemoveItem()
    {
        foreach (func_get_args() as $item) {
            $this->removeItem($item);
        }
    }


    private function removeItem($item)
    {
        $this->itemService->deleteItem($item);
        $this->assertFalse($item->exists());
    }

    public function tearDown(): void
    {

        foreach ($this->exportedZips as $path) {
            if (file_exists($path)) {
                $this->assertTrue(unlink($path));
            }
        }
    }

    /**
     * @param $item
     * @param $manifest
     * @return array
     * @throws \Exception
     */
    private function createZipArchive($item, $manifest = null)
    {
        $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('test_') . '.zip';
        $zipArchive = new ZipArchive();
        if ($zipArchive->open($path, ZipArchive::CREATE) !== true) {
            throw new \Exception('Unable to create archive at ' . $path);
        }

        if ($this->itemService->hasItemModel($item, [ItemModel::MODEL_URI])) {
            $exporter = new QTIPackedItemExporter($item, $zipArchive, $manifest);
            $exporter->export();
            $manifest = $exporter->getManifest();
        }

        $this->assertTrue($this->itemService->hasItemModel($item, [ItemModel::MODEL_URI]));

        $this->assertNotNull($manifest);

        $this->assertEquals(ZipArchive::ER_OK, $zipArchive->status, $zipArchive->getStatusString());

        $zipArchive->close();
        $this->assertTrue(file_exists($path), 'could not find path ' . $path);
        $this->exportedZips[] = $path;
        return [$path, $manifest];
    }
}