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

namespace oat\taoProctoring\model\implementation;

use Exception;
use oat\taoDelivery\model\execution\ServiceProxy;
use oat\taoProctoring\model\TestSessionHistoryService as TestSessionHistoryServiceInterface;
use \oat\oatbox\service\ConfigurableService;
use DateTime;
use tao_helpers_Date as DateHelper;
use oat\taoProctoring\model\deliveryLog\DeliveryLog;
use oat\tao\helpers\UserHelper;

/**
 * Service is used to retrieve test session history
 *
 * @author Aleh Hutnikau <hutnikau@1pt.com>
 * @package oat\taoProctoring
 */
class TestSessionHistoryService extends ConfigurableService implements TestSessionHistoryServiceInterface
{
    /**
     * List of event ids which should be excluded from history (in lower case)
     */
    protected static $eventsToExclude = ['heartbeat'];

    /**
     * List of event ids which should be represented in the brief history report
     */
    protected static $briefEvents = [
        'section_exit_code',
        'test_exit_code',
        'test_pause',
        'test_run',
        'test_run',
        'test_authorise',
        'test_terminate',
        'test_irregularity',
        'pause',
        'unsecured-launch-prohibited',
        'focus-loss-prohibited',
        'leave-fullscreen-prohibited',
        'pause-on-disconnect',
        'test_adjust_time',
    ];

    /**
     * @var \core_kernel_classes_Resource[] list of user instances
     */
    private $authors = [];

    /**
     * @var \core_kernel_classes_Resource[]
     */
    private $authorRoles = [];

    /**
     * @var \core_kernel_classes_Resource[]
     */
    private $proctorRoles = [];

    /**
     * TestSessionHistoryService constructor.
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        parent::__construct($options);
        $roles = $this->getOption(self::PROCTOR_ROLES);
        if(is_null($roles)){
            $roles = [];
        }
        $this->proctorRoles = array_merge([new \core_kernel_classes_Resource('http://www.tao.lu/Ontologies/TAOProctor.rdf#ProctorRole')], $roles);

    }

    /**
     * @param array $sessions List of session ids
     * @param array $options The following option is handled:
     * - periodStart: a date/time string.
     * - periodEnd: a date/time string.
     * - detailed: whether to retrieve detailed or brief report. Defaults to false (brief).
     * - sortBy: column name string.
     * - sortOrder: order direction (asc|desc) string.
     * @return array
     */
    public function getSessionsHistory(array $sessions, $options)
    {
        $history = [];
        $periodStart = $this->getPeriodStart($options);
        $periodEnd = $this->getPeriodEnd($options);

        /** @var DeliveryLog $deliveryLog */
        $deliveryLog = $this->getServiceManager()->get(DeliveryLog::SERVICE_ID);

        //empty array means that all events (except listed in self::$eventsToExclude) will be represented in the report
        $eventsToInclude = $options['detailed'] ? [] : self::$briefEvents;

        foreach ($sessions as $sessionUri) {
            $deliveryExecution = ServiceProxy::singleton()->getDeliveryExecution($sessionUri);
            $logs = $deliveryLog->get($deliveryExecution->getIdentifier());
            $exportable = [];

            foreach ($logs as $data) {
                $eventId = $data['data']['type'] ?? $data[DeliveryLog::EVENT_ID];
                $eventName = strtolower(explode('.', $eventId)[0]);

                if (
                    (!empty($eventsToInclude) && !in_array($eventName, $eventsToInclude)) || //event should not be included
                    in_array($eventName, self::$eventsToExclude) //event must be excluded
                ) {
                    continue;
                }

                $author = $this->getAuthor($data);
                $details = $this->getEventDetails($data);
                $context = $this->getEventContext($data);
                $role = $this->getUserRole($author);

                $exportable['timestamp'] = (isset($data['data']['timestamp']))?$data['data']['timestamp']:$data['created_at'];
                if (($periodStart && $exportable['timestamp'] < $periodStart) || ($periodEnd && $exportable['timestamp'] > $periodEnd)) {
                    continue;
                }
                $exportable['date'] = DateHelper::displayeDate($exportable['timestamp'], DateHelper::FORMAT_LONG_MICROSECONDS);
                $exportable['role'] = $role;
                $exportable['actor'] = _dh($this->getActorName($author->getUri()));
                $exportable['event'] = $eventId;
                $exportable['details'] = $details;
                $exportable['context'] = $context;
                $history[] = $exportable;
            }
        }

        $this->sortHistory($history, $options);

        return $history;
    }

    /**
     * Gets the url that leads to the page listing the history
     * @param $delivery
     * @return string
     */
    public function getHistoryUrl($delivery = null)
    {
        $params = [];
        if ($delivery) {
            if ($delivery instanceof \core_kernel_classes_Resource) {
                $delivery = $delivery->getUri();
            }
            $params['delivery'] = $delivery . '';
        }
        return _url('index', 'Reporting', 'taoProctoring', $params);
    }

    /**
     * Gets the back url that returns to the page listing the sessions
     * @param $delivery
     * @return string
     */
    public function getBackUrl($delivery = null)
    {
        $params = [];
        if ($delivery) {
            if ($delivery instanceof \core_kernel_classes_Resource) {
                $delivery = $delivery->getUri();
            }
            $params['delivery'] = $delivery . '';
        }
        return _url('index', 'Monitor', 'taoProctoring', $params);
    }


    /**
     * @param array $data event data from delivery log
     * @return string|array
     */
    private function getEventDetails($data)
    {
        $details = '';
        if(isset($data['data']['type'])) {
            $details = $data['data']['context']['shortcut'] ?? '';
        } elseif (isset($data['data']['reason'], $data['data']['reason']['reasons'])) {
            $details = is_array($data['data']['reason']['reasons']) ?
                array_merge(array_values($data['data']['reason']['reasons']), [__($data['data']['reason']['comment'])])
                : array_merge([$data['data']['reason']['reasons']], [__($data['data']['reason']['comment'])]);
        } elseif (isset($data['data']['exitCode'])) {
            $details = $data['data']['exitCode'];
        } elseif (isset($data['data']['itemId'])) {
            $details = $data['data']['itemId'];
        } elseif (isset($data['data']['web_browser_name'])) {
            $details = ($data['data']['web_browser_name'] . ' ') .
                (isset($data['data']['web_browser_version']) ? $data['data']['web_browser_version'] . '; ' : '') .
                (isset($data['data']['os_name']) ? $data['data']['os_name'] . ' ' : '') .
                (isset($data['data']['os_version']) ? $data['data']['os_version'] . ' ' : '');
        } elseif (is_string($data['data'])) {
            $details = $data['data'];
        }

        if (isset($data['data']['increment']) && is_array($details)) {
            $details[] = $data['data']['increment'] . __(' sec');
        }

        return $details;
    }

    /**
     * @param array $data event data from delivery log
     * @return string
     */
    private function getEventContext($data): string
    {
        if (isset($data['data']['type'])) {
            $context = $data['data']['context']['readable'] ?? '';
        } else {
            $context = (isset($data['data']['context']) && !is_null($data['data']['context'])) ? $data['data']['context'] : '';
        }
        return $context;
    }

    /**
     * @param $options
     * @return null|number timestamp
     * @throws Exception
     */
    private function getPeriodStart(array $options)
    {
        $periodStart = null;

        if (!empty($options['periodStart'])) {
            $periodStart = new DateTime($options['periodStart']);
            $periodStart->setTime(0, 0, 0);
            $periodStart = DateHelper::getTimeStamp($periodStart->getTimestamp());
        }
        return $periodStart;
    }

    /**
     * @param $options
     * @return null|number timestamp
     */
    private function getPeriodEnd(array $options)
    {
        $periodEnd = null;

        if (!empty($options['periodEnd'])) {
            $periodEnd = new DateTime($options['periodEnd']);
            $periodEnd->setTime(23, 59, 59);
            $periodEnd = DateHelper::getTimeStamp($periodEnd->getTimestamp());
        }

        return $periodEnd;
    }

    /**
     * Sort events
     * @param array $history
     * @param array $options
     */
    private function sortHistory(array &$history, array $options)
    {
        $sortBy = isset($options['sortBy']) ? $options['sortBy'] : 'timestamp';
        $sortOrder = isset($options['sortOrder']) ? $options['sortOrder'] : 'desc';
        if ($sortOrder == 'asc') {
            $sortOrder = 1;
        } else {
            $sortOrder = -1;
        }
        if ($sortBy == 'timestamp' || $sortBy == 'id') {
            usort($history, function($a, $b) use($sortOrder) {
                $result = $sortOrder * (floatval($a['timestamp']) - floatval($b['timestamp']));
                if ($result === 0) {
                    return $result;
                }
                return $result > 0 ? 1 : -1;
            });
        } else {
            usort($history, function($a, $b) use($sortBy, $sortOrder) {
                return $sortOrder * strnatcasecmp($a[$sortBy], $b[$sortBy]);
            });
        }
    }

    /**
     * @param array $data event data from delivery log
     * @return \core_kernel_classes_Resource
     */
    protected function getAuthor(array $data)
    {
        if (!isset($this->authors[$data['created_by']])) {
            $this->authors[$data['created_by']] = new \core_kernel_classes_Resource($data['created_by']);
        }
        return $this->authors[$data['created_by']];
    }

    /**
     * @param $userId
     * @return string
     */
    protected function getActorName($userId)
    {
        $user = UserHelper::getUser($userId);

        return UserHelper::getUserName($user, true);
    }

    /**
     * @param \core_kernel_classes_Resource $user
     * @return string
     */
    private function getUserRole(\core_kernel_classes_Resource $user)
    {
        $userService = \tao_models_classes_UserService::singleton();
        if (!isset($this->authorRoles[$user->getUri()])) {
            $userRole = '';
            $allUserRoles = $userService->getUserRoles($user);
            if (!empty($allUserRoles)) {
                $userRole = $userService->userHasRoles($user, $this->proctorRoles) ? __('Proctor') : __('Test-Taker');
            }

            $this->authorRoles[$user->getUri()] = $userRole;
        }
        return $this->authorRoles[$user->getUri()];
    }
}