#################################################################################
#  Copyright notice
#
#  (c) 2008-2012
#  Copyright: Dr. Olaf Droegehorn
#  o.droegehorn@dhs-computertechnik.de
#  www.dhs-computertechnik.de
#  All rights reserved
#
#  This script 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; either version 2 of the License, or
#  (at your option) any later version.
#
#  The GNU General Public License can be found at
#  http://www.gnu.org/copyleft/gpl.html.
#  A copy is found in the textfile GPL.txt and important notices to the license
#  from the author is found in LICENSE.txt distributed with these scripts.
#
#  This script 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.
#
#  This copyright notice MUST APPEAR in all copies of the script!
#################################################################################

package main;

use strict;
use warnings;

use IO::Socket;


###################
# Config
use vars qw($__ME);
my $FHEMRENDERERdir = "$attr{global}{modpath}/FHEM";    # 

use vars qw(%defs);
use vars qw(%attr);

# Nothing to config below
#########################

#########################
# Forward declaration

sub FHEMRENDERER_getAttr($$);
sub FHEMRENDERER_setAttr($$);
sub FHEMRENDERER_parseXmlList($);
sub FHEMRENDERER_render($);
sub FHEMRENDERER_fatal($);
sub FHEMRENDERER_pF($@);
sub FHEMRENDERER_pO(@);
#sub FHEMRENDERER_zoomLink($$$$);
sub FHEMRENDERER_calcWeblink($$);

#########################
# As we are _not_ multithreaded, it is safe to use global variables.
my %__icons;                    # List of icons
my $__iconsread;                # Timestamp of last icondir check
my %__rooms;                    # hash of all rooms
my %__devs;                     # hash of all devices ant their attributes
my %__types;                    # device types, for sorting
my $__room;                     # currently selected room
my $__detail;                   # durrently selected device for detail view
my $__title;                    # Page title
my $__cmdret;                   # Returned data by the fhem call
my $__scrolledweblinkcount;     # Number of scrolled weblinks
my %__pos;                      # scroll position
my $__RET;                      # Returned data (html)
my $__RETTYPE;                  # image/png or the like
my $__SF;                       # Short for submit form
my $__ti;                       # Tabindex for all input fields
my @__zoom;                     # "qday", "day","week","month","year"
my %__zoom;                     # the same as @__zoom
my $__wname;                    # instance name
my $__plotmode;					        # Current plotmode
my $__plotsize;                 # Size for a plot
my $__timeinterval;				      # Time-Intervall for Renderer
my $__data;                     # Filecontent from browser when editing a file
my $__svgloaded;                # Do not load the SVG twice
my $__lastxmllist;              # last time xmllist was parsed
my $FHEMRENDERER_tmpfile;       # TempDir & File for the rendered graphics
my $__mp;

#####################################
sub
FHEMRENDERER_Initialize($)
{
  my ($hash) = @_;

#  $hash->{ReadFn}  = "FHEMRENDERER_Read";
  $hash->{DefFn}   = "FHEMRENDERER_Define";
  $hash->{UndefFn} = "FHEMRENDERER_Undef";
  $hash->{AttrList}= "loglevel:0,1,2,3,4,5,6 plotmode:gnuplot,gnuplot-scroll plotsize refresh tmpfile status multiprocess";
  $hash->{SetFn}   = "FHEMRENDERER_Set";
  $hash->{GetFn}   = "FHEMRENDERER_Get";
}

#####################################
sub
FHEMRENDERER_Define($$)
{
  my ($hash, $def) = @_;
  my ($name, $type, $global) = split("[ \t]+", $def);
  return "Usage: define <name> FHEMRENDERER [global]"
        if($global && $global ne "global");

  $hash->{STATE} = "Initialized";
  Log(2, "FHEMRENDERER defined");
  
  ###############
  # Initialize internal structures
  my $n = 0;
  @__zoom = ("qday", "day","week","month","year");
  %__zoom = map { $_, $n++ } @__zoom;
  $__wname = $hash->{NAME};

  $__timeinterval = FHEMRENDERER_getAttr("refresh", "00:10:00");
  $__plotmode = FHEMRENDERER_getAttr("plotmode", "gnuplot");
  $__plotsize = FHEMRENDERER_getAttr("plotsize", "800,200");
  $__mp = FHEMRENDERER_getAttr("multiprocess", "off");
  $FHEMRENDERER_tmpfile = FHEMRENDERER_getAttr("tmpfile", "/tmp/");
  FHEMRENDERER_setAttr("status", "off");
  
  return undef;
}

#####################################
sub
FHEMRENDERER_Undef($$)
{
  my ($hash, $arg) = @_;
  return undef;
}


###################################
sub
FHEMRENDERER_Set($@)
{
  my ($hash, @a) = @_;
  my $ret = undef;
  my $na = int(@a);
  $__wname = $hash->{NAME};
  
  return "no set value specified" if($na < 2 || $na > 3);
  
#  if($__plotmode eq "SVG" && !$modules{SVG}{LOADED}) {
#    my $ret = CommandReload(undef, "98_SVG");
#    Log 0, $ret if($ret);
#  }

  if($a[1] eq "on") {
	  $__timeinterval = FHEMRENDERER_getAttr("refresh", "00:10:00");
	  CommandDefine(undef, $hash->{NAME} . "_trigger at +*$__timeinterval get $a[0]");
	  FHEMRENDERER_setAttr("status", "on");
	} elsif($a[1] eq "off") {
		CommandDelete(undef, $__wname . "_trigger");
		FHEMRENDERER_setAttr("status", "off");
	}
  
  return $ret;
}


#####################################
sub
FHEMRENDERER_Get($@)
{
  my ($hash, @a) = @_;

  my $ret = undef;
  my $v;
  my $t;
  my $pid;
  
	FHEMRENDERER_parseXmlList(0);

  $__wname = "";
  $__wname = $hash->{NAME};
  
  $__plotmode = FHEMRENDERER_getAttr("plotmode", "gnuplot");
  $__plotsize = FHEMRENDERER_getAttr("plotsize", "800,200");
  $FHEMRENDERER_tmpfile = FHEMRENDERER_getAttr("tmpfile", "/tmp/");
  $__mp = FHEMRENDERER_getAttr("multiprocess", "off");
  
   if (@a <= 2) {
  	if (@a == 2) {
  		my ($p,$v) = split("=",$a[1], 2);

     	# Multiline: escape the NL for fhem
     	$v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data");
   	  Log(2, "P: $p, V: $v");

   	  if($p eq "pos") {
       	%__pos =  split(/[=&]/, $v);
      }
    }

	  if ($__mp ne "off") {
	  	$pid = fork();
	  	if (not defined $pid) {
	  		return ("ERROR: No MultiProcessing possible");
	  	}
	  	if ($pid > 0) {
	  		return $ret;
	  	}
  	}

    foreach my $type (sort keys %__types) {
	  	if($type eq "weblink") {
      	foreach my $d (sort keys %__devs ) {
		  	  next if($__devs{$d}{type} ne $type);

          $v = $__devs{$d}{INT}{LINK}{VAL};  
          $t = $__devs{$d}{INT}{WLTYPE}{VAL};
          if($t eq "fileplot") {
            my @va = split(":", $v, 3);
            if(@va != 3 || !$__devs{$va[0]}{INT}{currentlogfile}) {
		  			    FHEMRENDERER_pO "<td>Broken definition: $v</a></td>";
            } else {
              if($va[2] eq "CURRENT") {
                $__devs{$va[0]}{INT}{currentlogfile}{VAL} =~ m,([^/]*)$,;
                $va[2] = $1;
              }      
    					FHEMRENDERER_render ("undef $d $va[0] $va[1] $va[2]");
    				}
    			}
  	  	}
  	  }
    }

  } elsif (@a == 4 ) {
			FHEMRENDERER_render ("undef $a[3] $a[1] $a[2] $a[3]"); 

  } elsif (@a == 5 ) {
			my ($p,$v) = split("=",$a[4], 2);

      # Multiline: escape the NL for fhem
      $v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data");
      Log(2, "P: $p, V: $v");

      if($p eq "pos") {
      	%__pos =  split(/[=&]/, $v);
  			FHEMRENDERER_render ("undef $a[3] $a[1] $a[2] $a[3]"); 

		  } else {
			  FHEMRENDERER_render ("undef $a[1] $a[2] $a[3] $a[4]"); 
			}

  } elsif (@a == 6 ) {
			my ($p,$v) = split("=",$a[5], 2);

      # Multiline: escape the NL for fhem
      $v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data");
      Log(2, "P: $p, V: $v");

      if($p eq "pos") {
      	%__pos =  split(/[=&]/, $v);
  			FHEMRENDERER_render ("undef $a[1] $a[2] $a[3] $a[4]"); 

		  } else {
  	    return "\"get FHEMRENDERER\" needs either none, 1(pos) or 3-5 arguments ([file-name] device type logfile [pos=zoom=XX&off=YYY])";
			}
  } else {
  	return "\"get FHEMRENDERER\" needs either none, 1(pos) or 3-5 arguments ([file-name] device type logfile [pos=zoom=XX&off=YYY])";
  }
 	if (not defined $pid) {
		return $ret;
	} else {
		exit(0);
	}
}

#####################
# Get the data and parse it. We are parsing XML in a non-scientific way :-)
sub
FHEMRENDERER_parseXmlList($)
{
  my $docmd = shift;
  my $name;

  if(!$docmd && $__lastxmllist && (time() - $__lastxmllist) < 2) {
    $__room = $__devs{$__detail}{ATTR}{room}{VAL} if($__detail);
    return;
  }

  $__lastxmllist = time();
  %__rooms = ();
  %__devs = ();
  %__types = ();
  $__title = "";

  foreach my $l (split("\n", FHEMRENDERER_fC("xmllist"))) {

    ####### Device
    if($l =~ m/^\t\t<(.*) name="(.*)" state="(.*)" sets="(.*)" attrs="(.*)">/){
      $name = $2;
      $__devs{$name}{type}  = ($1 eq "HMS" ? "KS300" : $1);
      $__devs{$name}{state} = $3;
      $__devs{$name}{sets}  = $4;
      $__devs{$name}{attrs} = $5;
      next;
    }
    ####### INT, ATTR & STATE
    if($l =~ m,^\t\t\t<(.*) key="(.*)" value="([^"]*)"(.*)/>,) {
      my ($t, $n, $v, $m) = ($1, $2, $3, $4);
      $v =~ s,&lt;br&gt;,<br/>,g;
      $__devs{$name}{$t}{$n}{VAL} = $v;
      if($m) {
        $m =~ m/measured="(.*)"/;
        $__devs{$name}{$t}{$n}{TIM} = $1;
      }

      if($t eq "ATTR" && $n eq "room") {
        $__rooms{$v}{$name} = 1;
	if($name eq "global") {
	  $__rooms{$v}{LogFile} = 1;
	  $__devs{LogFile}{ATTR}{room}{VAL} = $v;
	}
      }

      if($name eq "global" && $n eq "logfile") {
	my $ln = "LogFile";
	$__devs{$ln}{type}  = "FileLog";
        $__devs{$ln}{INT}{logfile}{VAL} = $v;
        $__devs{$ln}{state} = "active";
      }
    }

  }
  if(defined($__devs{global}{ATTR}{archivedir})) {
    $__devs{LogFile}{ATTR}{archivedir}{VAL} = 
     $__devs{global}{ATTR}{archivedir}{VAL};
  }

  #################
  #Tag the gadgets without room with "Unsorted"
  if(%__rooms) {
    foreach my $name (keys %__devs ) {
      if(!$__devs{$name}{ATTR}{room}) {
        $__devs{$name}{ATTR}{room}{VAL} = "Unsorted";
        $__rooms{Unsorted}{$name} = 1;
      }
    }
  }

  ###############
  # Needed for type sorting
  foreach my $d (sort keys %__devs ) {
    $__types{$__devs{$d}{type}} = 1;
  }
  $__title = $__devs{global}{ATTR}{title}{VAL} ? 
               $__devs{global}{ATTR}{title}{VAL} : "First page";
  $__room = $__devs{$__detail}{ATTR}{room}{VAL} if($__detail);
}


######################
# Generate an image from the log via gnuplot
sub
FHEMRENDERER_render($)
{
  my ($cmd) = @_;
  my (undef, $wl, $d, $type, $file) = split(" ", $cmd, 5);

  my $gplot_pgm = "$FHEMRENDERERdir/$type.gplot";
  return FHEMRENDERER_fatal("Cannot read $gplot_pgm") if(!-r $gplot_pgm);
  FHEMRENDERER_calcWeblink($d,$wl);
 
  if($__plotmode =~ m/gnuplot/) {
    if($__plotmode eq "gnuplot" || !$__devs{$d}{from}) {

      # Looking for the logfile....

      $__devs{$d}{INT}{logfile}{VAL} =~ m,^(.*)/([^/]*)$,; # Dir and File
      my $path = "$1/$file";
      $path = $__devs{$d}{ATTR}{archivedir}{VAL} . "/$file" if(!-f $path);
      return FHEMRENDERER_fatal("Cannot read $path") if(!-r $path);

      open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); 
      my $gplot_script = join("", <FH>);
      close(FH);

      $gplot_script =~ s/<OUT>/$FHEMRENDERER_tmpfile$wl/g;
      $gplot_script =~ s/<SIZE>/$__plotsize/g;
      $gplot_script =~ s/<IN>/$path/g;
      $gplot_script =~ s/<TL>/$file/g;

      if($__devs{$wl} && $__devs{$wl}{ATTR}{fixedrange}) {
        my $fr = $__devs{$wl}{ATTR}{fixedrange}{VAL};
        $fr =~ s/ /\":\"/;
        $fr = "set xrange [\"$fr\"]\n";
        $gplot_script =~ s/(set timefmt ".*")/$1\n$fr/;
      }

      open(FH, "|gnuplot > /dev/null");# feed it to gnuplot
      print FH $gplot_script;
      close(FH);

    } elsif($__plotmode eq "gnuplot-scroll") {

      ############################
      # Read in the template gnuplot file.  Digest the #FileLog lines.  Replace
      # the plot directive with our own, as we offer a file for each line

      my (@filelog, @data, $plot);
      open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); 
      while(my $l = <FH>) {
        if($l =~ m/^#FileLog (.*)$/) {
          push(@filelog, $1);
        } elsif($l =~ "^plot" || $plot) {
          $plot .= $l;
        } else {
          push(@data, $l);
        }
      }
      close(FH);

      my $gplot_script = join("", @data);
      $gplot_script =~ s/<OUT>/$FHEMRENDERER_tmpfile$wl/g;
      $gplot_script =~ s/<SIZE>/$__plotsize/g;
      $gplot_script =~ s/<TL>/$file/g;

      my ($f,$t)=($__devs{$d}{from}, $__devs{$d}{to});

      my @path = split(" ", FHEMRENDERER_fC("get $d $file $FHEMRENDERER_tmpfile$wl $f $t " .
                                  join(" ", @filelog)));
      my $i = 0;
      $plot =~ s/\".*?using 1:[^ ]+ /"\"$path[$i++]\" using 1:2 "/gse;
      my $xrange = "set xrange [\"$f\":\"$t\"]\n";
      foreach my $p (@path) {   # If the file is empty, write a 0 line
        next if(!-z $p);
        open(FH, ">$p");
        print FH "$f 0\n";
        close(FH);
      }

      open(FH, "|gnuplot > /dev/null");# feed it to gnuplot
      print FH $gplot_script, $xrange, $plot;
      close(FH);
      foreach my $p (@path) {
        unlink($p);
      }
    }

  } #elsif($__plotmode eq "SVG") {

#    my (@filelog, @data, $plot);
#    open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); 
#    while(my $l = <FH>) {
#      if($l =~ m/^#FileLog (.*)$/) {
#        push(@filelog, $1);
#      } elsif($l =~ "^plot" || $plot) {
#        $plot .= $l;
#      } else {
#        push(@data, $l);
#      }
#    }
#    close(FH);
#    my ($f,$t)=($__devs{$d}{from}, $__devs{$d}{to});
#    $f = 0 if(!$f);     # From the beginning of time...
#    $t = 9 if(!$t);     # till the end
#
#    my $ret = FHEMRENDERER_fC("get $d $file INT $f $t " . join(" ", @filelog));
#    SVG_render($file, $__plotsize, $f, $t, \@data, $internal_data, $plot);
#
#		open (FH, ">$FHEMRENDERER_tmpfile$wl.svg");
#		print FH $__RET;
#		close (FH);
#  }
}



##################
sub
FHEMRENDERER_fatal($)
{
  my ($msg) = @_;
  FHEMRENDERER_pO "<html><body>$msg</body></html>";
}

##################
# print formatted
sub
FHEMRENDERER_pF($@)
{
  my $fmt = shift;
  $__RET .= sprintf $fmt, @_;
}

##################
# print output
sub
FHEMRENDERER_pO(@)
{
  $__RET .= shift;
}

##################
# fhem command
sub
FHEMRENDERER_fC($)
{
  my ($cmd) = @_;
  #Log 0, "Calling $cmd";
  my $oll = $attr{global}{verbose};
  $attr{global}{verbose} = 0 if($cmd ne "save");
  my $ret = AnalyzeCommand(undef, $cmd);
  $attr{global}{verbose} = $oll if($cmd !~ m/attr.*global.*verbose/);
  return $ret;
}

##################
sub
FHEMRENDERER_getAttr($$)
{
  my ($aname, $def) = @_;
  if($attr{$__wname} && defined($attr{$__wname}{$aname})) {
  	return $attr{$__wname}{$aname};
  } else {
  	CommandAttr (undef, $__wname . " $aname $def");
  }
  return $def;
}

##################
sub
FHEMRENDERER_setAttr($$)
{
  my ($aname, $def) = @_;
 	CommandAttr (undef, $__wname . " $aname $def");
}


##################
# Calculate either the number of scrollable weblinks (for $d = undef) or
# for the device the valid from and to dates for the given zoom and offset
sub
FHEMRENDERER_calcWeblink($$)
{
  my ($d,$wl) = @_;

  return if($__plotmode eq "gnuplot");
  my $now = time();

  my $zoom = $__pos{zoom};
  $zoom = "day" if(!$zoom);

  if(!$d) {
    foreach my $d (sort keys %__devs ) {
      next if($__devs{$d}{type} ne "weblink");
      next if(!$__room || ($__room ne "all" && !$__rooms{$__room}{$d}));
      next if($__devs{$d}{ATTR} && $__devs{$d}{ATTR}{noscroll});
      next if($__devs{$d}{ATTR} && $__devs{$d}{ATTR}{fixedrange});
      $__scrolledweblinkcount++;
    }
    return;
  }


#  return if(!$__devs{$wl});
  return if($__devs{$wl} && $__devs{$wl}{ATTR}{noscroll});

  if($__devs{$wl} && $__devs{$wl}{ATTR}{fixedrange}) {
    my @range = split(" ", $__devs{$wl}{ATTR}{fixedrange}{VAL});
    $__devs{$d}{from} = $range[0];
    $__devs{$d}{to}   = $range[1];
    return;
  }

  my $off = $__pos{$d};
  $off = 0 if(!$off);
  $off += $__pos{off} if($__pos{off});

  if($zoom eq "qday") {

    my $t = $now + $off*21600;
    my @l = localtime($t);
    $l[2] = int($l[2]/6)*6;
    $__devs{$d}{from}
        = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]);
    $__devs{$d}{to}
        = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]+6);

  } elsif($zoom eq "day") {

    my $t = $now + $off*86400;
    my @l = localtime($t);
    $__devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);
    $__devs{$d}{to}   = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]+1);

  } elsif($zoom eq "week") {

    my @l = localtime($now);
    my $t = $now - ($l[6]*86400) + ($off*86400)*7;
    @l = localtime($t);
    $__devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);

    @l = localtime($t+7*86400);
    $__devs{$d}{to}   = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);


  } elsif($zoom eq "month") {

    my @l = localtime($now);
    while($off < -12) {
      $off += 12; $l[5]--;
    }
    $l[4] += $off;
    $l[4] += 12, $l[5]-- if($l[4] < 0);
    $__devs{$d}{from} = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1);

    $l[4]++;
    $l[4] = 0, $l[5]++ if($l[4] == 12);
    $__devs{$d}{to}   = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1);

  } elsif($zoom eq "year") {

    my @l = localtime($now);
    $l[5] += $off;
    $__devs{$d}{from} = sprintf("%04d", $l[5]+1900);
    $__devs{$d}{to}   = sprintf("%04d", $l[5]+1901);

  }
}


1;