The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Prophet::Server::Controller;
use Any::Moose;
use Prophet::Util;
use Prophet::Web::Result;

has cgi             => ( is => 'rw', isa => 'CGI' );
has failure_message => ( is => 'rw', isa => 'Str' );
has functions       => ( is => 'rw', isa => 'HashRef' );
has app_handle      => ( is => 'rw', isa => 'Prophet::App' );
has result          => ( is => 'ro', isa => 'Prophet::Web::Result' );

=head1 NAME

=head1 METHODS

=head1 DESCRIPTION

=cut

=head1 METHODS

=cut

sub extract_functions_from_cgi {
    my $self = shift;

    my $functions = {};
    foreach my $param ( $self->cgi->all_parameters ) {
        next unless $param =~ /^prophet-function-(.*)$/;
        my $name = $1;
        $self->app_handle->log_fatal( "Duplicate function definition for @{[$name]}." ) if ( exists $functions->{$name} );

        my $function_data = $self->cgi->param($param);
        my $attr          = $self->string_to_hash($function_data);
        $attr->{name} = $name;

        # For now, always execute
        $attr->{execute} = 1;

        # We MUST validate any function we're going to canonicalize
        $attr->{validate} = 1 if $attr->{execute};

        # We MUST canonicalize any function we're going to validate
        $attr->{canonicalize} = 1 if $attr->{validate};

        $functions->{$name} = $attr;
        $functions->{$name}->{params} = $self->params_for_function_from_cgi($name);
    }
    $self->functions($functions);
}

sub params_for_function_from_cgi {
    my $self     = shift;
    my $function = shift;

    my $values;
    for my $field ( $self->cgi->all_parameters ) {
        if ( $field =~ /^prophet-field-function-$function-prop-(.*)$/ ) {
            my $name = $1;
            $values->{$name} = {
                prop           => $name,
                value          => ($self->cgi->param($field) || undef),
                original_value => ($self->cgi->param( "original-value-" . $field ) || undef)
            };
        } elsif ( $field =~ /^prophet-fill-function-$function-prop-(.*)$/ ) {
            my $name  = $1;
            my $meta  = {};
            my $value = $self->cgi->param($field);
            next unless ( $value =~ /^function-(.*)\|result-(.*)$/ );
            $values->{$name} = {
                prop          => $name,
                from_function => $1,
                from_result   => $2
            };
        } else {
            next;
        }

    }

    return $values;
}

sub handle_functions {
    my $self = shift;

   my @workflow = qw(
       extract_functions_from_cgi 
       canonicalize_functions
       validate_functions
       execute_functions    
    );
    eval {
        for (@workflow) { 
        $self->$_() ;
        }
    }; 
    
    if (my $err = $@) {
        $self->result->success(0);
        $self->result->message($err);   
    }
}

sub canonicalize_functions {
    my $self    = shift;
    my $functions = $self->functions;
    foreach my $function ( sort { $functions->{$a}->{order} <=> $functions->{$b}->{order}}  keys %{$functions}) {
        next unless ($functions->{$function}->{canonicalize});
        foreach my $param ( keys %{ $functions->{$function}->{params} } ) {
            if ( 
                defined $functions->{$function}->{params}->{$param}->{original_value} &&
                ($functions->{$function}->{params}->{$param}->{original_value} eq
                $functions->{$function}->{params}->{$param}->{value} )) {
                delete $functions->{$function}->{params}->{$param};
                next;
            }

        }

    }

}

sub validate_functions {
    my $self    = shift;
    my $functions = $self->functions;
    foreach my $function ( sort { $functions->{$a}->{order} <=> $functions->{$b}->{order}}  keys %{$functions}) {
        next unless ($functions->{$function}->{validate});
        foreach my $param ( keys %{ $functions->{$function}->{params} } ) {
            if (0) {
            }
        }

}

sub execute_functions {
    my $self = shift;
    my $functions = $self->functions;

    foreach my $function ( sort { $functions->{$a}->{order} <=> $functions->{$b}->{order}}  keys %{$functions}) {
        $self->app_handle->log_debug("About to execute a function - ".$function);
        $self->_fill_params_from_previous_functions($function); 


        next unless ($functions->{$function}->{execute});

        if ($functions->{$function}->{action} eq 'update') {
            $self->_exec_function_update($functions->{$function});
        } elsif ($functions->{$function}->{action} eq 'create') {
            $self->_exec_function_create($functions->{$function});
        } else {
            die "I don't know how to handle a ".$functions->{$function}->{action};
        }
    }
}

sub _fill_params_from_previous_functions {
    my $self     = shift;
    my $function = shift;
    my $params = $self->functions->{$function}->{params};
    foreach my $param ( keys %$params ) {
        if ( my $from_function = $params->{$param}->{from_function} ) {
            my $from_result = $params->{$param}->{from_result};
            my $function_result = $self->result->get($from_function);

            # XXX TODO - $from_result should be locked down tighter
            if ( $function_result->can($from_result) ) {
                $params->{$param}->{value} = $function_result->$from_result();
            }
        }
    }
}


sub _get_record_for_function {
    my $self = shift;
    my $function = shift;

    my $functions = $self->functions;
        if ($functions->{$function}->{action} eq 'update') {

            return Prophet::Util->instantiate_record( uuid => $functions->{$function}->{uuid}, class=>$functions->{$function}->{class}, app_handle=> $self->app_handle);
        } elsif ($functions->{$function}->{action} eq 'create') {
                die $functions->{$function}->{class} ." is not a valid class " unless (UNIVERSAL::isa($functions->{$function}->{class}, 'Prophet::Record'));
                return  $functions->{$function}->{class}->new(  app_handle => $self->app_handle);
        } else {
            die "I don't know how to handle a ".$functions->{$function}->{action};
        }
    }

}

sub _exec_function_create {
    my $self = shift;
    my $function = shift;

    my $object = $self->_get_record_for_function($function->{name});
    my ( $val, $msg ) = $object->create(
        props => {
            map { $function->{params}->{$_}->{prop} => $function->{params}->{$_}->{value}
                } keys %{ $function->{params} }
            }

    ); 

    my $res = Prophet::Web::FunctionResult->new( function_name => $function->{name}, 
                                                 class => $function->{class},
                                                 success => $object->uuid? 1 :0,
                                                 record_uuid => $object->uuid,
                                                 msg => ($msg || 'Record created'));
                     

    $self->result->set($function->{name} => $res);

}

sub _exec_function_update {
    my $self = shift;
    my $function = shift;
    my $object = $self->_get_record_for_function($function->{name});
    my ( $val, $msg ) = $object->set_props(
        props => {
            map {
                $function->{params}->{$_}->{prop} => $function->{params}->{$_}->{value}
                } keys %{ $function->{params} }
            }

    );
    my $res = Prophet::Web::FunctionResult->new( function_name => $function->{name}, 
                                                 class => $function->{class},
                                                 success => $val? 1 :0,
                                                 record_uuid => $object->uuid,
                                                 msg => ($msg || 'Record updated'));
                                                
    $self->result->set($function->{name} => $res);

}

sub string_to_hash {
    my $self = shift;
    my $data = shift;
    my @bits = grep {$_} split( /\|/, $data );
    my %attr = map { split( /=/, $_ ) } @bits;
    return \%attr;
}

__PACKAGE__->meta->make_immutable;
no Any::Moose;

1;