<?php

namespace Velis\Pdf;

use Laminas\Filter\Word\CamelCaseToDash;
use RuntimeException;
use Velis\App;

/**
 * PDF document creator based on wkhtmltopdf converter
 * @see https://code.google.com/p/wkhtmltopdf/
 *
 * @author Olek Procki <olo@velis.pl>
 */
class Document implements RenderableInterface
{
    /**
     * HTML content
     * @var string
     */
    protected $_content;


    /**
     * HTML header (optional)
     * @var string
     */
    protected $_header;


    /**
     * HTML footer (optional)
     * @var string
     */
    protected $_footer;

    /**
     * HTML files name
     * @var string
     */
    protected $hash;

    /**
     * Render from prepared files.
     * Hash is required.
     * @var bool
     */
    protected $prepared;

    /**
     * Configuration options
     * @var array
     */
    protected $_config;
    private string|false $outputFilePath;


    /**
     * Constructor
     *
     * @param string $content
     * @param array $config
     */
    public function __construct($content = null, $config = null)
    {
        if ($content) {
            $this->setContent($content);
        }
        $this->setConfig($config ?: []);
    }

    /**
     * Sets content HTML
     * @param string $content
     * @return $this
     */
    public function setContent($content)
    {
        $this->_content = $content;

        return $this;
    }

    /**
     * Sets document header
     *
     * @param string $header
     * @return $this
     */
    public function setHeader($header)
    {
        $this->_header = $header;

        return $this;
    }

    /**
     * Sets document footer
     *
     * @param string $footer
     * @return $this
     */
    public function setFooter($footer)
    {
        $this->_footer = $footer;

        return $this;
    }

    /**
     * Sets hash name of the protocol html files
     * @param string $hash
     * @return $this
     */
    public function setHash(string $hash)
    {
        $this->hash = $hash;

        return $this;
    }

    /**
     * Returns hash name
     * @return string
     */
    public function getHash()
    {
        return $this->hash;
    }

    /**
     * Sets renderer config
     * @param array $config
     * @return $this
     */
    public function setConfig(array $config)
    {
        $this->_config = $config;

        return $this;
    }

    public function getConfig(): array
    {
        return $this->_config;
    }

    /**
     * Sets one config value
     *
     * @param string $attr
     * @param mixed $value
     * @return $this
     */
    public function setConfigValue($attr, $value)
    {
        $this->_config[$attr] = $value;

        return $this;
    }

    /**
     * Sets one config value
     *
     * @param string $attr
     * @return mixed
     */
    public function getConfigValue($attr)
    {
        return $this->_config[$attr];
    }

    /**
     * @param string $name
     * @param array $arguments
     * @return Document|void
     * @throws RuntimeException
     */
    public function __call($name, $arguments)
    {
        $filter = new CamelCaseToDash();

        if (strpos($name, 'set') === 0) {
            $attr = strtolower($filter->filter(substr($name, 3)));
            $this->setConfigValue($attr, $arguments[0]);
        } elseif (strpos($name, 'get') === 0) {
            $attr = strtolower($filter->filter(substr($name, 3)));
            return $this->getConfigValue($attr);
        } else {
            throw new RuntimeException('Unknown method');
        }
    }

    public function saveHeader()
    {
        $headerFile = DATA_PATH . 'temp/pdf-header-' . $this->hash . '.html';
        !$this->prepared && file_put_contents($headerFile, $this->_header);

        return $headerFile;
    }

    public function saveContent()
    {
        $contentFile = DATA_PATH . 'temp/pdf-content-' . $this->hash . '.html';
        !$this->prepared && file_put_contents($contentFile, $this->_content);

        return $contentFile;
    }

    public function saveFooter()
    {
        $footerFile = DATA_PATH . 'temp/pdf-footer-' . $this->hash . '.html';
        !$this->prepared && file_put_contents($footerFile, $this->_footer);

        return $footerFile;
    }

    private function createOutputFile(): void
    {
        if (!App::$config->settings->pdfRenderer) {
            throw new RuntimeException('PDF renderer not set in server configuration!');
        }

        $command = ROOT_PATH . App::$config->settings->pdfRenderer;
        if (!file_exists($command)) {
            $command = App::$config->settings->pdfRenderer;
        } else {
            $command .= ' --load-error-handling ignore';
        }

        $pdfSettings = App::$config->settings->pdf;
        if ($pdfSettings && $pdfSettings->localFileAccess && strpos($command, 'wkhtmltopdf') !== false) {
            $command .= ' --enable-local-file-access ';
        }

        foreach ($this->_config as $attr => $value) {
            $command .= ' --' . $attr . ' ' . $value;
        }

        $this->prepared = (bool) $this->hash;

        !$this->prepared && $this->setHash(md5(time() . session_id() . '-pdf'));

        if ($this->_header) {
            $headerFile = $this->saveHeader();
            $command .= ' --header-html ' . $headerFile;
        }

        $contentFile = $this->saveContent();

        if ($this->_footer) {
            $footerFile = $this->saveFooter();
            $command .= ' --footer-html ' . $footerFile;
        }

        $outputFile = tempnam(DATA_PATH . 'temp', 'pdf');
        $command .= " $contentFile $outputFile";

        if (App::isConsole() || App::isTesting()) {
            $command .= ' > /dev/null 2>&1';
        }

        exec($command, $execOutput);

        unlink($contentFile);

        if ($this->_header) {
            unlink($headerFile);
        }
        if ($this->_footer) {
            unlink($footerFile);
        }

        $this->outputFilePath = $outputFile;
    }

    /**
     * Returns rendered file path
     * @throws RuntimeException
     */
    public function getRenderedFilePath(): string
    {
        $this->createOutputFile();

        if (!$this->outputFilePath) {
            throw new RuntimeException('Output file not created');
        }

        return $this->outputFilePath;
    }

    /**
     * {@inheritDoc}
     */
    public function render(): string
    {
        $this->createOutputFile();
        $outputFile = $this->outputFilePath;

        $pdf = file_get_contents($outputFile);
        unlink($outputFile);

        return $pdf;
    }

    /**
     * Invoke magic function
     * @return string
     */
    public function __invoke()
    {
        return $this->render();
    }
}
