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

/**
 * @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
 */

namespace oat\taoQtiTest\models\runner;

use oat\libCat\CatSession;
use oat\libCat\Exception\CatEngineException;
use oat\libCat\result\AbstractResult;
use oat\libCat\result\ItemResult;
use oat\taoDelivery\model\execution\DeliveryServerService;
use oat\taoQtiTest\helpers\TestSessionMemento;
use oat\taoQtiTest\models\CompilationDataService;
use oat\taoQtiTest\models\event\QtiTestChangeEvent;
use oat\taoQtiTest\models\QtiTestCompilerIndex;
use oat\taoQtiTest\models\runner\session\TestSession;
use oat\taoQtiTest\models\SessionStateService;
use oat\taoQtiTest\models\cat\CatService;
use oat\taoQtiTest\models\ExtendedStateService;
use oat\taoQtiTest\models\SectionPauseService;
use oat\tao\helpers\UserHelper;
use qtism\data\AssessmentTest;
use qtism\data\AssessmentItemRef;
use qtism\data\NavigationMode;
use qtism\runtime\storage\binary\AbstractQtiBinaryStorage;
use qtism\runtime\storage\binary\BinaryAssessmentTestSeeker;
use qtism\runtime\tests\AssessmentTestSession;
use qtism\runtime\tests\RouteItem;
use oat\oatbox\event\EventManager;
use oat\taoQtiTest\models\event\SelectAdaptiveNextItemEvent;
use oat\libCat\result\ResultVariable;
use taoQtiTest_models_classes_QtiTestService;

/**
 * Class QtiRunnerServiceContext
 *
 * Defines a container to store and to share runner service context of the QTI implementation
 *
 * @package oat\taoQtiTest\models
 */
class QtiRunnerServiceContext extends RunnerServiceContext
{
    /**
     * The session storage
     * @var AbstractQtiBinaryStorage
     */
    protected $storage;

    /**
     * @var \taoQtiTest_helpers_SessionManager
     */
    protected $sessionManager;

    /**
     * The assessment test definition
     * @var AssessmentTest
     */
    protected $testDefinition;

    /**
     * The path of the compilation directory.
     *
     * @var \tao_models_classes_service_StorageDirectory[]
     */
    protected $compilationDirectory;

    /**
     * The meta data about the test definition being executed.
     *
     * @var array
     */
    private $testMeta;

    /**
     * The index of compiled items.
     *
     * @var QtiTestCompilerIndex
     */
    private $itemIndex;

    /**
     * The URI of the assessment test
     * @var string
     */
    protected $testDefinitionUri;

    /**
     * The URI of the compiled delivery
     * @var string
     */
    protected $testCompilationUri;

    /**
     * The URI of the delivery execution
     * @var string
     */
    protected $testExecutionUri;

    /**
     * Whether we are in synchronization mode
     * @var boolean
     */
    private $syncingMode = false;

    /**
     * @var string
     */
    private $userUri;

    /**
     * QtiRunnerServiceContext constructor.
     *
     * @param string $testDefinitionUri
     * @param string $testCompilationUri
     * @param string $testExecutionUri
     */
    public function __construct($testDefinitionUri, $testCompilationUri, $testExecutionUri)
    {
        $this->testDefinitionUri = $testDefinitionUri;
        $this->testCompilationUri = $testCompilationUri;
        $this->testExecutionUri = $testExecutionUri;
    }

    /**
     * Starts the context
     */
    public function init()
    {
        $this->retrieveItemIndex();
    }

    /**
     * Extracts the path of the compilation directory
     */
    protected function initCompilationDirectory()
    {
        $fileStorage = \tao_models_classes_service_FileStorage::singleton();
        $directoryIds = explode('|', $this->getTestCompilationUri());
        $directories = [
            'private' => $fileStorage->getDirectoryById($directoryIds[0]),
            'public' => $fileStorage->getDirectoryById($directoryIds[1])
        ];

        $this->compilationDirectory = $directories;
    }

    /**
     * Loads the test definition
     */
    protected function initTestDefinition()
    {
        $this->testDefinition = \taoQtiTest_helpers_Utils::getTestDefinition($this->getTestCompilationUri());
    }

    /**
     * Loads the storage
     * @throws \common_exception_Error
     * @throws \common_ext_ExtensionException
     */
    protected function initStorage()
    {
        /** @var DeliveryServerService $deliveryServerService */
        $deliveryServerService = $this->getServiceManager()->get(DeliveryServerService::SERVICE_ID);
        $resultStore = $deliveryServerService->getResultStoreWrapper($this->getTestExecutionUri());
        $testResource = new \core_kernel_classes_Resource($this->getTestDefinitionUri());
        $sessionManager = new \taoQtiTest_helpers_SessionManager($resultStore, $testResource);

        $seeker = new BinaryAssessmentTestSeeker($this->getTestDefinition());
        $userUri = $this->getUserUri();

        $config = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiTest')->getConfig('testRunner');
        $storageClassName = $config['test-session-storage'];
        $this->storage = new $storageClassName($sessionManager, $seeker, $userUri);
        $this->sessionManager = $sessionManager;
    }

    /**
     * Loads the test session
     * @throws \common_exception_Error
     */
    protected function initTestSession()
    {
        $storage = $this->getStorage();
        $sessionId = $this->getTestExecutionUri();

        if ($storage->exists($sessionId) === false) {
            \common_Logger::d("Instantiating QTI Assessment Test Session");
            $this->setTestSession($storage->instantiate($this->getTestDefinition(), $sessionId));

            $testTaker = $this->getTestTakerFromSessionOrRds();
            \taoQtiTest_helpers_TestRunnerUtils::setInitialOutcomes($this->getTestSession(), $testTaker);
        } else {
            \common_Logger::d("Retrieving QTI Assessment Test Session '${sessionId}'...");
            $this->setTestSession($storage->retrieve($this->getTestDefinition(), $sessionId));
        }

        \taoQtiTest_helpers_TestRunnerUtils::preserveOutcomes($this->getTestSession());
    }

    /**
     * @deprecated
     */
    protected function retrieveTestMeta()
    {
    }

    /**
     * Retrieves the index of compiled items.
     */
    protected function retrieveItemIndex()
    {
        $this->itemIndex = new QtiTestCompilerIndex();
        try {
            $directories = $this->getCompilationDirectory();
            $data = $directories['private']->read(taoQtiTest_models_classes_QtiTestService::TEST_COMPILED_INDEX);
            if ($data) {
                $this->itemIndex->unserialize($data);
            }
        } catch (\Exception $e) {
            \common_Logger::d('Ignoring file not found exception for Items Index');
        }
    }

    /**
     * Sets the test session
     * @param mixed $testSession
     * @throws \common_exception_InvalidArgumentType
     */
    public function setTestSession($testSession)
    {
        if ($testSession instanceof TestSession) {
            parent::setTestSession($testSession);
        } else {
            throw new \common_exception_InvalidArgumentType(
                'QtiRunnerServiceContext',
                'setTestSession',
                0,
                'oat\taoQtiTest\models\runner\session\TestSession',
                $testSession
            );
        }
    }

    /**
     * Gets the session storage
     * @return AbstractQtiBinaryStorage
     * @throws \common_exception_Error
     * @throws \common_ext_ExtensionException
     */
    public function getStorage()
    {
        if (!$this->storage) {
            $this->initStorage();
        }
        return $this->storage;
    }

    /**
     * @return EventManager
     * @throws \Zend\ServiceManager\Exception\ServiceNotFoundException
     */
    protected function getEventManager()
    {
        return $this->getServiceLocator()->get(EventManager::SERVICE_ID);
    }

    /**
     * @return \taoQtiTest_helpers_SessionManager
     * @throws \common_exception_Error
     * @throws \common_ext_ExtensionException
     */
    public function getSessionManager()
    {
        if (null === $this->sessionManager) {
            $this->initStorage();
        }
        return $this->sessionManager;
    }

    /**
     * Gets the assessment test definition
     * @return AssessmentTest
     */
    public function getTestDefinition()
    {
        if (null === $this->testDefinition) {
            $this->initTestDefinition();
        }
        return $this->testDefinition;
    }

    /**
     * Gets the path of the compilation directory
     * @return \tao_models_classes_service_StorageDirectory[]
     */
    public function getCompilationDirectory()
    {
        if (null === $this->compilationDirectory) {
            $this->initCompilationDirectory();
        }
        return $this->compilationDirectory;
    }

    /**
     * Gets the meta data about the test definition being executed.
     * @return array
     */
    public function getTestMeta()
    {
        if (!isset($this->testMeta)) {
            $directories = $this->getCompilationDirectory();

            /** @var CompilationDataService $compilationDataService */
            $compilationDataService = $this->getServiceLocator()->get(CompilationDataService::SERVICE_ID);
            $this->testMeta = $compilationDataService->readCompilationMetadata($directories['private']);
        }

        return $this->testMeta;
    }

    /**
     * Gets the URI of the assessment test
     * @return string
     */
    public function getTestDefinitionUri()
    {
        return $this->testDefinitionUri;
    }

    /**
     * Gets the URI of the compiled delivery
     * @return string
     */
    public function getTestCompilationUri()
    {
        return $this->testCompilationUri;
    }

    /**
     * Gets the URI of the delivery execution
     * @return string
     */
    public function getTestExecutionUri()
    {
        return $this->testExecutionUri;
    }

    /**
     * Gets info from item index
     * @param string $id
     * @return mixed
     * @throws \common_exception_Error
     */
    public function getItemIndex($id)
    {
        return $this->itemIndex->getItem($id, \common_session_SessionManager::getSession()->getInterfaceLanguage());
    }


    /**
     * @return string
     * @throws \common_exception_Error
     */
    public function getUserUri()
    {
        if ($this->userUri === null) {
            $this->userUri = \common_session_SessionManager::getSession()->getUserUri();
        }
        return $this->userUri;
    }

    /**
     * @param string $userUri
     */
    public function setUserUri($userUri)
    {
        $this->userUri = $userUri;
    }

    /**
     * Gets a particular value from item index
     * @param string $id
     * @param string $name
     * @return mixed
     * @throws \common_exception_Error
     */
    public function getItemIndexValue($id, $name)
    {
        return $this->itemIndex->getItemValue($id, \common_session_SessionManager::getSession()->getInterfaceLanguage(), $name);
    }

    /**
     * Get Cat Engine Implementation
     *
     * Get the currently configured Cat Engine implementation.
     *
     * @return \oat\libCat\CatEngine
     */
    public function getCatEngine(RouteItem $routeItem = null)
    {
        $compiledDirectory = $this->getCompilationDirectory()['private'];
        $adaptiveSectionMap = $this->getServiceManager()->get(CatService::SERVICE_ID)->getAdaptiveSectionMap($compiledDirectory);
        $routeItem = $routeItem ? $routeItem : $this->getTestSession()->getRoute()->current();

        $sectionId = $routeItem->getAssessmentSection()->getIdentifier();
        $catEngine = false;

        if (isset($adaptiveSectionMap[$sectionId])) {
            $catEngine = $this->getServiceManager()->get(CatService::SERVICE_ID)->getEngine($adaptiveSectionMap[$sectionId]['endpoint']);
        }

        return $catEngine;
    }

    /**
     * @return AssessmentTestSession
     * @throws \common_exception_Error
     */
    public function getTestSession()
    {
        if (!$this->testSession) {
            $this->initTestSession();
        }
        return parent::getTestSession(); // TODO: Change the autogenerated stub
    }


    /**
     * Get the current CAT Session Object.
     *
     * @param RouteItem|null $routeItem
     * @return \oat\libCat\CatSession|false
     */
    public function getCatSession(RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getCatSession(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    /**
     * Persist the CAT Session Data.
     *
     * Persist the current CAT Session Data in storage.
     *
     * @param string $catSession JSON encoded CAT Session data.
     * @param RouteItem|null $routeItem
     * @return mixed
     */
    public function persistCatSession($catSession, RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->persistCatSession(
            $catSession,
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    /**
     * Persist seen CAT Item identifiers.
     *
     * @param string $seenCatItemId
     */
    public function persistSeenCatItemIds($seenCatItemId)
    {
        $sessionId = $this->getTestSession()->getSessionId();
        $items = $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->getCatValue(
            $sessionId,
            $this->getCatSection()->getSectionId(),
            'cat-seen-item-ids'
        );
        if (!$items) {
            $items = [];
        } else {
            $items = json_decode($items);
        }

        if (!in_array($seenCatItemId, $items)) {
            $items[] = $seenCatItemId;
        }

        $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->setCatValue(
            $sessionId,
            $this->getCatSection()->getSectionId(),
            'cat-seen-item-ids',
            json_encode($items)
        );
    }

    /**
     * Get Last CAT Item Output.
     *
     * Get the last CAT Item Result from memory.
     */
    public function getLastCatItemOutput()
    {
        $sessionId = $this->getTestSession()->getSessionId();

        $itemOutput = $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->getCatValue(
            $sessionId,
            $this->getCatSection()->getSectionId(),
            'cat-item-output'
        );

        $output = [];

        if (!is_null($itemOutput)) {
            $rawData = json_decode($itemOutput, true);

            foreach ($rawData as $result) {
                /** @var ItemResult $itemResult */
                $itemResult = ItemResult::restore($result);
                $output[$itemResult->getItemRefId()] = $itemResult;
            }
        }

        return $output;
    }

    /**
     * Persist CAT Item Output.
     *
     * Persist the last CAT Item Result in memory.
     */
    public function persistLastCatItemOutput(array $lastCatItemOutput)
    {
        $sessionId = $this->getTestSession()->getSessionId();

        $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->setCatValue(
            $sessionId,
            $this->getCatSection()->getSectionId(),
            'cat-item-output',
            json_encode($lastCatItemOutput)
        );
    }

    /**
     * Get Current CAT Section.
     *
     * Returns the current CatSection object. In case of the current Assessment Section is not adaptive, the method
     * returns the boolean false value.
     *
     * @return \oat\libCat\CatSection|boolean
     */
    public function getCatSection(RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getCatSection(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    /**
     * Is the Assessment Test Session Context Adaptive.
     *
     * Determines whether or not the current Assessment Test Session is in an adaptive context.
     *
     * @param AssessmentItemRef $currentAssessmentItemRef (optional) An AssessmentItemRef object to be considered as the current assessmentItemRef.
     * @return boolean
     */
    public function isAdaptive(AssessmentItemRef $currentAssessmentItemRef = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->isAdaptive($this->getTestSession(), $currentAssessmentItemRef);
    }

    /**
     * Contains Adaptive Content.
     *
     * Whether or not the current Assessment Test Session has some adaptive contents.
     *
     * @return boolean
     */
    public function containsAdaptive()
    {
        $adaptiveSectionMap = $this->getServiceManager()->get(CatService::SERVICE_ID)->getAdaptiveSectionMap($this->getCompilationDirectory()['private']);

        return !empty($adaptiveSectionMap);
    }

    /**
     * Select the next Adaptive Item and store the retrieved results from CAT engine
     *
     * Ask the CAT Engine for the Next Item to be presented to the candidate, depending on the last
     * CAT Item ID and last CAT Item Output currently stored.
     *
     * This method returns a CAT Item ID in case of the CAT Engine returned one. Otherwise, it returns
     * null meaning that there is no CAT Item to be presented.
     *
     * @return mixed|null
     * @throws \common_Exception
     */
    public function selectAdaptiveNextItem()
    {
        $lastItemId = $this->getCurrentCatItemId();
        $lastOutput = $this->getLastCatItemOutput();
        $catSession = $this->getCatSession();

        $preSelection = $catSession->getTestMap();

        try {
            if (!$this->syncingMode) {
                $selection = $catSession->getTestMap(array_values($lastOutput));

                if (!$this->saveAdaptiveResults($catSession)) {
                    \common_Logger::w('Unable to save CatService results.');
                }
                $isShadowItem = false;
            } else {
                $selection = $catSession->getTestMap();
                $isShadowItem = true;
            }
        } catch (CatEngineException $e) {
            \common_Logger::e('Error during CatEngine processing. ' . $e->getMessage());
            $selection = $catSession->getTestMap();
            $isShadowItem = true;
        }

        $event = new SelectAdaptiveNextItemEvent($this->getTestSession(), $lastItemId, $preSelection, $selection, $isShadowItem);
        $this->getServiceManager()->get(EventManager::SERVICE_ID)->trigger($event);

        $this->persistCatSession($catSession);
        if (is_array($selection) && count($selection) > 0) {
            \common_Logger::d("New CAT item selection is '" . implode(', ', $selection) . "'.");
            return $selection[0];
        } else {
            \common_Logger::d('No new CAT item selection.');
            return null;
        }
    }

    /**
     * Get Current AssessmentItemRef object.
     *
     * This method returns the current AssessmentItemRef object depending on the test $context.
     *
     * @return \qtism\data\ExtendedAssessmentItemRef|false
     */
    public function getCurrentAssessmentItemRef()
    {
        if ($this->isAdaptive()) {
            return $this->getServiceManager()->get(CatService::SERVICE_ID)->getAssessmentItemRefByIdentifier(
                $this->getCompilationDirectory()['private'],
                $this->getCurrentCatItemId()
            );
        } else {
            return $this->getTestSession()->getCurrentAssessmentItemRef();
        }
    }

    public function getPreviouslySeenCatItemIds(RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getPreviouslySeenCatItemIds(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    public function getShadowTest(RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getShadowTest(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    public function getCurrentCatItemId(RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getCurrentCatItemId(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $routeItem
        );
    }

    public function persistCurrentCatItemId($catItemId)
    {
        $session = $this->getTestSession();
        $sessionId = $session->getSessionId();
        $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->setCatValue(
            $sessionId,
            $this->getCatSection()->getSectionId(),
            'current-cat-item-id',
            $catItemId
        );

        $event = new QtiTestChangeEvent($session, new TestSessionMemento($session));
        $this->getServiceManager()->propagate($event);
        $this->getEventManager()->trigger($event);
    }

    public function getItemPositionInRoute($refId, &$catItemId = '')
    {
        $route = $this->getTestSession()->getRoute();
        $routeCount = $route->count();

        $i = 0;
        $j = 0;

        while ($i < $routeCount) {
            $routeItem = $route->getRouteItemAt($i);

            if ($this->isAdaptive($routeItem->getAssessmentItemRef())) {
                $shadow = $this->getShadowTest($routeItem);

                for ($k = 0; $k < count($shadow); $k++) {
                    if ($j == $refId) {
                        $catItemId = $shadow[$k];
                        break 2;
                    }

                    $j++;
                }
            } else {
                if ($j == $refId) {
                    break;
                }

                $j++;
            }

            $i++;
        }

        return $i;
    }

    /**
     * Get Real Current Position.
     *
     * This method returns the real position of the test taker within
     * the item flow, by considering CAT sections.
     *
     * @return integer A zero-based index.
     */
    public function getCurrentPosition()
    {
        $route = $this->getTestSession()->getRoute();
        $routeCount = $route->count();
        $routeItemPosition = $route->getPosition();
        $currentRouteItem = $route->getRouteItemAt($routeItemPosition);

        $finalPosition = 0;

        for ($i = 0; $i < $routeCount; $i++) {
            $routeItem = $route->getRouteItemAt($i);

            if ($routeItem !== $currentRouteItem) {
                if (!$this->isAdaptive($routeItem->getAssessmentItemRef())) {
                    $finalPosition++;
                } else {
                    $finalPosition += count($this->getShadowTest($routeItem));
                }
            } else {
                if ($this->isAdaptive($routeItem->getAssessmentItemRef())) {
                    $finalPosition += array_search(
                        $this->getCurrentCatItemId($routeItem),
                        $this->getShadowTest($routeItem)
                    );
                }

                break;
            }
        }

        return $finalPosition;
    }

    public function getCatAttempts($identifier, RouteItem $routeItem = null)
    {
        return $this->getServiceManager()->get(CatService::SERVICE_ID)->getCatAttempts(
            $this->getTestSession(),
            $this->getCompilationDirectory()['private'],
            $identifier,
            $routeItem
        );
    }

    public function persistCatAttempts($identifier, $attempts)
    {
        $sessionId = $this->getTestSession()->getSessionId();
        $sectionId = $this->getCatSection()->getSectionId();

        $catAttempts = $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->getCatValue(
            $sessionId,
            $sectionId,
            'cat-attempts'
        );

        $catAttempts = ($catAttempts) ? $catAttempts : [];
        $catAttempts[$identifier] = $attempts;

        $this->getServiceManager()->get(ExtendedStateService::SERVICE_ID)->setCatValue(
            $sessionId,
            $sectionId,
            'cat-attempts',
            $catAttempts
        );
    }

    /**
     * Can Move Backward
     *
     * Whether or not the Test Taker is able to navigate backward.
     * This implementation takes the CAT sections into consideration.
     *
     * @return boolean
     */
    public function canMoveBackward()
    {
        $moveBack = false;
        $session  = $this->getTestSession();
        if ($this->isAdaptive()) {
            $positionInCatSession = array_search(
                $this->getCurrentCatItemId(),
                $this->getShadowTest()
            );

            if ($positionInCatSession === 0) {
                // First item in cat section.
                if ($session->getRoute()->getPosition() !== 0) {
                    $moveBack = $session->getPreviousRouteItem()->getTestPart()->getNavigationMode() === NavigationMode::NONLINEAR;
                }
            } else {
                $moveBack = $session->getRoute()->current()->getTestPart()->getNavigationMode() === NavigationMode::NONLINEAR;
            }
        } else {
            $moveBack = $session->canMoveBackward();

            //check also if the sectionPause prevents you from moving backward
            if ($moveBack) {
                $moveBack = $this->getServiceManager()->get(SectionPauseService::SERVICE_ID)->canMoveBackward($session);
            }
        }

        return $moveBack;
    }

    /**
     * Save the Cat service result for tests and items
     *
     * @param CatSession $catSession
     * @return bool
     */
    protected function saveAdaptiveResults(CatSession $catSession)
    {
        $testResult = $catSession->getTestResult();
        $testResult = empty($testResult) ? [] : [$testResult];
        return $this->storeResult(array_merge($testResult, $catSession->getItemResults()));
    }

    /**
     * Store a Cat Result variable
     *
     * The result has to be an ItemResult and TestResult to embed CAT variables
     * After converted them to taoResultServer variables
     * Use the runner service to store the variables
     *
     * @param AbstractResult[] $results
     * @return bool
     */
    protected function storeResult(array $results)
    {
        /** @var QtiRunnerService $runnerService */
        $runnerService = $this->getServiceLocator()->get(QtiRunnerService::SERVICE_ID);

        $success = true;
        try {
            foreach ($results as $result) {
                if (!$result instanceof AbstractResult) {
                    throw new \common_Exception(__FUNCTION__ . ' requires a CAT result to store it.');
                }

                $variables = $this->convertCatVariables($result->getVariables());
                if (empty($variables)) {
                    \common_Logger::t('No Cat result variables to store.');
                    continue;
                }

                if ($result instanceof ItemResult) {
                    $itemId = $result->getItemRefId();
                    $itemUri = $this->getItemUriFromRefId($itemId);
                } else {
                    $itemUri = $itemId = null;
                    $sectionId = $this
                        ->getTestSession()
                        ->getRoute()
                        ->current()
                        ->getAssessmentSection()
                        ->getIdentifier();
                    /** @var \taoResultServer_models_classes_Variable $variable */
                    foreach ($variables as $variable) {
                        $variable->setIdentifier($sectionId . '-' . $variable->getIdentifier());
                    }
                }

                if (!$runnerService->storeVariables($this, $itemUri, $variables, $itemId)) {
                    $success = false;
                }
            }
        } catch (\Exception $e) {
            \common_Logger::w('An error has occurred during CAT result storing: ' . $e->getMessage());
            $success = false;
        }

        return $success;
    }

    /**
     * Convert CAT variables to taoResultServer variables
     *
     * Following the variable type, use the Runner service to get the appropriate variable
     * The method manage the trace, response and outcome variable
     *
     * @param array $variables
     * @return array
     * @throws \common_exception_NotImplemented If variable type is not managed
     */
    protected function convertCatVariables(array $variables)
    {
        /** @var QtiRunnerService $runnerService */
        $runnerService = $this->getServiceLocator()->get(QtiRunnerService::SERVICE_ID);
        $convertedVariables = [];

        foreach ($variables as $variable) {
            switch ($variable->getVariableType()) {
                case ResultVariable::TRACE_VARIABLE:
                    $getVariableMethod = 'getTraceVariable';
                    break;
                case ResultVariable::RESPONSE_VARIABLE:
                    $getVariableMethod = 'getResponseVariable';
                    break;
                case ResultVariable::OUTCOME_VARIABLE:
                    $getVariableMethod = 'getOutcomeVariable';
                    break;
                case ResultVariable::TEMPLATE_VARIABLE:
                default:
                    $getVariableMethod = null;
                    break;
            }

            if (is_null($getVariableMethod)) {
                \common_Logger::w('Variable of type ' . $variable->getVariableType() . ' is not implemented in ' . __METHOD__);
                throw new \common_exception_NotImplemented();
            }

            $convertedVariables[] = call_user_func_array(
                [$runnerService, $getVariableMethod],
                [$variable->getId(), $variable->getValue()]
            );
        }

        return $convertedVariables;
    }

    /**
     * Get item uri associated to the given $itemId.
     *
     * @return string The uri
     */
    protected function getItemUriFromRefId($itemId)
    {
        $ref = $this->getServiceManager()->get(CatService::SERVICE_ID)->getAssessmentItemRefByIdentifier(
            $this->getCompilationDirectory()['private'],
            $itemId
        );
        return explode('|', $ref->getHref())[0];
    }

    /**
     * Are we in a synchronization mode
     * @return bool
     */
    public function isSyncingMode()
    {
        return $this->syncingMode;
    }

    /**
     * Set/Unset the synchronization mode
     * @param bool $syncing
     */
    public function setSyncingMode($syncing)
    {
        $this->syncingMode = (bool) $syncing;
    }

    /**
     * @return \oat\oatbox\user\User
     * @throws \common_exception_Error
     */
    private function getTestTakerFromSessionOrRds()
    {
        try {
            $session = \common_session_SessionManager::getSession();
        } catch (\common_exception_Error $exception) {
            $session = null;
            \common_Logger::w($exception->getMessage());
        }

        if ($session == null || $session->getUser() == null) {
            $testTaker = UserHelper::getUser($this->getUserUri());
        } else {
            $testTaker = $session->getUser();
        }

        return $testTaker;
    }
}