package Flash::FLAP::App::Executive;
# Copyright (c) 2003 by Vsevolod (Simon) Ilyushchenko. All rights reserved.
# This program is free software; you can redistribute it and/or modify it
# under the same terms as Perl itself.
# The code is based on the -PHP project (http:#amfphp.sourceforge.net/)

=head1 NAME
    Flash::FLAP::App::Executive
        
==head1 DESCRIPTION    

    Executive package figures out whether to call an explicitly registered package or to look one up in a registered directory. Then it executes the desired method in the package
    
==head1 CHANGES

Sun Mar  23 13:27:00 EST 2003
Synching with AMF-PHP:
Replaced packagepath, packagename, packageConstruct with classpath, classname, classConstruct.
Added _instanceName, _origClassPath and _headerFilter.
Added subs setHeaderFilter(), setInstanceName()
Renamed setClassPath to setTarget and removed extra junk from that function.
Eliminated _getPackage() and _getMethod().
Removed safeExecution().
	
Tue Mar 11 21:59:27 EST 2003
Passing @$a instead of $a to user functions. $a always is an array.

=cut


use strict;
use Flash::FLAP::Util::RemotingService;


#The above variable declarations are not needed, as hash keys are used. They are useful just for the comments.
# the directory which should be used for the basic packages default "../"
# my $_basecp = "../";
# the classpath which is the path of the file from $_basecp
#my $_classpath;
# the string name of the package derived from the classpath
#my $_classname;
# the object we build from the package
#my $_classConstruct;
# the method to execute in the construct
#my $_methodname;
# the defined return type
#my $_returnType;
# the instance name to use for this gateway executive
#my $_instanceName;
# the list with registered service-packagees
#my $services = {};
# The original incoming classpath
#my $_target;
# The original classpath
#my $_origClassPath;
# switch to take different actions based on the header
#my $_headerFilter;
        
# constructor
sub new
{
    my ($proto)=@_;
    my $self={};
    bless $self, $proto;
    return $self;
    # nothing really to do here yet?
}


# setter for the _headerFilter
sub setHeaderFilter 
{
    my ($self, $header) = @_;
    $self->{_headerFilter} = $header;
}

# Set the base classpath. This is the path from which will be search for the packagees and functions
# $basecp should end with a "/";
sub setBaseClassPath
{
    my ($self, $basecp) = @_; 
    $self->{_basecp} = $basecp; 
}

sub setInstanceName
{  
    my ($self, $name) = @_; 
    $self->{_instanceName} = $name;
}

# you pass directory.script.method to this and it will build
# the classpath, classname and methodname values
sub setTarget
{
    my ($self, $target)=@_;
    $self->{target} = $target;
    # grab the position of the last . char
    my $lpos = strrpos($target, ".");
    # there were none
    unless ($lpos) 
    {
        print STDERR "Service name $target does not contain a dot.\n";
        # throw an error because there has to be atleast 1
    } 
    else
    {
        # the method name is the very last part
        $self->{_methodname} = substr($target, $lpos+1);
    }
    # truncate the method name from the string
    my $trunced = substr($target, 0, $lpos);
    
    $self->{_classname} = $trunced;
}

sub registerService
{
    my ($self, $package, $servicepackage) = @_;
    $self->{services}->{$package} = $servicepackage;
}

# returns the return type for this method
sub getReturnType
{
    my ($self)=@_;
    return $self->{_returnType};
}

# execute the method using dynamic inclusion of Perl files
sub doMethodCall 
{
    my ($self, $a) = @_;
    
    #First try to call a registered class...
    my $package = $self->{_classname};
    my $method = $self->{_methodname};
    
    my $calledMethod = $method;
    
    if(exists $self->{services}->{$package})
    {    
        return $self->doMethodCall_registered($package, $method, $a);
    }
    
    #Otherwise, browse in the directory specified by the user.

    push @INC, $self->{_basecp};

    # build the class object
    
    $package =~ s#\.#::#g;
    
    unless (eval ("require " . $package))
    {
        # report back to flash that the class wasn't properly formatted
        print STDERR  "Class $package does not exist or could not be loaded.\n";
	print STDERR $@;
        return;
    }

    # build the construct from the extended class
    my $object = $package->new;
    
    # Check to see if the DescribeService header has been turned on
    if ($self->{_headerFilter} && $self->{_headerFilter} eq "DescribeService")
    {
        my $wrapper = new Flash::FLAP::Util::RemotingService($package, $object);

        $self->{_classConstruct} = $wrapper;

        $method =  "__describeService";

# override the method name to the __describeService method
        $self->{_methodname} = $method;

# add the instance to the methodrecord to control registered discover
        my $methodTable = $self->{_classConstruct}->methodTable;
        $methodTable->{$method}{'instance'} = $self->{_instanceName};

    }
    else
    {
        $self->{_classConstruct} = $object;
    }

# was this defined in the methodTable -- required to enable FLAP service approach
    if (exists ($self->{_classConstruct}->methodTable->{$method}))
    {
# create a shortcut to the methodTable
        my %methodrecord = %{$self->{_classConstruct}->methodTable->{$method}};

# check to see if this method name is aliased
        if (exists ($methodrecord{'alias'}))
        {
# map the _methodname to the alias
            $method = $methodrecord{'alias'};
        }

        if (exists($methodrecord{'instance'}))
        {
# check the instance names to see if they match.  If so, then let this happen
            if (!exists($methodrecord{'instance'}) || $self->{_instanceName} != $methodrecord{'instance'})
            {	
# if they don't match then print STDERR  with this error
            print STDERR  "Access error for " . $self->{_headerFilter} . ".\n";
            return;
            }
        }
        
        # check to see if an explicit return type was defined
        if (exists($methodrecord{'returns'}))
        {
            $self->{_returnType} = $methodrecord{'returns'};
        }
        # set the default return type of "unknown"
        else
        {
            $self->{_returnType} = "unknown";
        }
        # set to see if the access was set and the method as remote permissions.
        if ( (exists($methodrecord{'access'})) && (lc ($methodrecord{'access'}) eq "remote"))
        {
            # finally check to see if the method existed
            if ($self->{_classConstruct}->can($method))
            {
                # execute the method and return it's results to the gateway
                return $self->{_classConstruct}->$method(@$a);
            }
            else
            {
                # print STDERR  with error
                print STDERR  "Method " . $calledMethod . " does not exist in class ".$self->{_classConstruct}.".\n";
            }
        }
        else
        {
            # print STDERR  with error
            print STDERR  "Access Denied to " . $calledMethod . "\n";
        }
            
        
    }
    else
    {
        # print STDERR  with error
        print STDERR  "Function " . $calledMethod . " does not exist in class ".$self->{_classConstruct}.".\n";
    }

}

sub doMethodCall_registered
{
    my ($self, $package, $method, $a) = @_;
    
    my $serviceobject = $self->{services}->{$package};

    if(length($package) == 0)
    {
    # TODO: handle non packaged functions
    #trigger_error("ERROR: no package in call",E_USER_ERROR);
        return;
    } 
    elsif(!$serviceobject)
    {
        print STDERR "Package ".$package." not registerd on server\n";
        return;
    } 
    elsif(!$serviceobject->can($method))
    {
        print STDERR "Function ".$method." does not exist in package ".$package."\n";
        return;
    }
    else
    {
        $self->{_returnType} = "unknown";
        return $serviceobject->$method(@$a);
    }    
}

sub strrpos
{
    my ($string)=@_;
    my $reversed = reverse $string;
    my $firstDotIndex = index($reversed, ".");
    return length($string)-$firstDotIndex-1;
}

1;
