package Apache::ASP::GlobalASA;

# GlobalASA Object
# global.asa processes, whether or not there is a global.asa file.
# if there is not one, the code is left blank, and empty routines
# are filled in

use strict;
no strict qw(refs);
use vars qw(%stash *stash @ISA @Routines);

# these define the default routines that get parsed out of the 
# GLOBAL.ASA file
@Routines = 
    (
     "Application_OnStart", 
     "Application_OnEnd", 
     "Session_OnStart", 
     "Session_OnEnd",
     "Script_OnStart",
     "Script_OnEnd",
     "Script_OnParse",
     "Script_OnFlush"
     );
my $match_events = join('|', @Routines);

sub new {
    my $asp = shift || die("no asp passed to GlobalASA");

    my $filename = $asp->{global}.'/global.asa';
    my $id = &Apache::ASP::FileId($asp, $asp->{global}, undef, 1);
    my $package = $asp->{global_package} ? $asp->{global_package} : "Apache::ASP::Compiles::".$id;
    $id .= 'x'.$package; # need to recompile when either file or namespace changes

    # make sure that when either the file or package changes, that we 
    # update the global.asa compilation

    my $self = bless {
	asp => $asp,
	'package' => $package,
#	filename => $filename,
#	id => $id,
    };

    # assign early, since something like compiling reference the global asa,
    # and we need to do that in here
    $asp->{GlobalASA} = $self;

    $asp->{dbg} && $asp->Debug("GlobalASA package $self->{'package'}");
    my $compiled = $Apache::ASP::Compiled{$id};
    if($compiled && ! $asp->{stat_scripts}) {

#	$asp->{dbg} && $asp->Debug("no stat: GlobalASA already compiled");
	$self->{'exists'} = $compiled->{'exists'};
	$self->{'compiled'} = $compiled; # for event lookups
	return $self;
    }

    if($compiled) {
#	$asp->{dbg} && $asp->Debug("global.asa was cached for $id");
    } else {
	$asp->{dbg} && $asp->Debug("global.asa was not cached for $id");
	$compiled = $Apache::ASP::Compiled{$id} = { mtime => 0, 'exists' => 0 };
    }
    $self->{compiled} = $compiled;
    
    my $exists = $self->{'exists'} = -e $filename;
    my $changed = 0;
    if(! $exists && ! $compiled->{'exists'}) {
	# fastest exit for simple case of no global.asa
	return $self;
    } elsif(! $exists && $compiled->{'exists'}) {
	# if the global.asa disappeared
	$changed = 1;
    } elsif($exists && ! $compiled->{'exists'}) {
	# if global.asa reappeared
	$changed = 1;
    } else {
	$self->{mtime} = $exists ? (stat(_))[9] : 0;
	if($self->{mtime} > $compiled->{mtime}) {
	    # if the modification time is greater than the compile time
	    $changed = 1;
	}
    }
    $changed || return($self);

    my $code = $exists ? ${$asp->ReadFile($filename)} : "";
    my $strict = $asp->{use_strict} ? "use strict" : "no strict";

    if($code =~ s/\<script[^>]*\>((.*)\s+sub\s+($match_events).*)\<\/script\>/$1/isg) {
	$asp->Debug("script tags removed from $filename for IIS PerlScript compatibility");
    }
    $code = (
	     "\n#line 1 $filename\n".
	     join(" ;; ",
		  "package $self->{'package'};",
		  $strict,
		  "use vars qw(\$".join(" \$",@Apache::ASP::Objects).');',
		  "use lib qw($self->{asp}->{global});",
		  $code,
		  'sub exit { $main::Response->End(); } ',
		  "no lib qw($self->{asp}->{global});",
		  '1;',
		 )
	     );

    $asp->{dbg} && $asp->Debug("compiling global.asa $self->{'package'} $id exists $exists", $self, '---', $compiled);
    $code =~ /^(.*)$/s;
    $code = $1;

    # turn off $^W to suppress warnings about reloading subroutines
    # which is a valid use of global.asa.  We cannot just undef 
    # all the events possible in global.asa, as global.asa can be 
    # used as a general package library for the web application
    # --jc, 9/6/2002
    local $^W = 0;

    # only way to catch strict errors here    
    if($asp->{use_strict}) { 
	local $SIG{__WARN__} = sub { die("maybe use strict error: ", @_) };
	eval $code;
    } else {
	eval $code;
    }

    # if we have success compiling, then update the compile time
    if(! $@) {
	# if file mod times are bad, we need to use them anyway
	# for relative comparison, time() was used here before, but
	# doesn't work
	$compiled->{mtime} = $self->{mtime} || (stat($filename))[9];
	
	# remember whether the file really exists
	$compiled->{'exists'} = $exists;
	
	# we cache whether the code was compiled so we can do quick
	# lookups before executing it
	my $routines = {};
	local *stash = *{"$self->{'package'}::"};
	for(@Routines) {
	    if($stash{$_}) {
		$routines->{$_} = 1;
	    }
	}
	$compiled->{'routines'} = $routines;
	$asp->Debug('global.asa routines', $routines);
	$self->{'compiled'} = $compiled;
    } else {
	$asp->CompileErrorThrow($code, "errors compiling global.asa: $@");
    }

    $self;
}

sub IsCompiled {
    my($self, $routine) = @_;
    $self->{'compiled'}{routines}{$routine};
}

sub ExecuteEvent {
    my($self, $event) = @_;
    if($self->{'compiled'}{routines}{$event}) {
	$self->{'asp'}->Execute($event);
    }
}

sub SessionOnStart {
    my $self = shift;
    my $asp = $self->{asp};
    my $zero_sessions = 0;

    if($asp->{session_count}) {
	$asp->{Internal}->LOCK();
	my $session_count = $asp->{Internal}{SessionCount} || 0;
	if($session_count <= 0) {
	    $asp->{Internal}{SessionCount} = 1;	
	    $zero_sessions = 1;
	} else {
	    $asp->{Internal}{SessionCount} = $session_count + 1;
	}
	$asp->{Internal}->UNLOCK();
    }

    #X: would like to run application startup code here after
    # zero sessions is true, but doesn't seem to account for 
    # case of busy server, then 10 minutes later user comes in...
    # since group cleanup happens after session, Application
    # never starts.  Its only when a user times out his own 
    # session, and comes back that this code would kick in.
    
    $asp->Debug("Session_OnStart", {session => $asp->{Session}->SessionID});
    $self->ExecuteEvent('Session_OnStart');
}

sub SessionOnEnd {
    my($self, $id) = @_;
    my $asp = $self->{asp};
    my $internal = $asp->{Internal};

    # session count tracking
    if($asp->{session_count}) {
	$internal->LOCK();
	if((my $count = $internal->{SessionCount}) > 0) {
	    $internal->{SessionCount} = $count - 1;
	} else {
	    $internal->{SessionCount} = 0;
	}	    
	$internal->UNLOCK();
    }

    # only retie session if there is a Session_OnEnd event to execute
    if($self->IsCompiled('Session_OnEnd')) {
	my $old_session = $asp->{Session};
	my $dead_session;
	if($id) {
	    $dead_session = &Apache::ASP::Session::new($asp, $id);
	    $asp->{Session} = $dead_session;
	} else {
	    $dead_session = $old_session;
	}
	
	$asp->{dbg} && $asp->Debug("Session_OnEnd", {session => $dead_session->SessionID()});
	$self->ExecuteEvent('Session_OnEnd');
	$asp->{Session} = $old_session;
	
	if($id) {
	    untie %{$dead_session};
	}
    }

    1;
}

sub ApplicationOnStart {
    my $self = shift;
    $self->{asp}->Debug("Application_OnStart");
    %{$self->{asp}{Application}} = (); 
    $self->ExecuteEvent('Application_OnStart');
}

sub ApplicationOnEnd {
    my $self = shift;
    my $asp = $self->{asp};
    $asp->Debug("Application_OnEnd");
    $self->ExecuteEvent('Application_OnEnd');
    %{$self->{asp}{Application}} = (); 

    # PROBLEM, since we are not resetting ASP objects
    # every execute now, useless code anyway

    #    delete $asp->{Internal}{'application'};    
    #    local $^W = 0;
    #    my $tied = tied %{$asp->{Application}};
    #    untie %{$asp->{Application}};
    #    $tied->DESTROY(); # call explicit DESTROY
    #    $asp->{Application} = &Apache::ASP::Application::new($self->{asp})
    #      || $self->Error("can't get application state");
}

sub ScriptOnStart {
    my $self = shift;
    $self->{asp}{dbg} && $self->{asp}->Debug("Script_OnStart");
    $self->ExecuteEvent('Script_OnStart');
}

sub ScriptOnEnd {
    my $self = shift;
    $self->{asp}{dbg} && $self->{asp}->Debug("Script_OnEnd");
    $self->ExecuteEvent('Script_OnEnd');
}

sub ScriptOnFlush {
    my $self = shift;
    $self->{asp}{dbg} && $self->{asp}->Debug("Script_OnFlush");
    $self->ExecuteEvent('Script_OnFlush');
}

sub EventsList {
    @Routines;
}

1;