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

namespace oat\taoLti\models\classes;

use common_http_Request;
use core_kernel_classes_Resource;
use OAT\Library\Lti1p3Core\Message\Payload\LtiMessagePayloadInterface;
use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage;
use oat\taoLti\models\classes\Platform\LtiPlatform;
use tao_helpers_Request;
use oat\oatbox\log\LoggerAwareTrait;
use Psr\Http\Message\ServerRequestInterface;

class LtiLaunchData implements \JsonSerializable
{
    use LoggerAwareTrait;

    const OAUTH_CONSUMER_KEY  = 'oauth_consumer_key';
    const RESOURCE_LINK_ID    = 'resource_link_id';
    const RESOURCE_LINK_TITLE = 'resource_link_title';
    const CONTEXT_ID          = 'context_id';
    const CONTEXT_LABEL       = 'context_label';
    const CONTEXT_TITLE       = 'context_title';

    const USER_ID                          = 'user_id';
    const ROLES                            = 'roles';
    const LIS_PERSON_NAME_GIVEN            = 'lis_person_name_given';
    const LIS_PERSON_NAME_FAMILY           = 'lis_person_name_family';
    const LIS_PERSON_NAME_FULL             = 'lis_person_name_full';
    const LIS_PERSON_CONTACT_EMAIL_PRIMARY = 'lis_person_contact_email_primary';

    const LAUNCH_PRESENTATION_LOCALE     = 'launch_presentation_locale';
    const LAUNCH_PRESENTATION_RETURN_URL = 'launch_presentation_return_url';

    const TOOL_CONSUMER_INSTANCE_ID          = 'tool_consumer_instance_id';
    const TOOL_CONSUMER_INSTANCE_NAME        = 'tool_consumer_instance_name';
    const TOOL_CONSUMER_INSTANCE_DESCRIPTION = 'tool_consumer_instance_description';

    const LTI_VERSION = 'lti_version';
    const LTI_MESSAGE_TYPE = 'lti_message_type';

    const LIS_RESULT_SOURCEDID = 'lis_result_sourcedid';
    const LIS_OUTCOME_SERVICE_URL = 'lis_outcome_service_url';

    // review mode
    const LTI_SHOW_SCORE = 'custom_show_score';
    const LTI_SHOW_CORRECT = 'custom_show_correct';

    /**
     * LTI variables
     *
     * @var array
     */
    private $variables;

    /**
     * Custom parameters of the LTI call
     *
     * @var array
     */
    private $customParams;

    /**
     * @var core_kernel_classes_Resource
     */
    private $ltiConsumer;

    /**
     * Spawns an LtiSession
     *
     * @param array $ltiVariables
     * @param array $customParameters
     */
    public function __construct(array $ltiVariables, array $customParameters)
    {
        $this->variables = $ltiVariables;
        $this->customParams = $customParameters;
    }

    /**
     * @param array $json
     * @return LtiLaunchData
     */
    public static function fromJsonArray($json)
    {
        return new static($json['variables'], $json['customParams']);
    }

    /**
     *
     * @param common_http_Request $request
     * @return LtiLaunchData
     * @throws \ResolverException
     */
    public static function fromPsrRequest(ServerRequestInterface $request)
    {
        $extra = self::getParametersFromUrl($request->getUri()->__toString());
        $combined = array_merge($request->getQueryParams(), $request->getParsedBody());
        return new static($combined, $extra);
    }

    /**
     *
     * @param common_http_Request $request
     * @return LtiLaunchData
     * @throws \ResolverException
     */
    public static function fromRequest(common_http_Request $request)
    {
        $extra = self::getParametersFromUrl($request->getUrl());
        return new static($request->getParams(), $extra);
    }

    public static function fromLti1p3MessagePayload(LtiMessagePayloadInterface $ltiMessagePayload, LtiPlatform $platform = null)
    {
        $variables[self::OAUTH_CONSUMER_KEY] = '';
        $variables[self::RESOURCE_LINK_ID] = $ltiMessagePayload->getResourceLink() ? $ltiMessagePayload->getResourceLink()->getIdentifier() : null;
        $variables[self::RESOURCE_LINK_TITLE] = $ltiMessagePayload->getResourceLink() ? $ltiMessagePayload->getResourceLink()->getTitle() : null;
        $variables[self::CONTEXT_ID] = $ltiMessagePayload->getContext() ? $ltiMessagePayload->getContext()->getIdentifier() : null;
        $variables[self::CONTEXT_LABEL] = $ltiMessagePayload->getContext() ? $ltiMessagePayload->getContext()->getLabel() : null;
        $variables[self::CONTEXT_TITLE] = $ltiMessagePayload->getContext() ? $ltiMessagePayload->getContext()->getTitle() : null;
        $variables[self::USER_ID] = $ltiMessagePayload->getUserIdentity() ? $ltiMessagePayload->getUserIdentity()->getIdentifier() : null;
        $variables[self::ROLES] = implode(',', $ltiMessagePayload->getRoles());
        $variables[self::LIS_PERSON_NAME_GIVEN] = $ltiMessagePayload->getUserIdentity() ? $ltiMessagePayload->getUserIdentity()->getGivenName() : null;
        $variables[self::LIS_PERSON_NAME_FAMILY] = $ltiMessagePayload->getUserIdentity() ? $ltiMessagePayload->getUserIdentity()->getFamilyName() : null;
        $variables[self::LIS_PERSON_NAME_FULL] = $ltiMessagePayload->getUserIdentity() ? $ltiMessagePayload->getUserIdentity()->getName() : null;
        $variables[self::LIS_PERSON_CONTACT_EMAIL_PRIMARY] = $ltiMessagePayload->getUserIdentity() ? $ltiMessagePayload->getUserIdentity()->getEmail() : null;
        $variables[self::LAUNCH_PRESENTATION_LOCALE] = $ltiMessagePayload->getLaunchPresentation() ? $ltiMessagePayload->getLaunchPresentation()->getLocale(): null;
        $variables[self::LAUNCH_PRESENTATION_RETURN_URL] = $ltiMessagePayload->getLaunchPresentation() ? $ltiMessagePayload->getLaunchPresentation()->getReturnUrl() : null;
        $variables[self::LTI_VERSION] = $ltiMessagePayload->getVersion();
        $variables[self::LTI_MESSAGE_TYPE] = $ltiMessagePayload->getMessageType();
        $variables[self::LIS_RESULT_SOURCEDID] = $ltiMessagePayload->getBasicOutcome() ? $ltiMessagePayload->getBasicOutcome()->getLisResultSourcedId() : null;
        $variables[self::LIS_OUTCOME_SERVICE_URL] = $ltiMessagePayload->getBasicOutcome() ? $ltiMessagePayload->getBasicOutcome()->getLisOutcomeServiceUrl() : null;

        if ($platform) {
            // we need to have inner platform ID
            $variables[self::TOOL_CONSUMER_INSTANCE_ID] = $platform->getId();

            if ($platformFromClaim = $ltiMessagePayload->getPlatformInstance()) {
                $variables[self::TOOL_CONSUMER_INSTANCE_NAME] = $platformFromClaim->getName();
                $variables[self::TOOL_CONSUMER_INSTANCE_DESCRIPTION] = $platformFromClaim->getDescription();
            } else {
                $variables[self::TOOL_CONSUMER_INSTANCE_NAME] = $platform->getLabel();
                $variables[self::TOOL_CONSUMER_INSTANCE_DESCRIPTION] = $platform->getLabel();
            }

        }

        $customParams = $ltiMessagePayload->getCustom();

        // review mode
        if (isset($customParams[self::LTI_SHOW_SCORE])) {
            $variables[self::LTI_SHOW_SCORE] = true;
        }

        if (isset($customParams[self::LTI_SHOW_CORRECT])) {
            $variables[self::LTI_SHOW_CORRECT] = true;
        }

        return new static($variables, $customParams);
    }

    /**
     * @param string $url
     * @return array
     * @throws \ResolverException
     */
    private static function getParametersFromUrl($url)
    {
        $returnValue = [];

        // get parameters
        parse_str(parse_url($url, PHP_URL_QUERY), $returnValue);

        // encoded in url
        $parts = explode('/', tao_helpers_Request::getRelativeUrl($url), 4);
        if (count($parts) == 4) {
            list ($extension, $module, $action, $codedUri) = $parts;
            $base64String = base64_decode($codedUri);
            if ($base64String !== false) {
                // old serialised url
                if (substr($base64String, 0, strlen('a:')) == 'a:') {
                    $additionalParams = unserialize($base64String);
                } else {
                    $additionalParams = json_decode($base64String, true);
                }
                if ($additionalParams !== false && is_array($additionalParams)) {
                    foreach ($additionalParams as $key => $value) {
                        $returnValue[$key] = $value;
                    }
                }
            }
        }

        return $returnValue;
    }

    /**
     * @param string $key
     * @return mixed|null
     */
    public function getCustomParameter($key)
    {
        return isset($this->customParams[$key]) ? $this->customParams[$key] : null;
    }

    /**
     * Get all custom parameters provided during launch.
     *
     * @return array
     */
    public function getCustomParameters()
    {
        return $this->customParams;
    }

    /**
     * Get all lti variables provided during launch.
     *
     * @return array
     */
    public function getVariables()
    {
        return $this->variables;
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getResourceLinkID()
    {
        return $this->getVariable(self::RESOURCE_LINK_ID);
    }

    /**
     * @param $key
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getVariable($key)
    {
        if (isset($this->variables[$key])) {
            return $this->variables[$key];
        } else {
            throw new LtiVariableMissingException($key);
        }
    }

    /**
     * @param string $key
     * @return boolean mixed
     *
     * @throws LtiException
     * @throws LtiVariableMissingException
     */
    public function getBooleanVariable($key)
    {
        $var = mb_strtolower($this->getVariable($key));

        if ($var === 'true') {
            return true;
        } elseif ($var === 'false') {
            return false;
        } else {
            throw new LtiInvalidVariableException(
                'Invalid value of `' . $key . '` variable, boolean string expected.',
                LtiErrorMessage::ERROR_INVALID_PARAMETER
            );
        }
    }

    /**
     * @return mixed|string
     * @throws LtiVariableMissingException
     */
    public function getResourceLinkTitle()
    {
        if ($this->hasVariable(self::RESOURCE_LINK_TITLE)) {
            return $this->getVariable(self::RESOURCE_LINK_TITLE);
        } else {
            return __('link');
        }
    }

    public function hasVariable($key)
    {
        return isset($this->variables[$key]);
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getUserID()
    {
        return $this->getVariable(self::USER_ID);
    }

    /**
     * @return mixed
     */
    public function getUserGivenName()
    {
        if ($this->hasVariable(static::LIS_PERSON_NAME_GIVEN)) {
            return $this->getVariable(static::LIS_PERSON_NAME_GIVEN);
        }
    }

    /**
     * @return mixed
     */
    public function getUserFamilyName()
    {
        if ($this->hasVariable(static::LIS_PERSON_NAME_FAMILY)) {
            return $this->getVariable(static::LIS_PERSON_NAME_FAMILY);
        }
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getUserFullName()
    {
        if ($this->hasVariable(self::LIS_PERSON_NAME_FULL)) {
            return $this->getVariable(self::LIS_PERSON_NAME_FULL);
        }
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getUserEmail()
    {
        return $this->getVariable(self::LIS_PERSON_CONTACT_EMAIL_PRIMARY);
    }

    /**
     * @return array
     * @throws LtiVariableMissingException
     */
    public function getUserRoles()
    {
        return explode(',', $this->getVariable(self::ROLES));
    }

    public function hasLaunchLanguage()
    {
        return $this->hasVariable(self::LAUNCH_PRESENTATION_LOCALE);
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getLaunchLanguage()
    {
        return $this->getVariable(self::LAUNCH_PRESENTATION_LOCALE);
    }

    /**
     * Tries to return the tool consumer name
     *
     * Returns null if no name found
     *
     * @return string
     * @throws LtiVariableMissingException
     */
    public function getToolConsumerName()
    {
        $consumerName = null;

        if ($this->hasVariable(self::TOOL_CONSUMER_INSTANCE_NAME)) {
            $consumerName = $this->getVariable(self::TOOL_CONSUMER_INSTANCE_NAME);
        }

        if (
            $consumerName === null
            && $this->hasVariable(self::TOOL_CONSUMER_INSTANCE_DESCRIPTION)
        ) {
            $consumerName = $this->getVariable(self::TOOL_CONSUMER_INSTANCE_DESCRIPTION);
        }

        return $consumerName;
    }

    /**
     * @return core_kernel_classes_Resource
     * @throws LtiVariableMissingException
     */
    public function getLtiConsumer()
    {
        if (is_null($this->ltiConsumer)) {
            $dataStore = new \tao_models_classes_oauth_DataStore();
            $this->ltiConsumer = $dataStore->findOauthConsumerResource($this->getOauthKey())->getUri();
        }

        return new \core_kernel_classes_Resource($this->ltiConsumer);
    }

    /**
     * @return mixed
     * @throws LtiVariableMissingException
     */
    public function getOauthKey()
    {
        return $this->getVariable(self::OAUTH_CONSUMER_KEY);
    }

    /**
     * @return bool
     * @throws LtiException
     */
    public function hasReturnUrl()
    {
        if ($this->hasVariable(self::LAUNCH_PRESENTATION_RETURN_URL)) {
            $returnUrl = $this->getReturnUrl();

            if (!empty($returnUrl)) {
                if (filter_var($returnUrl, FILTER_VALIDATE_URL)) {
                    return true;
                } else {
                    $this->logWarning("Invalid LTI Return URL '${returnUrl}'.");
                }
            }
        }

        return false;
    }

    /**
     * Return the returnUrl to the tool consumer
     *
     * @return string
     * @throws LtiException
     */
    public function getReturnUrl()
    {
        return $this->getVariable(self::LAUNCH_PRESENTATION_RETURN_URL);
    }

    /**
     * Specify data which should be serialized to JSON
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by <b>json_encode</b>,
     * which is a value of any type other than a resource.
     * @since 5.4.0
     */
    public function jsonSerialize()
    {
        return [
            'variables' => $this->variables,
            'customParams' => $this->customParams,
        ];
    }
}