#=====================================================================#
# Implementation of the Main_Window widget in SushiMon                #
# Author: Peter Koczan (koczan@cs.wisc.edu)
#=====================================================================#
# $Id: Main_Window.pm,v 1.3 2011/11/21 17:09:43 koczan Exp $

package Main_Window;

use strict;

# Databases
use DBI;
use CSL::DBs;
use CSL::Pg;

# GUIs
use CSL::Gnome2;
use Gnome2;
use Gtk2::GladeXML;

# Getting cricket graphs
use LWP::UserAgent;
use IO::File;
use File::Temp qw(tempfile);

# Misc
use Carp;
use POSIX qw(strftime);

our $GLADEPATH = '/s/lab/common/lib/sushimon';

# global reference to this object to make signal handlers work
my $window_obj;

# convenience globals
my @STATES = qw(Pending Scheduled Active Success Fail);

# Sushimon main_window constructor
sub new {
  my $this = shift;
  my $class = ref($this) || $this;

  my $self = { };
  bless $self, $class;

  $self->{dbh} = DBI->connect($CSL::DBs::sushi);
  croak "Could not open database: $!" if (!$self->{dbh});
  $self->{dbh}->do("set search_path to sushi");

  $self->{xml} = Gtk2::GladeXML->new("$GLADEPATH/sushimon.glade", 'main_window');
  croak "Error opening Glade file" if (!$self->{xml});

  $self->{xml}->signal_autoconnect_from_package($self);

  return $self;
}

# Sushimon main_window execution
sub run {
  my ($class) = @_;

  Gnome2::Program->init("sushimon", "2");
  my $self = $class->new;
  $window_obj = $self;

  #my @states = qw(Pending Scheduled Active Success Fail);
  my @col_types = ();
  foreach (0..4) { push @col_types, 'Glib::String'; }
  my @columns = qw/Atom Type Level Runtime Size/;
  my @widths = (80, 80, 80, 80, 80);

  foreach my $state (@STATES) {
    my $state_lc = lc $state;

    # atom list for this state
    $self->{$state}->{atom_list} = [];

    # Create and set models/widgets for this state
    $self->{$state}->{treestore} = Gtk2::TreeStore->new(@col_types);
    $self->{$state}->{widget} = $self->wgt($state_lc . '_list');
    $self->{$state}->{widget}->set_model($self->{$state}->{treestore});
    $self->{$state}->{widget}->get_selection()->set_mode('single');

    for(my $i = 0; $i <= $#columns; $i++) {
      $self->{$state}->{widget}->insert_column_with_attributes(-1, $columns[$i], Gtk2::CellRendererText->new(), "text", $i);
      $self->{$state}->{widget}->get_column($i)->set_resizable(1);
      $self->{$state}->{widget}->get_column($i)->set_fixed_width($widths[$i]);
    }

    $self->{$state}->{widget}->set_rules_hint(1);
    $self->{$state}->{widget}->set_headers_clickable(0);
    $self->{$state}->{widget}->set_reorderable(0);
  }

  $self->{sort} = "atom";

  # Fill in data
  $self->get_atoms;
  $self->get_graphs;

  my $num = scalar @{$window_obj->{Pending}->{atom_list}};
  $self->wgt('num_atoms')->set_text($num);

  $self->wgt('main_window')->show;

  # update atom list every minute
  $self->{atom_update_id} = Glib::Timeout->add(300000, \&get_atoms, $self);
  # update graphs every 15 minutes
  $self->{graph_update_id} = Glib::Timeout->add(900000, \&get_graphs, $self);
  Gtk2->main;
};

# grab and process atoms from the database, then put them in the lists
sub get_atoms {
  my ($self) = @_;

  # Just grab all the atoms you have today, and process them afterward
  my $group = strftime("%Y.%m.%d", localtime());
  my $query_text =
"select 
  a.name, a.type, a.level, r.runtime, r.raw_size,
  a.id, a.state, r.result, r.timestamp 
from sushi.atom a
  left outer join sushi.results r
    on (r.id = a.id)
where schedule_group = ?
order by a.name, r.timestamp";

  my $query = $self->{dbh}->prepare($query_text);
  $query->execute($group);

  # process atoms
  # I am counting on the ordering of the query above for correctness
  # because we are taking the last result.
  # Technically, we should take the first successful result to count
  # success, but we don't re-run successful atoms, so there is no need.
  my %atoms = ();
  while (my $row = $query->fetchrow_hashref) {
    $atoms{$row->{name}} = {%$row};
  }
  $query->finish;

  my $i = 0;
  my %iter = ();
  foreach (@STATES) {
    $self->{$_}->{treestore}->clear();
    $self->{$_}->{atom_list} = [];
    $iter{$_} = 0;
  }

  foreach my $name (sort keys %atoms) {
    $i++;

    my $atom = $atoms{$name};

    $atom->{runtime} = $atom->{runtime} || 0;
    $atom->{runtime} = strftime("%T", localtime($atom->{runtime}+6*3600));
    $atom->{raw_size} = $atom->{raw_size} || 0;
    if ($atom->{raw_size} > 2**30) {
      $atom->{raw_size} = sprintf("%.2fGB", $atom->{raw_size}/(2**30));
    } elsif ($atom->{raw_size} > 2**20)	{
      $atom->{raw_size} = sprintf("%.2fMB", $atom->{raw_size}/(2**20));
    } elsif ($atom->{raw_size} > 2**10) {
      $atom->{raw_size} = sprintf("%.2fKB", $atom->{raw_size}/(2**10));
    }

    if ($atom->{state} eq 'Pending' || $atom->{state} eq 'Scheduled') {
      push @{$self->{$atom->{state}}->{atom_list}}, $atom;
      $iter{$atom->{state}} = $self->{$atom->{state}}->{treestore}->append(undef);
      $self->{$atom->{state}}->{treestore}->set(
        $iter{$atom->{state}},
        0, $atom->{name},
        1, $atom->{type},
        2, $atom->{level},
        3, undef,
        4, undef,
      );
      
    } elsif ($atom->{state} eq 'Active') {
      push @{$self->{Active}->{atom_list}}, $atom;
      if (!defined $atom->{result} || $atom->{result} ne 'Active') {
        $atom->{runtime} = '00:00:00';
        $atom->{raw_size} = 0;
      }
      $iter{Active} = $self->{Active}->{treestore}->append(undef);
      $self->{Active}->{treestore}->set(
        $iter{Active},
        0, $atom->{name},
        1, $atom->{type},
        2, $atom->{level},
        3, $atom->{runtime},
        4, $atom->{raw_size},
      );

    } elsif ($atom->{state} eq 'Done') {
      my $state = (defined $atom->{result} && $atom->{result} eq 'Success') ?
        'Success' : 'Fail';

      push @{$self->{$state}->{atom_list}}, $atom;
      $iter{$state} = $self->{$state}->{treestore}->append(undef);
      $self->{$state}->{treestore}->set(
        $iter{$state},
        0, $atom->{name},
        1, $atom->{type},
        2, $atom->{level},
        3, $atom->{runtime},
        4, $atom->{raw_size},
      );
    }
    # else we don't care or it's a really weird state
  }
  %atoms = undef;

  $i = 0E0 if $i == 0; # return a "true" value so the refresh timer still works

  return $i;
}

# get the cricket graphs
sub get_graphs {
  my ($self) = @_;

  my %urls = ();
  my $secs = (localtime)[2]*60*60 + (localtime)[1]*60 + (localtime)[0];

# stupid graphite needs stupid complete dates now

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();

$year += 1900;
$mon += 1;

my $stupidassdate = sprintf("%04d%02d%02d", $year, $mon, $mday);

# GRAPHITE GRAPHS:
  $urls{'today_status_graph'} = "https://graphite.cs.wisc.edu/render/?target=alias(color(stacked(sushi.Pending)%2C%22cyan%22)%2C%22Pending%22)&target=alias(color(stacked(sushi.Scheduled)%2C%22yellow%22)%2C%22Scheduled%22)&target=alias(color(stacked(sushi.Active)%2C%22blue%22)%2C%22Active%22)&target=alias(color(stacked(sushi.Success)%2C%22green%22)%2C%22Success%22)&target=alias(color(stacked(sushi.Fail)%2C%22red%22)%2C%22Failed%22)&width=595&height=255&vtitle=Atoms&yMax=&minorY=&from=00%3A00_$stupidassdate&until=23%3A59_$stupidassdate";

  $urls{'hourly_status_graph'} = "https://graphite.cs.wisc.edu/render/?target=alias%28color%28stacked%28sushi.Pending%29%2C%22cyan%22%29%2C%22Pending%22%29&target=alias%28color%28stacked%28sushi.Scheduled%29%2C%22yellow%22%29%2C%22Scheduled%22%29&target=alias%28color%28stacked%28sushi.Active%29%2C%22blue%22%29%2C%22Active%22%29&target=alias%28color%28stacked%28sushi.Success%29%2C%22green%22%29%2C%22Success%22%29&target=alias%28color%28stacked%28sushi.Fail%29%2C%22red%22%29%2C%22Failed%22%29&width=595&height=255&from=-1hours";

# CRICKET GRAPHS: Uncomment these to get cricket graphs instead.
# $urls{'today_status_graph'} = "http://research.cs.wisc.edu/cgi-bin/cricket/grapher.cgi?type=png;target=%2Fsushi%2Fsushi;dslist=todayPending%2CtodayScheduled%2CtodayActive%2CtodaySuccess%2CtodayFailed;range=$secs"; 
#  $urls{'hourly_status_graph'} = "http://research.cs.wisc.edu/cgi-bin/cricket/grapher.cgi?type=png;target=%2Fsushi%2Fsushi;dslist=todayPending%2CtodayScheduled%2CtodayActive%2CtodaySuccess%2CtodayFailed;range=3600";

  my $ua = new LWP::UserAgent;
  foreach my $graph (keys %urls) {
    my $widget = $self->wgt($graph);

    $widget->clear;

    my $request = new HTTP::Request("GET", $urls{$graph});
    my $response = $ua->request($request);

    if ($response->is_success) {
      my $image = $response->content;
      my $file = new IO::File ">/tmp/sushi$$.png";
      print $file $image;
      $file->close;
      $widget->set_from_file("/tmp/sushi$$.png");

      unlink "/tmp/sushi$$.png";
    } else {
      print "Big error!\n";
      print "$urls{$graph}\n";
    }
  }

  return 1;
}

sub get_selected_row {
  my ($self) = @_;

  my $page_index = $self->wgt('atom_tabs')->get_current_page();
  my $state = lc $STATES[$page_index];
  my $selection = $self->wgt($state . '_list')->get_selection();

  my ($model, $iter) = $selection->get_selected();
  return if (!$model);
  my @values = $model->get($iter);

  return @values;
}

# signal handlers

# EVENT: destruction of main window
# ACTION: exit and kill the database connection
sub on_main_window_destroy {
  $window_obj->quit;
}

# EVENT: switching tabs in the atom state box
# ACTION: put the proper count in the num_label
sub on_atom_tabs_switch_page {
  # redisplay the number of atoms in the num_atoms label
  my $page_index = $window_obj->wgt('atom_tabs')->get_current_page();
  my $state = $STATES[$page_index];
  my $num = scalar @{$window_obj->{$state}->{atom_list}};
  $window_obj->wgt('num_atoms')->set_text($num);
}

# EVENT: a new atom is selected
# ACTION: get its dump_stderr and display it in the textarea
sub on_atom_cursor_changed {
  my @values = $window_obj->get_selected_row;
  my $name = $values[0];

  my $group = strftime("%Y.%m.%d", localtime());

  my $length_query_text = "select length(dump_stderr) as stderr_len
from sushi.atom a
  left outer join sushi.results r
    on (a.id = r.id)
where a.name = ?
  and a.schedule_group = ?
order by timestamp desc
limit 1";

  my $query = $window_obj->{dbh}->prepare($length_query_text);
  $query->execute($name, $group);
  my $len = $query->fetchrow_hashref->{stderr_len} || 0;
  $query->finish;

  my $text = '';
  if ($len >= 2**20) {
    # 1 MB is a lot of data to display, so let's not
    $text = "WARNING: STDERR is really large.\n";
  } else {
    my $query_text = "select dump_stderr
from sushi.atom a
  left outer join sushi.results r
    on (a.id = r.id)
where a.name = ?
  and a.schedule_group = ?
order by timestamp desc
limit 1";

    my $query = $window_obj->{dbh}->prepare($query_text);
    $query->execute($name, $group);
    my $row = $query->fetchrow_hashref;
    $text = $row->{dump_stderr} || '';
    $query->finish;
  }

  # need to wrap this in a text buffer
  my $buf = Gtk2::TextBuffer->new;
  $buf->set_text($text);
  $window_obj->wgt('dump_stderr')->set_buffer($buf);
}

# EVENT: "Refresh Atoms" button clicked
# ACTION: Updated atom_list, reset the timeout, and redisplay atoms/count
sub on_refresh_atoms_clicked {
  # update atoms
  $window_obj->get_atoms;

  # remove and reset the update_atoms timeout
  Glib::Source->remove($window_obj->{atom_update_id});
  $window_obj->{atom_update_id} = Glib::Timeout->add(300000, \&get_atoms, $window_obj);

  # reset the count label
  my $page_index = $window_obj->wgt('atom_tabs')->get_current_page();
  my $state = $STATES[$page_index];
  my $num = scalar @{$window_obj->{$state}->{atom_list}};
  $window_obj->wgt('num_atoms')->set_text($num);
}

# EVENT: "Reschedule Atom" button clicked
# ACTION: run "sushi atom reschedulefailed" for this atom
sub on_reschedule_atom_clicked {
  my @values = $window_obj->get_selected_row();
  my $name = $values[0];

  system("xterm -e /s/sushi/bin/sushi atom reschedulefailed -n $name");
  $window_obj->get_atoms;
}

# EVENT: "Reschedule All" button clicked
# ACTION: run "sushi atom reschedulefailed" for today's atoms
sub on_reschedule_all_clicked {
  system("xterm -e /s/sushi/bin/sushi atom reschedulefailed");
  $window_obj->get_atoms;
}

# helper functions

sub quit {
  my ($self) = @_;

  # Close database
  $self->{dbh}->disconnect;
  exit;
}
    
sub wgt {
  my ($self, $widget) = @_;

  return $self->{xml}->get_widget($widget);
}

1;
