<?php

use dokuwiki\Extension\SyntaxPlugin;
use dokuwiki\HTTP\DokuHTTPClient;

/**
 * graphviz-Plugin: Parses graphviz-blocks
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Carl-Christian Salvesen <calle@ioslo.net>
 * @author     Andreas Gohr <andi@splitbrain.org>
 */

if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../../') . '/');
if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
require_once(DOKU_PLUGIN . 'syntax.php');

class syntax_plugin_graphviz extends SyntaxPlugin
{
    /**
     * What about paragraphs?
     */
    public function getPType()
    {
        return 'normal';
    }

    /**
     * What kind of syntax are we?
     */
    public function getType()
    {
        return 'substition';
    }

    /**
     * Where to sort in?
     */
    public function getSort()
    {
        return 200;
    }

    /**
     * Connect pattern to lexer
     */
    public function connectTo($mode)
    {
        $this->Lexer->addSpecialPattern('<graphviz.*?>\n.*?\n</graphviz>', $mode, 'plugin_graphviz');
    }

    /**
     * Handle the match
     */
    public function handle($match, $state, $pos, Doku_Handler $handler)
    {
        $info = $this->getInfo();

        // prepare default data
        $return = ['width'     => 0, 'height'    => 0, 'layout'    => 'dot', 'align'     => '', 'version'   => $info['date']];

        // prepare input
        $lines = explode("\n", $match);
        $conf = array_shift($lines);
        array_pop($lines);

        // match config options
        if (preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1];
        if (preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) {
            $return['width']  = $match[1];
            $return['height'] = $match[2];
        }
        if (preg_match('/\b(dot|neato|twopi|circo|fdp)\b/i', $conf, $match)) {
            $return['layout'] = strtolower($match[1]);
        }
        if (preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1];
        if (preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1];


        $input = implode("\n", $lines);
        $return['md5'] = md5($input); // we only pass a hash around

        // store input for later use
        io_saveFile($this->_cachename($return, 'txt'), $input);

        return $return;
    }

    /**
     * Cache file is based on parameters that influence the result image
     */
    public function _cachename($data, $ext)
    {
        unset($data['width']);
        unset($data['height']);
        unset($data['align']);
        return getcachename(implode('x', array_values($data)), '.graphviz.' . $ext);
    }

    /**
     * Create output
     */
    public function render($format, Doku_Renderer $R, $data)
    {
        if ($format == 'xhtml') {
            $img = DOKU_BASE . 'lib/plugins/graphviz/img.php?' . buildURLparams($data);
            $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" alt=""';
            if ($data['width'])  $R->doc .= ' width="' . $data['width'] . '"';
            if ($data['height']) $R->doc .= ' height="' . $data['height'] . '"';
            if ($data['align'] == 'right') $R->doc .= ' align="right"';
            if ($data['align'] == 'left')  $R->doc .= ' align="left"';
            $R->doc .= '/>';
            return true;
        } elseif ($format == 'odt') {
            $src = $this->_imgfile($data);
            $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']);
            return true;
        }
        return false;
    }

    /**
     * Return path to the rendered image on our local system
     */
    public function _imgfile($data)
    {
        $cache  = $this->_cachename($data, 'png');

        // create the file if needed
        if (!file_exists($cache)) {
            $in = $this->_cachename($data, 'txt');
            if ($this->getConf('path')) {
                $ok = $this->_run($data, $in, $cache);
            } else {
                $ok = $this->_remote($data, $in, $cache);
            }
            if (!$ok) return false;
            clearstatcache();
        }

        // resized version
        if ($data['width']) {
            $cache = media_resize_image($cache, 'png', $data['width'], $data['height']);
        }

        // something went wrong, we're missing the file
        if (!file_exists($cache)) return false;

        return $cache;
    }

    /**
     * Render the output remotely at google
     */
    public function _remote($data, $in, $out)
    {
        if (!file_exists($in)) {
            if ($conf['debug']) {
                dbglog($in, 'no such graphviz input file');
            }
            return false;
        }

        $http = new DokuHTTPClient();
        $http->timeout = 30;

        $pass = [];
        $pass['cht'] = 'gv:' . $data['layout'];
        $pass['chl'] = io_readFile($in);

        $img = $http->post('http://chart.apis.google.com/chart', $pass, '&');
        if (!$img) return false;

        return io_saveFile($out, $img);
    }

    /**
     * Run the graphviz program
     */
    public function _run($data, $in, $out)
    {
        global $conf;

        if (!file_exists($in)) {
            if ($conf['debug']) {
                dbglog($in, 'no such graphviz input file');
            }
            return false;
        }

        $cmd  = $this->getConf('path');
        $cmd .= ' -Tpng';
        $cmd .= ' -K' . $data['layout'];
        $cmd .= ' -o' . escapeshellarg($out); //output
        $cmd .= ' ' . escapeshellarg($in); //input

        exec($cmd, $output, $error);

        if ($error != 0) {
            if ($conf['debug']) {
                dbglog(implode("\n", $output), 'graphviz command failed: ' . $cmd);
            }
            return false;
        }
        return true;
    }
}
