<?php
/*
 * Copyright (c) 2003-2022 Willem Dijkstra
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * Graph class
 *
 * Generates rrdtool recipes by rewriting a template using a joined set of all
 * variables (layout vars, graph vars, session vars - in order of precendence).
 */
require_once(__DIR__ . '/class_session.inc');
require_once(__DIR__ . '/class_cache.inc');
require_once(__DIR__ . '/setup.inc');
require_once(__DIR__ . '/class_vars.inc');
require_once(__DIR__ . '/graph_template.inc');
require_once(__DIR__ . '/tools.inc');

class Graph {
    public $definition;
    public $graph_vars;
    public $group_vars;
    public $template;
    public $url;
    public $vars;

    function __construct(&$group_vars) {
        $this->group_vars = $group_vars;
    }

    function set_graph_vars(&$graph_vars) {
        $this->graph_vars = $graph_vars;
    }

    function _makevars() {
        $n = null;
        $template = null;
        global $session;
        global $symon;

        $this->definition = '';
        unset($this->url);

        /* create local state by combining group and local graph args */
        $this->vars = new Vars();
        $this->vars->addvars($session->getvars());
        $this->vars->addvars($this->graph_vars);
        $this->vars->addvars($this->group_vars);

        if ($this->vars->defp('rrdfile')) {
            $this->vars->addvars(parse_filename($this->vars->get('name'), $this->vars->get('rrdfile')));
        } elseif ($this->vars->defp('rrdfile0')) {
            $n = 0;
            $this->vars->addvars(parse_filename($this->vars->get('name'), $this->vars->get('rrdfile0')));
            while ($this->vars->defp('rrdfile'.$n)) {
                $this->vars->addvars(parse_filename($this->vars->get('name'), $this->vars->get('rrdfile'.$n), $n));
                $n++;
            }
        }

        if (!$this->vars->defp('template')) {
            if ($this->vars->defp('rrdtype0')) {
                $template = get_combined_template($this->vars->get('rrdtype0'), $n);
            } elseif ($this->vars->defp('rrdtype')) {
                $template = get_single_template($this->vars->get('rrdtype'));
            } else {
                warning('graph: cannot load graph template: filename does not yield a graphtype ('. $this->vars->get('rrdfile'));
                if (isset($symon['graph_debug'])) {
                    $this->_display();
                }
            }
        } else {
            $template = $this->vars->get('template');
        }

        if ($template != "") {
            $this->template = preg_split("/\n/", (string) $template);
        } elseif (isset($symon['graph_debug'])) {
            $this->_display();
            warning('graph: template not set');
        }
    }

    function parse(&$lexer) {
        $this->graph_vars = new Vars();
        $this->graph_vars->parse($lexer);
    }

    function render() {
        $this->_save();
        if ($this->template !== null) {
            if ($this->vars->defp('drilldown'))
                print '<a href="index.php?'.$this->vars->get('drilldown').'">';
            print '<img align="top" src="graph.php?'. $this->_url() . '" class="graph">';
            if ($this->vars->defp('drilldown'))
                print '</a>';
            print '
';
        }
    }

    function _url() {
        if ($this->url === null) {
            runtime_error("graph: internal error : need to save the graph before an url can be requested");
            return '';
        } else {
            return $this->url;
        }
    }


    function _save() {
        global $cache;
        global $symon;

        $this->_makevars();

        if (isset($symon['graph_debug'])) {
            print "<pre>";
            $this->_display();
            print "</pre>";
        }

        $this->_preprocess();

        if (isset($symon['graph_debug'])) {
            print "<pre>\xa preprocessed template = ";
            if ($this->definition !== null && $this->definition != "") {
                foreach ($this->definition as $line) {
                    print "\xa    '$line'";
                }
            }
            print "</pre>";
        }

        $this->_constrain();

        if (isset($symon['graph_debug'])) {
            print "<pre>\xa constrained = ";
            if ($this->definition !== null && $this->definition != "") {
                foreach ($this->definition as $line) {
                    print "\xa    '$line'";
                }
            }
            print "</pre>";
        }

        $this->_justify();

        if (isset($symon['graph_debug'])) {
            print "<pre>\xa justified = ";
            if ($this->definition !== null && $this->definition != "") {
                foreach ($this->definition as $line) {
                    print "\xa    '$line'";
                }
            }
            print "</pre>";
        }

        if ($this->definition != "") {
            $seed = $this->vars->tostring();
            $graph_command = implode("\n", $this->definition);
            $this->url = $cache->insert($graph_command, '.txt', $seed);

            if (isset($symon['graph_debug'])) {
                print "<pre>\xa cmd = ". $graph_command ."</pre>";
                print "<pre>\xa url = ". $this->url ."</pre>";
            }
        }
    }

    /* fill template with variables */
    function _preprocess() {
        $definition = '';
        if ($this->template === null || !is_array($this->template)) {
            return;
        }
        reset($this->template);

        foreach ($this->template as $t) {
            $startpos = strpos((string) $t, "%", 0);
            $endpos = $startpos + 1 > strlen((string) $t) ? false : strpos((string) $t, "%", $startpos + 1);
            while ($startpos !== false && $endpos !== false) {
                $leader = substr((string) $t, 0, $startpos);
                $key = substr((string) $t, $startpos + 1, $endpos - $startpos - 1);
                $tailer = substr((string) $t, $endpos + 1);

                $definition .= $leader;

                if ($this->vars->defp($key)) {
                    $definition .= $this->vars->get($key);
                } else {
                    $definition .= "%" . $key;
                    $tailer = "%" . $tailer;
                }

                $t = $tailer;

                $startpos=strpos($t, "%", 0);
                $endpos = $startpos + 1 > strlen($t) ? false : strpos($t, "%", $startpos + 1);
            }

            $definition .= $t . "\n";
        }

        $this->definition = preg_split("/\n/", $definition);
    }

    function _justify() {
        reset($this->definition);

        /* determine max length of legend items */
        $oplen = 0;
        foreach ($this->definition as $t) {
            if (preg_match("/^ *(AREA|LINE.|STACK):([^:]+):([^:]+)(.*)$/", (string) $t, $match)) {
                $oplen = max($oplen, strlen($match[3]));
            }
        }

        $justified = '';
        reset($this->definition);

        /* justify legend items */
        foreach ($this->definition as $t) {
            if (preg_match("/^ *(AREA|LINE.|STACK):([^:]+):([^:]+)(.*)$/", (string) $t, $match)) {
                $t = $match[1].':'.$match[2].':'.'% -'.$oplen.'s'.$match[4];
                $t = sprintf($t, $match[3]);
            } elseif (preg_match("/^ *(COMMENT):(min *[^ ]+.*)$/", (string) $t, $match)) {
                $t = $match[1].':'.'% -'.$oplen.'s%s';
                $t = sprintf($t, ' ', '       ' . $match[2]);
            }
            $justified .= $t . "\n";
        }

        $this->definition = preg_split("/\n/", $justified);
    }

    /* constrain arguments */
    function _constrain() {
        global $symon;

        $definition = '';
        if (!is_array($this->definition)) {
            return 1;
        }

        reset($this->definition);
        foreach ($this->definition as $t) {
            if (preg_match("/^([^-]*)(-[^ ]+) ([^ ]+)(.*)$/", (string) $t)) {
                while (preg_match("/^([^-]*)(-[^ ]+) ([^ ]+)(.*)$/", (string) $t, $match)) {
                    $definition .= $match[1];
                    foreach ($symon['constraints'] as $k => $v) {
                        if ($k == $match[2]) {
                            if (is_array($symon['constraints'][$k])
                                && isset($symon['constraints'][$k]["max"])
                                && ($match[3] > $symon['constraints'][$k]["max"])) {
                                $match[3] = $symon['constraints'][$k]["max"];
                            }
                            if (is_array($symon['constraints'][$k])
                                && isset($symon['constraints'][$k]["min"])
                                && ($match[3] < $symon['constraints'][$k]["min"])) {
                                $match[3] = $symon['constraints'][$k]["min"];
                            }
                        }
                    }
                    $definition .= $match[2].' '.$match[3];
                    $t = $match[4];
                }
            }
            $definition .= $t . "\n";
        }
        $this->definition = preg_split("/\n/", $definition);
    }

    function _display() {
        print "\xa graph ";
        if ($this->template !== null) {
            print "\xa  template=";
            foreach ($this->template as $line) {
                print "\xa    '$line'";
            }
        }
        if ($this->vars !== null) {
            $vars = $this->vars->tostring();
            if (strlen((string) $vars) > 0) {
                print $vars;
            }
            print ";";
        } elseif ($this->graph_vars !== null) {
            $vars = $this->graph_vars->tostring();
            if (strlen((string) $vars) > 0) {
                print $vars;
            }
            print ";";
        }
    }
}
?>
