#!/usr/local/bin/perl -w

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-f02";
my $ASSN_HOME = "$CLASS_HOME/L7/";
my $TESTS_HOME = "$ASSN_HOME/tests/";

my $PROXY_LOGFILE;
my $PROXY_FILTER = "proxy.filter";
my $PROXY_FORWARD = "$TESTS_HOME/proxy.forward";
my $PROXY_TEST = "proxy.test";

my @filter_tests = ();
my @forward_tests = ();

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 student specified 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 gradeof {
    my ($crashes, $total) = @_;
    return $total - $crashes;
}

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 check_log_entry {
    my $log_entry = shift;
    my $url = shift;

    return 1 if ($log_entry =~ /(\d+\.\d+\.\d+\.\d+)\s+$url\s+\d+/);
    return 0;
}

sub do_request {
    my $url = shift;

    # Send the request
    my $headerobj = new HTTP::Headers();
    my $response = $UA->request(HTTP::Request->new("GET" => $url, $headerobj));
    my $reply = $response->content();
    return $reply;
}

sub test_filtering {
    my $needed = shift;
    my $passed=0;
    my $failed=0;
    my ($tests, $tried);
    my $random = rand(20000) % 6;

    @filter_tests = ();

    open FILTER_FILE, $PROXY_FILTER;
    while (<FILTER_FILE>) {
	chomp;
	$tests++;

#        if ($tests % 10 == $random) {
	    push @filter_tests, $_;
	    print "\t Request $_ ...\n";
	    my $reply = do_request $_;
	    if ($reply =~ /You are not allowed to access this web page./i) {
		$passed++;
		print "\t \t passed\n";
	    }
	    else {
		print "\t \t did not pass\n";
                $failed++;
	    }
	    $tried++;
#	    last if ($tried == $needed);
#        }
    }
    close FILTER_FILE;

    return $failed;
}

sub test_forwarding {
    my $needed = shift;
    my ($passed, $tried);
    my $random = rand(20000) % 10;

    @forward_tests = ();

    open FORWARD_FILE, $PROXY_FORWARD;
    while (<FORWARD_FILE>) {
	chomp;

	my ($url, $filename) = split;

	push @forward_tests, $url;
	print "\t Request $url ...\n";
	my $reply = do_request($url);
	my $expected = read_file $TESTS_HOME.$filename;
	if ($reply ne $expected) {
	    print "\t \t did not pass\n";
	}
	else {
	    $passed++;
	    print "\t \t passed\n";
	}
	$tried++;

	last if ($tried == $needed);
    }
    close FORWARD_FILE;

    return $passed;
}

sub test_logfile {
    my $score;
    sleep(2);
    my $existlog = 1;
    open(LOG, "< $PROXY_LOGFILE") or $existlog = 0;

    if ($existlog == 0) {
	print "\t \t did not pass\n";
    }
    else {
	# Just checking the last line should be good enough...
	my @lines = <LOG>;
        chomp @lines;
	if (check_log_entry($lines[$#lines], $forward_tests[$#forward_tests])) {
	    $score += 6;
	    print "\t \t passed\n";
	} else {
	    $score += 0;
	    print "\t \t did not pass\n";
	}
    }
    close LOG;
    return $score;
}

sub test_displaylog {

    open(FILE, "display.log");
    my $line = 0;
    my $highest = 0;

    $line = <FILE>;

    if ($line != 1) {
        print "1st line is not 1\n";
        return 0;
    }

    my $last_line = $line;

    while ($line = <FILE>) {

           if ($line !~ /^\d+$/) { 
               print "line has more than 1 number\n";
               return 0;
           }

           if ($line != $last_line + 1  and $line != $last_line - 1) {
              print "difference with previous line is not 1\n";
              return 0;
           }

           if ($line < 0) {
              print "negative entry\n";
              return 0;
           }

           $last_line = $line;

           if ($last_line > $highest) {
               $highest = $last_line;
           }
    }

    if ($last_line != 0) {
        print "last line is not 0\n";
        return 0;
    }
    
    if ($highest < 7) {
        print "your proxy does not handle concurrent connections\n";
        return 0;
    }

    return 10;
}



############################################################
#
# PART I: test correctness
#
#    Grading criteria for correctness (total 30)
#       10   filtering
#       10   forwarding
#       6    logging
#       4    SIGPIPE handling
############################################################

sub test_correctness {
    $PID = run_proxy();
    return -1 if($PID == -1);

    print "\nTesting Correctness...\n" if($options{'debug'});

    print "\n  1. Testing filtering...\n" if($options{'debug'});
    my $tmp_score = test_filtering(10);

    my $score = 10 - $tmp_score;

    if ($score < 0) {
        $score = 0; 
    }

    print "\n  2. Testing forwarding...\n" if($options{'debug'});
    $score += test_forwarding(10);

    print "\n  3. Testing crashing...\n" if ($options{'debug'});
    my $temp = waitpid(-1, &WNOHANG);
    if ($temp == $PID) {
	$PORT ++;
	print "Oops, proxy crashed! restarting...\n";
	last if($PID == -1);
	print "\t \t did not pass\n" if ($options{'debug'});
    }
    else {
	print "\t \t passed\n" if($options{'debug'});
    }
    return -1 if($PID == -1);

    print "\n  4. Testing log file...\n";
#    $score += test_logfile; 
	
    print "\n  5. Testing SIGPIPE signal handling ...\n";
    socket SOCK, AF_INET, SOCK_STREAM, getprotobyname('tcp');
    my $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) {
	$PORT ++;
	print "\t \t Oops, proxy crashed! did not pass\n";
    } else {
	$score += 4;
	kill 9, $PID;
	print "\t \t passed\n";
    }
    $PORT++;

    return $score;
}

############################################################
#
# PART II: test threading
#
#   Grading criteria for threading
#       2  filtering
#       2  forwarding
#       6  concurrency handling
#      10  proper serialization of display variable
#
############################################################
sub test_threading {
    $PID = run_proxy();
    return -1 if($PID == -1);

    print "\nTesting 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');
    my $addr = sockaddr_in($PORT, inet_aton("127.0.0.1"));
    connect SOCK, $addr;
    print SOCK "GET ";

    my $tmp_score = 0;
    
    $tmp_score = test_filtering(2);

    my $passed = 0;

    $passed = 2 - $tmp_score;

    if ($passed < 0) {
        $passed = 0; 
    }

    $passed += test_forwarding(2);

    # Test crash?
    my $temp = waitpid(-1, &WNOHANG);
    if ($temp == $PID) {
	print "\t \t Oops, proxy crashed!\n";
	close SOCK;
    } else {
	###
	# finish our first request
	###
	print SOCK "http://www.cs.cmu.edu/~droh HTTP/1.0\r\n\r\n";
	sleep(3);
	close SOCK;

	$passed += 6;
	kill 9, $PID;
        sleep(3);
    }
    
    $PID = run_proxy();
    return -1 if($PID == -1);
    print "creating concurrent connections using lynx\n";
    `$TESTS_HOME/test_concurrent.sh $PORT`;    
    print "waiting 45 seconds for all the connections to end\n";
    sleep(45);
    kill 9, $PID;    
    print "testing that numbers in display.log are properly serialized\n";
    $passed += test_displaylog;

    return $passed;
}

###
# 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();

# test the correctness
if ($PART1 == 1) {
    $correctness_score = test_correctness();
}

if ($PART2 == 1) {
    $threading_score = test_threading();
}

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;
