# Setup our package
package ZeddWeb;


# 00Jun14 Joe MacDonald   modified run() to dump the static pages to
#                         $documentRoot_g/$staticDir_g/... instead of
#                         $zeddDir_g/static/...
#                         Added 'undefined' directive.

# ------------- 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( run logMsg blockStdout unblockStdout 
	      debugEnabled debugDisabled staticModeOn staticModeOff 
	      zeddRoot_g );
                   
%EXPORT_TAGS = ();

# Symbols to export on request
@EXPORT_OK  = qw( run logMsg blockStdout unblockStdout 
		  debugEnabled debugDisabled @systemArgs_g
		  staticModeOn staticModeOff
		  $extractResult_g @queryArgs_g $zeddRoot_g 
		  $currentURL_g $getTimeout_g);

# Exported Global Vars
use vars qw( @systemArgs_g $extractResult_g @queryArgs_g 
	     $zeddRoot_g $currentURL_g $getTimeout_g);

# Non-Exported Global Vars                   
use vars qw( %macros_g $activePage_g $cacheFileName_g
	     @systemMacros_g $debug_g %dependencies_g 
	     $bodyLine_g $tableFlags_g $host_g %pageInfoStruct_g 
	     $queryString_g $logFileName_g $equalCols_g 
	     %rowInfoStruct_g @preconditions_g 
	     @postconditions_g @resultArrayOfHashes_g 
	     $staticMode_g $duplicateAsStatic_g $gPage_g
	     $documentRoot_g $staticDir_g );

# ------------- Package Definition / Export -------------------

use ZeddIO ( 'getUrlAsString', 'createFile', 'updateFile', 'readFile', 'postItem', 'getItem' );
use ZeddUtil;

use Socket;
use Getopt::Std;
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Response;
use HTTP::Request::Common qw(POST);
require( 'tainted.pl' );

# *********************************************************
# *********************************************************

sub loadModule 
{
    eval "require $_[0]";
    $_[0]->import(@_[1 .. $#_]);    

    # $@ is non-zero if we had trouble loading the module
    if( $@ )
    {
	logMsg( $@ );
	# Couldn't load the module
	return 0;
    }
    else
    {
	# Loaded the module, let 'er rip.
	return 1;
    }
}

sub expandVariables 
{
    my ($searchString) = shift;
    my ($arguments) = shift;
    my ($targets) = shift;
    my (%replaceVals);

    my ($arg);

    my ($returnValue) = $searchString;
    my ($target);
    my ($index);

    if( ! defined( $targets ) )
    {
	$targets = \@systemArgs_g;
    }

    # Build the replace value hash
    $index = 0;
    for $target (@$targets)
    {
	$target =~ s/^\s*//;
	$target =~ s/\s*$//;
	
	$replaceVals{ $target } = $arguments->[$index];
	$index++;
    }

    # Now loop through checking for each target ...
    for $target (@$targets)
    {
	$target =~ s/^\s*//;
        $target =~ s/\s*$//;

	if( $returnValue =~ /(.*)$target(.*)/ )
	{
	    $returnValue =~ s/$target/$replaceVals{ $target }/g;
	}
    }

    if ( $returnValue =~ /(.*)\#extractResult\#(.*)/ ) 
    {
	if( $extractResult_g =~ /None/ ) 
	{
	} 
	else 
	{
	    $returnValue =~ s/\#extractResult\#/$extractResult_g/g;	    
	    $extractResult_g = "None";

	    logMsg( "======== Enter expandVariables ========" );
	    logMsg( "Expanded to: $returnValue" );
	    logMsg( "======== Exit expandVariables ========" );
	}
    }

    $returnValue =~ s/\s*$//;

    return $returnValue;
}

sub conditionProcessing
{
    # Grab all of the conditions
    my @conditions = @_;

    while( my $current = pop @conditions ) 
    {
	logMsg( "--> $current <--" );
	processZeddWebDirectives( $current );
    }
}

sub expandMacro 
{
    my ($directive) = shift;
    my ($arguments) = shift;
    my ($inFile) = shift;

    logMsg( "======== Enter expandMacro ========" );
    logMsg( "\n\nUser Macro: $directive" );

    my @args= @$arguments;
    my $macroHash = $macros_g{$directive};
    my $body = $macroHash->{'body'};

    my $targets = $macroHash->{'args'};
    my @targetArray = split /,/, $targets;
	    
    $body = expandVariables( $body, \@args, \@targetArray );

    logMsg( "\nexpandMacro: Expanded -> $body\n" );
    logMsg( "======== Exit expandMacro ========" );

    return $body;
}

sub performDirective 
{
    my ($directive) = shift;
    my ($arguments) = shift;
    my ($directives) = "";
    my ($argNum);
    my ($outLine);
    my ($iFile, $inValue);    
    my ($line);
    my ($getLine);
    my (@parms, $parm);
    my ($command);
    my ($getString); 
    my ($tempURL);
    my ($sub);
    my (@workingArray);
    my ($value, $numLines, $count);
    my ($tempHash, %tempHash, $currentHash, %currentHash, $resultHash);
    my ($exitKey );
    my ($cacheOutput, $postValue);
    
    logMsg( "======== Enter performDirective ========" );    

    $argNum = @$arguments;

    #    print "systemDirective, args: $argNum\n";
    #    print $arguments->[0];
    #    print $arguments->[1];

    if( $directive =~ /echo/ ) 
    {
	logMsg( " Echo: $arguments->[0]" );
	for( my $i = 0; $i < $argNum; $i++ )
	{
	    $gPage_g .= $arguments->[$i];
	    $gPage_g .= "\n";
	}
    }

    # Strictly for use in if statements ...
    if( $directive =~ /undefined/ )
    {
      $outLine = expandVariables( $arguments->[0], \@queryArgs_g );
      
      if( $outLine )
      {
         return 0;
      }
      else
      {
         return 1;
      }
    }

    if( $directive =~ /defined/ )
    {
      $outLine = expandVariables( $arguments->[0], \@queryArgs_g );
      
      if( $outLine )
      {
         return 1;
      }
      else
      {
         return 0;
      }
    }

    if( $directive =~ /print/ )
    {
        logMsg( "Print: $arguments->[0]" );

        for( my $i = 0; $i < $argNum; $i++ )
        {
            $outLine = expandVariables( $arguments->[$i], \@queryArgs_g );
            $gPage_g .= $outLine;
	    $gPage_g .= "\n";
        }
    }
    
    if( $directive =~ /include/ ) 
    {
	$iFile = $arguments->[0];
	$iFile =~ s/^\s*//;
	
	$iFile = expandVariables( $iFile, \@queryArgs_g );

	$inValue = readFile( "$zeddRoot_g/html/$iFile" );

	@workingArray = split /\n/, $inValue;
	@workingArray = reverse @workingArray;

	$line = pop @workingArray;
	while( defined( $line ) )
	{
	    chomp( $line );

	    # Look for directives in the html ...
            if( $line =~ /^\s*?\<\!\-\-\s*?ZeddWeb$/ )
	    {		
		$directives = readZeddWebDirectives( \@workingArray );
		processZeddWebDirectives( $directives ); 
	    } 
	    else 
	    {
		$outLine = expandVariables( $line, \@queryArgs_g );
		$gPage_g .= $outLine;
		$gPage_g .= "\n";
	    }	    	    

	    $line = pop @workingArray;
	}
    }   
    
    if( $directive =~ /^\s*exec\s*(.*)$/ ) 
    {
	# chomp($arguments);
	$command = $arguments->[0];
	$command = expandVariables( $command, \@queryArgs_g );
	
	logMsg( "\n\n**** Executing: $command ****" );
	if( !tainted( $command ) ) 
	{
	    system( $command );
	} 
    }

    # Provides a manual way to get content into a filter handling routine ...
    if( $directive =~ /simulateUrlGet/ )
    {
	if( defined( $arguments->[0] ) ) 
	{
	    $currentURL_g = expandVariables( $arguments->[0], \@queryArgs_g );
	    $currentURL_g = checkHtml( $currentURL_g );
	}
    }

    # We have two forms of urlAsString, hidden in the form of optional
    # parameters.
    #   Form 1: urlAsString( "your site" );
    #   Form 2: urlAsString( "your site", "regex" );
    # The resulting HTML is stored in the $currentURL_g variable, and is passed
    # to any filters for processing.

    if( $directive =~ /urlAsString/ ||
	$directive =~ /setDocument/ ) 
    {
	if( defined( $arguments->[0] ) ) 
	{
	    $getString = expandVariables( $arguments->[0], \@queryArgs_g );
	    # The only question is, should I use this raw form or go with
	    # LWP::UserAgent ? ,,, UserAgent right now ... 
	    $currentURL_g = getUrlAsString( $getString );
	}   
	    
	if( defined( $arguments->[1] ) ) 
	{
	    logMsg( "\n** Searching for regex: " );
	    logMsg( $arguments->[1] );
	    $currentURL_g = getHtmlRegion( $currentURL_g, 
					   $arguments->[1] );
	    logMsg( "--> $currentURL_g <--" );
	}
    }
    
    # Use:  
    #   storeURL( "http://www.site.com/bruce.gif", "bruce.gif" );
    #
    # Note .. this directive ignores caching ...
    #
    if( $directive =~ /storeURL/ ) 
    {
	logMsg( "\nArg1: " );
	logMsg( quotemeta($arguments->[0]) );
	logMsg( "\nArg2: " );
	logMsg( quotemeta($arguments->[1]) );
	logMsg( quotemeta($currentURL_g) );

        if( defined( $arguments->[0] ) && defined( $arguments->[1] ) ) 
	{
	    # Check to see if any arguments need to be expanded in the URL
	    # as a result of tag processing.
	    if( $arguments->[0] =~ /(.*)\-\>(.*)/ ) 
	    {
		$arguments->[0] =~ s/\-\>/$currentURL_g/g;
	    }	    	    

	    logMsg( "Storing: $arguments->[0]" );

	    $tempURL = getUrlAsString( $arguments->[0] );

	    createFile( "$zeddRoot_g/scratch/$arguments->[1]",
			$tempURL );
	}
    }

    # Calls a handler on our behalf ..
    # Filters take an array of input html and produce a hash of elements.
    # Each hash represents a "tuple" of data that can be referred to through
    # implicit looping. 

    if( $directive =~ /extractTags/ ||
	$directive =~ /processDocument/ ) 
    {	
	logMsg( "extractTags ..." );

	if( defined( $arguments->[0] ) ) 
	{ 
	    $sub = $arguments->[0];
	    
	    logMsg( "ExtractTags -> Active Page, loading " .  $arguments->[0] );
	    @resultArrayOfHashes_g = ();
	    
	    # Better load that module first!
	    if( loadModule( $arguments->[0] ) )
	    {		    
		
		$parm = 1;
		while( defined( $arguments->[$parm] ) )
		{
		    push @parms, $arguments->[$parm];
		    $parm++;
		}
		
		# Redirect IO
		blockStdout();  		    
		no strict 'refs';
		
		# Assign the result to a global array of hashes.
		@resultArrayOfHashes_g = 
		    eval { $sub->( $currentURL_g, @parms ) }; 
		
		if( $@ )
		{
		    $gPage_g .= "Message: $@";
		}
		
		# Redirect IO
		unblockStdout();  
		use strict 'refs';
		
		# Flip the order to make it into a queue
		@resultArrayOfHashes_g = reverse( @resultArrayOfHashes_g );		
	    }
	    else 
	    {
		$gPage_g .= "<br> Error: Couldn't Load Handler ($arguments->[0]) <br>";
	    }
	} 	
    }

    # Arg 0 is the output templage.
    # Arg 1 is an optional stopping condition.
    #   -> it can be a number, i.e. 10, which means we will only
    #      output that many lines before stopping.
    #   -> it can be a exit condition, i.e. a hash value that must
    #      be true. We'll loop and ouput while that value is true.
    #   -> The default is to loop through everthing.

    if( $directive =~ /tagsToHtml/ ) 
    {	
	if( defined( $arguments->[1] ) ) 
	{	  
	    if( $arguments->[1] =~ /(\-\>\[(.+?)\])/ ) 
	    {
		$exitKey = $2;
		$resultHash = pop @resultArrayOfHashes_g;
		while( defined( $resultHash->{$exitKey} ) ) 
		{
		    $outLine = $arguments->[0];
		    $outLine =~ s/(\-\>\[(.+?)\])/$resultHash->{$2}/g;
		    
		    $gPage_g .= "$outLine\n";
		    
		    $resultHash = pop @resultArrayOfHashes_g;
		}
		push @resultArrayOfHashes_g, $resultHash;
	    } 
	    else 
	    {
		$numLines = $arguments->[1];
		for( $count = 0; $count < $numLines; $count++ ) 
		{
		    $resultHash = pop @resultArrayOfHashes_g;
		    $outLine = $arguments->[0];
		    $outLine =~ s/(\-\>\[(.+?)\])/$resultHash->{$2}/g;
		    
		    $gPage_g .= "$outLine\n";
		}
	    }
	} 
	else 
	{
	    $resultHash = pop @resultArrayOfHashes_g;
	    while( defined($resultHash) ) 
	    {
		$outLine = $arguments->[0];
		$outLine =~ s/(\-\>\[(.+?)\])/$resultHash->{$2}/g;

		logMsg( "parm: $2" );
                logMsg( "Output: ", $resultHash->{'Desc'} );

		$gPage_g .= "$outLine\n";

		$resultHash = pop @resultArrayOfHashes_g;
	    }
	}
    }
    
    ## Arg0 -> What is being posted
    ## Arg1 -> What to call it
    ## Arg2 -> How long it's good for (in seconds) 
    if( $directive =~ /postItem/ )
    {
	if( $arguments->[0] =~ /\-\>/ )
	{
	    $postValue = $currentURL_g;
	}
	else
	{
	    $postValue = $arguments->[0];
	}
	
        postItem( $postValue, 
		  $arguments->[1], 
		  $arguments->[2] );
    }

    ## Arg0 -> What is being gotten    
    if( $directive =~ /getItem/ )
    {	
	if( ! defined($arguments->[2]) )
	{
	    $postValue = 0;
	}
	else
	{
	    $postValue = $arguments->[2];
	}

        $currentURL_g = getItem( $arguments->[0], $postValue );

	if( defined( $arguments->[1] ) ) 
	{
	    logMsg( "\n** Searching for regex: " );
	    logMsg( $arguments->[1] );
	    $currentURL_g = getHtmlRegion( $currentURL_g, 
					   $arguments->[1] );
	    logMsg( "--> $currentURL_g <--" );
	}
    }

    logMsg( "======== Exit performDirective ========" );
}

sub performIfStatement 
{
    my ($condition) = shift;
    my ($ifBody) = shift;
    my ($elseBody) = shift;
    my ($nested);
    my ($leftArg, $rightArg, $operand, $arg);
    my ($inString);
    my ($expression, $result);

    logMsg( "======== Enter performIfStatement ========" );
    logMsg( "Condition: $condition" );

    if( $condition =~ /\s*(\w+)\((.*)\)/ )
    {
	# tack a semi-colon onto the condition and call 
	# processZeddWebDirectives() save the return value and
	# check it as a boolean.
	$nested = "$1($2);";
	
	if( processZeddWebDirectives( $nested ) )
	{
	    processZeddWebDirectives( $ifBody );
	}
	else
	{
	    processZeddWebDirectives( $elseBody );
	}
    }

    if( $condition =~ /\s*(.+)\s+([=><!][=\~]?)+\s+(.*)$/ ) 
    {       	
	$leftArg = expandVariables( $1, \@queryArgs_g );		
	$rightArg = expandVariables( $3, \@queryArgs_g );
	$operand = $2;

	if( !( $leftArg =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ ) )
	{
	    $leftArg = "\"$leftArg\"";
	}
	if( !( $rightArg =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ ) )
	{
	    $rightArg =~ s/^\s*//;
	    $rightArg =~ s/\s*$//;
	    $rightArg = "\/$rightArg\/";
	}

	$expression = "$leftArg $operand $rightArg";
	$result = eval "$expression";
	if( $result )
	{
	    processZeddWebDirectives( $ifBody );
	}
	else
	{
	    if( length( $elseBody ) > 0 )
	    {
		processZeddWebDirectives( $elseBody );
	    }
	}
    }	   

    logMsg( "======== Exit performIfStatement ========" );
}

sub getMacroArgs 
{
    my ($inString) = shift;
    my ($done) = 0;
    my (@mArgs) = ();
    my (@workArray) = ();
    my ($arg);

    logMsg( "======== Enter getMacroArgs ========" );
    
    $inString =~ s/\n//g;
    $inString =~ s/^\s*//;
    $inString =~ s/\s*$//;
    
    push(@workArray, $+ ) while $inString =~ m{
        # the first part groups the phrase inside the quotes.
        # see explanation of this pattern in MRE
        \s*"([^\"\\]*(?:\\.[^\"\\]*)*)"\s*,?
           | ([^,]+),?
           | ,
	   }gx;
    push(@workArray, undef) if substr($inString, -1,1) eq ',';

    while( $arg = pop @workArray ) 
    {
	$arg =~ s/\n//g;
	$arg =~ s/^\s*//;
	$arg =~ s/\s*$//;
	$arg =~ s/\\\"/\"/g;
	push @mArgs, $arg;
	logMsg( "arg: " . $arg );
    }

    logMsg( "======== Exit getMacroArgs ========" );

    return reverse @mArgs; # list of values that were comma-separated
}

sub matchToken
{
    # Warning we are modifying the input string, which should
    # have been passed as a reference.
    my( $inString )= shift;
    my( $openTarget ) = shift;
    my( $closeTarget ) = shift;

    my ($returnString) = "";
    my (@bits);
    my ($bit);
    my ($done) = 0;
    my ($orig) = "";
    my ($index) = 0;

    $orig = $closeTarget;
    $closeTarget = quotemeta($closeTarget);
    $openTarget = quotemeta($openTarget);

    @bits = split $closeTarget, $$inString;
    @bits = reverse @bits;

    while( ! $done  )
    {
	$bit = pop @bits;
	$bit =~ s/^\s*//;

	$returnString .= $bit;

	if( $bit =~ /$openTarget/ )
	{
	    $returnString .= $orig;
	}
	else
	{
	    $done = 1;
	}
    }

    $$inString = "";

    while( $#bits > 0 )
    {  
	$bit = pop @bits;
	$$inString .= $bit . $orig;	
    }
    $bit = pop @bits;
    $$inString .= $bit;   
   
    return $returnString;
}

sub processZeddWebDirectives 
{
    my ($inString) = shift;
    my ($done) = 0;
    my ($directive);
    my ($ifClause);
    my ($elseClause);
    my ($condition);
    my ($arguments, @args);
    my ($retVal) = 0;
    my (@systemElements);

    logMsg( "======== Enter processZeddWebDirectives ========" );

    # Get rid of line returns.
    $inString =~ s/\n//g;
    $inString =~ s/^\s*//;
    $inString =~ s/\s*$//;
    
    while( $done == 0 ) 
    {	
	if( $inString =~ /^if\s*?\((.*?)\)\s*?\{/ ) 
	{
	    $inString = $';

	    $ifClause = matchToken( \$inString, "{", "}" );

	    $condition = $1;
            undef $elseClause;

	    logMsg( "if-stmt: if ($1) { $ifClause }" );

	    $inString =~ s/^\s*//;

	    $elseClause = "";
	    if( $inString =~ /^else\s*?\{/ ) 
	    {
		$inString = $';
		$elseClause = matchToken( \$inString, "{", "}" );
		logMsg( "Else-Clause Found: $elseClause" );
	    }	
            $inString =~ s/^\s*//;

	    performIfStatement( $condition, $ifClause, $elseClause );
	} 
	#elsif ( $inString =~ /^(\w+?)\s*?\((.*?)\)\;/ ) 
	elsif ( $inString =~ /^(\w+?)\s*?\(/ )
	{
	    logMsg( "Statement: $1" );
	    $directive = $1;	    
	    $inString = $';

	    $arguments = matchToken( \$inString, "(", ")" );
	    @args = getMacroArgs( $arguments );

	    # Quick test to make sure everything went ok .. there should
	    # be a little 'ol semi colon starting the returned line, let's
	    # test and strip it off ...
	    if( $inString =~ /^\;(.*)/ )
	    {
		$inString = $1;

		# Check to see if this is a user macro to be expanded.
		@systemElements = grep { $directive =~ $_ } @systemMacros_g;
		
		if( scalar( @systemElements ) == 0 ) 
		{
		    logMsg( "Expanding Macro..." );
		    # User Macro
		    $inString = join( ' ', 
				      expandMacro( $directive, \@args ),
				      $inString );
		} 
		else 
		{		    
		    $retVal = performDirective( $directive, \@args );
		}
	    } 
	}
	elsif ( $inString =~ /^\/\*.*?\*\// ) 
	{
	    # Just Drop the comment...
	    $inString = $';
	}
	else 
	{
	    if( length($inString) == 0 ) 
	    {
		$done = 1;
	    }
	    else
	    {
		logMsg( "\nProblem with Syntax, Advancing One Word" );
	        logMsg( "Possible Problem String: $inString\n" );
		if( $inString =~ /^\s*[^ ]/ ) 
		{
		    $inString = $';
		}
		else
		{
		    logMsg( "\nFatal Error, aborting directive processing\n" );
		    $done = 1;
		}
	    }
	}

	$inString =~ s/^\s*//;
    };    

    logMsg( "======== Exit processZeddWebDirectives ========" );

    return $retVal;
}

sub performStaticModeTransforms
{
    my($outPage) = shift;
    my($tempPage) = $outPage;
    my($layoutName);
    my($layoutFile);
    my($match);
    my($rule);
    my($args);
    my(@targets);
    my(%transforms);

    # Look through the commands supplied in the dependencies 
    # file and transform any dynamic links into static ones.
    while( $tempPage =~ s/cgi\-bin\/page\.pl\?(\w+?)\((.*?)\)//i )
    {
	$match = $&;
	$layoutName = $1;
	$args = $2;

	$layoutFile = readFile( "$zeddRoot_g/layouts/$layoutName.layout" );
	if( $layoutFile =~ /pageRule:\s*?\{(.*?)\}/ )
	{
	    $rule = $1;
	    $rule =~ s/^\s*//si;
	    $rule =~ s/\s*$//si;	   	    

	    # Look for arguments in the rule ...
	    @targets = split /\,/ , $args;

	    $rule = expandVariables( $rule, \@targets );
	    $rule = "$staticDir_g/$rule";

	    $transforms{$match} = $rule;
	    
	}
	else
	{
	    $transforms{$match} = "$staticDir_g/$layoutName.html";
	}
    }
    
    $outPage =~ s/cgi\-bin\/page\.pl\?(\w+?)\((.*?)\)/$transforms{$&}/gsi;
    $outPage =~ s/Content\-Type\: text\/html//gsi;

    #$outPage =~ s/\<BASE href.*?\>//gsi;

    return $outPage;
}

sub logMsg
{
    my( $outString ) = shift;

    if( $debug_g )
    {       
	print logFile_g $outString;
	print logFile_g "\n";
    }
}

sub checkDependencies 
{
    my( $fileName ) = shift;
    my( $dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
	$atime,$mtime,$ctime,$blksize,$blocks );
    my( $fileInfo );
        
    logMsg( "======== Enter checkDependencies ========" );

    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks)
                 	= stat "$zeddRoot_g/cache/$fileName.cache";

    if( defined( $mtime ) ) 
    {
	logMsg( "Checking: cache/$fileName.cache" );
	logMsg( "Last Update Time: $mtime" );
	logMsg( "Current Time: " . time() );

	$fileInfo = $dependencies_g{$fileName};

	logMsg( "Timeout Value: ", $$fileInfo{'Timeout'} );
	
	if( defined( $fileInfo ) ) 
	{
	    if( time() > $mtime + $$fileInfo{'Timeout'} ) 
	    {
		logMsg( "Active Page, no caching ..." );
		$activePage_g = 1;
	    } 
	    else 
	    {
		logMsg( "Use Cached Version ..." );
		$activePage_g = 0;
	    }
	}
    } 
    else 
    {
	# Nothing right now.
	logMsg( "No dependency entry for: $fileName.cache" );
    }

    logMsg( "======== Exit checkDependencies ========" );
}

sub readZeddWebDirectives 
{
    my ($inFile) = shift;
    my ($returnLine) = "";
    my ($done);
    my ($inline);

    logMsg( "======== Enter readZeddWebDirectives ========" );

    $done = 0;    
    do 
    {
	$inline = pop @$inFile;

	if( $inline =~ /\s*\-\-\>$/ ) 
	{
	    $done = 1;
	} 
	else 
	{	    
	    $returnLine = join( ' ', $returnLine, $inline ); 
	}

    } until( $done == 1 );

    logMsg( "======== Exit readZeddWebDirectives ========" );

    # We've popped everything we were looking for, let's check for
    # any optional ZeddWeb attributes
    if( $returnLine =~ /\<\!\-\-\s*?ZeddWeb\s*?\((.+?)\)(.*)/gsi )
    {
	$returnLine = $2;
	@systemArgs_g = split /,/, $1;

	# Let's get out while the going is good.
	return $returnLine;	
    }

    if( $returnLine =~ /\<\!\-\-\s*?ZeddWeb(.*)/gsi )
    {
	$returnLine = $1;
    }

    return $returnLine;
}

sub parseZeddWebMacros 
{
    my ($done) = 0;
    my ($mName) = "None";
    my ($mLine);

    logMsg( "======== Enter parseZeddWebMacros ========" );

    $mLine = readFile( "$zeddRoot_g/zeddMacros" );

    $mLine =~ s/\n//g;
    $mLine =~ s/^\s*//;
    $mLine =~ s/$\s*//;
    
    while( $done == 0 ) 
    {
	if( $mLine =~ /^(\w+)\s*?\((.*?)\)\s*?\{(.*?)\}/s )
	{
	    logMsg( "Read Macro: $1" );
	    logMsg( "Args: $2\n" );
	    logMsg( "Body: $3\n" );

	    $macros_g{$1} = { 
		            'args' => $2,
			    'body' => $3 
                          };

	    $mLine = $';
	    $mLine =~ s/\n//g;
	    $mLine =~ s/^\s*//;
	    $mLine =~ s/\s*$//;
        } 
	else 
	{
            $done = 1;
        }
    }

    logMsg( "======== Exit parseZeddWebMacros ========" );
}

sub parseGlobalSettings 
{
    my( $inValue );
    my( $line );

    logMsg( "======== Enter parseGlobalSettings ========" );

    $inValue = readFile( "$zeddRoot_g/zeddWebGlobal" );

    while ( $inValue =~ s/\s*?(\w+?):\s*?\{(.*?)\}//si ) 
    {
	$line = $2;
	if( $1 =~ /body/ )
	{
	    $bodyLine_g = $line;
	    $bodyLine_g =~ s/\n//g;
	}
	if( $1 =~ /host/ )
	{
	    $host_g = $line;
	    $host_g =~ s/^\s*//g;
	    $host_g =~ s/\s*$//g;
	}
	if( $1 =~ /flags/ )
	{
	    # These are any overriding global table flags...
	    $tableFlags_g = $line;
	    $tableFlags_g =~ s/^\s*//g;
	    $tableFlags_g =~ s/\s*$//g;
	    $tableFlags_g =~ s/\n//g;
	}
	if( $1 =~ /staticDir/i )
	{
	    $staticDir_g = $line;
	    $staticDir_g =~ s/^\s*//g;
	    $staticDir_g =~ s/\s*$//g;
	    $staticDir_g =~ s/\n//g;
	}

	if( $1 =~ /getTimeout/i )
	{	    
	    $getTimeout_g = $line;
	    $getTimeout_g =~ s/^\s*//g;
	    $getTimeout_g =~ s/\s*$//g;
	    $getTimeout_g =~ s/\n//g;
	}
    }
    
    logMsg( "======== Exit parseGlobalSettings ========" );
}

sub blockStdout
{
    # Only block output when running in non-debug mode
    if( $debug_g == 0 )
    {
	open( SAVEOUT, ">&STDOUT" );
	open( SAVEERR, ">&STDERR" );
	open( STDOUT, ">/dev/null" );
	open( STDERR, ">&STDOUT" );
    }
}

sub unblockStdout
{
    # Only block output when running in non-debug mode
    if( $debug_g == 0 )
    {
	close( STDOUT );
	close( STDERR );
	open( STDOUT, ">&SAVEOUT" );
	open( STDERR, ">&SAVEERR" );
    }
}

sub debugEnabled
{
    $debug_g = 1;
}

sub debugDisabled
{
    $debug_g = 0;
}

sub staticModeOn
{
    $staticMode_g = 1;
}

sub staticModeOff
{
    $staticMode_g = 0;
}

sub parseLayoutFile
{
    my $contents = shift;   
    my @lines = reverse( split /\n/, $contents );
    my ($line, $subLine, $done );
    my ($row, $col, $colInfo, $returnInfo);
    my (@sections, $section);
    my ($height, $width);
    my ($rowCount, $tmp);
    my ($style, $pStyle);
    my ($pEqual, $pWidth, $pHeight, $pTitle, $pFlags);
    my (%colInfo);
    my ($flag, $rowFlags);
    my ($file);
    my ($colWidth,$colString,$entry);
    my ($pageInfo);
    my ($timeout);

    logMsg( "======== Enter parseLayoutFile ========" );

    $rowCount = 0;
    $line = pop( @lines );
    while( defined( $line ) )
    {
#	print "$line\n";
	
	if( $line =~ /^\s*\=/ )
	{
	    #print "Page Level\n";
	    
	    $done = 0;
	    $pEqual = 1;
	    $subLine = pop @lines;
	    while( defined( $subLine ) && ! $done )
	    {
		#print "Header: $subLine \n";

		if( $subLine =~ /^\s*\-/ )
		{
		    $done = 1;
		    push @lines, $subLine;
		}
		else
		{
		    $pageInfo .= $subLine;
		    $subLine = pop @lines;
		}
	    }
	    
	    # Remove all "|" we found during parsing ...
	    $pageInfo =~ s/\|//gsi;
	    
	    # Now let's test the information we retrieved ...	    
	    if( $pageInfo =~ /Size\s*?\:\s*?\{\s*?(\d+)\s*x\s*(\d+)\s*?\}/si )
	    {
		$pWidth = $1;
		$pHeight = $2;
		#print "\tDEBUG Width: $1\n";
		#print "\tDEBUG Height: $2\n";
	    }		    
	    if( $pageInfo =~ /Title\s*\:\s*?\{(.*?)\}/i )
	    {
		#print "\tDEBUG Title: $1\n";
		$pTitle = $1;
	    }
	    if( $pageInfo =~ /EqualWidth\s*?\:\s*?\{(.*?)\}/si )
	    {
		#print "\tDEBUG Equalwidth: $1\n";
		if( $1 =~ /False/i )
		{
		    $pEqual = 0;
		}
		else
		{
		    $pEqual = 1;
		}
	    }
	    if( $pageInfo =~ /Style\s*?\:\s*?\{(.*?)\}/si )
	    {
		#print "\tDEBUG Style: $1\n";
		$pStyle = $1;
		$pStyle =~ s/^\s+//;
		$pStyle =~ s/\s+$//;
	    }
	    if( $pageInfo =~ /Flags\s*?\:\s*?\{(.*?)\}/si )
	    {
		#print "\tDEBUG Flags: $1\n";
		$pFlags = $1;
		$pFlags =~ s/^\s+//;
		$pFlags =~ s/\s+$//;
	    }
	    
	    # Store what we've learned ...
	    $pageInfoStruct_g{'title'} = $pTitle;
	    $pageInfoStruct_g{'width'} = $pWidth;
	    $pageInfoStruct_g{'height'} = $pHeight;
	    $pageInfoStruct_g{'equalWidth'} = $pEqual;
	    $pageInfoStruct_g{'style'} = $pStyle;
	    $pageInfoStruct_g{'flags'} = $pFlags;

	}
	
	if( $line =~ /^\s*\-/ )
	{
	    #print "Row\n";

	    $done = 0;
	    $subLine = pop @lines;
	    $row = "";
	    while( defined( $subLine ) && ! $done )
	    {
		if( $subLine =~ /^\s*\-/ ||
		    $subLine =~ /^\s*\=/ )
		{
#		    print "Done\n";
		    $done = 1;
		    push @lines, $subLine;
		    
		    @sections = reverse( split /\|/, $row );
		    $section = pop( @sections );
		    $col = 0;
		    while( defined( $section ) )
		    {
#			print "section($col): ", quotemeta($section), "\n";
			if( $section =~ /^$/ )
			{
			    # This is the start of a new row within
			    # the row ...
#			    print "NewLine ... reseeting counters!\n";

			    $row = "";
			    $col = 0;
			}
			else
			{
#			    print "Section for col($col): $section\n";
			    # Some info here ...
			    push (@{$colInfo{$col}}, $section);

			    $col++;
			}

			$section = pop( @sections );
		    }

		    for( $tmp = 0; $tmp < $pWidth; $tmp++ )
		    {
			$flag = "";
			$rowFlags = "";
			$file = "";
			$style = "";
			$colWidth = 1;
			$colString = "";

			foreach $entry (@{$colInfo{$tmp}})
			{
			    $colString .= $entry;
			}

			if( $colString =~ /Flags\s*:\s*\{(.+?)\}/si )
			{
			    $flag = $1;
			    $flag =~ s/^\s+//;
			    $flag =~ s/\s+$//;
			    $flag =~ s/(\s+)/ /;
			}  
			
			if( $colString =~ /File\s*:\s*\{(.+?)\}/si )
			{			    
			    $file = $1;
			    $file =~ s/^\s+//;
			    $file =~ s/\s+$//;
			    $file = expandVariables( $file, 
						     \@queryArgs_g );
			}  
			
			if( $colString =~ /Row\s*:\s*\{(.+?)\}/si )
			{
			    $rowFlags = $1;
			    $rowFlags =~ s/^\s+//;
			    $rowFlags =~ s/\s+$//;
			    $rowFlags =~ s/\s+/ /;
			}
			
			if( $colString =~ /Style\s*:\s*\{(.+?)\}/si )
			{
			    $style = $1;
			    $style =~ s/^\s+//;
			    $style =~ s/\s+$//;
			}
			
			if( $colString =~ /Width\s*:\s*\{(.+?)\}/si )
			{
			    $colWidth = $1;
			    $colWidth =~ s/^\s+//;
			    $colWidth =~ s/\s+$//;
			}			    

			# Push this cols entry in to the final
			# Structure ..
			if( $file =~ /^$/ )
			{
#			    print "End of Row!\n";
			    # We have a no information for this column ..
			    # increase the previous columns colspan to fill
			    # the row ...
			    # print "Row: $rowCount Col: $tmp\n";
			    # print "Increasing colspan to: ", ($pWidth - $tmp) + 1, "\n";

			    $rowInfoStruct_g{$rowCount}{$tmp - 1}{'width'} 
			                                = ($pWidth - $tmp) + 1;
			}
			else
			{
			    $rowInfoStruct_g{$rowCount}{$tmp} =
			        {
				    'flags' => $flag,
				    'file' => $file,
				    'row' => $rowFlags,
				    'style' => $style,
				    'width' => $colWidth
				};
			}
			
			# Zero the struct for the next iteration ...
			$colInfo{$tmp} = ();
		    }
		}
		else
		{
		    $row .= $subLine;

		    $subLine = pop @lines;
		}
	    }

	    $rowCount++;
	}

	$line = pop( @lines );
    }

    # Process the conditions we gathered on our way through ..
    while( $contents =~ s/pre\-condition\s*:\s*\{(.+?)\}//si )
    {
	push @preconditions_g, $1;
    }
    while( $contents =~ s/post\-condition\s*:\s*\{(.+?)\}//si )
    {
	push @postconditions_g, $1
    }

    if( $contents =~ /pageRule\s*?:\s*?\{(.+?)\}/ )
    {
	$cacheFileName_g = $1;
	$cacheFileName_g = expandVariables( $cacheFileName_g,
					    \@queryArgs_g );

	$cacheFileName_g =~ s/^\s*//;
	$cacheFileName_g =~ s/\s*$//;	
    }

    if( $contents =~ /timeout\s*?:\s*?\{(.+?)\}/ )
    {
	$timeout = $1;
	$timeout =~ s/^\s*//;
        $timeout =~ s/\s*$//;

	$dependencies_g{$cacheFileName_g} =
	                                   { 
					       'Timeout' => $timeout,
					       'LastUpdate' => 0,
					       'FilterCache' => ""
					   };
    }

    $duplicateAsStatic_g = 0;
    if( $contents =~ /static\s*?:\s*?\{(.+?)\}/ )
    {
	if( $1 =~ /True/i )
	{
	    $duplicateAsStatic_g = 1;
	}
	else
	{
	    $duplicateAsStatic_g = 0;
	}
    }

    logMsg( "======== Exit parseGlobalSettings ========" );
}

# -------------------------------
# ******** Main Routine *********
# -------------------------------

sub run
{   
    $zeddRoot_g = shift;
    $documentRoot_g = shift;
    $queryString_g = shift;

    my ($layoutFileName);
    my ($row, $col, $directives, $outLine);
    my ($arguments,$layoutContents);
    my (@workingArray, $line, $inValue );

    # Open the log file 
    $logFileName_g = "$zeddRoot_g/zeddLog";
    open( logFile_g, "> $logFileName_g" ); 

    $extractResult_g = "None";
    $currentURL_g = "";
    $bodyLine_g = "";
    $tableFlags_g = "border=0 cellspacing=0 cellpadding=0 width=\"100%\"";
    $staticDir_g = "zeddStatic";
    $equalCols_g = 0;
    $host_g = "";
    $gPage_g = "";
    $getTimeout_g = 30;
    # Everyone is active by default.
    $activePage_g = 1;
    
    if( length( $queryString_g ) > 0 ) 
    {
	if( $queryString_g =~ /^\s*(.*)\((.*)\)$/ ) 
	{
	    $layoutFileName = $1 . ".layout";
	    $cacheFileName_g = "$1.html";	    
	    $arguments = $2;
	    foreach (split(/\,/, $arguments)) 
	    {
		# Replace any spaces (%20 in the cgi world) with "real"
		# spaces for the rest of the script.
		$_ =~ s/\+/ /g;	    
		push @queryArgs_g, $_;
	    }
	}
    } 
    else 
    {
	$layoutFileName = "index.layout";
	$cacheFileName_g = "index.html";
    }
    
    @systemMacros_g = ( "include", "exec", "if", "else", "print", "defined",
			"echo", "postForUrl", "simulateUrlGet",
			"urlAsString", "extractTags", "tagsToHtml", "storeURL",
		        "postItem", "getItem", "setDocument", "processDocument" );

    @systemArgs_g = ( "\@arg0", "\@arg1","\@arg2","\@arg3","\@arg4","\@arg5",
                      "\@arg6","\@arg7","\@arg8","\@arg9" );
       
    # First We need to read in the layout description ...
    $layoutContents = readFile( "$zeddRoot_g/layouts/$layoutFileName" );

    parseLayoutFile( $layoutContents );
    parseZeddWebMacros();
    parseGlobalSettings();

    # Caching, do we need to do anything ?
    checkDependencies( $cacheFileName_g );
    
    if( $activePage_g )
    {    
	# Get these things in the correct order
	@preconditions_g = reverse @preconditions_g;
	@postconditions_g = reverse @postconditions_g;
    
	# Do the precondition processing
	conditionProcessing( @preconditions_g );
    
	# Let the browser know we'll be dumping html
	$gPage_g .= "Content-Type: text/html\n\n";
    
	# Header info .. Need to make the base tag a variable ..
	$gPage_g .= "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n";
	$gPage_g .= "<HTML>\n";
	$gPage_g .= "<HEAD>\n";
	$gPage_g .= "  <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=iso-8859-1\">\n";
	$gPage_g .= "  <BASE href=\"http://$host_g/\">\n";
	$gPage_g .= "  <TITLE>$pageInfoStruct_g{'title'}";
	$gPage_g .= "  </TITLE>\n";
	
	if( ! $pageInfoStruct_g{'style'} =~ /^$/ )
	{
	    $gPage_g .= "  <link rel=stylesheet type=\"text/css\"\n";
	    $gPage_g .= "         href=\"zeddStyles/$pageInfoStruct_g{'style'}\"\n";
	    $gPage_g .= "         title=\"$pageInfoStruct_g{'style'}\">\n";

#	$inValue = readFile (
#	      "$zeddRoot_g/styles/$pageInfoStruct_g{'style'}" );
#	$gPage_g .= $inValue

	}
	
	$gPage_g .= "</HEAD>\n\n";
	
	# Dump the page level information
	$gPage_g .= "<body $bodyLine_g>\n\n";
	
	# Check for any top-level-table flags ...
	if( !( $pageInfoStruct_g{'flags'} =~ /^$/) )
	{
	    $tableFlags_g = $pageInfoStruct_g{'flags'};
	}
	
	# Let's start the table off ...
	if( $pageInfoStruct_g{'equalWidth'} == 1 )
	{
	    $gPage_g .= "<table $tableFlags_g";
	    $gPage_g .= " cols=$pageInfoStruct_g{'width'}>\n\n";	   
	}
	else
	{
	    $gPage_g .= "<table $tableFlags_g>\n\n";	   
	}
	
	for ( $row = 0; $row < $pageInfoStruct_g{'height'}; $row++ ) 
	{
	    # Check to see if a zone has been defined for this row.
	    if( ! ($rowInfoStruct_g{$row}{0}{'row'} =~ /^$/ ) )
	    {
		# There is a zone for this row, add it's formatting flags
		$gPage_g .= "<tr $rowInfoStruct_g{$row}{0}{'row'}>\n";
	    } 
	    else 
	    {
		# Nothing special, just start the row.
		$gPage_g .= "<tr>\n";
	    }
	    
	    $col = 0;
	    while( $col < $pageInfoStruct_g{'width'} )
	    {
		# Check to see if this row, column pair starts a new zone
		if( !( $rowInfoStruct_g{$row}{$col}{'file'} =~ /^$/) )
		{
		    # We are in a zone, check to see if it has a span
		    if( ! $rowInfoStruct_g{$row}{$col}{'width'} =~ /^$/ )
		    {
			# We have a span that is greater than 1 cell, output the
			# span and increment the counter to skip the other columns
			$gPage_g .= "   <td $rowInfoStruct_g{$row}{$col}{'flags'} ";
			
			if( $rowInfoStruct_g{$row}{$col}{'width'} > 1 )
			{
			    $gPage_g .= "colspan=$rowInfoStruct_g{$row}{$col}{'width'}>\n";
			}
		    } 
		    else 
		    {
			# We are in a zone, but it only occupies a single cell
			$gPage_g .= "   <td $rowInfoStruct_g{$row}{$col}{'flags'}>\n";
		    }
		    
		    # Check to see if any Style has been defined for this zone ..
		    if( ! $rowInfoStruct_g{$row}{$col}{'style'} =~ /^$/ )
		    {
			$inValue = readFile( 
					     "$zeddRoot_g/styles/$rowInfoStruct_g{$row}{$col}{'style'}" );
			$gPage_g .= $inValue;
		    }
		    
		    $outLine = expandVariables( $rowInfoStruct_g{$row}{$col}{'file'},
						\@queryArgs_g );
		    
		    logMsg( "\n============ Processing File ===================" );
		    logMsg( "$outLine" );
		    
		    # Dump it to the html for readability ...
		    $gPage_g .= "\n<!-- Starting Zone : $outLine -->\n\n";
		
		    $inValue = readFile( "$zeddRoot_g/html/$outLine" );
		    @workingArray = split /\n/, $inValue;
		    @workingArray = reverse @workingArray;
		    
		    $line = pop @workingArray;
		    while ( defined( $line ) )
		    {
			chomp( $line );
			
			# Look for directives in the html ...
			if( $line =~ /^\s*?\<\!\-\-\s*?ZeddWeb/ ) 
			{
			    # Push the line back on for the parser ...
			    push @workingArray, $line;
			    $directives = readZeddWebDirectives( \@workingArray );
			    processZeddWebDirectives( $directives ); 
			} 
			else 
			{	
			    # Provide variable substition, in case someone used one of
			    # the default SystemArgs right in their html ...
			    $outLine = expandVariables( $line, \@queryArgs_g );
			    
			    # Try our hand at enforcing some half decent indenting.
			    for( my $indent = 0; $indent < (3 * ($col + 1)); $indent++ )
			    {
				$gPage_g .= " ";
			    }
			    
			    $gPage_g .= $outLine;
			    $gPage_g .= "\n";
			}				
			
			$line = pop @workingArray;
		    };
		    
		    $gPage_g .= "\n<!-- Ending Zone : $rowInfoStruct_g{$row}{$col}{'file'} -->\n\n";
		    
		    $directives = "";
		    $col += $rowInfoStruct_g{$row}{$col}{'width'}
		} 
		else 
		{
		    # There is no column zone defined for this cell, dump out a 
		    # start cell marker.
		    $gPage_g .= "   <td>\n";
		}
		
		# Close the cell
		$gPage_g .= "   </td>\n";
	    }
	    
	    $gPage_g .= "</tr>\n";
	}
	
	# Close things up ..
	$gPage_g .= "</table>\n";
	
	$gPage_g .= "</BODY></HTML>\n";
	
	conditionProcessing( @postconditions_g );
    
	# ** Remember we are active, we better write a cached version ..
	#    This is always the dynamic version.
	createFile( "$zeddRoot_g/cache/$cacheFileName_g.cache", $gPage_g );	
    }
    else
    {
	# ******* We have a cached file, open it up!
	$gPage_g = readFile( "$zeddRoot_g/cache/$cacheFileName_g.cache" );	
    }

    # Are we doing some sort of static transformation ..
    if( $staticMode_g || $duplicateAsStatic_g )
    {
      my $staticPage = performStaticModeTransforms( $gPage_g );
      
      # Only re-write the static version if the page was active .. i.e.
      # we didn't read the cached version.
      if( $activePage_g == 1 )
      {
         createFile( "$documentRoot_g/$staticDir_g/$cacheFileName_g", $staticPage );
      }
    }
    
    if( $staticMode_g == 0 )
    {   
	# Out to the world!
	print $gPage_g;
    }
    
    close( logFile_g );
}

1;




