#!/s/std/bin/perl

package Crashbackend;

use strict;
use CSL::DBs;
use CSL::Pg;
use DBI;
use Time::ParseDate;

use Mail::Internet;

my $db = $CSL::DBs::csdb;

# FIXME: Is it better to have a persistent database_handle or to spawn a new
#        one all the time??  Talk to Peter about this.

######## db_disk_set ########
#
# Takes parameters of the disk to set and all the stuff for the disk
#
#############################

sub db_disk_set {
	my ($disk_name, $disk_status, $user_status, $key_status, $user_name, $due_date, $comment) = @_;
	# postgres needs the due date handled like this.
	if ($due_date) {
		$due_date = "'$due_date'"; # quote a real due date
	} else {
		$due_date = "NULL"; # set to NULL with no quotes for empty
	}
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	# action will set everything specified in params
	# note due_date is not in quotes because of how it is set above.
	$comment =~ s/(\'|\"|\\)/\\$1/g;
	my $action = "update disks set disk_status = '$disk_status', user_status = '$user_status', key_status = '$key_status', user_name = '$user_name', due_date = $due_date, comment = '$comment' where disk_name = '$disk_name'";
	# UNCOMMENT NEXT LINE FOR DEBUG
	#print "setting disk $disk_name to disk_status=\"$disk_status\", user_status=\"$user_status\", key_status=\"$key_status\", user_name=\"$user_name\", due_date=\"$due_date\", comment=\"$comment\"\n";
	my $sh = $dbh->prepare($action);
	$sh->execute();
	$dbh->disconnect();
}

####### read_db_disks ##########
#
# Takes no params, returns an array of hashes containing all the crash disks
# 
################################

sub read_db_disks {
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select * from disks order by disk_name";
	my $sh = $dbh->prepare($command);
	$sh->execute();

	my @disks;
	
	while (my $row = $sh->fetchrow_hashref()) {
		push @disks, $row; # push the hash into our array
	}
	$dbh->disconnect();
	return @disks;
}

####### get_overdue_disks ########
#
# Takes no params, returns an array of hashes containing all overdue disks
#
##################################

sub get_overdue_disks {
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select * from disks where due_date < current_timestamp";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	my @disks;
	while (my $row = $sh->fetchrow_hashref()) {
		push @disks, $row;
	}
	$dbh->disconnect();
	return @disks;
}

###### get_disk #########
#
# Takes param of string of disk wanted (e.g. "crash10") and returns the disk
#   as a hash retaining all info.
#
#########################

sub get_disk {
	my ($disk_wanted) = @_;
	my @disks = &read_db_disks();
	foreach my $disk (@disks) {
		if ($disk->{disk_name} eq $disk_wanted) {
			return $disk;
		}
	}

	return 0;
}

####### add_disk ########
# 
# Takes param of the disk name to add and inserts it into the DB
#   Also logs it.
#
#########################

sub add_disk {
	my ($disk_name) = @_;
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	#my $command = "select max(disk_id) from disks;";
	#my $sh = $dbh->prepare($command);
	#$sh->execute();
	#my $high_id = $sh->fetchrow_hashref->{max};
	#my $new_id = $high_id + 1;
	my $command = "insert into disks values ('$disk_name', NULL, 'Good', 'Available', 'Available', NULL, NULL)";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	$dbh->disconnect();
	&log_action($disk_name, "", "Added disk to pool");
}

######## delete_disk #########
#
# Takes param of the disk name to remove from the pool.  Removes from DB
#   Also logs action
#
##############################

sub delete_disk {
	my ($disk_name) = @_;
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "delete from disks where disk_name = '$disk_name'";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	$dbh->disconnect();
	&log_action($disk_name, "", "Deleted disk from pool");
}

sub validate_and_remove_renewal {
	my ($disk_wanted) = @_;

	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select due_date from pending where disk_name = '$disk_wanted'";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	
	my $row = $sh->fetchrow_hashref();
	unless ($row) {
		$dbh->disconnect();
		return undef;
	}
	my $date = $row->{due_date};
	$command = "delete from pending where disk_name = '$disk_wanted'";
	$sh = $dbh->prepare($command);
	$sh->execute();
	$dbh->disconnect();

	return $date;
}

sub log_action {
	my ($disk, $user, $comment) = @_;

	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	chomp(my $WHOAMI = getpwuid $>);
	my $command = "insert into log values ('$disk', '$user', '$comment', current_timestamp, '$WHOAMI')";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	$dbh->disconnect();
}

sub read_log {
	my ($disk) = @_;

	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select * from log where disk_name = '$disk'";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	my @log_entries;
	while (my $row = $sh->fetchrow_hashref()) {
		push @log_entries, $row;
	}
	$dbh->disconnect();
	# return it in reversed (most recent first) form
	return (reverse @log_entries);
}

sub check_in_disk {
	my ($disk_name, $user_name) = @_;

	&db_disk_set($disk_name, 'Good', 'Checked In', 'Available', '', '', 'Needs to be erased');
	&log_action($disk_name, $user_name, "Disk checked in via crashman3");
}

sub check_out_disk {
	my ($disk_name, $user_name, $due_date, $comment) = @_;

	&db_disk_set($disk_name, 'Good', 'Checked Out', 'Available', $user_name, $due_date, $comment);
	&log_action($disk_name, $user_name, "Disk checked out via crashman3");
}

sub get_wait_list {
	# args??
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select * from waitlist order by date";
	my $sh = $dbh->prepare($command);
	$sh->execute();

	my @waitlist_entries;

	while (my $row = $sh->fetchrow_hashref()) {
		push @waitlist_entries, $row;
	}
	$dbh->disconnect();

	return @waitlist_entries;
}

	

sub make_disk_available {
	my ($disk_name) = @_; # we throw some stuff out, but no disk should be
						  # made available unless it works and we have a key.
						  # This currently is only called via the right-click
						  # menu anyways.

	&db_disk_set($disk_name, 'Good', 'Available', 'Available', '', '', '');
	&log_action($disk_name, '', "Disk made available via crashman3");

	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");

	my @waitlist = &get_wait_list();
	
	my $waitlist_entry = @waitlist[0];
	if ($waitlist_entry) {
		# assign the disk to that person
		my $user_name = $waitlist_entry->{user_name};
		&db_disk_set($disk_name, 'Good', 'Assigned', 'Available', $user_name, '', $waitlist_entry->{comment});
		&log_action($disk_name, $user_name, "Disk automatically assigned upon \"Make Available\" in crashman3");
	
		# delete it from the waiting list table
		
		my $command = "delete from waitlist where user_name = '$user_name'";
		my $sh = $dbh->prepare($command);
		$sh->execute();

		# mail the user
		#$command = "select * from crashmaster";
		#$sh = $dbh->prepare($command);
		#$sh->execute();
		
		#my $crashmaster = $sh->fetchrow_hashref()->{user_name};
		#my $crashmaster = 'stefan@cs.wisc.edu';
		#while($sh->fetchrow_hashref()) {} # why the fuck is this needed??
										  # we get 
										  # DBI::db=HASH(0x929a140)->disconnect invalidates 1 active statement handle (either destroy statement handles or call finish on them before disconnecting) at /u/s/t/stefan/cvsstuff/crashman-2.0/src/Crashbackend.pm line 251. 
										  # errors otherwise.
		# set up a req
		my $file = "/tmp/crash.message";
		open FH, "> $file";
		chomp(my $me = getpwuid $>);
		print FH "From: $me\@cs.wisc.edu
To: lab\@cs.wisc.edu
Subject: Crash disk for $user_name automatically assigned

A message will follow this that goes to the user.  Resolve this when the user picks the disk up.";
		close FH;
		my $line = `/s/req/bin/req -mcreate $file`;
		unlink $file;

		$line =~ /\(#(\d+)\) created/;
		my $req_num = $1;
		my $msg = new Mail::Internet;
		$msg->head->add("From", "crashmaster\@cs.wisc.edu");
		$msg->head->add("To", "$user_name\@cs.wisc.edu");
		$msg->head->add("Cc", 'lab@cs.wisc.edu');
		$msg->head->add("Bcc", "crashmaster\@cs.wisc.edu");
		$msg->head->add("X-Request-Do", "give crash");
		$msg->head->add("X-Request-No-Reply", "yes");
		$msg->head->add("Subject", "[CSL #$req_num] $user_name: your crash disk is ready");
		$msg->body(<<"EOF"
Hello $user_name,

You have been assigned the crash disk $disk_name.
Please come to the lab (CS Room 2350) during normal business hours
(8:00 A.M. - Noon, 1:00 P.M. - 5:00 P.M, Monday - Friday) to pick up your disk.

If you have any questions, please reply to this message and make sure to keep
the \"Cc: lab\@cs.wisc.edu\" header intact.

Thank you,
Crashman3 Automated Mailer
EOF
                          );
		$msg->smtpsend();
		
	}
	$dbh->disconnect();
	
}

sub approve_disk_renewal {
	my ($disk_name, $user_name, $due_date, $comment) = @_;

	&db_disk_set($disk_name, 'Good', 'Renewal Approved', 'Available', $user_name, $due_date, $comment);
	&log_action($disk_name, $user_name, "Renewal Approved via crashman3");
}

sub reject_disk_renewal {
	my ($disk_name, $user_name, $due_date, $comment) = @_;

	&db_disk_set($disk_name, 'Good', 'Renewal Rejected', 'Available', $user_name, $due_date, $comment);
	&log_action($disk_name, $user_name, "Renewal Rejected via crashman3");
}

sub get_end_semester_date {
	my $dbh = DBI->connect($db);
	$dbh->do("set search_path to crash");
	my $command = "select * from calendar where end_date >= current_date order by end_date";
	my $sh = $dbh->prepare($command);
	$sh->execute();
	my $final_date = $sh->fetchrow_hashref()->{end_date};

	return $final_date;
}

sub parse_due_date
{
    my ($due_date_text) = @_;

    return parsedate($due_date_text, DATE_REQUIRED => 1,
                                     NO_RELATIVE => 1);
}

sub late_due_date
{
    my ($due_date_text) = @_;
    my $due_date = parse_due_date($due_date_text);
    return undef if !$due_date;
    return 1
        if ($due_date > parsedate(&get_end_semester_date));
    return 0;
}

1;
