#!/usr/bin/perl
#
# Get the PV values from a PV list in a file or files
# And display in html
#
# 2008.05.05 mjf
#

use strict;
use lib '/nfs/erl/erp/epics/R3.14.12/support/base-3.14.12.2/lib/perl';
use lib '/nfs/acc/user/mjf7/software/perllib';
use CA;
use Template;
use Mail::Sendmail;
use Getopt::Long;
use Pod::Usage;
#use Data::Dumper;

my $verbose = 1;
my $send = 0;
my $help = '';
my $man = '';
my $output = '';

GetOptions ('verbose' => \$verbose,
	    'sendmail' => \$send,
            'help|?' => \$help,
            'man' => \$man,
            'output=s' => \$output) or pod2usage(2);

pod2usage(1) if ($help);
pod2usage(-exitstatus => 0, -verbose => 2) if $man;

unless (@ARGV) {
  die "Please give a pv list filename on the command line\n";
}

my @pvs;
my %chan;
my %cname;
my %cval;
my %cstat;
my %csevr;
my %cunits;
my %alarm;
my %ackd;  # acknowledged already?
my %cacks; # current acks state

my $alarm_refresh = 3600;  # in seconds

my $file = shift @ARGV;
my $base = $file;
$base =~ s/(\S+)\..*/$1/g;
$base =~ s/.*\/(\S+)/$1/g;
my $title = $base;
$title =~ s/_/ /g;
print "<$title>\n";
print "Mail will be sent if necessary\n" if $send;


open FIN, "<$file"  or die "Couldn't open $file\n$!\n";
while (<FIN>) {
  if ( m/^\s*(\#.*)/ ) {
    push @pvs, $1;
    next;
  }
  if ( m/^\s*(\S+)/) {
    push @pvs, $1;
  }
}
close FIN;

foreach (@pvs) {
  print "$_\n" if $verbose;
  if ( m/\#/ ) {
    s/\#//g;
    next;
  }
  $chan{$_} = CA->new( $_ );
}
eval {
  CA->pend_io(10);
}; 
if ($@) {
  foreach (keys %chan) {
    if ($chan{$_}->state =~ m/never/i) {
      printf "%s status: %s => deleted\n", $chan{$_}->name, $chan{$_}->state;
      delete $chan{$_};
    }
  }
}
CA->pend_io(10);

## TODO: create subscriptions just for alarm changes?
foreach (keys %chan) {

  $chan{$_}->create_subscription('av', \&alarm_callback, 'DBR_STSACK_STRING');

}

CA->pend_io(5);

foreach (keys %chan) {

  if ($chan{$_}->field_type =~ m/DOUBLE/i) {
    $chan{$_}->create_subscription('v', \&dbl_callback, 'DBR_GR_DOUBLE');

  } elsif ($chan{$_}->field_type =~ m/ENUM/i) {
    $chan{$_}->create_subscription('v', \&enum_callback, 'DBR_GR_ENUM');

  } else {
    $chan{$_}->create_subscription('v', \&gen_callback);
  }
}
CA->pend_event(1);


while(1) {
  eval {
    my ($sec, $min, $hour, $day, $month, $year) = (localtime)[0,1,2,3,4,5];
    my $date = sprintf("%4d.%02d.%02d %02d:%02d:%02d", $year + 1900,
		       $month + 1, $day, $hour, $min, $sec);

    my $tt = Template->new({
      INCLUDE_PATH => '/home/jad/public_html/temp/webdisp/templates', INTERPOLATE => '1',
    }) || die "$Template::ERROR\n";

    my $tt_vars = {
      pvs => \@pvs,
      cname => \%cname,
      cval => \%cval,
      cstat => \%cstat,
      csevr => \%csevr,
      cunits => \%cunits,
      title => "$title",
      datetime => "$date"
	};

    my $fout = $base . ".html";
    $tt->process('caphtml.tmpl', $tt_vars, $fout) 
	|| die $tt->error(), "\n";


  };  warn $@ if $@;
  CA->pend_event(30);
}

exit(0);

sub dbl_callback {
  my ($chan, $status, $data) = @_;
  my $name = $chan->name();

  eval {
    $cval{$name} = $data->{value};
    my $fmt = sprintf("%%.%dg", $data->{precision} + 3);
    $cval{$name} = sprintf("$fmt", $data->{value});
    $cstat{$name} = $data->{status};
    if ($cstat{$name}) {
      &check_alarm($chan);
    } else {
      $alarm{$name} = undef;
    }
    $cstat{$name} = "Ok" unless $cstat{$name};
    $csevr{$name} = $data->{severity};
    $cunits{$name} = $data->{units};
  }; warn $@ if $@;
}

sub gen_callback {
  my ($chan, $status, $data) = @_;
  my $name = $chan->name();

  eval {
    $cval{$name} =  $data->{value};
    $cstat{$name} = $data->{status};
    if ($cstat{$name}) {
      &check_alarm($chan);
    } else {
      $alarm{$name} = undef;
    }
    $cstat{$name} = "Ok" unless $cstat{$name};
    $csevr{$name} = $data->{severity};
    $cunits{$name} = $data->{units};
  }; warn $@ if $@;
}

sub enum_callback {
  my ($chan, $status, $data) = @_;
  my $name = $chan->name();

  eval {
    $cval{$name} =  $data->{value};
    $cstat{$name} = $data->{status};
    &check_alarm($chan) if $cstat{$name};
    $cstat{$name} = "Ok" unless $cstat{$name};
    $csevr{$name} = $data->{severity};
    $cunits{$name} = $data->{units};
  }; warn $@ if $@;
}

sub alarm_callback {
  my ($chan, $status, $data) = @_;
  my $name = $chan->name();

  eval {
    $cacks{$name} =  $data->{acks};
  }; warn $@ if $@;
}

sub check_alarm($) {
  my ($chan) = @_;
  my $name = $chan->name();
  my $akey = "$name";
  my $time = time();

  # If already in alarm and not at the alarm refresh time, go back
  if ($alarm{$akey} and ($time - ($alarm{$akey}) < $alarm_refresh)) {
    return;
  }

  # Check for LINK or WRITE or ... errors and treat differently
  #  -- only send one message per $alarm_refresh for 
  #     each of the errors
  if ($cstat{$name} =~ m/(WRITE|LINK|TIMEOUT|UDF)/) {
    $akey = 'access_alarm';
    if ($alarm{$akey} and ( ($time - $alarm{$akey}) < $alarm_refresh) ) {
      return;
    }
  }

  $alarm{$akey} = $time;

  my $text;

  unless ($cacks{$name}) {
    # alarm has been acknowledged
    if ($ackd{$name}) {
      return;
    } else {
      $text = "$name is now alarmed in the " .
	  $cstat{$name} . " state at a value of " .
	  $cval{$name} . 
	  "\nThe alarm has been acknowledged in the ERL control room. " .
	  "Suspending email notices until non-acknowledged alarm occurs..\n" .
	  "\nSee <a href=\"http://www.lepp.cornell.edu/proxy/epics/" . 
	  "erpview/getdata?chan=$name&dur=12\">last 12 hours</a>.\n";
      $ackd{$name} = 1;
    }
  } else {
    $ackd{$name} = 0;

    $text = "$name is now alarmed in the " .
	$cstat{$name} . " state at a value of " .
	$cval{$name} . "\nSee <a href=\"http://www.lepp.cornell.edu/proxy/epics/erpview/getdata?chan=$name&dur=12\">last 12 hours</a>.\n";
  }



  my @tolist = ( 'john.dobbins@cornell.edu');
  my %mail = ( To => join(",",@tolist),
	       From => 'epicsioc@lns.cornell.edu',
	       Subject => "$name in alarm!",
	       Message => "$text"
	       );
  
  if ($send) {
    print "Sending mail due to: $text\n";
    sendmail(%mail) or warn $Mail::Sendmail::error;
  }
}

__END__

=head1 NAME

DOCUMENTATION NOT DONE YET
scriptname - briefly what the script does

=head1 SYNOPSIS

scriptname [-OPTIONS] [input_filename]

 Options:
   -help            brief help message
   -man             print full documentation
   -verbose         print info while running
   -output          specify output file

=head1 AUTHOR

Michael Forster

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exit.

=item B<-man>

Print the full documentation and exit.

=item B<-verbose>

While running, give verbose output to STDOUT. 

=back

=head1 DESCRIPTION


=cut
