<?php
namespace Cadoles\PortalBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpKernel\KernelInterface;
use Doctrine\DBAL\Connection as DBALConnection;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Validator\Constraints\DateTime;

class jsonRPCClient
{
    protected $url = null, $is_debug = false, $parameters_structure = 'array';

    /**
     * Default options for curl
     *
     * @var array
     */
    protected $curl_options = array(
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_TIMEOUT => 8,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_SSL_VERIFYPEER => false
    );

    /**
     * Http error statuses
     *
     * Source: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
     *
     * @var array
     */
    private $httpErrors = array(
        400 => '400 Bad Request',
        401 => '401 Unauthorized',
        403 => '403 Forbidden',
        404 => '404 Not Found',
        405 => '405 Method Not Allowed',
        406 => '406 Not Acceptable',
        408 => '408 Request Timeout',
        500 => '500 Internal Server Error',
        502 => '502 Bad Gateway',
        503 => '503 Service Unavailable'
    );

    /**
     * Takes the connection parameter and checks for extentions
     *
     * @param string $pUrl - url name like http://example.com/
     */
    public function __construct($pUrl)
    {
        $this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded for using this class!');
        $this->validate(false === extension_loaded('json'), 'The json extension must be loaded for using this class!');

        // set an url to connect to
        $this->url = $pUrl;
    }

    /**
     * Return http error message
     *
     * @param $pErrorNumber
     *
     * @return string|null
     */
    private function getHttpErrorMessage($pErrorNumber)
    {
        return isset($this->httpErrors[$pErrorNumber]) ? $this->httpErrors[$pErrorNumber] : null;
    }

    /**
     * Set debug mode
     *
     * @param boolean $pIsDebug
     *
     * @return jsonRPCClient
     */
    public function setDebug($pIsDebug)
    {
        $this->is_debug = !empty($pIsDebug);
        return $this;
    }

    /**
     * Set structure to use for parameters
     *
     * @param string $pParametersStructure 'array' or 'object'
     *
     * @throws UnexpectedValueException
     * @return jsonRPCClient
     */
    public function setParametersStructure($pParametersStructure)
    {
        if (in_array($pParametersStructure, array('array', 'object')))
        {
            $this->parameters_structure = $pParametersStructure;
        }
        else
        {
            throw new UnexpectedValueException('Invalid parameters structure type.');
        }

        return $this;
    }

    /**
     * Set extra options for curl connection
     *
     * @param array $pOptionsArray
     *
     * @throws InvalidArgumentException
     * @return jsonRPCClient
     */
    public function setCurlOptions($pOptionsArray)
    {
        if (is_array($pOptionsArray))
        {
            $this->curl_options = $pOptionsArray + $this->curl_options;
        }
        else
        {
            throw new InvalidArgumentException('Invalid options type.');
        }

        return $this;
    }

    /**
     * Performs a request and gets the results
     *
     * @param string $pMethod - A String containing the name of the method to be invoked.
     * @param array  $pParams - An Array of objects to pass as arguments to the method.
     *
     * @throws RuntimeException
     * @return array
     */
    public function __call($pMethod, $pParams)
    {
        static $requestId = 0;

        // generating uniuqe id per process
        $requestId++;

        // check if given params are correct
        $this->validate(false === is_scalar($pMethod), 'Method name has no scalar value');
        $this->validate(false === is_array($pParams), 'Params must be given as array');

        // send params as an object or an array
        $pParams = ($this->parameters_structure == 'object') ? $pParams[0] : array_values($pParams);

        // Request (method invocation)
        $request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams, 'id' => $requestId));

        // if is_debug mode is true then add url and request to is_debug
        $this->debug('Url: ' . $this->url . "\r\n", false);
        $this->debug('Request: ' . $request . "\r\n", false);

        $responseMessage = $this->getResponse($request);

        // if is_debug mode is true then add response to is_debug and display it
        $this->debug('Response: ' . $responseMessage . "\r\n", true);

        // decode and create array ( can be object, just set to false )
        $responseDecoded = json_decode($responseMessage, true);

        // check if decoding json generated any errors
        $jsonErrorMsg = $this->getJsonLastErrorMsg();
        $this->validate( !is_null($jsonErrorMsg), $jsonErrorMsg . ': ' . $responseMessage);

        // check if response is correct
        $this->validate(empty($responseDecoded['id']), 'Invalid response data structure: ' . $responseMessage);
        $this->validate($responseDecoded['id'] != $requestId, 'Request id: ' . $requestId . ' is different from Response id: ' . $responseDecoded['id']);
        if (isset($responseDecoded['error']))
        {
            $errorMessage = 'Request have return error: ' . $responseDecoded['error']['message'] . '; ' . "\n" .
                'Request: ' . $request . '; ';

            if (isset($responseDecoded['error']['data']))
            {
                $errorMessage .= "\n" . 'Error data: ' . $responseDecoded['error']['data'];
            }

            $this->validate( !is_null($responseDecoded['error']), $errorMessage);
        }

        return $responseDecoded['result'];
    }

    /**
     * When the method invocation completes, the service must reply with a response.
     * The response is a single object serialized using JSON
     *
     * @param string $pRequest
     *
     * @throws RuntimeException
     * @return string
     */
    protected function & getResponse(&$pRequest)
    {
        // do the actual connection
        $ch = curl_init();
        if ( !$ch)
        {
            throw new \RuntimeException('Could\'t initialize a cURL session');
        }

        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
        curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        if ( !curl_setopt_array($ch, $this->curl_options))
        {
            throw new \RuntimeException('Error while setting curl options');
        }

        // send the request
        $response = curl_exec($ch);

        // check http status code
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if (isset($this->httpErrors[$httpCode]))
        {
            echo 'Response Http Error - ' . $this->httpErrors[$httpCode];
            return $false;
            throw new \RuntimeException('Response Http Error - ' . $this->httpErrors[$httpCode]);
        }
        // check for curl error
        if (0 < curl_errno($ch))
        {
            echo 'Unable to connect to '.$this->url . ' Error: ' . curl_error($ch);
            return $response;
            throw new \RuntimeException('Unable to connect to '.$this->url . ' Error: ' . curl_error($ch));
        }
        // close the connection
        curl_close($ch);

        return $response;
    }

    /**
     * Throws exception if validation is failed
     *
     * @param bool   $pFailed
     * @param string $pErrMsg
     *
     * @throws RuntimeException
     */
    protected function validate($pFailed, $pErrMsg)
    {
        if ($pFailed)
        {
            return false;
            throw new \RuntimeException($pErrMsg);
        }
    }

    /**
     * For is_debug and performance stats
     *
     * @param string $pAdd
     * @param bool   $pShow
     */
    protected function debug($pAdd, $pShow = false)
    {
        static $debug, $startTime;

        // is_debug off return
        if (false === $this->is_debug)
        {
            return;
        }
        // add
        $debug .= $pAdd;
        // get starttime
        $startTime = empty($startTime) ? array_sum(explode(' ', microtime())) : $startTime;
        if (true === $pShow and !empty($debug))
        {
            // get endtime
            $endTime = array_sum(explode(' ', microtime()));
            // performance summary
            $debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n";
            echo nl2br($debug);
            // send output imidiately
            flush();
            // clean static
            $debug = $startTime = null;
        }
    }

    /**
     * Getting JSON last error message
     * Function json_last_error_msg exists from PHP 5.5
     *
     * @return string
     */
    function getJsonLastErrorMsg()
    {
        if (!function_exists('json_last_error_msg'))
        {
            function json_last_error_msg()
            {
                static $errors = array(
                    JSON_ERROR_NONE           => 'No error',
                    JSON_ERROR_DEPTH          => 'Maximum stack depth exceeded',
                    JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
                    JSON_ERROR_CTRL_CHAR      => 'Unexpected control character found',
                    JSON_ERROR_SYNTAX         => 'Syntax error',
                    JSON_ERROR_UTF8           => 'Malformed UTF-8 characters, possibly incorrectly encoded'
                );
                $error = json_last_error();
                return array_key_exists($error, $errors) ? $errors[$error] : 'Unknown error (' . $error . ')';
            }
        }

        // Fix PHP 5.2 error caused by missing json_last_error function
        if (function_exists('json_last_error'))
        {
            return json_last_error() ? json_last_error_msg() : null;
        }
        else
        {
            return null;
        }
    }
}


class GetLimesurveyCommand extends Command
{
    private $container;
    private $em;
    private $output;
    private $filesystem;
    private $rootlog;

    protected function configure()
    {
        $this
            ->setName('Portal:GetLimesurvey')
            ->setDescription('Get Datas from Limesurvey')
            ->setHelp('Synchronize Limesurvey')
            ->addArgument('cronid', InputArgument::OPTIONAL, 'ID Cron Job') 
            ->addArgument('lastchance', InputArgument::OPTIONAL, 'Lastchance to run the cron') 
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->container    = $this->getApplication()->getKernel()->getContainer();
        $this->em           = $this->container->get('doctrine')->getEntityManager();
        $this->output       = $output;
        $this->filesystem   = new Filesystem();
        $this->limefile     = $this->container->get('kernel')->getRootDir()."/../uploads/limesurvey";
        $this->rootlog      = $this->container->get('kernel')->getRootDir()."/../var/logs/";
        $alias              = $this->container->getParameter('alias');

        $activate_widlimesurvey = $this->container->getParameter('activate_widlimesurvey');
        if(!$activate_widlimesurvey) {
            $this->writeln('Widget Limesurvey désactivé');
            return 1;
        }

        $PROXYactivate  = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYactivate"])->getValue();
        $PROXYserver    = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYserver"])->getValue();
        $PROXYport      = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYport"])->getValue();

        $this->writelnred('');
        $this->writelnred('== Portal:GetLimesurvey');        
        $this->writelnred('==========================================================================================================');

        $now=new \DateTime('now');
        $fgdebug = false;

        // Instance Limesurvey
        $widlimesurvey_url = $this->container->getParameter('widlimesurvey_url')."/index.php";
        if(stripos($widlimesurvey_url,"/limesurvey")==0) {
            $weburl=$this->container->getParameter('weburl');
            $widlimesurvey_url="https://".$weburl.$widlimesurvey_url;
        }

        $widlimesurvey_apiuser = $this->container->getParameter('widlimesurvey_apiuser');
        $widlimesurvey_apikey = $this->container->getParameter('widlimesurvey_apikey');
        $myJSONRPCClient = new JsonRPCClient( $widlimesurvey_url.'/admin/remotecontrol' );        

        // Connection Limesurvey
        $sessionKey= $myJSONRPCClient->get_session_key( $widlimesurvey_apiuser, $widlimesurvey_apikey );        

        // Filesystem
        $fs = new Filesystem();

        // On supprime les potentiels yml.new restant
        $fs->remove($this->limefile.'-new');

        // list de l'ensemble des sondages invité
        $surveys= $myJSONRPCClient->list_surveys($sessionKey);
        if(is_array($surveys)) {
            foreach($surveys as $survey) {
                // Si le sondage est actif
                if($survey["active"]=="Y") {
                    $this->writeln("SURVEY = ".$survey["surveyls_title"]);

                    // Liste des participants 
                    $survey_id=$survey["sid"];
                    $users = $myJSONRPCClient->list_participants($sessionKey, $survey_id, 0, 10000);
                    $token="";
                    
                    foreach($users as $user) {
                        if(isset($user["participant_info"]["email"]) && $user["participant_info"]["email"]!="") {
                            $this->writeln("EMAIL = ".$user["participant_info"]["email"]);
                            
                            $entity = $this->em->getRepository('CadolesCoreBundle:User')->findOneBy(["email"=>$user["participant_info"]["email"]]);
                            if($entity) {
                                $surveypropconf     = $myJSONRPCClient->get_survey_properties($sessionKey,$survey["sid"],["datecreated"]);
                                $surveyproplang     = $myJSONRPCClient->get_language_properties($sessionKey,$survey["sid"],["surveyls_welcometext"]);
                                $surveydescription  = $surveyproplang["surveyls_welcometext"];
                                $surveydescription  = json_encode(str_replace("\r","",str_replace("\n","",$surveydescription)));
                                
                                $token=$user["token"];
                                $url=$widlimesurvey_url."/".$survey_id;
                                if($token!="")
                                    $url.="?token=".$token;

                                $string ='"'.$survey["sid"].'": {';
                                $string.='"title": '.json_encode($survey["surveyls_title"]).',';
                                $string.='"url": "'.$url.'",';
                                $string.='"date": "'.$surveypropconf["datecreated"].'",';
                                $string.='"description": '.$surveydescription;
                                $string.='},';

                                $this->filesystem->appendToFile($this->limefile.'-new/'.$entity->getId().'/invitation.json', $string."\n");
                            }
                        }
                    }
                }
            }
        }

        // Pour chaque user on remonte les sondages dont ils sont propriétaires
        $userlimesurveys=$myJSONRPCClient->list_users($sessionKey);
        if(is_array($userlimesurveys)) {
            foreach($userlimesurveys as $userlimesurvey) {
                $user = $this->em->getRepository('CadolesCoreBundle:User')->findOneBy(["username"=>$userlimesurvey["users_name"]]);
                if($user) {
                    $surveys = $myJSONRPCClient->list_surveys($sessionKey, $user->getUserName());
                    foreach($surveys as $survey) {
                        if(is_array($survey)) {
                            
                            if($survey["active"]=="Y") {
                                $surveypropconf     = $myJSONRPCClient->get_survey_properties($sessionKey,$survey["sid"],["datecreated"]);
                                $surveyproplang     = $myJSONRPCClient->get_language_properties($sessionKey,$survey["sid"],["surveyls_welcometext"]);
                                $surveydescription  = $surveyproplang["surveyls_welcometext"];
                                $surveydescription  = json_encode(str_replace("\r","",str_replace("\n","",$surveydescription)));

                                $url=$widlimesurvey_url."/admin/survey/sa/view/surveyid/".$survey["sid"];

                                $string ='"'.$survey["sid"].'": {';
                                $string.='"title": '.json_encode($survey["surveyls_title"]).',';
                                $string.='"url": "'.$url.'",';
                                $string.='"date": "'.$surveypropconf["datecreated"].'",';
                                $string.='"description": '.$surveydescription;
                                $string.='},';

                                $this->filesystem->appendToFile($this->limefile.'-new/'.$user->getId().'/survey.json', $string."\n");
                            }
                        }
                    }
                }
            }
        }

        // On supprime les encours pour le remplacer le new
        if($fs->exists($this->limefile.'-new')) {
            $fs->remove($this->limefile);
            $fs->rename($this->limefile.'-new',$this->limefile);
        }
        
        // release the session key
        $myJSONRPCClient->release_session_key( $sessionKey );        

        // essai
        /*
        $essai=file_get_contents($this->limefile.'/-100/survey.json');
        $json = substr($essai, 0, -2);  // sup dernier ,
        $decodedArray = json_decode("{".$json."}", true);
        dump($decodedArray);
        */

        $this->writeln('');
        return 1;
    }


    private function writelnred($string) { 
        $this->output->writeln('<fg=red>'.$string.'</>');
        $this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
    }
    private function writeln($string) { 
        $this->output->writeln($string);
        $this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
    }     
}
