#!/usr/local/bin/perl

use strict;
use LWP;
use HTML::LinkExtor;
use URI::URL;
use File::Copy;
use FileHandle;
use Socket;
use POSIX qw(ctime);
use POSIX qw(mktime);
use POSIX qw(strftime);
use POSIX qw(ceil);
use POSIX ":sys_wait_h";

###
# Declarations and Initialization
my $CLASS_HOME = "/afs/cs/academic/class/15213-f01";
my $ASSN_HOME = "$CLASS_HOME/L7";
my $TESTS_HOME = "$ASSN_HOME/tests";
my $CORRECT_TESTS = "$TESTS_HOME";
my $CORRECTNESS_POINTS = 30;
my $THREADING_POINTS = 25;
my $PROXY_LOGFILE;
my $PORT = 3128;
my $PWD = `pwd`;
my $MYLOG;
my $current_time = time;
my ($i, @ctests);
my %options = ('debug' => 1);
my $PID;
my $PART1 = 0;
my $PART2 = 0;
my $correctness_score;
my $threading_score;
my $cmd;
my $testcase;

###
# Objects
my $UA = new LWP::UserAgent;
my $LE = HTML::LinkExtor->new();

$UA->agent("Mozilla/8.0");
$UA->timeout(60);


###
# Print usage information
sub print_usage{
  die "usage: ./driver.pl [port_number] [proxy-log] [lab-part]\n";
}

###
# Reads an entire file.
sub read_file
{
  my $file = shift;
  my $contents;

  open MYFILE, "< $file" or return undef;
  $contents = "";
  while(<MYFILE>) {
    $contents .= $_;
  }
  close MYFILE;

  return $contents;
}

###
# partners()
#
# Returns the Andrew IDs of the two students specified as partners in proxy.c
# Also returns the team name
sub team
{
  my $dir = shift;
  my $team = "";
  my ($latest_file, $file, $file_contents, $temp, $temp2);
  
  $file_contents = read_file("proxy.c");

  $temp = index $file_contents, "{", (index $file_contents, "team_struct");
  $temp2 = substr $file_contents, $temp + 1, ((index $file_contents, "}", $temp) - $temp - 1);

  my @struct_lines = split /\n/, $temp2;
  $temp = 0;
  foreach $temp2 (@struct_lines) {
    chomp $temp2;
    if($temp2 =~ /\"(.*)\"(,|;)?/) {
      ++$temp;
      if($temp == 2) {
        $team = $1;
      }
      if ($temp == 4){
	$team .= "+";
	$team .= $1;
      }
    }
  }
  
  return $team;
}


sub run_proxy
{
  my $x;
  
  ###
  # Remove the logfile
#  unlink $PROXY_LOGFILE;
  
  ###
  # Set up the UA correctly
  $UA->proxy('http', "http://localhost:$PORT/");
  
  ###
  # Run the proxy
  my $pid = fork;
  return -1 if(!defined $pid);
  
  if(!$pid) {
    print "./proxy $PORT\n";
    exec ("./proxy $PORT");
    exit 10;
  } else {
    # Wait a little while and make sure it didn't crash up front
    sleep 3;
    $x = waitpid(-1, &WNOHANG);
    if ($x == $pid){
      print "Oops, proxy crashed! exit this test...\n";
      return -1;
    }
  }
  
  return $pid;
}

sub parse_test
{
  my $file = shift;
  my $url;
  my $method;
  my @headers = ();
  
  open FILE, "< $file";
  while(<FILE>) {
    chomp;
    last if($_ eq "");
    if($_ =~ /(\S+):\s+(.*)/) {
      push @headers, ($1 => $2);
    } elsif($_ =~ /(\S+)\s+(\S+)(\s+.*)?$/) {
      $method = $1;
      $url = $2;
    } else {
      die ("Invalid line: $_");
    }
  }
  close FILE;
  
  return ($method, $url, @headers);
}

sub gradeof
{
  my ($crashes, %results) = @_;
  my $grade = 0;
  my @keys = keys %results;
  my $frac = 1/($#keys + 1);
  my $k;
  
  foreach $k (@keys) {
    $grade += $frac if($results{$k} == 1);
  }

  $grade -= $frac*$crashes;
  $grade = 0 if($grade < 0); # Well, it *could* happen...

  return $grade;
}

sub test_correctness
{
  my $crashes = 0;
  my $logfile = "logfile";
  my $existlog = 1;
  my ($testname,$addr, $url, $method, $response, $expected, $temp, $i, $headerobj, @headers, %results, @lines);
  
  print "\n  Testing Correctness...\n" if($options{'debug'});
  
  $PID = run_proxy();
  return -1 if($PID == -1);
  
  ###
  # For each test
  #   Send the request to the proxy
  #   Retrieve the response
  #   Diff between response and answer
  #   Check that the proxy hasn't exited/crashed
  #     If so, ++crash, start the proxy again
  foreach $testname (@ctests) {
    #    sleep(3);
    print "    Testing: $testname... " if($options{'debug'});
    
    ($method, $url, @headers) = parse_test("$CORRECT_TESTS/$testname.tst");
    $expected = read_file("$CORRECT_TESTS/$testname.ans");
    next if(!(defined $expected) || !(defined $url));
    
    # Send the request
    $headerobj = new HTTP::Headers(@headers);
    $response = $UA->request(HTTP::Request->new($method => $url, $headerobj));
    
    # Compare
    if($response->content() ne $expected){
      $results{$testname} = 0;
    }else{
      $results{$testname} = 1;
    }
    
    
    # Test crash?
    $temp = waitpid(-1, &WNOHANG);
    if($temp == $PID) {
      ++$crashes;
      $PORT ++;
      print "Oops, proxy crashed! restarting...\n";
      $results{$testname} = 0;
      $PID = run_proxy();
      last if($PID == -1);
    }
    
    if ($results{$testname} == 1){
      print "passed\n" if($options{'debug'});
    }else{
      print "did not pass\n" if ($options{'debug'});
    }
  }
  
  return -1 if($PID == -1);
  
  ###
  # Test log file correctness
  # Just checking the last line should be good enough...
  print "    Testing log file... ";
  sleep(2);
  open(LOG, "< $PROXY_LOGFILE") or $existlog = 0;
    
  if ($existlog == 0){
    $results{$logfile} = 0;
    print "did not pass\n";
    return gradeof($crashes, %results);
  };

  @lines = <LOG>;
  if ($lines[$#lines] =~ /^\d+\-\d+\-\d+ \d+\-\d+\-\d+ \d+\.\d+\.\d+\.\d+ http\:\/\/www\-2\.cs\.cmu\.edu\/afs\/cs\/academic\/class\/15213-f00\/www\/ 2841.*/){
    $results{$logfile} = 1;
    print "passed\n";
  }else{
    $results{$logfile} = 0;
    print "did not pass\n";
  }

  
  ###
  # Test whether proxy handles SIGPIPE signal
  print "    Testing SIGPIPE signal handling ...";
  socket SOCK, AF_INET, SOCK_STREAM, getprotobyname('tcp');
  $addr = sockaddr_in($PORT, inet_aton("127.0.0.1"));
  connect SOCK, $addr;
  print SOCK "GET http://www.ietf.org/rfc/rfc2068.txt HTTP/1.0\r\n\r\n";
  close SOCK;

  sleep(5);

  # Test crash?
  $temp = waitpid(-1, &WNOHANG);
  if($temp == $PID) {
    ++$crashes;
    $PORT ++;
    print "Oops, proxy crashed! did not pass\n";
    if ($PART2 != 0){
      print "Restarting proxy...\n";
      $PID = run_proxy();
      last if($PID == -1);
    }
    $results{sigpipe} = 0;
  }else{
    $results{sigpipe} = 1;
    print "passed\n";
  }

  print "\n";
  close LOG;

###
# finish test if no part2
  if ($PART2 == 0){
    kill 9, $PID;
  }


  return gradeof($crashes, %results);
}

sub test_threading
{
  my $crashes = 0;
  my ($testname, $addr, $url, $method, $response, $expected, $temp, $i, $headerobj, @headers, %results);
  

  if (($PART1 == 0) || ($PID == -1)){
    $PID = run_proxy();
    return -1 if($PID == -1);
  }

  print "\n  Testing Threading... " if($options{'debug'});
  
  
  ###
  # Connect to the proxy, print a little, but not all of what we want
  socket SOCK, AF_INET, SOCK_STREAM, getprotobyname('tcp');
  $addr = sockaddr_in($PORT, inet_aton("127.0.0.1"));
  connect SOCK, $addr;
  print SOCK "GET ";
  
  ###
  # Now have the agent get the test
  $testname = $ctests[0];
  ($method, $url, @headers) = parse_test("$CORRECT_TESTS/$testname.tst");
  $expected = read_file("$CORRECT_TESTS/$testname.ans");
  next if(!(defined $expected) || !(defined $url));
  
  # Send the request
  $headerobj = new HTTP::Headers(@headers);
  $response = $UA->request(HTTP::Request->new($method => $url, $headerobj));

  # Compare
  if ($response->content() ne $expected){
    $results{$testname} = 0;
  }else{
    $results{$testname} = 1;
  }

  # Test crash?
  $temp = waitpid(-1, &WNOHANG);
  if($temp == $PID) {

    ++$crashes;
    print "Oops, proxy crashed!\n";
    $results{$testname} = 0;
    close SOCK;

  }else{
  
    ###
    # finish our first request
    ###
    print SOCK "http://www.google.com HTTP/1.0\r\n\r\n";
    sleep(3);
    close SOCK;
    kill 9, $PID;
  }

  if ($results{$testname} == 1){
    print "passed\n";
  }else{
    print "did not pass\n";
  }
  print ("\n");

  return -1 if($PID == -1);
  return gradeof($crashes, %results);
}

###
# Main program
if($#ARGV < 2){
  print_usage();
}
$PORT = $ARGV[0];
if ($ARGV[2] eq "part1"){
  $PART1 = 1;
}else{
  if ($ARGV[2] eq "part2"){
    $PART2 = 1;
  }else{
    if ($ARGV[2] eq "all"){
      $PART1 = 1;
      $PART2 = 1;
    }else{
      print_usage();
    }
  }
}
$PROXY_LOGFILE = $ARGV[1];

chomp $PWD;
autoflush STDOUT 1;

my $team = team();
###
# Find all correctness tests
@ctests = map { if($_ =~ /$CORRECT_TESTS\/(.*)\.tst/) { $1; } else { $_; } } glob "$CORRECT_TESTS/*.tst";

if ($PART1 == 1){
  
  foreach $testcase (@ctests) {
    print "Correctness Test Found: $testcase\n";
  }

  for($i = 0; $i <= $#ctests; ++$i) {
#    print "Correctness Test Found: $ctests[$i]\n";
    if(!(-e "$CORRECT_TESTS/$ctests[$i].ans")) {
      splice @ctests, $i, 1;
    }
  }

  $correctness_score = ceil(test_correctness()*$CORRECTNESS_POINTS);
  if ($correctness_score > $CORRECTNESS_POINTS){
    $correctness_score = $CORRECTNESS_POINTS;
  }
  exit 1 if($correctness_score < 0);
}

if ($PART2 == 1){
  $threading_score = ceil(test_threading()*$THREADING_POINTS);
  if ($threading_score > $THREADING_POINTS){
    $threading_score = $THREADING_POINTS;
  }
  exit 2 if($threading_score < 0);
}

if ($PART1 == 1){
  print "$team:\tcorrectness_score = $correctness_score\n";
}

if ($PART2 == 1){
  print "$team:\tthreading_score = $threading_score\n";
}

#open WEB, ">> $WEBLOG";
#print  WEB "$current_time <tr> <td bgcolor=#EEEEEE> $team </td> ";
#print  WEB "$current_time <tr> <td bgcolor=#EEEEEE> </td> ";
#print  WEB "<td bgcolor=#EEEEEE> $correctness_score </td> ";
#print  WEB "<td bgcolor=#EEEEEE> $threading_score </td> ";
#printf WEB "<td bgcolor=#EEEEEE> %.4f </td> </tr>\n", $cacheing_score*100;
#close WEB;
