<?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) 2015 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 */

namespace oat\taoQtiTest\models;

use oat\oatbox\service\ConfigurableService;
use oat\taoDelivery\model\execution\ServiceProxy;
use oat\taoQtiTest\models\cat\CatService;
use qtism\data\AssessmentItemRef;
use qtism\runtime\tests\AssessmentTestSession;
use oat\taoDelivery\model\execution\DeliveryExecution;
use qtism\runtime\tests\RouteItem;

/**
 * The SessionStateService
 *
 * Service used for pausing and resuming the delivery execution.
 * All timers in paused session will be paused.
 *
 * Usage example:
 * <pre>
 * //Pause session:
 * $sessionStateService = ServiceManager::getServiceManager()->get('taoQtiTest/SessionStateService');
 * $sessionStateService->pauseSession($session);
 *
 * //resume session:
 * $sessionStateService = ServiceManager::getServiceManager()->get('taoQtiTest/SessionStateService');
 * $sessionStateService->resumeSession($session);
 * </pre>
 * @author Aleh Hutnikau <hutnikau@1pt.com>
 */
class SessionStateService extends ConfigurableService
{
    const SERVICE_ID = 'taoQtiTest/SessionStateService';
    
    const OPTION_STATE_FORMAT = 'stateFormat';
    
    /**
     * @var ServiceProxy
     */
    private $deliveryExecutionService;

    public function __construct(array $options = [])
    {
        $this->deliveryExecutionService = ServiceProxy::singleton();
        parent::__construct($options);
    }

    /**
     * Pause delivery execution.
     * @param AssessmentTestSession $session
     * @return boolean success
     */
    public function pauseSession(AssessmentTestSession $session)
    {
        $session->updateDuration();
        return $this->getDeliveryExecution($session)->setState(DeliveryExecution::STATE_PAUSED);
    }

    /**
     * Resume delivery execution
     * @param AssessmentTestSession $session
     */
    public function resumeSession(AssessmentTestSession $session)
    {
        $deliveryExecutionState = $this->getSessionState($session);
        if ($deliveryExecutionState === DeliveryExecution::STATE_PAUSED) {
            $this->updateTimeReference($session);
            $this->getDeliveryExecution($session)->setState(DeliveryExecution::STATE_ACTIVE);
        }
    }

    /**
     * Get delivery execution state
     * @param AssessmentTestSession $session
     * @return string
     */
    public function getSessionState(AssessmentTestSession $session)
    {
        $deliveryExecution = $this->getDeliveryExecution($session);
        return $deliveryExecution->getState()->getUri();
    }

    /**
     * Set time reference of current assessment item session to <i>now</i> instead of time of last update.
     * This ensures that time when delivery execution was paused will not be taken in account.
     * Make sure that method invoked right after retrieving assessment test session
     * and before the first AssessmentTestSession::updateDuration method call
     * @param AssessmentTestSession $session
     * @param \DateTime|null $time Time to be specified. Current time by default. Make sure that $time has UTC timezone.
     */
    public function updateTimeReference(AssessmentTestSession $session, \DateTime $time = null)
    {
        if ($time === null) {
            $time = new \DateTime('now', new \DateTimeZone('UTC'));
        }

        $itemSession = $session->getCurrentAssessmentItemSession();

        if ($itemSession) {
            $itemSession->setTimeReference($time);
            $session->updateDuration();
        }
    }

    /**
     * @param AssessmentTestSession $session
     * @return DeliveryExecution
     */
    private function getDeliveryExecution(AssessmentTestSession $session)
    {
        return $this->deliveryExecutionService->getDeliveryExecution($session->getSessionId());
    }

    /**
     * Returns appropriate JS service implementation for testRunner
     *
     * @param boolean $resetTimerAfterResume
     *
     * @return string
     */
    public function getClientImplementation($resetTimerAfterResume = false)
    {
        if ($resetTimerAfterResume) {
            return 'taoQtiTest/testRunner/resumingStrategy/keepAfterResume';
        }
        return 'taoQtiTest/testRunner/resumingStrategy/resetAfterResume';
    }
    
    /**
     * Return a description of the test session
     *
     * @return string
     */
    public function getSessionDescription(\taoQtiTest_helpers_TestSession $session)
    {
        if ($session->isRunning()) {
            $config = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiTest')->getConfig('testRunner');
            $progressScope = isset($config['progress-indicator-scope']) ? $config['progress-indicator-scope'] : 'test';
            $progress = $this->getSessionProgress($session);
            $itemPosition = $progress[$progressScope];
            $itemCount = $progress[$progressScope . 'Length'];

            $map = [
                'title' => $session->getCurrentAssessmentSection()->getTitle(),
                'itemPosition' => $itemPosition,
                'itemCount' => $itemCount
            ];
            return json_encode($map);
        }
        return json_encode(['title' => 'finished']);
    }

    /**
     * Gets the current progress inside a delivery execution
     * @param \taoQtiTest_helpers_TestSession $session
     * @return array|bool
     */
    protected function getSessionProgress(\taoQtiTest_helpers_TestSession $session)
    {
        if ($session->isRunning() !== false) {
            $config = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiTest')->getConfig('testRunner');
            $categories = [];
            if (isset($config['progress-indicator']) && $config['progress-indicator'] == 'categories') {
                $categories = $config['progress-categories'];
            }
            $route = $session->getRoute();
            $routeItems = $route->getAllRouteItems();
            $offset = $route->getRouteItemPosition($routeItems[0]);
            $offsetPart = 0;
            $offsetSection = 0;
            $lastPart = null;
            $lastSection = null;

            $positions = [];
            $lengthParts = [];
            $lengthSections = [];
            $sectionIndex = 0;
            $partIndex = 0;

            // compute positions from the test map
            /** @var RouteItem $routeItem */
            foreach ($routeItems as $routeItem) {
                $testPart = $routeItem->getTestPart();
                $partId = $testPart->getIdentifier();
                if ($lastPart != $partId) {
                    $offsetPart = 0;
                    $lastPart = $partId;
                    $partIndex++;
                }

                $section = $routeItem->getAssessmentSection();
                $sectionId = $section->getIdentifier();
                if ($lastSection != $sectionId) {
                    $offsetSection = 0;
                    $lastSection = $sectionId;
                    $sectionIndex++;
                }

                $offset++;
                $offsetSection++;

                if ($categories && $config['progress-indicator-scope'] == 'testPart') {
                    /** @var AssessmentItemRef $assessmentItemRef */
                    $assessmentItemRef = $routeItem->getAssessmentItemRef();
                    $assessmenCategories = $assessmentItemRef->getCategories()->getArrayCopy();
                    if (array_intersect($categories, $assessmenCategories)) {
                        $offsetPart++;
                    }
                }
                $lengthParts[$partIndex] = $offsetPart;
                $lengthSections[$sectionIndex] = $offsetSection;

                $positions[] = [
                    'test' => $offset,
                    'part' => $offsetPart,
                    'partId' => $partIndex,
                    'section' => $offsetSection,
                    'sectionId' => $sectionIndex,
                ];
            }

            $progress = $positions[$route->getPosition()];

            $catService = $this->getServiceManager()->get(CatService::SERVICE_ID);
            $currentItem = $route->current();
            if ($catService->isAdaptive($session, $currentItem->getAssessmentItemRef())) {
                $testSessionService = $this->getServiceManager()->get(TestSessionService::SERVICE_ID);
                $testSessionData = $testSessionService->getTestSessionDataById($session->getSessionId());
                $sectionItems = $catService->getShadowTest($session, $testSessionData['compilation']['private'], $currentItem);
                $currentItem = $catService->getCurrentCatItemId($session, $testSessionData['compilation']['private'], $currentItem);
                $positionInSection = array_search($currentItem, $sectionItems);
                
                // When in an adaptive section, the actual section is just a placeholder that is dynamically
                // replaced by the adaptive content. To set the right position, just grab the offset within
                // this dynamic content and add it to the placeholder position.
                $progress['test'] += $positionInSection;
                $progress['part'] += $positionInSection;
                $progress['section'] += $positionInSection;
                $lengthSections[$progress['sectionId']] = count($sectionItems);
            }

            return [
                'test' => $progress['test'],
                'testPart' => $progress['part'],
                'testSection' => $progress['section'],
                'testLength' => $session->getRouteCount(),
                'testPartLength' => $lengthParts[$progress['partId']],
                'testSectionLength' => $lengthSections[$progress['sectionId']],
            ];
        }
        return false;
    }
}