# -*- perl -*-

# Copyright (c) 2002 by Jeff Weisberg
# Author: Jeff Weisberg <argus @ tcp4me.com>
# Date: 2002-Nov-01 15:57 (EST)
# Function: misc graph data handling
#
# $Id: GraphData.pm,v 1.11 2005/04/30 16:04:49 jaw Exp $

package GraphData;

$MAGIC = "AGD3";	# Argus Graphing Data 
$HDR_SIZE   = 1024;	# total size of header, currently mostly unused

# raw samples
$SAMP_SIZE  = 16;	# bytes per sample
$SAMP_NMAX  = 2048;	# default number of samples

# hourly summary data
$HOURS_SIZE = 32;
$HOURS_NMAX = 1024;

# daily summary data
$DAYS_SIZE  = 32;
$DAYS_NMAX  = 1024;

# Header:
#   magic,   lastT,    sampl_cnt, sampl_indx
#   hrs_cnt, hrs_indx, day_cnt,   day_indx
# 
#   hrs_min, hrs_max,  hrs_nsamp, hrs_sigm
#   hrs_sigm2-------,  hrs_flags, unused,
#
#   day_min, day_max,  day_nsamp, day_sigm
#   day_sigm2-------,  day_flags, unused,
#
#   smp_nmax,hrs_nmax, day_nmax,  unused
#
#
# Data:
#   samples: {time, value, flags, unused}
#   summary: {time, min, max, ave, stdev, nsamps, flags, unused}


sub new {
    my $c = shift;
    my $name = shift;
    my( $fh, $buf, $magic );
    my $me = {
	name => $name,
    };

    bless $me;
    $fh = anon_fh();
    $me->{fd}= $fh;
    open( $fh, "$::datadir/gdata/$name" )
	|| return ::error( "'$name' is stubborn and refuses to open: $!" );
    binmode $fh;
    
    # read file header
    $magic = $me->read_header();

    unless( $magic eq $MAGIC ){
	return ::error( "corrupt data file: $name" );
    }

    $me;
}

# But what Gherardo is it, who, as sample
# Of a lost race, thou sayest has remained
#   -- Dante, Divine Comedy
sub readsamples {
    my $me = shift;
    my $limit = shift;
    my( $fh, $start, $i, @samp );

    $fh = $me->{fd};
    
    $start = ($me->{sampl_index} - $me->{sampl_count} + $me->{sampl_nmax}) % $me->{sampl_nmax};
    # print STDERR "samples=$me->{sampl_count} index=$me->{sampl_index} start=$start\n";
    for( $i=0; $i<$me->{sampl_count}; $i++ ){
	my $n = ($i + $start) % $me->{sampl_nmax};
	my $off = $n * $SAMP_SIZE + $me->{sampl_start};
	my $buf;
	seek( $fh, $off, 0 );
	read( $fh, $buf, $SAMP_SIZE );
	my( $t, $v, $f ) =
	    unpack( "NfN", $buf );
	next unless $t;
	push @samp, {
	    time  => $t,
	    value => $v,
	    flag  => $f,
	    ($f ? (color =>  ($f==2 ? 'gray' : 'red')) : ()),
	};
    }

    # remove end-errors
    shift @samp
	if( $samp[0]{time} > $samp[1]{time} );
    
    # temporal limit
    my $et = $samp[-1]{time};
    $limit = (split /\s+/, $limit)[0] || 36*3600;
    while( $et - $samp[0]{time} > $limit ){
	shift @samp;
    }

    $me->{samples} = [ @samp ];
}

# Here's the scroll,
# The continent and summary of my fortune
#   -- Shakespeare, Merchant of Venice
sub readsummary {
    my $me = shift;
    my $wh = shift;
    my $ns = shift;
    my $limit = shift;
    my( $fh, $start, $first, $cnt, $idx, $nmax, $nsmall, @samp );

    $fh = $me->{fd};
    # day+hours data is same, different offset
    if( $wh eq 'hours' ){
	$cnt   = $me->{hours_count};
	$idx   = $me->{hours_index};
	$start = $me->{hours_start};
	$nmax  = $me->{hours_nmax};
    }else{
	$cnt   = $me->{days_count};
	$idx   = $me->{days_index};
	$start = $me->{days_start};
	$nmax  = $me->{days_nmax};
    }
    $cnt = $ns if $cnt > $ns;
    
    $first = ($idx - $cnt + $nmax) % $nmax;

    for( $i=0; $i<$cnt; $i++ ){
	my $n = ($i + $first) % $nmax;
	my $off = $n * $HOURS_SIZE + $start;
	my $buf;

	seek( $fh, $off, 0 );
	read( $fh, $buf, $HOURS_SIZE );
	my( $t, $min, $max, $ave, $sdv, $ns, $fl
	    ) = unpack( "NffffNNx4", $buf );
	next unless $t;
	$nsmall ++ unless $ns > 1;	# NB: otherwise the graphs get ugly
	push @samp, {
	    time => $t,
	    flag => $fl,
	    min  => $min, max  => $max,
	    ave  => $ave, stdv => $sdv,
	    ns   => $ns,
	    value => $ave,
	    ($f ? (color =>  ($f==2 ? 'gray' : 'red')) : ()),	    
	};
    }

    shift @samp
	if( $samp[0]{time} > $samp[1]{time} );

    @samp = grep { $_->{ns} > 1 } @samp if $nsmall < $cnt/2;
    
    # temporal limit
    my $et = $samp[-1]{time};
    if( $which eq 'hours' ){
	$limit = (split /\s+/, $limit)[1] || 180 * 3600;
    }else{
	$limit = (split /\s+/, $limit)[2] || 90 * 24 * 3600;
    }

    while( $et - $samp[0]{time} > $limit ){
	shift @samp;
    }
    
    $me->{samples} = [ @samp ];
}

sub read_header {
    my $me = shift;
    my $fh = $me->{fd};
    my( $buf, $magic );
    
    sysseek($fh, 0, 0);
    sysread($fh, $buf, $HDR_SIZE);

    ($me->{magic},       $me->{lastt},
     $me->{sampl_count}, $me->{sampl_index},
     $me->{hours_count}, $me->{hours_index},
     $me->{days_count},  $me->{days_index},
     
     $me->{hours_min},   $me->{hours_max},
     $me->{hours_nsamp}, $me->{hours_sigma},
     $me->{hours_sigm2}, $me->{hours_flags},
     
     $me->{days_min},   $me->{days_max},
     $me->{days_nsamp}, $me->{days_sigma},
     $me->{days_sigm2}, $me->{days_flags},

     $me->{sampl_nmax}, $me->{hours_nmax}, $me->{days_nmax},
     
     ) = unpack( "a4N NN NN NN  ffNfdNx4  ffNfdNx4  NNN", $buf );

    $me->header_init();

    $me->{magic};
}

sub header_init {
    my $me = shift;

    $me->{sampl_nmax} ||= $SAMP_NMAX;
    $me->{hours_nmax} ||= $HOURS_NMAX;
    $me->{days_nmax}  ||= $DAYS_NMAX;

    $me->{sampl_start} = $HDR_SIZE;
    $me->{hours_start} = $me->{sampl_start} + $SAMP_SIZE  * $me->{sampl_nmax};
    $me->{days_start}  = $me->{hours_start} + $HOURS_SIZE * $me->{hours_nmax};
    
}

sub write_header {
    my $me = shift;
    my $fh = $me->{fd};
    my $hdr;

    $hdr = $MAGIC . pack( "N NN NN NN ffNfdNx4 ffNfdNx4 NNN",
			  $me->{lastt},
			  $me->{sampl_count}, $me->{sampl_index},
			  $me->{hours_count}, $me->{hours_index},
			  $me->{days_count},  $me->{days_index},

			  $me->{hours_min},   $me->{hours_max},
			  $me->{hours_nsamp}, $me->{hours_sigma},
			  $me->{hours_sigm2}, $me->{hours_flags},

			  $me->{days_min},   $me->{days_max},
			  $me->{days_nsamp}, $me->{days_sigma},
			  $me->{days_sigm2}, $me->{days_flags},

			  $me->{sampl_nmax}, $me->{hours_nmax}, $me->{days_nmax},
			  
			  );
    sysseek( $fh, 0, 0 );
    syswrite($fh, $hdr );
}



sub anon_fh {
    do { local *FILEHANDLE };
}

1;

