# This module contains all of our input / output methods
package ZeddIO;

# ------------- Package Definition / Export -------------------
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);

use Exporter;
$VERSION = 1.00;              # Or higher
@ISA = qw(Exporter);

# Symbols to autoexport (:DEFAULT tag)
@EXPORT      = qw( getUrlAsString parse_URL open_TCP 
		   getUrlBySocket postForUrl 
		   createFile updateFile readFile postItem
		   getItem ); 
# Symbols to export on request
@EXPORT_OK   = qw( getUrlAsString parse_URL open_TCP 
		   getUrlBySocket postForUrl 
		   createFile updateFile readFile postItem
		   getItem );

# Non-exported Global Vars
use vars qw( %postedItems_g );  

# ------------- Package Definition / Export -------------------
     
use ZeddWeb;
use ZeddUtil ( 'absolutify_urls' );

use Socket;
use Getopt::Std;
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Response;
use HTTP::Request::Common qw(POST);

sub createFile
{
    my( $fileName ) = shift;
    my( $content ) = shift;
    my( $outHandle );

    # Open
    $fileName = "> $fileName";
    open( outHandle, $fileName );

    # print "Content: $content!\n";
	  
    # lock that file!
    lockFile( *outHandle );

    # update
    print outHandle $content;
    
    # Unlock
    unlockFile( *outHandle );

    # close
    close( outHandle );   
}

sub updateFile
{
    my( $fileName ) = shift;
    my( $content ) = shift;
    my( $outHandle );

    # Open
    $fileName = ">> $fileName";
    open( outHandle, $fileName );

    # lock that file!
    lockFile( *outHandle );

    # update
    print outHandle $content;

    # Unlock
    unlockFile( *outHandle );

    # close
    close( outHandle );
}

sub readFile
{
    my( $fileName ) = shift;
    my( $inHandle );
    my( $inValue );

    # Open the file    
    open( inHandle, $fileName );

    shareFile( *inHandle );

    $inValue = "";
    while( <inHandle> )
    {
	$inValue .= $_;
    }

    unlockFile( *inHandle );

    close( inHandle );
    
    return $inValue;
}

sub lockFile
{
    # We should have received a file handle ...
    my( $FH ) = shift;

    # 2 -> Exclusive Lock
    flock( $FH, 2 );    
}

sub unlockFile
{
    # We should have received a file handle ...
    my( $FH ) = shift;

    # 8 -> Unlock lock ..
    flock( $FH, 8 );
}

sub shareFile
{
    # We should have received a file handle ...
    my( $FH ) = shift;

    # Establish a shared lock -> 1
    flock( $FH, 1 );
}

sub open_TCP
{
    # get parameters
    my ($FS, $dest, $port) = @_;

    my $proto = getprotobyname('tcp');
    socket($FS, PF_INET, SOCK_STREAM, $proto);
    my $sin = sockaddr_in($port,inet_aton($dest));
    connect($FS,$sin) || return undef;

    my $old_fh = select($FS);
    $| = 1;                       # don't buffer output
    select($old_fh);
    1;
}
 
sub parse_URL
{
    # put URL into variable
    my ($URL) = @_;
    
    # attempt to parse.  Return undef if it didn't parse.
    (my @parsed = $URL =~ m@(\w+)://([^/:]+)(:\d*)?([^#]*)@) || return undef;
						    
    # remove colon from port number, even if it wasn't specified in the URL
    if( defined $parsed[2] )
    {
	$parsed[2]=~ s/^://;
    }
   
    # the path is "/" if one wasn't specified
    $parsed[3]='/' if ($parsed[0] =~ /http/i && (length $parsed[3]) == 0);

    # if port number was specified, we're done
    return @parsed if (defined $parsed[2]);

   # otherwise, assume port 80, and then we're done.
   $parsed[2] = 80;
						    
   @parsed;
}

sub getUrlBySocket
{
    my ($full_url) = shift;
    my ($responseString) = "";

    # if the URL isn't a full URL, assume that it is a http request
    $full_url="http://$full_url" if ($full_url !~
                                     m/(\w+):\/\/([^\/:]+)(:\d*)?([^\#]*)/);

    # break up URL into meaningful parts
    my @the_url = ZeddIO::parse_URL($full_url);
    if (!defined @the_url)
    {
        return ();
    }

    # we're only interested in HTTP URL's
    return if ($the_url[0] !~ m/http/i);

    # connect to server specified in 1st parameter
    if (!defined open_TCP('SOCKET', $the_url[1], $the_url[2]))
    {
        return ();
    }

    # request the path of the document to get
    print SOCKET "GET $the_url[3] HTTP/1.1\n";
    print SOCKET "Host: $the_url[1]\n";
    print SOCKET "Accept: */*\n";
    print SOCKET "User-Agent: zeddWeb/1.0\n\n";

    # print out server's response.

    # get the HTTP response line
    my $the_response = <SOCKET>;

    # get the header data
    while(<SOCKET>=~ m/^(\S.+):\s+(.+)$/)
    {
        # Eat and discard the header info
    }

    # get the entity body
    while (<SOCKET>)
    {
        $responseString .= $_;
    }

    # close the network connection
    close(SOCKET);

    return $responseString;
}

sub postForUrl
{

}

sub getUrlAsString
{
    my ($full_url) = shift;
    my @the_url = parse_URL($full_url);   

    ## First check the blackboard for the item in question ...
    my $item = getItem( $full_url, $the_url[1] );

    my $target = $the_url[0] . "://" . $the_url[1];
    return( absolutify_urls( $item, $target ) );
}

# Black board routines
sub postItem
{
    my($value) = shift;
    my($name) = shift;    
    my($timeout) = shift;

    ## ********* We should take out any // from the name ..
    $name =~ s/http\:\/\///gsi;
    $name =~ s/\///gsi;

    my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,
       $size,$atime,$mtime,$ctime,$blksize,$blocks);
    my($fileInfo);
        
    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks)   
	= stat "$ZeddWeb::zeddRoot_g/scratch/$name";

    if( defined( $mtime ) ) 
    {
	$fileInfo = readFile( "$ZeddWeb::zeddRoot_g/scratch/$name" );
	
	if( $fileInfo =~ /^ZeddBlackBoardEntry\{(.*?)\}(.*)/gsi )
	{
	    if( time() > $mtime + $1 ) 
	    {
		## Item is timed out do nothing.
	    }
	    else
	    {
		## Item is good .. update the timeout when writing
		$timeout = ( $mtime + $1 ) - time();
		$value = "ZeddBlackBoardEntry{$timeout}" . $value;
		createFile( "$ZeddWeb::zeddRoot_g/scratch/$name", $value );
	    }
	}
    }
    else
    {
	## Add a little annotation ...
	$value = "ZeddBlackBoardEntry{$timeout}" . $value;
	createFile( "$ZeddWeb::zeddRoot_g/scratch/$name", $value );
    }        
}

## URL flag means we can go out on the 
## net and look for the content ...
sub getItem
{
    my($fullName) = shift;
    my($target) = shift;
    my($ua) = new LWP::UserAgent;
    my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,
       $size,$atime,$mtime,$ctime,$blksize,$blocks);
    my($fileInfo);
    my($updateFlag) = 0;

    # Check 1 .. does the file exist ?   
    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks)   
                         = stat "$ZeddWeb::zeddRoot_g/scratch/$target";
    
    if( defined( $mtime ) ) 
    {	
	# File exists. Check 2, does it have an annotation ?
	$fileInfo = readFile( "$ZeddWeb::zeddRoot_g/scratch/$target" );	
	if( $fileInfo =~ /^ZeddBlackBoardEntry\{(.*?)\}(.*)/gsi )
	{
	    # We have an annotation, check to see if it has timed out
	    if( time() > $mtime + $1 ) 
	    {
		# Item is out of date ...
		#   remove the file and get a new one
		unlink( "$ZeddWeb::zeddRoot_g/scratch/$target" );

		$updateFlag = 1;		
	    } 
	    else 
	    {		
		# Item is still good
		$fileInfo = $2;
		$updateFlag = 0;
	    }
	}
	else
	{
	    # No annotation, maybe it was just a local file get ?
	    $updateFlag = 1;
	}
    } 
    else 
    {
	# File didn't exists, we'll have to get it ourselves.
	$updateFlag = 1;
    }


    if( $updateFlag == 1 )
    {
	## Add some checks to deal with timeouts and bad URLs
	$ua->timeout( $ZeddWeb::getTimeout_g );
	my $request = new HTTP::Request('GET', "$fullName");
	my $response = $ua->request($request);

	$fileInfo = $response->content;	
    }

    return $fileInfo;
}

1;

