package Munin::Master::HTMLOld;

=begin comment
-*- perl -*-

This is Munin::Master::HTMLOld, a minimal package shell to make
munin-html modular (so it can loaded persistently in
munin-fastcgi-graph for example) without making it object oriented
yet.  The non-"old" module will feature propper object orientation
like munin-update and will have to wait until later.


Copyright (C) 2002-2009 Jimmy Olsen, Audun Ytterdal, Kjell Magne
Øierud, Nicolai Langfeldt, Linpro AS, Redpill Linpro AS and others.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 dated June,
1991.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

$Id: HTMLOld.pm 4130 2011-01-20 15:51:37Z jo $


This is the hierarchy of templates

  * munin-overview.tmpl - Overview with all groups and hosts shown (2 levels down)
    
    * munin-domianview.tmpl - all members of one domain, showing links down to each single service
      and/or sub-group

      * munin-nodeview.tmpl - two (=day, week) graphs from all plugins on the node

	* munin-serviceview.tmpl - deepest level of view, shows all 4 graphs from one timeseries

        OR

    	* munin-nodeview.tmpl - multigraph sub-level.  When multigraph sublevels end ends
           the next is a munin-serviceview.

 * Comparison pages (x4) are at the service level.  Not sure how to work multigraph into them so
   avoid it all-together.

=end comment

=cut

use warnings;
use strict;

use Exporter;

our (@ISA, @EXPORT);
@ISA    = qw(Exporter);
@EXPORT = qw(html_startup html_main);

use HTML::Template;
use POSIX qw(strftime);
use Getopt::Long;
use Time::HiRes;

use Munin::Master::Logger;
use Munin::Master::Utils;
use Munin::Master::HTMLOld;

use Log::Log4perl qw( :easy );

my @times = ("day", "week", "month", "year");

my $DEBUG      = 0;
my $conffile   = "$Munin::Common::Defaults::MUNIN_CONFDIR/munin.conf";
my $do_usage   = 0;
my $do_version = 0;
my $stdout     = 0;
my $config;
my $limits;
my $htmltagline;
my %comparisontemplates;
my $tmpldir;
my $htmldir;

my $do_dump = 0;
my $do_fork = 1;
my $max_running=6;
my $running=0;


sub html_startup {

    my ($args) = @_;
    local @ARGV = @{$args};
    $do_usage = 1
	unless GetOptions (
	    "host=s"    => [],
	    "service=s" => [],
	    "config=s"  => \$conffile,
	    "debug!"    => \$DEBUG,
	    "stdout!"   => \$stdout,
	    "help"      => \$do_usage,
	    "version!"  => \$do_version,
	    "dump!"     => \$do_dump,
	    "fork!"     => \$do_fork,
        );

    print_usage_and_exit() if $do_usage;
    print_version_and_exit() if $do_version;

    exit_if_run_by_super_user();

    $config = munin_config($conffile, $config);
    $limits = munin_readconfig($config->{dbdir} . "/limits", 1, 1);

    logger_open($config->{'logdir'});
    logger_debug() if $DEBUG;

    $tmpldir = $config->{tmpldir};
    $htmldir = $config->{htmldir};

    $max_running = &munin_get($config, "max_html_jobs", $max_running);

    %comparisontemplates = instanciate_comparison_templates($tmpldir);

    copy_web_resources($tmpldir, $htmldir);

    if (!defined $config->{'cgiurl_graph'}) {
        if (defined $config->{'cgiurl'}) {
            $config->{'cgiurl_graph'}
                = $config->{'cgiurl'} . "/munin-cgi-graph";
        }
        else {
            $config->{'cgiurl_graph'} = "/cgi-bin/munin-cgi-graph";
        }
	INFO "[INFO] Determined that cgiurl_graph is ".$config->{'cgiurl_graph'} if
	    munin_get($config,"graph_strategy","cron") eq 'cgi';
    }
}


sub html_main {

    my $update_time = Time::HiRes::time;

    my $lockfile = "$config->{rundir}/munin-html.lock";

    INFO "[INFO] Starting munin-html, getting lock $lockfile";

    munin_runlock($lockfile);

    # For timestamping graphs
    my $timestamp = strftime("%Y-%m-%d %T%z (%Z)", localtime);
    if ($timestamp =~ /%z/) {
	# %z (numeric timezone offset) may not be available, but %Z
	# (timeszone name) seems to be universaly supported though the
	# timezone names are not really standardized.
	$timestamp = strftime("%Y-%m-%d %T%Z", localtime);
    }
    $htmltagline = "This page was generated by <a href='http://munin-monitoring.org/'>Munin</a> version ".$Munin::Common::Defaults::MUNIN_VERSION." at $timestamp";
    # Preparing the group tree...
    my $groups = get_group_tree($config);

    if (!defined($groups) or scalar(%{$groups} eq '0')) {
	LOGCROAK "[FATAL] There is nothing to do here, since there are no nodes with any plugins.  Please refer to http://munin-monitoring.org/wiki/FAQ_no_graphs";
    };
	
    if (defined $groups->{"name"} and $groups->{"name"} eq "#%#root") {
        $groups = $groups->{"groups"};    # root->groups
    }

    if ($do_dump) {
	use Data::Dumper;

	$Data::Dumper::Sortkeys = sub { my $a=shift; my @b = grep (!/#%#parent/, keys %$a); \@b; };
	print Dumper $groups;
	exit 0;
    }

    generate_group_templates($groups);

    emit_main_index($groups,$timestamp);

    INFO "[INFO] Releasing lock file $lockfile";

    munin_removelock("$lockfile");

    $update_time = sprintf("%.2f", (Time::HiRes::time - $update_time));

    INFO "[INFO] munin-html finished ($update_time sec)";
}

sub find_complinks{
    my($type) = @_;
    
    my @links = ();
    
    foreach my $current (qw(day week month year)) {
        my $data = {};

        if ($type eq $current) {
            $data->{'LINK'} = undef;
        } 
        else {
            $data->{'LINK'} = "comparison-$current.html";            
        }

        $data->{'NAME'} = $current;
        push(@links, $data);
    }

    return \@links;

}


sub emit_comparison_template {
    my ($key, $t) = @_;

    ( my $file = $key->{'filename'}) =~ s/index.html$//;

    $file .= "comparison-$t.html";

    DEBUG "[DEBUG] Creating comparison page $file";

    $comparisontemplates{$t}->param(
                                    INFO_OPTION => 'Groups on this level',
                                    NAME        => $key->{'name'},
                                    GROUPS      => $key->{'comparegroups'},
                                    PATH        => $key->{'path'},
                                    CSS_NAME    => $key->{'css_name'},
                                    ROOT_PATH   => $key->{'root_path'},
                                    COMPLINKS   => find_complinks($t),
                                    LARGESET    => decide_largeset($key->{'peers'}), 
                                    PEERS       => $key->{'peers'},
                                    PARENT      => $key->{'path'}->[-2]->{'name'},
                                    CATEGORIES  => $key->{'comparecategories'},
                                    NCATEGORIES => $key->{'ncomparecategories'},
                                    TAGLINE     => $htmltagline,
    );

    open(my $FILE, '>', $file)
        or die "Cannot open $file for writing: $!";
    print $FILE $comparisontemplates{$t}->output;
    close $FILE;
}


sub emit_graph_template {
    my ($key) = @_;

    my $graphtemplate = HTML::Template->new(
	filename => "$tmpldir/munin-nodeview.tmpl",
	die_on_bad_params => 0,
	loop_context_vars => 1,
	filter            => sub {
	    my $ref = shift;
	    $$ref =~ s/URLX/URL$key->{'depth'}/g;
	});

    DEBUG "[DEBUG] Creating graph(nodeview) page ".$key->{filename};

    $graphtemplate->param(
                          INFO_OPTION => 'Nodes on this level',
                          GROUPS      => $key->{'groups'},
                          PATH        => $key->{'path'},
                          CSS_NAME    => $key->{'css_name'},
                          ROOT_PATH   => $key->{'root_path'},
                          PEERS       => $key->{'peers'},
                          LARGESET    => decide_largeset($key->{'peers'}), 
                          PARENT      => $key->{'path'}->[-2]->{'name'},
                          NAME        => $key->{'name'},
                          CATEGORIES  => $key->{'categories'},
                          NCATEGORIES => $key->{'ncategories'},
                          TAGLINE     => $htmltagline,
                         );

    my $filename = $key->{'filename'};
    open(my $FILE, '>', $filename)
	or die "Cannot open $filename for writing: $!";
    print $FILE $graphtemplate->output;
    close $FILE;
}


sub emit_group_template {
    my ($key) = @_;

    my $grouptemplate = HTML::Template->new(
	filename => "$tmpldir/munin-domainview.tmpl",
	die_on_bad_params => 0,
	loop_context_vars => 1,
	filter            => sub {
	    my $ref = shift;
	    $$ref =~ s/URLX/URL$key->{'depth'}/g;
	});

    DEBUG "[DEBUG] Creating group page ".$key->{filename};

    $grouptemplate->param(
                          INFO_OPTION => 'Groups on this level',
                          GROUPS    => $key->{'groups'},
                          PATH      => $key->{'path'},
                          ROOT_PATH => $key->{'root_path'},
                          CSS_NAME  => $key->{'css_name'},
                          PEERS     => $key->{'peers'},
                          LARGESET  => decide_largeset($key->{'peers'}), 
                          PARENT    => $key->{'path'}->[-2]->{'name'} || "Overview",
                          COMPARE   => $key->{'compare'},
                          TAGLINE   => $htmltagline,
	);

    my $filename = $key->{'filename'};
    open(my $FILE, '>', $filename)
	or die "Cannot open $filename for writing: $!";
    print $FILE $grouptemplate->output;
    close $FILE or die "Cannot close $filename after writing: $!";
}


sub emit_service_template {
    my ($srv, $pathnodes, $peers, $css_name, $root_path, $service) = @_;

    my $servicetemplate = HTML::Template->new(
        filename          => "$tmpldir/munin-serviceview.tmpl",
        die_on_bad_params => 0,
        loop_context_vars => 1
    );

    #remove underscores from peers and title (last path element)
    if ($peers){
        $peers = [ map { $_->{'name'} =~ s/_/ /g; $_;} @$peers ];
    }
    
    $pathnodes->[scalar(@$pathnodes) - 1]->{'name'} =~ s/_/ /g;

    $servicetemplate->param(
                            INFO_OPTION => 'Graphs in same category',
                            SERVICES  => [$srv],
                            PATH      => $pathnodes,
                            PEERS     => $peers,
                            LARGESET  => decide_largeset($peers), 
                            ROOT_PATH => $root_path,
                            CSS_NAME  => $css_name,
                            CATEGORY  => ucfirst $srv->{'category'},
                            TAGLINE   => $htmltagline,
                           );

    # No stored filename for this kind of html node.
    my $filename = munin_get_html_filename($service);

    my $dirname  = $filename;
    $dirname =~ s/\/[^\/]*$//;

    DEBUG "[DEBUG] Creating service page $filename";
    munin_mkdir_p($dirname, oct(755));

    open(my $FILE, '>', $filename)
        or die "Cannot open '$filename' for writing: $!";
    print $FILE $servicetemplate->output;
    close $FILE or die "Cannot close '$filename' after writing: $!";
}

sub decide_largeset {

    my ($peers) = @_;
    return scalar(@$peers) > $config->{'dropdownlimit'} ? 1 : 0;

}

sub emit_main_index {
    # Draw main index

    my ($groups, $timestamp) = @_;

    my $template = HTML::Template->new(
        filename          => "$tmpldir/munin-overview.tmpl",
        die_on_bad_params => 0,
        loop_context_vars => 1
    );

    # FIX: this sometimes bugs:

    # HTML::Template::param() : attempt to set parameter 'groups' with
    # a scalar - parameter is not a TMPL_VAR! at
    # /usr/local/share/perl/5.10.0/Munin/Master/HTMLOld.pm line 140

    $template->param(
                     TAGLINE   => $htmltagline,
                     GROUPS    => $groups,
                     CSS_NAME  => get_css_name(),
    );

    my $filename = munin_get_html_filename($config);

    DEBUG "[DEBUG] Creating main index $filename";

    open(my $FILE, '>', $filename)
        or die "Cannot open $filename for writing: $!";
    print $FILE $template->output;
    close $FILE;
}


sub copy_web_resources {
    my ($tmpldir, $htmldir) = @_;

    # Make sure the logo and the stylesheet file is in the html dir
    # NOTE: The templates have hardcoded path to definitions.html, and it is not right, esp. when
    # we have nested groups and nested services.
    my @files = ("style.css", "logo.png", "logo-h.png", "definitions.html", "favicon.ico");

    foreach my $file ((@files)) {
        if (   (!-e "$htmldir/$file")
            or (-e "$tmpldir/$file")
            and ((stat("$tmpldir/$file"))[9] > (stat("$htmldir/$file"))[9])) {
            unless (system("cp", "$tmpldir/$file", "$htmldir/")) {
                INFO "[INFO] copied $file into " . $htmldir;
            }
            else {
                ERROR "[ERROR] Could not copy $file from $tmpldir to $htmldir";
                die "[ERROR] Could not copy $file from $tmpldir to $htmldir\n";
            }
        }
    }
}

sub instanciate_comparison_templates {
    my ($tmpldir) = @_;

    return (
        day => HTML::Template->new(
            filename          => "$tmpldir/munin-comparison-day.tmpl",
            die_on_bad_params => 0,
            loop_context_vars => 1
        ),
        week => HTML::Template->new(
            filename          => "$tmpldir/munin-comparison-week.tmpl",
            die_on_bad_params => 0,
            loop_context_vars => 1
        ),
        month => HTML::Template->new(
            filename          => "$tmpldir/munin-comparison-month.tmpl",
            die_on_bad_params => 0,
            loop_context_vars => 1
        ),
        year => HTML::Template->new(
            filename          => "$tmpldir/munin-comparison-year.tmpl",
            die_on_bad_params => 0,
            loop_context_vars => 1
        ));
}


sub get_png_size {
    my $filename = shift;
    my $width    = undef;
    my $height   = undef;

    if (open(my $PNG, '<', $filename)) {
        my $incoming;
        binmode($PNG);
        if (read($PNG, $incoming, 4)) {
            if ($incoming =~ /PNG$/) {
                if (read($PNG, $incoming, 12)) {
                    if (read($PNG, $incoming, 4)) {
                        $width = unpack("N", $incoming);
                        read($PNG, $incoming, 4);
                        $height = unpack("N", $incoming);
                    }
                }
            }
        }
        close($PNG);
    }

    return ($width, $height);
}


sub get_peer_nodes {
    my $hash      = shift || return;
    my $category  = shift;
    my $ret       = [];
    my $link      = "index.html";
    my $parent    = munin_get_parent($hash) || return;
    my $me        = munin_get_node_name($hash);
    my $pchildren = munin_get_children($parent);

    foreach my $peer (sort {munin_get_node_name($b) cmp munin_get_node_name($a)}
        @$pchildren) {
        next unless defined $peer and ref($peer) eq "HASH";
        next
            if defined $category
                and lc(munin_get($peer, "graph_category", "other")) ne
                $category;
        next
            if (!defined $peer->{'graph_title'}
            and (!defined $peer->{'#%#visible'} or !$peer->{'#%#visible'}));
        next
            if (defined $peer->{'graph_title'}
            and !munin_get_bool($peer, "graph", 1));
        my $peername = munin_get_node_name($peer);
        next
            if $peername eq "contact"
                and munin_get_node_name($parent) eq "#%#root";
        if ($peername eq $me) {
            unshift @$ret, {"name" => $peername, "link" => undef};
        }
        else {
            # Handle different directory levels between subgraphs and regular graphs
	    if (munin_has_subservices ($hash)) {
		if (munin_has_subservices ($peer)) {
		    # I've got subgraphs, peer's got subgraphs
		    unshift @$ret,
			{"name" => $peername, "link" => "../$peername/index.html"};
		} else { 
		    # I've got subgraphs, peer's a regular graph
		    unshift @$ret,
			{"name" => $peername, "link" => "../$peername.html"};
		} 
	    } elsif (munin_has_subservices ($peer)) {
		# I'm a regular graph, peer's got subgraphs
		unshift @$ret,
		    {"name" => $peername, "link" => "$peername/index.html"};
	    } else {
		if (defined $peer->{'graph_title'}) {
		    # Both me and peer are regular graphs
		    unshift @$ret,
			{"name" => $peername, "link" => "$peername.html"};
		}
		else {
		    # We're not on the graph level -- handle group peering
		    unshift @$ret,
			{"name" => $peername, "link" => "../$peername/index.html"};
		}
	    }
        }
    }
    return $ret;
}


sub get_group_tree {
    my $hash    = shift;
    my $base    = shift || "";

    my $graphs  = [];     # Pushy array of graphs, [ { name => 'cpu'}, ...]
    my $groups  = [];     # Slices of the $config hash
    my $cattrav = {};     # Categories, array of strings
    my $cats    = [];     # Array of graph information ('categories')
    my $path    = [];     # (temporary) array of paths relevant to . (here)
    my $rpath   = undef;
    my $visible = 0;
    my $css_name;

    my $children = munin_get_sorted_children($hash);

    foreach my $child (@$children) {
        next unless defined $child and ref($child) eq "HASH" and keys %$child;

	$child->{"#%#ParentsNameAsString"} = munin_get_node_name($hash);

        if (defined $child->{"graph_title"}
            and munin_get_bool($child, "graph", 1)) {

	    $child->{'#%#is_service'} = 1;

            my $childname = munin_get_node_name($child);
            my $childnode = generate_service_templates($child);

            push @$graphs, {"name" => $childname};
            $childnode->{'name'} = $child->{"graph_title"};

	    # Make sure the link gets right even if the service has subservices
	    if (munin_has_subservices ($child)) {
		$childnode->{'url'}  = $base . $childname . "/index.html";
	    } else {
		$childnode->{'url'}  = $base . $childname . ".html";
	    }

            for (my $shrinkpath = $childnode->{'url'}, my $counter = 0;
		 $shrinkpath;
		 $shrinkpath =~ s/^[^\/]+\/?//, $counter++)
	    {
                $childnode->{'url' . $counter} = $shrinkpath;
            }

            push @{$cattrav->{lc munin_get($child, "graph_category", "other")}},
                $childnode;

	    # IFF this is a multigraph plugin there may be sub-graphs.
	    push( @$groups,
		  grep {defined $_}
		       get_group_tree($child,
				      $base.munin_get_node_name($child)."/"));

            $visible = 1;
	}
        elsif (ref($child) eq "HASH" and !defined $child->{"graph_title"}) {

            push( @$groups,
		  grep {defined $_}
		       get_group_tree($child,
				      $base.munin_get_node_name($child) . "/"));

	    if (scalar @$groups) {
		$visible = 1;
	    }
	}
    }

    return unless $visible;

    $hash->{'#%#visible'} = 1;

    # We need the categories in another format.
    foreach my $cat (sort keys %$cattrav) {
        my $obj = {};
        $obj->{'name'}     = $cat;
        $obj->{'url'}      = $base . "index.html#" . $cat;
        $obj->{'services'} = [sort {lc($a->{'name'}) cmp lc($b->{'name'})}
                @{$cattrav->{$cat}}];
        $obj->{'state_' . lc munin_category_status($hash, $limits, $cat, 1)}
            = 1;
        for (
            my $shrinkpath = $obj->{'url'}, my $counter = 0;
            $shrinkpath =~ /\//;
            $shrinkpath =~ s/^[^\/]+\/*//, $counter++
            ) {
            $obj->{'url' . $counter} = $shrinkpath;
        }
        push @$cats, $obj;
    }

    # ...and we need a couple of paths available.
    @$path = reverse map {
        {
            "name" => $_,
            "path" => (
                defined $rpath
                ? ($rpath .= "../") . "index.html"
                : ($rpath = ""))}
    } reverse(undef, split('\/', $base));

    my $root_path = get_root_path($path);
    $css_name = get_css_name();

    # We need a bit more info for the comparison templates
    my $compare         = munin_get_bool($hash, "compare", 1);
    my $comparecats     = [];
    my $comparecatshash = {};
    my $comparegroups   = [];

    if ($compare) {
        foreach my $tmpgroup (@$groups) {

            # First we gather a bit of data into comparecatshash...
            if ($tmpgroup->{'ngraphs'} > 0) {
                push @$comparegroups, $tmpgroup;
                foreach my $tmpcat (@{$tmpgroup->{'categories'}}) {
                    $comparecatshash->{$tmpcat->{'name'}}->{'groupname'}
                        = $tmpcat->{'name'};
                    foreach my $tmpserv (@{$tmpcat->{'services'}}) {
                        $comparecatshash->{$tmpcat->{'name'}}->{'services'}
                            ->{$tmpserv->{'name'}}->{'nodes'}
                            ->{$tmpgroup->{'name'}} = $tmpserv;
                        $comparecatshash->{$tmpcat->{'name'}}->{'services'}
                            ->{$tmpserv->{'name'}}->{'nodes'}
                            ->{$tmpgroup->{'name'}}->{'nodename'}
                            = $tmpgroup->{'name'};
                    }
                }
            }
        }
        if (scalar @$comparegroups > 1) {

            # ...then we restructure it, comparecats need to end up looking like: ->[i]->{'service'}->[i]->{'nodes'}->[i]->{*}
            $compare = 1;
            foreach my $tmpcat (sort keys %$comparecatshash) {
                foreach my $tmpserv (
                    sort keys %{$comparecatshash->{$tmpcat}->{'services'}}) {
                    my @nodelist = map {
                        $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}
                            ->{'nodes'}->{$_}
                        }
                        sort keys
                        %{$comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}
                            ->{'nodes'}};
                    delete $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}
                        ->{'nodes'};
                    $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}
                        ->{'nodes'} = \@nodelist;
                }
                my @servlist
                    = map {$comparecatshash->{$tmpcat}->{'services'}->{$_}}
                    sort keys %{$comparecatshash->{$tmpcat}->{'services'}};
                delete $comparecatshash->{$tmpcat}->{'services'};
                $comparecatshash->{$tmpcat}->{'services'} = \@servlist;
            }
            @$comparecats
                = map {$comparecatshash->{$_}} sort keys %$comparecatshash;
        }
        else {
            $compare = 0;
        }
    }


    my $ret = {
        "name"     => munin_get_node_name($hash),
        "hashnode" => $hash,
        "url"      => $base . "index.html",
        "path"     => $path,
        "depth" => scalar(my @splitted_base = split("/", $base . "index.html"))
            - 1,
        "filename"           => munin_get_html_filename($hash),
        "css_name"           => $css_name,
        "root_path"          => $root_path,
        "groups"             => $groups,
        "graphs"             => $graphs,
        "multigraph"         => munin_has_subservices ($hash),
        "categories"         => $cats,
        "ngroups"            => scalar(@$groups),
        "ngraphs"            => scalar(@$graphs),
        "ncategories"        => scalar(@$cats),
        "compare"            => $compare,
        "comparegroups"      => $comparegroups,
        "ncomparegroups"     => scalar(@$comparegroups),
        "comparecategories"  => $comparecats,
        "ncomparecategories" => scalar(@$comparecats),
    };

    if ($ret->{'url'} ne "/index.html") {
        for (
            my $shrinkpath = $ret->{'url'}, my $counter = 0;
            $shrinkpath =~ /\//;
            $shrinkpath =~ s/^[^\/]+\/*//, $counter++
            ) {
            $ret->{'url' . $counter} = $shrinkpath;
        }
    }

    return $ret;
}


=pod

Method to find the relative url to the root of the munin tree. This is 
needed to refer to stuff in the root from deeper in the levels. 

returns the url path as a string.

=cut

sub get_root_path{
    my ($path) = @_;
    if ($path) {
        (my $root = $path->[0]->{'path'}) =~ s/\/index.html$//;
        return $root;
    }
    return "";
}


sub get_css_name{
    #NOTE: this will do more in future versions. knuthaug 2009-11-15
    return "style.css";
}


# This is called both at group level, service level, and subservice level
sub munin_get_sorted_children {
    my $hash        = shift || return;

    my $children    = munin_get_children($hash);
    my $group_order;
    my $ret         = [];

    if (defined $hash->{'group_order'}) {
	$group_order = $hash->{'group_order'};
    } elsif (defined $hash->{'domain_order'}) {
	$group_order = $hash->{'domain_order'};
    } elsif (defined $hash->{'node_order'}) {
	$group_order = $hash->{'node_order'};
    } else {
    	$group_order = "";
    } 

    my %children = map {munin_get_node_name($_) => $_} @$children;

    foreach my $group (split /\s+/, $group_order) {
        if (defined $children{$group}) {
            push @$ret, $children{$group};
            delete $children{$group};
        }
        elsif ($group =~ /^(.+)=([^=]+)$/) {

            # "Borrow" the graph from another group
            my $groupname = $1;
            my $path      = $2;
            my $borrowed  = munin_get_node_partialpath($hash, $path);
            if (defined $borrowed) {
                munin_copy_node_toloc($borrowed, $hash, [$groupname]);
                $hash->{$groupname}->{'#%#origin'} = $borrowed;
            }
            push @$ret, $hash->{$groupname};
        }
    }

    foreach my $group (sort {$a cmp $b} keys %children) {
        push @$ret, $children{$group};
    }

    return $ret;
}


sub fork_and_work {
    my ($work) = @_;

    if (!$do_fork) {

        # We're not forking.  Do work and return.
        DEBUG "[DEBUG] Doing work synchrnonously";
        &$work;
        return;
    }

    # Make sure we don't fork too much
    while ($running >= $max_running) {
        DEBUG
            "[DEBUG] Too many forks ($running/$max_running), wait for something to get done";
        look_for_child("block");
        --$running;
    }

    my $pid = fork();

    if (!defined $pid) {
        ERROR "[ERROR] fork failed: $!";
        die "fork failed: $!";
    }

    if ($pid == 0) {

        # This block does the real work.  Since we're forking exit
        # afterwards.

        &$work;

        # See?!

        exit 0;

    }
    else {
        ++$running;
        DEBUG "[DEBUG] Forked: $pid. Now running $running/$max_running";
        while ($running and look_for_child()) {
            --$running;
        }
    }
}


sub generate_group_templates {
    my $arr = shift || return;
    return unless ref($arr) eq "ARRAY";

    foreach my $key (@$arr) {
        if (defined $key and ref($key) eq "HASH") {
            $key->{'peers'} = get_peer_nodes($key->{'hashnode'}, lc munin_get($key->{'hashnode'}, "graph_category", "other"));

	    # This was only kept there for getting the peers
            delete $key->{'hashnode'}; 
            
            if (defined $key->{'ngroups'} and $key->{'ngroups'}) {
                # WTF: $key->{'groups'} = $key->{'groups'};
                fork_and_work(sub {generate_group_templates($key->{'groups'})});

                emit_group_template($key);
                
                if ($key->{'compare'}) { # Create comparison templates as well 
                    foreach my $t (@times) {
                        emit_comparison_template($key,$t);
                    }
                }
            }

            if (defined $key->{'ngraphs'} and $key->{'ngraphs'}) {
                emit_graph_template($key);
            }
        }
    }
}


sub borrowed_path {
    # I wish I knew what this function does.  It appears to make
    # .. path elements to climb up the directory hierarchy.  To
    # "borrow" something from a different directory level.

    my $hash     = shift;
    my $prepath  = shift || "";
    my $postpath = shift || "";

    return unless defined $hash and ref($hash) eq "HASH";

    if (defined $hash->{'#%#origin'}) {
        return
	    $prepath . "../"
            . munin_get_node_name($hash->{'#%#origin'}) . "/"
            . $postpath;
    }
    else {
        if (defined $hash->{'#%#parent'}) {
            if (defined $hash->{'graph_title'}) {
                return borrowed_path($hash->{'#%#parent'}, $prepath . "../",
                    $postpath);
            }
            else {
                return borrowed_path(
                    $hash->{'#%#parent'},
                    $prepath . "../",
                    munin_get_node_name($hash) . "/" . $postpath
                );
            }
        }
        else {
            return;
        }
    }
}


sub generate_service_templates {

    my $service = shift || return;

    return unless munin_get_bool($service, "graph", 1);

    my %srv;
    my $fieldnum = 0;
    my @graph_info;
    my @field_info;
    my @loc       = @{munin_get_node_loc($service)};
    my $pathnodes = get_path_nodes($service);
    my $peers     = get_peer_nodes($service,
        lc munin_get($service, "graph_category", "other"));
    my $parent = munin_get_parent_name($service);

    
    my $root_path = get_root_path($pathnodes);
    my $css_name = get_css_name();
    my $bp = borrowed_path($service) || ".";

    $srv{'node'} = munin_get_node_name($service);
    DEBUG "[DEBUG] processing service: $srv{node}";
    $srv{'service'}  = $service;
    $srv{'label'}    = munin_get($service, "graph_title");
    $srv{'category'} = lc(munin_get($service, "graph_category", "other"));

    my $method = munin_get($service, "graph_strategy", "cron");

    $srv{'url'} = "$srv{node}.html";

    my $path = join('/', @loc);

    if ($method eq "cgi") {
        $srv{'imgday'}   = $config->{'cgiurl_graph'} . "/$path-day.png";
        $srv{'imgweek'}  = $config->{'cgiurl_graph'} . "/$path-week.png";
        $srv{'imgmonth'} = $config->{'cgiurl_graph'} . "/$path-month.png";
        $srv{'imgyear'}  = $config->{'cgiurl_graph'} . "/$path-year.png";

        if (munin_get_bool($service, "graph_sums", 0)) {
            $srv{'imgweeksum'}
                = $config->{'cgiurl_graph'} . "/$path-week-sum.png";
            $srv{'imgyearsum'}
                = $config->{'cgiurl_graph'} . "/$path-year-sum.png";
        }
    }
    else {

        # graph strategy cron

        # Image locations for regular pages
        $srv{'imgday'}   = "$bp/$srv{node}-day.png";
        $srv{'imgweek'}  = "$bp/$srv{node}-week.png";
        $srv{'imgmonth'} = "$bp/$srv{node}-month.png";
        $srv{'imgyear'}  = "$bp/$srv{node}-year.png";

        # Image locations for comparison pages
        $srv{'cimgday'}   = "$bp/$parent/$srv{node}-day.png";
        $srv{'cimgweek'}  = "$bp/$parent/$srv{node}-week.png";
        $srv{'cimgmonth'} = "$bp/$parent/$srv{node}-month.png";
        $srv{'cimgyear'}  = "$bp/$parent/$srv{node}-year.png";
    }

    for my $scale (@times) {
        if (my ($w, $h)
            = get_png_size(munin_get_picture_filename($service, $scale))) {
            $srv{"img" . $scale . "width"}  = $w;
            $srv{"img" . $scale . "height"} = $h;
        }
    }

    if (munin_get_bool($service, "graph_sums", 0)) {
        $srv{imgweeksum} = "$srv{node}-week-sum.png";
        $srv{imgyearsum} = "$srv{node}-year-sum.png";
        for my $scale (["week", "year"]) {
            if (my ($w, $h)
                = get_png_size(munin_get_picture_filename($service, $scale, 1)))
            {
                $srv{"img" . $scale . "sumwidth"}  = $w;
                $srv{"img" . $scale . "sumheight"} = $h;
            }
        }
    }

    # Do "help" section
    if (my $info = munin_get($service, "graph_info")) {
        my %graph_info;
        $graph_info{info} = $info;
        push @{$srv{graphinfo}}, \%graph_info;
    }

    $srv{fieldlist}
        .= "<tr><th align='left' valign='top'>Field</th><th align='left' valign='top'>Type</th><th align='left' valign='top'>Warn</th><th align='left' valign='top'>Crit</th><th></tr>";
    foreach my $f (@{munin_get_field_order($service)}) {
        $f =~ s/=(.*)$//;
        my $path = $1;
        next if (!defined $service->{$f});
        my $fieldobj = $service->{$f};
        next if (ref($fieldobj) ne "HASH" or !defined $fieldobj->{'label'});
        next if (!munin_draw_field($fieldobj));

        #DEBUG "DEBUG: single_value: Checking field \"$f\" ($path).";

        if (defined $path) {

            # This call is to make sure field settings are copied
            # for aliases, .stack, et al. Todo: put that part of
            # munin_get_rrd_filename into its own functino.
            munin_get_rrd_filename($f, $path);
        }

        my %field_info;
        $fieldnum++;

        $field_info{'hr'}    = 1 unless ($fieldnum % 3);
        $field_info{'field'} = $f;
        $field_info{'label'} = munin_get($fieldobj, "label", $f);
        $field_info{'type'}  = lc(munin_get($fieldobj, "type", "GAUGE"));
        $field_info{'warn'}  = munin_get($fieldobj, "warning");
        $field_info{'crit'}  = munin_get($fieldobj, "critical");
        $field_info{'info'}  = munin_get($fieldobj, "info");
        $field_info{'extinfo'} = munin_get($fieldobj, "extinfo");

        my $state = munin_field_status($fieldobj, $limits, 1);

        if (defined $state) {
            $field_info{'state_warning'}  = 1 if $state eq "warning";
            $field_info{'state_critical'} = 1 if $state eq "critical";
            $field_info{'state_unknown'}  = 1 if $state eq "unknown";
        }
        push @{$srv{'fieldinfo'}}, \%field_info;
    }

    my $state = munin_service_status($service, $limits, 1);

    if (defined $state) {
        $srv{'state_warning'}  = 1 if $state eq "warning";
        $srv{'state_critical'} = 1 if $state eq "critical";
        $srv{'state_unknown'}  = 1 if $state eq "unknown";
    }

    emit_service_template(\%srv, $pathnodes, $peers, $css_name, $root_path, $service);

    return \%srv;
}


sub get_path_nodes {
    my $hash = shift || return;
    my $ret  = [];
    my $link = "index.html";

    unshift @$ret, {"name" => munin_get_node_name($hash), "path" => undef};
    while ($hash = munin_get_parent($hash)) {
        unshift @$ret, {"name" => munin_get_node_name($hash), "path" => $link};
        $link = "../" . $link;
    }

    $ret->[0]->{'name'} = undef;
    return $ret;
}


sub print_usage_and_exit {

    print "Usage: $0 [options]

Options:
    --help		View this message.
    --debug		View debug messages.
    --version		View version information.
    --nofork            Compatibility. No effect.
    --service <service>	Compatibility. No effect.
    --host <host>	Compatibility. No effect.
    --config <file>	Use <file> as configuration file. 
			[/etc/munin/munin.conf]

";
    exit 0;
}

1;


=head1 NAME

munin-html - A program to draw html-pages on an Munin installation

=head1 SYNOPSIS

munin-html [options]

=head1 OPTIONS

=over 5

=item B<< --service <service> >>

Compatibility. No effect.

=item B<< --host <host> >>

Compatibility. No effect.

=item B<< --nofork >>

Compatibility. No effect.

=item B<< --config <file> >>

Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin.conf]

=item B<< --help >>

View help message.

=item B<< --[no]debug >>

If set, view debug messages. [--nodebug]

=back

=head1 DESCRIPTION

Munin-html is a part of the package Munin, which is used in combination
with Munin's node.  Munin is a group of programs to gather data from
Munin's nodes, graph them, create html-pages, and optionally warn Nagios
about any off-limit values.

If munin.conf sets "graph_strategy cgi" then munin-html generates URLs
referencing the graph CGI instead of referencing pre-generated
graphs (made by munin-graph).

Munin-html creates the html pages.

=head1 FILES

	@@CONFDIR@@/munin.conf
	@@DBDIR@@/datafile
	@@LOGDIR@@/munin-html
	@@HTMLDIR@@/*
	@@STATEDIR@@/*

=head1 VERSION

This is munin-html version @@VERSION@@

=head1 AUTHORS

Knut Haugen, Audun Ytterdal and Jimmy Olsen.

=head1 BUGS

munin-html does, as of now, not check the syntax of the configuration file.

Please report other bugs in the bug tracker at L<http://munin.sf.net/>.

=head1 COPYRIGHT

Copyright (C) 2002-2009 Knut Haugen, Audun Ytterdal, and Jimmy Olsen /
Linpro AS.

This is free software; see the source for copying conditions. There is
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

This program is released under the GNU General Public License

=head1 SEE ALSO

For information on configuration options, please refer to the man page for
F<munin.conf>.

=cut

# vim:syntax=perl:ts=8
