package LIMS::Local::ErrorHandler;

# http://www.stonehenge.com/merlyn/PerlJournal/col05.html

use strict;

$|++;

use Carp;
use Data::Dumper;
use POSIX qw(strftime);
use LIMS::Model::Email;
use LIMS::Local::ScriptHelpers;

use Sub::Exporter -setup => { exports => [ qw(set_errorhandler_args) ] };

BEGIN {
    use vars qw($cfg);
    
    use FindBin qw($Bin); # warn 'BIN:'.$Bin; exit;

    use LIMS::Local::Config;
    $cfg = LIMS::Local::Config->instance;

    use CGI::Application;
=begin
    use Devel::Events::Objects;
    use Devel::Cycle;
    use Devel::Events::Handler::ObjectTracker;
    use Devel::Events::Filter::RemoveFields;
    use Devel::Events::Generator::Objects;

    my $tracker = Devel::Events::Handler::ObjectTracker->new();

    my $gen = Devel::Events::Generator::Objects->new(
        handler => Devel::Events::Filter::RemoveFields->new(
            fields => [qw/generator/], # don't need to have a ref to $gen in each event
            handler => $tracker,
        ),
    );
=cut    
    no warnings 'redefine';
    *CGI::Application::__get_body = sub {
        my $self  = shift;
        my $rm    = shift;
    
        my ($rmeth, $is_autoload) = $self->__get_runmeth($rm);
    
        my $body;
#        $gen->enable();        
        eval {
            $body = $is_autoload ? $self->$rmeth($rm) : $self->$rmeth();
        };
#        $gen->disable();
=begin
        my @leaked_objects = keys %{ $tracker->live_objects };

        print "leaked ", scalar(@leaked_objects), " objects\n";

        foreach my $object ( @leaked_objects ) {
            print "Leaked object: $object\n";

            # the event that generated it
            # print Dumper( $object, $tracker->live_objects->{$object} );
            print '$object: ' . $object . "\n";
            print '$tracker->live_objects->{$object}' .  $tracker->live_objects->{$object}. "\n";
            find_cycle( $object );
        }
=cut        
        if ($@) { 
            my $error = $@; # warn Dumper [$error, $self->error_mode];
            $self->call_hook('error', $error);
            if (my $em = $self->error_mode) {
                $body = $self->$em( $error );
            }
            else {
                my $error_string = "Error executing run mode '$rm': $error";
                # warn $error_string;
                $body = LIMS::Local::ErrorHandler::fatal_error($error_string);
            }
        }

        # Make sure that $body is not undefined (suppress 'uninitialized value'
        # warnings)
        return defined $body ? $body : '';
    }
}

################################################################################
# need 'to', 'from' & 'smtp' vars supplying via $args to initiate email to admin
################################################################################

use vars qw($args);
my $tools = LIMS::Local::ScriptHelpers->new();
$tools->use_path("$Bin/..");  # for ScriptHelpers to find correct path for templates

sub set_errorhandler_args {
    $args = shift; # warn Dumper $args;
}

sub fatal_error {
    my $message = shift;

#    my ($package, $file, $line) = caller; warn Dumper ($package, $file, $line); # don't need it
    my $incident = unpack "H*", pack "NS", time, $$;

    my $time = strftime "%d-%b-%Y %H:%M:%S", localtime;
    my $user = uc $args->{user};
    
    my %report = (
		settings => $cfg->{settings},
        incident => $incident,
        message  => $message,
        user     => $user,
    );

    # record reason for death into the error log
    warn join "", map {
        sprintf "crash %s [%s %s]: %s\n", $incident, $user, $time, $_;
       }
       split /\n+/, $message;

    # maybe alert admin:
    if ( $ENV{ROSEDB_DEVINIT} =~ /prod/ ) {
        my $response = _send_email(\%report);
        $report{mail_response} = $response->string; # if $response->type ne 'success'; # for tt
    }

    # now give the user a place to record their experience
	my $output = $tools->process_template('error/errorhandler.tt', \%report);
	# warn Dumper $output;
    return \$output;
}

sub _send_email {
    my $report = shift;

    my $settings = $cfg->{settings};

    # need 'to' & 'from' attr from settings:
    my $from = $settings->{email_from}     || return 0;
    my $smtp = $settings->{smtp}           || return 0;
    my $to   = $settings->{sysadmin_email} || return 0;
    
    my $subject = sprintf '%s [%s]', $report->{incident}, $report->{user};
    my $message = $report->{message};
    
    my %mail = (
        recipient => $to,
        from      => $from,
		config    => $settings,
		message   => $message,
		subject   => $subject,
	); # DEBUG(\%mail);

    my $res = LIMS::Model::Email->send_message(\%mail); # Return::Value object
    return $res;
}

1;
