######################################################################
#
# Crash_Window.pm                       
# The main window for crashman3; adapted from crashman2, orig by stefan
# $Id$                                  
#
######################################################################

package Crash_Window;

use strict;
use CSL::DBs;
use CSL::Pg;
use Admin_Window;
use Crashbackend;
use Date_Window;
use Log_Window;
use Wait_List_Window;

use DBI;
use Date::Manip;
use Text::Wrap;
use Time::ParseDate;
use Date::Format;
#use Data::Dumper;
use Switch;

use CSL::Gnome2;
use Gnome2;
use Gtk2::GladeXML;


####################
# Globals
our $db = $CSL::DBs::csdb;
chomp(our $WHOAMI = getpwuid $>); # who is running this - used for logging,etc

sub new
{
    my $this = shift;
    my $class = ref($this) || $this;

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

    my $GLADEDIR = $main::GLADEDIR || ".";
    $self->{xml} = Gtk2::GladeXML->new("$GLADEDIR/crashman3.glade",
                                       'crash_window');
    die "Error opening Glade file" if !$self->{xml};    
    $self->{xml}->signal_autoconnect_from_package($self);
    
    return $self;
}

sub run
{
    my ($class) = @_;

    Gnome2::Program->init("crashman", "3");
    my $self = $class->new;

    # Initialize the crash list store
    $self->{crash_store} = Gtk2::ListStore->new('Glib::String',  # Disk name
                                                'Glib::String',  # Disk status
                                                'Glib::String',  # Key status
                                                'Glib::String',  # User status
                                                'Glib::String',  # User name
                                                'Glib::String',  # Due date
                                                'Glib::Scalar'); # Raw data

    # Initialize the crash list view (GtkTreeView)
    my $crash_list = $self->wgt('crash_list');
    $crash_list->set_model($self->{crash_store});
    my $sel = $crash_list->get_selection;
    $sel->set_mode('single');
    $sel->signal_connect_swapped(
        changed => \&on_crash_list_selection_changed, $self);

    # Set up the columns
    my @column_titles = ("Disk", "Disk Status", "Key Status", "User Status",
                         "User", "Due Date");
    for (my $i = 0; $i <= $#column_titles; $i++)
    {
        my $tvc = Gtk2::TreeViewColumn->new;
        $tvc->set_title($column_titles[$i]);
        $tvc->set_resizable(1);

        my $crt = Gtk2::CellRendererText->new;
        $tvc->pack_start($crt, 0);
        $tvc->set_cell_data_func($crt, \&crash_list_cell_renderer_func, $i);

        $crash_list->insert_column($tvc, $i);
    }

    # Load disk data
    $self->refresh_disks;

    # Ready
    $self->wgt('statusbar')->push(1, "Ready...");
    Gtk2->main;
}

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

    exit(0);
}

sub on_add_crash_disk_button_clicked
{
    my ($self) = @_;
    
    my $box = Gtk2::MessageDialog->new(undef, 'destroy-with-parent',
                                              'question', 'yes-no',
        "Are you absolutely sure you want to add a new crash disk??");

    my $ret = $box->run;
    $box->destroy;

    if ($ret eq 'yes')
    {
        my $done = 0;
        while (!$done)
        {
            my $dlg = Gtk2::Dialog->new("Enter new disk name",
                                        $self->wgt('crash_window'),
                                        'modal',
                                        'gtk-ok' => 'ok',
                                        'gtk-cancel' => 'cancel');
            my $disk_name_entry = Gtk2::Entry->new;
            $dlg->vbox->add($disk_name_entry);
            $disk_name_entry->show;
            $dlg->set_default_response('ok');
            my $response = $dlg->run;
            if ($response eq 'ok')
            {
                my $disk_name = $disk_name_entry->get_text;

                # Check if disk name is in use or invalid
                if (Crashbackend::get_disk( $disk_name ))
                {
                    my $box = Gtk2::MessageDialog->new( undef,
                                                        'destroy-with-parent',
                                                        'error', 'ok',
                                    "Error: Disk $disk_name is in use." );
                    $box->run;
                    $box->destroy;
                }
                elsif ($disk_name eq "" || $disk_name =~ /[^A-Za-z0-9_-]/)
                {
                    my $box = Gtk2::MessageDialog->new( undef,
                                                        'destroy-with-parent',
                                                        'error', 'ok',
                                    "Error: Name '$disk_name' is invalid.\n".
                                    "Valid names may contain letters, numbers,".
                                    " _ or - characters, but nothing else.");
                    $box->run;
                    $box->destroy;
                }
                else
                {
                    if ($ret eq 'yes')
                    {
                        Crashbackend::add_disk( $disk_name );
                    }
                    my $box = Gtk2::MessageDialog->new( undef,
                                                        'destroy-with-parent',
                                                        'info', 'ok',
                                              "$disk_name added to the pool");
                    my $ret = $box->run;
                    $box->destroy;
                    $done = 1;
                }
            }
            else # canceled
            {
                $done = 1;
            }
            $dlg->destroy;
        }
    }

    $self->refresh_disks;
}

sub on_refresh_disks_button_clicked
{
    my ($self) = @_;
    
    $self->refresh_disks;
}

sub on_crash_list_button_press_event
{
    my ($self, $widget, $event) = @_;

    # If it's not an RMB click, pass the event through to the list's handler
    if ($event->button != 3) {
        return 0;
    }

    # Build the menu items
    my $menu = Gtk2::Menu->new;
    my @menu_labels;

    my $selection = $self->wgt('crash_list')->get_selection or return 1;
    my ($model, $iter) = $selection->get_selected or return 1;
    my $row_data = $model->get($iter, 6);

    switch ($row_data->{user_status})
    {
        case "Checked Out"      { push @menu_labels, "Check In"; }
        case "Renewal Approved" { push @menu_labels, "Check In"; }
        case "Assigned"         { push @menu_labels, "Check Out"; }
        case "Checked In"       { push @menu_labels, "Make Available"; }
        case "Renewal Pending"  { push @menu_labels, "Approve Renewal",
                                                     "Reject Renewal"; }
    }
    push @menu_labels, "Admin Mode", "View Log";

    # Build the popup menu
    foreach my $ml (@menu_labels)
    {
        my $menu_item;
        $menu_item = Gtk2::MenuItem->new_with_label($ml);
        $menu_item->show;
        $menu->append($menu_item);
        $menu_item->signal_connect("activate", \&Crash_Window::select_popup,
                                   [$self, $ml, $row_data]);
    }

    # pop the menu up
    $menu->popup(undef, undef, undef, undef, $event->button, $event->time);

    return 1;
}

sub select_popup
{
    my ($widget) = $_[0];
    my ($self, $choice, $row_data) = @{$_[1]};

    switch ($choice)    
    {
        case "Admin Mode"       {
            Admin_Window->run($row_data);
        }
        case "View Log"         {
            Log_Window->run($row_data->{disk_name});
        }
        case "Check In"         {
            $self->wgt('statusbar')->push(1, "Checking In...");
            $self->do_db_stuff("in", $row_data);
            $self->wgt('statusbar')->pop(1);
        }
        case "Check Out"        {
            $self->wgt('statusbar')->push(1, "Checking Out...");
            $self->do_db_stuff("out", $row_data);
            $self->wgt('statusbar')->pop(1);
        }
        case "Make Available"   {
            $self->wgt('statusbar')->push(1, "Making Available...");
            $self->do_db_stuff("make_available", $row_data);
            $self->wgt('statusbar')->pop(1);
        }
        case "Approve Renewal"  {
            $self->wgt('statusbar')->push(1, "Approving Renewal...");
            $self->do_db_stuff("approve_renewal", $row_data);
            $self->wgt('statusbar')->pop(1);
        }
        case "Reject Renewal"  {
            $self->wgt('statusbar')->push(1, "Rejecting Renewal...");
            $self->do_db_stuff("reject_renewal", $row_data);
            $self->wgt('statusbar')->pop(1);
        }
        else                   {
            return;
        }
    }

    $self->refresh_disks;
}

sub on_crash_list_selection_changed
{
    my ($self, $selection, $event) = @_;
    
    my ($model, $iter) = $selection->get_selected;

    if ($iter)
    {
        my $buf = $self->wgt('crash_text')->get_buffer;
        my ($start, $end) = $buf->get_bounds;
        $buf->delete($start, $end);
        my $row_data = $model->get($iter, 6);
        $buf->insert($start, "$row_data->{user_status}: $row_data->{comment}");
        $self->wgt('statusbar')->pop(2);
        $self->wgt('statusbar')->push(2, "Disk: $row_data->{disk_name}");
    } else {
        $self->wgt('statusbar')->pop(2);
    }
        
    1; # Handled
}

sub do_db_stuff
{
    my ($self, $command, $row) = @_;

    my $disk = $row->{disk_name};
    my $disk_status = $row->{disk_status};
    my $user_status = $row->{user_status};
    my $key_status = $row->{key_status};
    my $user_name = $row->{user_name};
    my $due_date = $row->{due_date};
    my $comment = $row->{comment};

    switch ($command)
    {
        case "in"           { Crashbackend::check_in_disk($disk,
                                                          $user_name) }
        case "out"          {
            my $new_due_date = Date_Window->run;
            Crashbackend::check_out_disk($disk, $user_name,
                                     $new_due_date, $comment) if $new_due_date;
        }
        case "make_available"   { Crashbackend::make_disk_available($disk); }
        case "approve_renewal"  {
            my $new_date = Crashbackend::validate_and_remove_renewal($disk);
            if (!$new_date)
            {
                my $b = Gtk2::MessageDialog->new(
                            undef,'destroy-with-parent',
                            'error','ok',
                            "Renewal was not found in the database!\n\n".
                            "Try fixing the user status in admin mode");
                $b->run;
                $b->destroy;
            }
            else
            {
                $comment .= " / Renewal Approved by $WHOAMI on " .
                            Date::Format::time2str("%c", time);
                Crashbackend::approve_disk_renewal($disk, $user_name, $new_date,
                                                   $comment);
            }
        }
        case "reject_renewal"   {
            my $new_date = Crashbackend::validate_and_remove_renewal($disk);
            if (!$new_date)
            {
                my $b = Gtk2::MessageDialog->new(
                            undef,'destroy-with-parent',
                            'error','ok',
                            "Renewal was not found in the database!\n\n".
                            "Try fixing the user status in admin mode");
                $b->run;
                $b->destroy;
            }
            else
            {
                $comment .= " / Renewal Rejected by $WHOAMI on " .
                            Date::Format::time2str("%c", time);
                Crashbackend::reject_disk_renewal($disk, $user_name, $new_date,
                                                  $comment);
            }
        } 
    }
}

############################################################
# on_view_wait_list_button_clicked
# Event handler
sub on_view_wait_list_button_clicked
{
    Wait_List_Window->run;
}

sub refresh_disks
{
    my ($self) = @_;
    
    # Write message to statusbar
    my $sb = $self->wgt('statusbar');
    $sb->push(1, "Loading disks...");

    # Read disk info from DB
    my @disks = Crashbackend::read_db_disks;

    my $crash_list = $self->wgt('crash_list');
    my $crash_store = $self->{crash_store};

    # Clear and refill the list store with the new data
    $crash_store->clear;
    foreach my $row (@disks)
    {
        $row->{due_date} =~ s/(\d+)-0*(\d+)-0*(\d+)/$2\/$3\/$1/;
        my $row_iter = $crash_store->append;
        $crash_store->set($row_iter,
                          0, $row->{disk_name},
                          1, $row->{disk_status},
                          2, $row->{key_status},
                          3, $row->{user_status},
                          4, $row->{user_name},
                          5, $row->{due_date},
                          6, $row);
    }

    # Remove message from statusbar
    $sb->pop(1);
}

sub crash_list_cell_renderer_func
{
    my ($column, $cell, $model, $iter, $colnum) = @_;

    my @row = $model->get($iter);
    # Col 6 is a hashref containing the row data from the db
    my $row_data = $row[6];

    # Set colors based on status.
    my ($front, $back);

    if      ($row_data->{disk_status} eq "Broken") {
            $front = 'red';
    } elsif ($row_data->{disk_status} eq "Lost") {
            $front = 'darkgreen';
    } elsif ($row_data->{key_status} eq "Lost") {
            $front = 'orange';
    } elsif ($row_data->{user_status} eq "Renewal Pending") {
            $front = 'darkcyan';
    } elsif ($row_data->{user_status} eq "Renewal Rejected") {
            $front = 'black';
            $back = 'misty rose';
    } elsif ($row_data->{user_status} eq "Renewal Approved") {
            $front = 'blue';
    } elsif ($row_data->{user_status} eq "Checked Out") {
            $front = 'purple';
    } elsif ($row_data->{user_status} eq "Assigned") {
            $front = 'black';
            $back = 'LightYellow';
    } elsif ($row_data->{user_status} eq "Checked In") {
            $front = 'black';
            $back = 'LightSteelBlue';
    } else {
            # default
            $front = 'black';
    }

    # override all this for overdue
    my $due_date = $row_data->{due_date};
    my $ddate = Time::ParseDate::parsedate("$due_date");
    my $now = localtime;
    my $curdate = Time::ParseDate::parsedate("$now");
    if (($curdate > $ddate) && ($row_data->{user_name} ne "")) {
            #overdue!
            $front = 'black';
            $back = 'red';
    }

    # Set the properties on the cell renderer
    $cell->set_property('foreground-set', 1);
    $cell->set_property('background-set', 1);
    $cell->set_property('foreground', $front);
    $cell->set_property('background', $back);
    $cell->set_property('text', $row[$colnum]);
}

sub wgt { return $_[0]->{xml}->get_widget($_[1]); }

1;

