<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 4/17/17
 * Time: 10:17 PM
 *
 * Check: CODING.md > 'Print' and 'Download'
 *
 */

namespace IMATHUZH\Qfq\Core\Report;


use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\SessionCookie;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;


/**
 * Class Html2Pdf
 * @package qfq
 */
class Html2Pdf {

    /**
     * @var array
     */
    private $config = array();

    /**
     * @var Session
     */
    private $session = null;

    /**
     * @var SessionCookie
     */
    private $sessionCookie = null;

    /**
     * @var Sip
     */
    private $sip = null;

    /**
     * @var string
     */
    private $logFilePathAbsolute = '';

    /**
     * Read QFQ config. Only SYSTEM_BASE_URL_PRINT and SYSTEM_WKHTMLTOPDF will be used.
     * Check and get all clean _GET Parameter. Build a URL based on SYSTEM_BASE_URL_PRINT and the delivered URL params.
     *
     * @param array $config
     * @param       $phpUnit
     *
     * @throws \UserFormException
     * @throws \exception
     */
    public function __construct(array $config = array(), $phpUnit = false) {

        #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"

        $this->config = $config;

        if (!isset($config[SYSTEM_BASE_URL]) || $config[SYSTEM_BASE_URL] == '') {
            throw new \exception('Please configure ' . SYSTEM_BASE_URL, ERROR_HTML2PDF_MISSING_CONFIG);
        }

        if (!isset($config[SYSTEM_CMD_WKHTMLTOPDF]) || $config[SYSTEM_CMD_WKHTMLTOPDF] == '') {
            throw new \exception('Please configure ' . SYSTEM_CMD_WKHTMLTOPDF, ERROR_HTML2PDF_MISSING_CONFIG);
        }

        $this->session = Session::getInstance($phpUnit);
        $this->sessionCookie = new SessionCookie($config);
        $this->sip = new Sip($phpUnit);

        $this->logFilePathAbsolute = Path::absoluteQfqLogFile();
    }

    /**
     * Return an array with GET params who are clean - they do not violate $pattern.
     *
     * @param array $get
     * @return array
     */
    private function readCleanGetParam(array $get) {

        $param = array();
        $pattern = '^[\-_\.,;:\/a-zA-Z0-9]*$';

        foreach ($get as $key => $value) {
            if (preg_match("/$pattern/", $value) === 1) {
                $param[$key] = $value;
            }
        }

        return $param;
    }

    /**
     * Set HTML Header to initiate PDF download.
     *
     * @param $filename
     */
    private function setHeader($filename) {

        header("Content-Disposition: inline; filename=\"$filename\"");
        header("Content-Type: application/pdf");
        header("Content-Transfer-Encoding: binary");
    }


    /**
     * Converts a Webpage (URL) to a PDF file.
     * The URL might be a local Typo3 page (without hostname, starting with the parameter) or a full URL.
     *
     * @param string $token TOKEN_URL | TOKEN_URL_PARAM | TOKEN_PAGE | TOKEN_UID
     * @param string $url id=exportPage&r=123, www.nzz.ch/issue?id=456
     *
     * @return string rendered file - please delete later
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \exception
     */
    public function page2pdf($token, $url, $downloadMode = DOWNLOAD_MODE_PDF) {
        $rcArgs = array();
        $urlParamString = '';
        $host = '';

        switch ($token) {
            case TOKEN_UID:
                $uidParsed = OnString::parseUidToken($url);
                $host = $uidParsed['name'];
                $urlParamString = $uidParsed['paramString'];
                break;
            case TOKEN_URL:
                $arr = explode('?', $url, 2);
                $host = $arr[0];
                $urlParamString = $arr[1] ?? '';
                break;
            case TOKEN_URL_PARAM:
                $host = $this->config[SYSTEM_BASE_URL];
                $urlParamString = $url;
                break;
            case TOKEN_PAGE:
                if (T3Handler::useSlugsInsteadOfPageAlias()) {
                    if (!OnString::strContains($url, '?')) {
                        $url = OnString::strReplaceFirst('&', '?', $url);
                    }
                    $urlParts = explode('?', $url, 2);
                    $host = Path::urlApp($urlParts[0]);
                    $urlParamString = $urlParts[1] ?? '';
                } else {
                    $host = $this->config[SYSTEM_BASE_URL];
                    $urlParamString = $url;
                }
                break;
            default:
                break;
        }

        $rcSipEncode = false;

        // Sets view port too
        $urlParam = OnString::splitParam($urlParamString, $rcArgs, $rcSipEncode);

        $rcArgs = OnArray::arrayEscapeshellarg($rcArgs);
        $options = KeyValueStringParser::unparse($rcArgs, ' ', ' ');

        $urlParamString = KeyValueStringParser::unparse($urlParam, '=', '&');
        if ($rcSipEncode) {
            $urlParamString = $this->sip->queryStringToSip($urlParamString, RETURN_URL);
        }

        $url = Support::mergeUrlComponents('', $host, $urlParamString);

        if (substr($url, 0, 4) != 'http' && $token != TOKEN_UID) {
            $url = 'http://' . $url;
        }

        $urlPrint = escapeshellarg($url);
        $filename = HelperFile::tempnam();
        $filenameEscape = escapeshellarg($filename);

        switch ($downloadMode) {
            case DOWNLOAD_MODE_PDF:
                $cookieOptions = '--cookie-jar ' . escapeshellarg($this->sessionCookie->getFile()) . ' --cookie ' . DEV_PANEL_PREVENT_BUILD_PARAM . ' 1';
                $customHeader = '--custom-header User-Agent ' . escapeshellarg($_SERVER['HTTP_USER_AGENT'] ?? '') . ' --custom-header-propagation'; // By default 'Typo3' expects the same User-Agent for the FE-Session
                $cmd = $this->config[SYSTEM_CMD_WKHTMLTOPDF] . " $customHeader $cookieOptions $options $urlPrint $filenameEscape";
                break;
            case DOWNLOAD_MODE_QFQPDF:
                // /opt/qfqpdf/qfqpdf "https://www.math.uzh.ch" fileadmin/outputNew.pdf
                //                    --page-size A5 --margin-top 22mm
                //                    --template-header "<h1>Test</h1>"
                //                    -Fb
                //                    -c "name:firstCookie,value:tunjngvnjfr23r" "name:secondCookie,value:249ur4jn233d"'
                $customHeader = '';
                $options .= ' -b';
//                $cookieOptions = '--cookies ' . escapeshellarg($this->sessionCookie->getCookieQfqPdf());
                $cookieOptions = '-c ' . $this->sessionCookie->getCookieQfqPdf() . ' -c ' . escapeshellarg('name:' . DEV_PANEL_PREVENT_BUILD_PARAM . ',value:1');

                $cmd = $this->config[SYSTEM_CMD_QFQPDF] . " $urlPrint $filenameEscape $customHeader $cookieOptions $options";
                break;
            default:
                throw new \exception("Unknown download mode: $downloadMode", ERROR_UNKNOWN_MODE);
        }

        if ($this->logFilePathAbsolute != '') {
            Logger::logMessage("Html2Pdf: $cmd", $this->logFilePathAbsolute);
        }

        // Very important: The current lock on session SESSION_NAME has to be freed, cause wkhtmltopdf will use the same
        // session in a few moments and this script remains active during that time and that would cause a deadlock else.
        $this->session->close();

        $rc = 0;
        $line = system($cmd, $rc);

        if ($rc != 0) {
            throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Error [RC=$rc] CMD:" . $this->config[SYSTEM_CMD_QFQPDF],
                    ERROR_MESSAGE_TO_DEVELOPER => "CMD: " . $cmd . "<br>RC: $rc<br>Output: " . $line])
                , ERROR_HTML2PDF_WKHTML_FAILED);
        }

        // RC=0 does not guarantee a valid result: the PDF tool can exit cleanly yet leave an empty
        // or non-PDF file (resource timeout, session race under load, ...).
        clearstatcache(true, $filename);
        if (!file_exists($filename) || filesize($filename) === 0
            || file_get_contents($filename, false, null, 0, 5) !== '%PDF-') {
            throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Error: PDF rendering produced an invalid file",
                    ERROR_MESSAGE_TO_DEVELOPER => "CMD: " . $cmd . "<br>RC: $rc<br>Produced file is missing, empty or not a PDF: " . $filename])
                , ERROR_HTML2PDF_WKHTML_FAILED);
        }

        return $filename;
    }

    /**
     * @throws \exception
     */
    public function outputHtml2Pdf() {

        $get = $this->readCleanGetParam($_GET);
        $urlParam = KeyValueStringParser::unparse($get, '=', '&');
        $pageId = Support::setIfNotSet($get, HTML2PDF_PAGEID, 0);

        $filename = $this->page2pdf(TOKEN_URL_PARAM, $urlParam);

        $this->setHeader('print.' . $pageId . '.pdf');
        @readfile($filename);
        @unlink($filename);

        exit; // Do an extremely hard exit here to make sure there are no more additional bytes sent (makes the delivered PDF unusable).

    }

}