Abstract Class

<?php
/**
 * Cesar Example S.L.
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web
 *
 * Encoding: UTF-8
 *
 * @package CesarEngineExample\Provider
 * @author Cesar Romero - 2015-10-15 11:02:37
 * @copyright Copyright (c) 2012-2015 Cesar Example S.L.
 */

namespace CesarEngineExample\Provider;

use CesarEngineExample\Entity\GameConfiguration;
use CesarEngineExample\Entity\Player;
use CesarEngineExample\Entity\PlayerToken as PlayerTokenEntity;
use CesarEngineExample\Entity\PlayerToken;
use CesarEngineExample\Entity\Provider as ProviderEntity;
use CesarEngineExample\Entity\Transaction as TransactionEntity;
use Cesar\Mvc\RepositoryLocator;
use Cesar\Mvc\ServiceLocator;
use Cesar\Redis\RedisLock;
use Phalcon\DiInterface;
use Phalcon\Http\Request;
use Phalcon\Http\Response;
use Phalcon\Mvc\View;
use CesarEngineExample\Provider\Caller\AbstractCaller;

/**
 * Abstract base provider
 *
 * All Application providers should extend this class. This class contains all the common methods that a provider
 * integration needs. Check token, get player by token, get game or lock a token are things that all providers use
 * when they are integrated, so we are going to set them here.
 *
 * @package CesarEngineExample\Provider
 *
 * @author César Romero
 * @since 20 Jan 2016
 * @see Please notify modifications here with a @modified tag, ain't much cost but it'll help you maintain this
 * beauty I have given you 🙂
 */
abstract class AbstractProvider implements ProviderInterface
{
    /**
     * DI Container, this is the overall magic phalcon Dependency Injector, it has inside a lot of info, nearly all
     * classes need this container.
     *
     * @var \Phalcon\DiInterface
     */
    protected $_di;

    /**
     * Store the player token
     *
     * @var \CesarEngineExample\Entity\PlayerToken
     */
    protected $playerToken;

    /**
     * Array of provider attributes
     *
     * @var array
     */
    protected $providerConfigurationAttributes;

    /**
     * Player retrieved from token
     *
     * @var \CesarEngineExample\Entity\Player
     */
    protected $player;

    /**
     * Stores the caller class, this is the class that will call the common API and fetch the response from the
     * merchant
     *
     * @var \CesarEngineExample\Provider\Caller\AbstractCaller
     */
    protected $_caller;

    /**
     * In case we have to change the token on a request we will store it here
     *
     * @var \CesarEngineExample\Entity\PlayerToken
     */
    protected $updatePlayerToken;

    /**
     * Response Data to give back to the provider
     *
     * @var array
     */
    protected $providerResponse;

    /**
     * Request Data to parse for the merchant
     *
     * @var array
     */
    protected $providerRequest;

    /**
     * Current GameConfiguration in transaction
     *
     * @var \CesarEngineExample\Entity\GameConfiguration
     */
    protected $gameConfiguration;

    /**
     * Current Provider object
     *
     * @var \CesarEngineExample\Entity\Provider
     */
    protected $provider;

    /**
     * The parameters parsed from the provider request
     *
     * @var array
     */
    protected $params;

    /**
     * Set DI Container, this method is called at the very beginning of the code initialization. When the Phalcon
     * application loads.
     *
     * @param \Phalcon\DiInterface $di
     * @return \CesarEngineExample\Provider\AbstractProvider
     */
    public function setDi(DiInterface $di)
    {
        $this->_di = $di;
        return $this;
    }

    /**
     * Get DI Container
     *
     * @return \Phalcon\DiInterface
     */
    public function getDi()
    {
        return $this->_di;
    }

    /**
     * Get Request Container, returns the Phalcon request object, you can find all the params send by POST or GET
     * inside the request object. Check the manual.
     *
     * @return \Phalcon\Http\Request
     */
    public function getRequest()
    {
        return $this->getDi()->get('request');
    }

    /**
     * Get Response Container, returns the Phalcon response object, you can find all the params send by POST or GET
     * inside the response object. Check the manual.
     *
     * @return \Phalcon\Http\Response
     */
    public function getResponse()
    {
        return $this->getDi()->get('response');
    }

    /**
     * Get the view, returns the Phalcon view object from the DI container, This method is just a direct access.
     *
     * @return \Phalcon\Mvc\View
     */
    public function getView()
    {
        return $this->getDi()->get('view');
    }

    /**
     * Get the PlayerToken from a hash and block it during the transaction.
     *
     * This is the main staring call from any provider action. It's fundamental to use this method,
     * it's the only one that will prevent 2 calls at the same time. And also will help you locate the Player,
     * the game and all the info that comes with them by a simple HASH, (the one you get from the transaction).
     *
     * @param string $token
     * @return \CesarEngineExample\Entity\PlayerToken|boolean
     * @throws \Exception
     */
    protected function getPlayerTokenByToken($token)
    {
        if ($this->playerToken instanceof PlayerTokenEntity) {
            return $this->playerToken;
        }

        /** @var \CesarEngineExample\Repository\PlayerToken $playerTokenRepository */
        $playerTokenRepository = RepositoryLocator::getRepository('PlayerToken');

        if (null === ($playerToken = $playerTokenRepository->getPlayerTokenByToken($token))) {
            return false;
        }

        $this->playerToken = $playerToken;
        $this->getPlayer();

        return $this->playerToken;
    }

    /**
     * Get the PlayerToken from an old transaction.
     *
     * We use it of some transactions like rollbacks that for our disgrace don't provide the player token.
     *
     * @param integer $providerTransactionId
     * @return \CesarEngineExample\Entity\PlayerToken|boolean
     * @throws \Exception
     */
    protected function getPlayerTokenByProviderTransactionId($providerTransactionId)
    {
        if ($this->playerToken instanceof PlayerTokenEntity) {
            return $this->playerToken;
        }

        if ($this->provider instanceof ProviderEntity) {
            $parameters = array(
                'conditions' => 'provider_transaction_id = :ptid: AND provider_id = :pid:',
                'bind' => array(
                    'ptid' => $providerTransactionId,
                    'pid' => $this->provider->getId(),
                ),
            );
        } else {
            $parameters = array(
                'conditions' => 'provider_transaction_id = :ptid:',
                'bind' => array(
                    'ptid' => $providerTransactionId,
                ),
            );
        }
        if (false === ($transaction = TransactionEntity::findFirst($parameters))
            || 0 === ($playerTokenId = (int) $transaction->getPlayerTokenId())
        ) {
            return false;
        }

        $playerToken = PlayerTokenEntity::findFirstById($playerTokenId);
        if (false === $playerToken) {
            return false;
        }

        $this->playerToken = $playerToken;
        $this->getPlayer();

        return $this->playerToken;
    }

    /**
     * Get the PlayerToken from an old transaction.
     *
     * We use it of some transactions like rollbacks that for our disgrace don't provide the player token.
     *
     * @param integer $externalTransactionId
     * @return \CesarEngineExample\Entity\PlayerToken|boolean
     * @throws \Exception
     */
    protected function getPlayerTokenByExternalTransactionId($externalTransactionId)
    {
        if ($this->playerToken instanceof PlayerTokenEntity) {
            return $this->playerToken;
        }

        if ($this->provider instanceof ProviderEntity) {
            $transformedTransactionId = chr(96 + $this->provider->getId()).$externalTransactionId;
            $parameters = array(
                'conditions' => 'external_transaction_id = :ptid: AND provider_id = :pid:',
                'bind' => array(
                    'ptid' => $transformedTransactionId,
                    'pid' => $this->provider->getId(),
                ),
            );
        } else {
            $parameters = array(
                'conditions' => 'external_transaction_id like "%:ptid:%"',
                'bind' => array(
                    'ptid' => $externalTransactionId,
                ),
            );
        }
        if (false === ($transaction = TransactionEntity::findFirst($parameters))
            || 0 === ($playerTokenId = (int) $transaction->getPlayerTokenId())
        ) {
            return false;
        }

        $playerToken = PlayerTokenEntity::findFirstById($playerTokenId);
        if (false === $playerToken) {
            return false;
        }

        $this->playerToken = $playerToken;
        $this->getPlayer();

        return $this->playerToken;
    }

    /**
     * Get the player from a previously retrieved token. Please don't call this method if you haven't called before
     * the method to check the token. You will just get a FALSE.
     *
     * @return \CesarEngineExample\Entity\Player
     */
    protected function getPlayer()
    {
        if ($this->player instanceof Player) {
            return $this->player;
        }

        if (!$this->playerToken instanceof PlayerTokenEntity) {
            throw new \RuntimeException('Player not found. Make sure the Player Token is configured.');
        }

        $this->player = $this->playerToken->getPlayer();
        return $this->player;
    }

    /**
     * Get the player from a previously retrieved token. Please don't call this method if you haven't called before
     * the method to check the token. You will just get a FALSE.
     *
     * @param null $playerId
     * @param null $token
     * @return Player
     */
    protected function getPlayerByIdOrToken ($playerId = null, $token = null)
    {
        //Return previously stored player if exists
        if ($this->player instanceof Player) {
            return $this->player;
        }

        //Get the player by it's ID
        if(!empty($playerId)){

            //Make sure $playerId is integer, sometime we add "ap_" to the userId. We remove it just in case, but ...
            //this should be done by the provider class anyway.
            $playerId = str_replace('ap_','',$playerId);

            /** @var \CesarEngineExample\Repository\Player $playerRepository */
            $playerRepository = RepositoryLocator::getRepository('Player');

            $this->player = $playerRepository->getPlayerById($playerId);

            if ($this->player instanceof Player) {
                return $this->player;
            }
            throw new \RuntimeException('Player not found. Make sure the PlayerId is correct.');
        }

        //Get the player by token if playerId is not set
        if(!empty($token)){

            $this->getPlayerTokenByToken($token);

            if ($this->player instanceof Player) {
                return $this->player;
            }
            throw new \RuntimeException('Player not found. Make sure the Player Token is correct.');
        }

        throw new \RuntimeException('Player not found. give correct playerId or token.');
    }


    /**
     * Check if the token can be locked or it's in use already.
     *
     * @param string $token
     * @return boolean
     */
    protected function checkTokenNotLocked($token)
    {
        if (empty($token)) {
            return false;
        }
        // BLOCK THE TOKEN IN REDIS:
        return RedisLock::lock($token);
    }

    /**
     * Unlock the token if it's locked, do nothing if it's not locked, you can call this method all the times you
     * desire, it's not going to give any error nor confirmation. If you have a doubt if a token has been unblocked
     * or not, please call it again, it's just generating a simple "del KEY*" in redis.
     *
     * @param string $token
     */
    protected function unlockToken($token)
    {
        if (empty($token)) {
            return;
        }
        RedisLock::unlock($token);
    }

    /**
     * Generates a new token if called, and expires the old ones, and stores it in the class.
     * this is used sometimes if a call needs to change the token
     *
     * @param \CesarEngineExample\Entity\Player $player
     * @param \CesarEngineExample\Entity\GameConfiguration $gameConfiguration
     * @param boolean $expirePrevious
     * @return \CesarEngineExample\Entity\PlayerToken
     */
    protected function generateToken(Player $player, GameConfiguration $gameConfiguration, $expirePrevious = true)
    {
        /** @var \CesarEngineExample\Repository\PlayerToken $playerTokenRepository */
        $playerTokenRepository = RepositoryLocator::getRepository('PlayerToken');

        $this->updatePlayerToken = $playerTokenRepository->generatePlayerToken(
            $player,
            $gameConfiguration,
            $expirePrevious
        );
        return $this->updatePlayerToken;
    }

    /**
     * Get the new playerToken that we generated for the call.
     *
     * @return \CesarEngineExample\Entity\PlayerToken|boolean
     */
    protected function getUpdatedPlayerToken()
    {
        return $this->updatePlayerToken ?: false;
    }

    /**
     * Get the stored previously player token
     *
     * @return \CesarEngineExample\Entity\PlayerToken|boolean
     */
    protected function getPlayerToken()
    {
        return $this->playerToken ?: false;
    }

    /**
     * Gets the latest token that a player used for a specific provider.
     * This is used for now in Netent for non game actions. It might help in other
     * providers. The idea is to use the last one, because it will match nearly all the time.
     *
     * @param $providerId
     * @param $playerId
     * @return mixed
     */
    protected function getLatestPlayerTokenByProviderIdAndPlayerId($providerId, $playerId)
    {
        /** @var \CesarEngineExample\Repository\PlayerToken $playerTokenRepository */
        $playerTokenRepository = RepositoryLocator::getRepository('PlayerToken');
        $playerToken = $playerTokenRepository->getLatestPlayerTokenByProviderIdAndPlayerId($providerId,$playerId);
        if(!$playerToken instanceof PlayerToken){
            throw new \RuntimeException('Player not found. Make sure the Player Id is correct.');
        }
        return $playerToken;
    }

    /**
     * Checks if the token has expired or not: we will know that it's expired if it came from mysql. The entity class
     * and the repository class are managing it so it's transparent for this class.
     *
     * Just remember:
     *
     * The token is stored both in redis and mysql at the generation, but we will always try to get it from Redis.
     * If it's not in redis, we know it's expired. Still we will go to mysql to know if it's expired or it never
     * existed. Some actions can work with a expired token but never will they work with a never existing one.
     *
     * @return boolean
     */
    protected function checkTokenNotExpired()
    {
        return $this->playerToken instanceof PlayerTokenEntity && !$this->playerToken->getExpired();
    }

    /**
     * Expires the player token from redis
     *
     * @param \CesarEngineExample\Entity\PlayerToken $playerToken
     */
    protected function expirePlayerToken(PlayerTokenEntity $playerToken)
    {
        /** @var \CesarEngineExample\Repository\PlayerToken $playerTokenRepository */
        $playerTokenRepository = RepositoryLocator::getRepository('PlayerToken');
        $playerTokenRepository->expirePlayerTokenFromRedisByPlayerToken($playerToken);
    }

    /**
     * Get all the attributes for a providerClassName, for example, provider endpoint, provider valid ip ...
     *
     * @param string $className
     * @return array
     * @throws \Exception
     */
    protected function getProviderConfigurationAttributes($className)
    {
        if(!$this->player instanceof Player){
            throw new \RuntimeException('Unable to find merchant Id, please be sure to set the Player before');
        }

        /** @var \CesarEngineExample\Repository\Provider $providerRepository */
        $providerRepository = RepositoryLocator::getRepository('Provider');
        /** @var \CesarEngineExample\Repository\ProviderConfigurationAttribute $providerConfigurationAttributeRepository */
        $providerConfigurationAttributeRepository = RepositoryLocator::getRepository('ProviderConfigurationAttribute');

        $this->provider = $providerRepository->getProviderByClassName($className);
        $this->providerConfigurationAttributes = $providerConfigurationAttributeRepository->
        getProviderConfigurationAttributesByProviderClassNameAndMerchantId($className, $this->getPlayer()->getMerchantId());

        return $this->providerConfigurationAttributes;
    }

    /**
     * Return false or the provider configuration attribute by name if found
     *
     * @param string $name
     * @param string|boolean $default
     * @return string|boolean
     */
    protected function getProviderConfigurationAttribute($name, $default = false)
    {
        return array_key_exists($name, $this->providerConfigurationAttributes) ? $this->providerConfigurationAttributes[$name] : $default;
    }

    /**
     * Get the current GameConfiguration in use. If the player token is not set, obviously this method will not work
     * Please always call the checkToken method from this class before.
     *
     * @return \CesarEngineExample\Entity\GameConfiguration
     */
    protected function getGameConfiguration()
    {
        if ($this->gameConfiguration instanceof GameConfiguration) {
            return $this->gameConfiguration;
        }

        if (!$this->playerToken instanceof PlayerTokenEntity) {
            throw new \RuntimeException(
                'Game Configuration not found. Make sure the Player Token is configured.'
            );
        }

        $this->gameConfiguration = $this->playerToken->getGameConfiguration();
        return $this->gameConfiguration;
    }

    /**
     * Get all the attributes from a game configuration. as an array
     *
     * @return array
     */
    protected function getGameConfigurationAttributes()
    {
        return $this->getGameConfiguration()->getAttributes();
    }

    /**
     * Set the provider request parameters
     *
     * @param array $providerRequest
     * @return \CesarEngineExample\Provider\AbstractProvider
     */
    protected function setProviderRequest(array $providerRequest)
    {
        $this->providerRequest = $providerRequest;
        return $this;
    }

    /**
     * Get the array of parameters from the provider request
     *
     * @return array|null
     */
    protected function getProviderRequest()
    {
        return $this->providerRequest ?: null;
    }

    /**
     * Set the provider response parameters to store them in the database. This is used to log in the DB all the
     * communication against the providers.
     *
     * @param array $providerResponse
     * @return \CesarEngineExample\Provider\AbstractProvider
     */
    protected function setProviderResponse(array $providerResponse)
    {
        $this->providerResponse = $providerResponse;

        /** @var \CesarEngineExample\Service\ProviderCommunication $providerCommunicationService */
        $providerCommunicationService = ServiceLocator::getService('ProviderCommunication');

        $providerCommunicationService
            ->setPlayerId(is_object($this->player) ? $this->getPlayer()->getId() : null)
            ->setProviderId(is_object($this->provider) ? $this->provider->getId() : null)
            ->setRequest($this->getProviderRequest())
            ->setResponse($this->getProviderResponse())
            ->store();

        return $this;
    }

    /**
     * Get the array of response to the provider
     *
     * @return array|null
     */
    protected function getProviderResponse()
    {
        return $this->providerResponse ?: null;
    }

    /**
     * Return the caller instance of the provider for the normalizer. This returns the provider specialized class
     * that will translate the communication within the common API
     *
     * @param string $className
     * @return \CesarEngineExample\Provider\Caller\AbstractCaller|null
     */
    protected function getCaller($className)
    {
        if (!$this->_caller instanceof AbstractCaller) {
            $callerClass = "\\CesarEngineExample\\Provider\\Caller\\{$className}Caller";
            $this->_caller = new $callerClass();
            $this->_caller->setProviderConfigurationId($this->getProviderConfigurationAttribute('provider-configuration-id'));
        }
        return $this->_caller;
    }

    /**
     * Parse the class name from \\namespace\\bar\\Mga  to Mga
     * so you can get the attributes of the provider just by the class name.
     *
     * @param \CesarEngineExample\Provider\AbstractProvider $object
     * @return string
     */
    protected function getClassName($object)
    {
        //Get the name of the class:
        $className = get_class($object);
        //Remove the namespace:
        if (false !== ($pos = strrpos($className, '\\'))) {
            return substr($className, $pos + 1);
        }
        return $className;
    }

    /**
     * Return the provider request parameter specified, checking it doesn't generate a warning
     *
     * @param string $name
     * @return mixed|boolean
     */
    protected function getRequestParameter($name)
    {
        return !empty($this->params[$name]) ? $this->params[$name] : false;
    }

    /**
     * Initialize the latency service with the action performed
     *
     * @param string $action
     */
    public function latencyStart($action)
    {
        /** @var \CesarEngineExample\Service\Latency $latencyService */
        $latencyService = ServiceLocator::getService('Latency');
        $latencyService->start($action);
    }

    /**
     * End the latency service, save the info and populate the heartbeat system
     */
    public function latencyEnd()
    {
        /** @var \CesarEngineExample\Service\Latency $latencyService */
        $latencyService = ServiceLocator::getService('Latency');
        if ($this->player instanceof Player)
            $latencyService->setMerchantId($this->player->getMerchantId());
        if ($this->playerToken instanceof PlayerTokenEntity)
            $latencyService->setProviderId($this->getPlayerToken()->getProviderId());
        $latencyService->end();
    }
}