<?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 ;
 *
 */

namespace oat\taoProctoring\controller;

use common_Exception;
use common_exception_Error;
use common_exception_NotFound;
use common_ext_ExtensionException;
use oat\generis\model\OntologyAwareTrait;
use oat\oatbox\service\exception\InvalidServiceManagerException;
use oat\oatbox\service\ServiceNotFoundException;
use oat\tao\model\accessControl\AclProxy;
use oat\tao\model\mvc\DefaultUrlService;
use oat\taoProctoring\helpers\DeliveryHelper;
use oat\taoProctoring\model\AssessmentResultsService;
use oat\taoProctoring\model\datatable\DeliveriesMonitorDatatable;
use oat\taoProctoring\model\execution\DeliveryExecutionManagerService;
use oat\taoProctoring\model\GuiSettingsService;
use oat\taoProctoring\model\implementation\DeliveryExecutionStateService;
use oat\taoProctoring\model\TestSessionConnectivityStatusService;
use oat\taoProctoring\model\TestSessionHistoryService;
use oat\taoQtiTest\models\QtiTestExtractionFailedException;

/**
 * Monitoring Delivery controller
 *
 * @author Open Assessment Technologies SA
 * @package taoProctoring
 * @license GPL-2.0
 *
 */
class Monitor extends SimplePageModule
{
    use OntologyAwareTrait;

    const ERROR_AUTHORIZE_EXECUTIONS = 1;
    const ERROR_PAUSE_EXECUTIONS = 2;
    const ERROR_TERMINATE_EXECUTIONS = 3;
    const ERROR_REPORT_IRREGULARITIES = 4;
    const ERROR_SET_EXTRA_TIME = 5;
    const ERROR_ADJUST_TIME = 6;

    /**
     * Returns the currently proctored delivery
     *
     * @return \core_kernel_classes_Resource
     */
    protected function getCurrentDelivery()
    {
        return $this->hasRequestParameter('delivery')
            ? $this->getResource($this->getRequestParameter('delivery'))
            : null;
    }

    /**
     * Gets the view parameters and data to display
     * @return array
     * @throws common_exception_Error
     */
    protected function getViewData()
    {
        $user = \common_session_SessionManager::getSession()->getUser();
        $hasAccessToReactivate = AclProxy::hasAccess($user, MonitorProctorAdministrator::class, 'reactivateExecutions', array());
        $delivery = $this->getCurrentDelivery();
        /** @var GuiSettingsService $guiSettingsService */
        $guiSettingsService = $this->getServiceLocator()->get(GuiSettingsService::SERVICE_ID);
        $assessmentResultsService = $this->getServiceLocator()->get(AssessmentResultsService::SERVICE_ID);
        $data = [
            'ismanageable' => false,
            'set' => [],
            'extrafields' => DeliveryHelper::getExtraFields(),
            'categories' => DeliveryHelper::getAllReasonsCategories($hasAccessToReactivate),
            'printReportButton' => $assessmentResultsService->getOption(AssessmentResultsService::OPTION_PRINT_REPORT_BUTTON),
            'printReportUrl' => $assessmentResultsService->getScoreReportUrlParts(),
            'timeHandling' => $this->getServiceLocator()->get(DeliveryExecutionStateService::SERVICE_ID)->getOption(DeliveryExecutionStateService::OPTION_TIME_HANDLING),
            'historyUrl' => $this->getServiceLocator()->get(TestSessionHistoryService::SERVICE_ID)->getHistoryUrl($delivery),
            'onlineStatus' => $this->getServiceLocator()->get(TestSessionConnectivityStatusService::SERVICE_ID)->hasOnlineMode(),
            'hasAccessToReactivate' => $hasAccessToReactivate
        ];

        $data = array_merge($data, $guiSettingsService->asArray());

        if (!is_null($delivery)) {
            $data['delivery'] = $delivery->getUri();
        }
        if ($this->hasRequestParameter('context')) {
            $data['context'] = $this->getRequestParameter('context');
        }

        return $data;
    }

    /**
     * Monitoring view of a selected delivery
     */
    public function index()
    {
        $this->setData('homeUrl', $this->getServiceManager()->get(DefaultUrlService::SERVICE_ID)->getUrl('ProctoringHome'));
        $this->setData('logout', $this->getServiceManager()->get(DefaultUrlService::SERVICE_ID)->getUrl('ProctoringLogout'));
        $this->composeView('delivery-monitoring', null, 'pages/index.tpl', 'tao');
    }

    /**
     * Lists all available deliveries
     */
    public function monitor()
    {
        $this->returnJson([
            'success' => true,
            'data' => $this->getViewData(),
        ]);
    }

    /**
     * Gets the list of current executions for a delivery
     *
     * @throws common_Exception
     */
    public function deliveryExecutions()
    {
        $dataTable = new DeliveriesMonitorDatatable($this->getCurrentDelivery(), $this->getRequest());
        $this->getServiceManager()->propagate($dataTable);
        $this->returnJson($dataTable);
    }

    /**
     * Authorises a delivery execution
     *
     * @throws common_Exception
     * @throws \oat\oatbox\service\ServiceNotFoundException
     */
    public function authoriseExecutions()
    {
        $deliveryExecution = $this->getRequestParameter('execution');
        $reason = $this->getRequestParameter('reason');
        $testCenter = $this->getRequestParameter('testCenter');

        if (!is_array($deliveryExecution)) {
            $deliveryExecution = array($deliveryExecution);
        }

        try {

            $data = DeliveryHelper::authoriseExecutions($deliveryExecution, $reason, $testCenter);

            $response = [
                'success' => !count($data['unprocessed']),
                'data' => $data
            ];

            if (!$response['success']) {
                $response['errorCode'] = self::ERROR_AUTHORIZE_EXECUTIONS;
                $response['errorMsg'] = __('Some delivery executions have not been authorized');
            }

            $this->returnJson($response);
        } catch (QtiTestExtractionFailedException $e) {
            $response = [
                'success' => false,
                'data' => [],
                'errorCode' => self::ERROR_AUTHORIZE_EXECUTIONS,
                'errorMsg' => __('Decryption failed because of using the wrong customer app key.'),
            ];

            $this->returnJson($response);
        } catch (ServiceNotFoundException $e) {
            \common_Logger::w('No delivery service defined for proctoring');
            $this->returnError('Proctoring interface not available');
        }
    }


    /**
     * Terminates delivery executions
     *
     * @throws common_Exception
     * @throws \oat\oatbox\service\ServiceNotFoundException
     */
    public function terminateExecutions()
    {
        $deliveryExecution = $this->getRequestParameter('execution');
        $reason = $this->getRequestParameter('reason');

        if (!is_array($deliveryExecution)) {
            $deliveryExecution = array($deliveryExecution);
        }

        try {
            $data = DeliveryHelper::terminateExecutions($deliveryExecution, $reason);

            $response = [
                'success' => !count($data['unprocessed']),
                'data' => $data
            ];

            if (!$response['success']) {
                $response['errorCode'] = self::ERROR_TERMINATE_EXECUTIONS;
                $response['errorMsg'] = __('Some delivery executions have not been terminated');
            }

            $this->returnJson($response);
        } catch (ServiceNotFoundException $e) {
            \common_Logger::w('No delivery service defined for proctoring');
            $this->returnError('Proctoring interface not available');
        }
    }

    /**
     * Pauses delivery executions
     *
     * @throws common_Exception
     * @throws \oat\oatbox\service\ServiceNotFoundException
     */
    public function pauseExecutions()
    {
        $deliveryExecution = $this->getRequestParameter('execution');
        $reason = $this->getRequestParameter('reason');

        if (!is_array($deliveryExecution)) {
            $deliveryExecution = array($deliveryExecution);
        }

        try {
            $data = DeliveryHelper::pauseExecutions($deliveryExecution, $reason);

            $response = [
                'success' => !count($data['unprocessed']),
                'data' => $data
            ];

            if (!$response['success']) {
                $response['errorCode'] = self::ERROR_PAUSE_EXECUTIONS;
                $response['errorMsg'] = __('Some delivery executions have not been paused');
            }

            $this->returnJson($response);
        } catch (ServiceNotFoundException $e) {
            \common_Logger::w('No delivery service defined for proctoring');
            $this->returnError('Proctoring interface not available');
        }
    }

    /**
     * Report irregularities in delivery executions
     *
     * @throws common_Exception
     * @throws \oat\oatbox\service\ServiceNotFoundException
     */
    public function reportExecutions()
    {
        $deliveryExecution = $this->getRequestParameter('execution');
        $reason = $this->getRequestParameter('reason');

        if (!is_array($deliveryExecution)) {
            $deliveryExecution = array($deliveryExecution);
        }

        try {
            $data = DeliveryHelper::reportExecutions($deliveryExecution, $reason);

            $response = [
                'success' => !count($data['unprocessed']),
                'data' => $data
            ];

            if (!$response['success']) {
                $response['errorCode'] = self::ERROR_REPORT_IRREGULARITIES;
                $response['errorMsg'] = __('Some delivery executions have not been reported');
            }

            $this->returnJson($response);
        } catch (ServiceNotFoundException $e) {
            \common_Logger::w('No delivery service defined for proctoring');
            $this->returnError('Proctoring interface not available');
        }
    }

    /**
     * Extra Time handling: add or remove time on delivery executions
     *
     * @throws common_Exception
     */
    public function extraTime()
    {
        $deliveryExecution = $this->getRequestParameter('execution');
        $extraTime = floatval($this->getRequestParameter('time'));

        if (!is_array($deliveryExecution)) {
            $deliveryExecution = array($deliveryExecution);
        }

        try {
            /** @var DeliveryExecutionManagerService $deliveryExecutionManagerService */
            $deliveryExecutionManagerService = $this->getServiceLocator()->get(DeliveryExecutionManagerService::SERVICE_ID);
            $data = $deliveryExecutionManagerService->setExtraTime($deliveryExecution, $extraTime);

            $response = [
                'success' => !count($data['unprocessed']),
                'data' => $data
            ];

            if (!$response['success']) {
                $response['errorCode'] = self::ERROR_SET_EXTRA_TIME;
                $response['errorMsg'] = __('Some delivery executions have not been updated');
            }

            $this->returnJson($response);
        } catch (ServiceNotFoundException $e) {
            \common_Logger::w('No delivery service defined for proctoring');
            $this->returnError('Proctoring interface not available');
        }
    }

    /**
     * @throws QtiTestExtractionFailedException
     * @throws common_Exception
     * @throws common_exception_Error
     * @throws common_exception_NotFound
     * @throws common_ext_ExtensionException
     * @throws InvalidServiceManagerException
     */
    public function adjustTime(): void
    {
        $deliveryExecutions = $this->getPostParameter('execution');
        $seconds = $this->getPostParameter('time');
        $reason = [
            'reasons' => $this->getPostParameter('reasons', []),
            'comment' => $this->getPostParameter('comment', ''),
        ];

        if (!is_array($deliveryExecutions)) {
            $deliveryExecutions = [$deliveryExecutions];
        }

        /** @var DeliveryExecutionManagerService $deliveryExecutionManagerService */
        $deliveryExecutionManagerService = $this->getServiceLocator()->get(DeliveryExecutionManagerService::SERVICE_ID);
        $data = $deliveryExecutionManagerService->adjustTimers($deliveryExecutions, $seconds, $reason);

        $response = [
            'success' => !count($data['unprocessed']),
            'data' => $data
        ];

        if (!$response['success']) {
            $response['errorCode'] = self::ERROR_ADJUST_TIME;
            $response['errorMsg'] = __('Some delivery executions have not been updated');
        }

        $this->returnJson($response);
    }
}