# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be 
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: Error.pm 3154 2008-06-29 10:43:57Z josh $

package BigMed::Error;
use Carp;
use strict;
use utf8;
use base qw(Exporter);

my @error;

sub error { @error }

sub set_error {
    my $self = shift;
    @_ % 2
      && croak "set_error accepts a hash of arguments, but an odd number "
      . "of arguments were submitted.";

    push( @error, {@_} );
    return undef;
}

sub clear_error { @error = () }

sub error_stop {
    if (!$_[0]->_display_error()) {
        croak 'error_stop called without any errors in the queue';
    }
    exit 0;
}

sub set_io_error {
    my ( $self, $fh, $action, $filepath, $error ) = @_;
    if ($fh) {
        $action eq 'closedir' || $action eq 'opendir'
          ? closedir $fh
          : close $fh;
    }

    #format => [event, verb, object, ' for what purpose']
    my %io_error = (
        open    => ['ERROR_opening', 'ERROR_open', 'ERROR_file',],
        opendir => ['ERROR_opening', 'ERROR_open', 'ERROR_directory',],
        copy    => ['ERROR_copying', 'ERROR_copy', 'ERROR_file',],
        lock_ex => [
            'ERROR_locking', 'ERROR_lock', 'ERROR_file', 'ERROR_for exclusive'
        ],
        lock_sh =>
          ['ERROR_locking', 'ERROR_lock', 'ERROR_file', 'ERROR_for shared'],
        rewind   => ['ERROR_rewinding',  'ERROR_rewind',   'ERROR_file',],
        truncate => ['ERROR_truncating', 'ERROR_truncate', 'ERROR_file',],
        write    => ['ERROR_writing',    'ERROR_write',    'ERROR_file',],
        close    => ['ERROR_closing',    'ERROR_close',    'ERROR_file',],
        closedir => ['ERROR_closing',  'ERROR_close',  'ERROR_directory',],
        unlink   => ['ERROR_deleting', 'ERROR_remove', 'ERROR_file',],
        rmtree   => ['ERROR_deleting', 'ERROR_remove', 'ERROR_directory',],
        move     =>
          ['ERROR_moving or renaming', 'ERROR_move or rename', 'ERROR_file',],
    );

    if (!$action || !defined $io_error{$action}) {
        carp "set_io_error: Unknown action";
        return $self->set_error(
            head => 'ERROR_I/O Error',
            text => ['ERROR_I/O Generic text', $filepath, $error],
        );
    }
    
    require BigMed;
    my $bigmed = BigMed->bigmed;
    my @attr = map { $bigmed->language($_) } @{ $io_error{$action} };
    $attr[3] ||= '';
    return $self->set_error(
        head => ['ERROR_IO_descriptive_headline', $attr[0], $attr[2]],
        text =>
          ['ERROR_IO_descriptive_text', @attr[0 .. 3], $filepath, $error],
    );
}

sub _display_error {
    my $self = shift;
    @_ % 2
      && croak "display_error accepts a hash of arguments, but an odd number "
      . "of arguments were submitted.";
    my %passed = @_;
    return undef unless @error;
    my %message = $self->error_html_hash;
    require BigMed;
    my $bigmed = BigMed->bigmed;
    return $passed{give_back}
      ? $bigmed->message( %passed, %message )    #pass back display text
      : $bigmed->message( %message, exit => 1 ); #display the message and stop
}

sub error_html_hash {   #returns a head/text pair ready for localization
    my $self = shift;
    return ()if !@error;
    require BigMed;
    my $bigmed = BigMed->bigmed;
    my %message;
    if ( @error == 1 ) {
        %message = %{ $error[0] };
    }
    else {
        my @list = map { $_->{text} } @error;
        %message = (
            head => 'ERROR_HEAD_Multiple errors',
            text => [
                'ERROR_TEXT_Multiple errors',
                scalar @list,
                '%BM' . $bigmed->language_list(@list) . '%' #no 2x escapes
            ],
        );
    }
    %message;
}


1;
__END__

=head1 NAME

BigMed::Error

=head1 SYNOPSIS

BigMed::Error's routines can be called as static methods:

    use BigMed::Error;  
    BigMed::Error->set_error(
        cat => 'vocab_category',
        id =>  'vocab_id'
    );
    my @error = BigMed::Error->error();
    BigMed::Error->error_stop();
    
Or, for object-oriented subclasses, BigMed::Error's methods can
be called as methods of the subclass's objects

    package Foo;
    use BigMed::Error;
    use base qw(BigMed::Error); #make it a subclass of BigMed::Error
    
    ...
    
    my $foo = Foo->new();
    $foo->set_error(
        cat => 'vocab_category',
        id =>  'vocab_id'
    );
    my @error = $foo->error();
    $foo->error_stop();

=head1 DESCRIPTION

BigMed::Error is a base class that helps glue together the error handling
of Big Medium's disparate modules. Any BigMed::Error subclass can use
these methods to set and retrieve error values set by any other
BigMed::Error subclass.

As a result, when any subclass of BigMed::Error sets an error message,
this error message gets added to the cumulative stack of messages across
all other subclasses. Likewise, retrieving error messages retrieves all
messages set by all subclasses.

=head2 Methods

=over 4

=item BigMed::Error->set_error(head=>'headline key', text=>'text key')

Adds an error message to the current stack of error messages (if any).
The arguments are a hash of two key/value pairs, C<head> and C<text>,
which hold the BigMed::Language lexicon keys for the text to display.

Always returns an undef value, which is an appropriate value to return
from a routine that has failed:

    if ($action_failed) {
        return BigMed::Error->set_error(%args);
    }

=item BigMed::Error->set_io_error($fh, $action, $filepath, $error)

Similar to C<set_error> but specifically tuned to errors encountered
when reading and writing files. Customizes the error message to
include the type of action, the file path and the system error
received. As with C<set_error>, this method returns an undef value.

The arguments are:

=over 4

=item * $fh

File handle of the file that caused the error. C<set_io_error> closes
the handle.

=item * $action

What was being done that caused the error. Options:

=over 4

=item * 'open'

=item * 'opendir'

=item * 'lock_ex'

=item * 'lock_sh'

=item * 'rewind'

=item * 'truncate'

=item * 'write'

=item * 'close'

=item * 'closedir'

=item * 'unlink'

=item * 'rmtree'

=item * 'move'

=item * 'copy'

=back

=item * $filepath

The absolute path to the file that caused the error.

=item * $error

The error in $!.

=back

=item BigMed::Error->error()

Returns an array of hash references to all of the errors set so far.
The hash references each have the same C<head> and C<text> key/value
pairs set via the C<set_error>a routine.

Calling the C<error> method can be a useful way to find out if there
are any errors in the queue:

    if ( BigMed::Error->error ) {
        #we have an error
    }

=item BigMed::Error->error_html_hash()

Returns a two-pair hash containing the current error message. Both
values are unlocalized but are ready to be passed as-is to
BigMed's C<language> method.

=over 4

=item * head

The headline of the error message.

=item * text

The body of the error message. If there are multiple error messages,
these are formatted into a HTML unordered list.

=back

=item C<error_stop>

If there are any errors in the queue, all action is stopped and the
error messages are displayed (via the BigMed object's C<message>
method).

=item C<clear_error>

Clear's all error messages in the error queue.

=back

=head1 AUTHOR & COPYRIGHTS

This module and all Big Medium modules are copyright Josh Clark
and Global Moxie. All rights reserved.

Use of this module and the Big Medium content
management system are governed by Global Moxie's software licenses
and may not be used outside of the terms and conditions outlined
there.

For more information, visit the Global Moxie website at
L<http://globalmoxie.com/>.

Big Medium and Global Moxie are service marks of Global Moxie
and Josh Clark. All rights reserved.

=cut

