# 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: Status.pm 3043 2008-03-31 14:00:38Z josh $

package BigMed::Status;
use strict;
use warnings;
use utf8;
use Carp;
use BigMed;
use BigMed::JSON;
use BigMed::Error;
use BigMed::Log;

###########################################################
# CONSTRUCTOR METHODS
###########################################################

sub new {
    my ($class, $rparam) = @_;
    $rparam ||= {};
    
    my $self     = bless {
        progress   => ( $rparam->{progress} || 0 ),
        steps      => ( $rparam->{steps} || 0) ,
        message    => ( $rparam->{message} || 'STATUS_Starting...' ),
        container  => $rparam->{container},
        inline     => $rparam->{inline},
    }, $class;
    
    return $self->initialize($rparam);
}

sub initialize {
    my ($self, $rparam) = @_;
    
    $self->set_signals($rparam);
    $self->init_bar() or return;
    return $self;
}

sub init_bar {
    my $self = shift;
    
    #expects to have a BigMed::App::Web
    my $app = BigMed->bigmed->app;
    $|=1;
    if ($self->{inline}) {
        print $app->html_template_screen(
            'wi_statusbar_start.tmpl',
            container => $self->{container},
        );
    }
    else {
        print $app->_send_headers();
        print $app->html_template_screen(
            'shell_statusbar_top.tmpl',
            container => $self->{container} || $app->utf8_param('BMCont'),
        );
    }
    $self->update_status() or return;
    return 1;
}

sub set_signals {
    my ($self, $rparam) = @_;

    #make sure that we do *something* with warn and die messages; at least
    #put them in the system log if we don't specify some other behavior.
    $SIG{__WARN__} = $rparam->{'warn'}
      || sub { $self->log( info => "Warning: $_[0]" ) };
    $SIG{__DIE__} = $rparam->{'die'} || \&die_handler;
    return;
}

sub die_handler {
    #this gets ugly...
    #we don't want to do this handling on eval'd code.
    #because the whole op is wrapped in eval (via CGI::Application),
    #$^S testing prescribed by perldoc for 'die' won't work,
    #because $^S is always true -- *every* call is an eval call.
    #instead, test for callers that are eval and *not* cgi::app
    my $i = 0;
    my @fields;
    while (@fields = caller($i++) ) {
        die @_ if $fields[3] eq '(eval)' && $fields[0] ne 'CGI::Application';
    }

    #otherwise, it's a regular death, log it and show the error
    BigMed::Log->log(info => "Fatal: $_[0]" );
    BigMed::Status->send_error($_[0]);
}

###########################################################
# ACCESSORS
###########################################################

sub progress {
    return $_[0]->{progress};
}

sub steps {
    return $_[0]->{steps};
}


###########################################################
# STATUS BAR MESSENGERS
###########################################################

sub update_status {
    my $self   = shift;
    my %update = @_;
    my %progress;
    my $updated;
    foreach my $s (qw(progress steps message)) {
        if (defined $update{$s}) {
            my $test = $update{$s};
            $updated ||= !defined $self->{$s} || $self->{$s} ne "$test";
            $self->{$s} = $update{$s};
        }
        $progress{$s} = $self->{$s};
    }
    if (!$updated) {
        return 1 if $self->{printed}; #no dupes;
        $self->{printed} = 1;
    }

    $progress{percent} =
      $progress{steps} ? $progress{progress} / $progress{steps} : 0;
    $progress{message} = BigMed->bigmed->language( $progress{message} );

    my $app = BigMed->bigmed->app;
    $progress{message} = $app->js_escape($progress{message});
    $|=1;
    print $app->html_template(
        'wi_statusbar_update.tmpl',
        inline => $self->{inline},
        %progress,
    );
    return 1;
}

sub ping {
    print qq{ \n}; #keep the connection alive
}

sub send_error {
    my ( $self_or_class, $error_msg ) = @_;
    if ( !$error_msg ) {
        my %error = BigMed::Error->error_html_hash();
        $error_msg = BigMed->bigmed->language( $error{text} ) if $error{text};
    }
    $error_msg ||= $! || BigMed->bigmed->language('BM_Unknown error');

    my $app = BigMed->bigmed->app;
    $|=1;
    print $app->html_template(
        'wi_statusbar_update.tmpl',
        error => $app->js_escape($error_msg),
    );
    $app->teardown() if $app->can('teardown');
    exit(0);
}

sub mark_done {
    $|=1;
    print BigMed->bigmed->app->html_template(
        'wi_statusbar_update.tmpl',
        done => 1,
    );
    return 1;
}



1;

__END__

=head1 NAME

BigMed::Status - Generate HTML to generate Big Medium's web-based progress
bars

=head1 SYNOPSIS

    use BigMed::Status;
    
    # create statusbar object and print start of HTML to STDOUT to
    # drive statusbar
    my $status = BigMed::Status->new(
        steps     => 5,
        container => 'statusbarDiv',
    );
    
    #update status (prints more HTML to STDOUT)
    $status->update_status(
        progress => 1,
        message => 'BM_Updating status 1', #lexicon key
    );
    
    #ping -- no update to status message, just keeps http connection
    #alive
    $status->ping();
    
    #finish up (completes status bar and wraps up HTML page to STDOUT)
    $status->mark_done();


=head1 DESCRIPTION

BigMed::Status generates HTML to update Big Medium's BM.StatusBar Javascript
objects.

It can return HTML appropriate to be returned within the same HTML page
as the javascript (with its inline option enabled) or, by default,
HTML to be returned as the response to URLs in
Big Medium's BM.StatusDriver JavaScript object. (BM.StatusDriver
sets up an iframe to drive a GUI status bar.)

BigMed::Status objects print HTML to STDOUT, providing appropriate 
JavaScript calls to update the status bar based on the content of the
BigMed::Status object.

=head1 HTML SETUP

BigMed::Status should be used to respond to requests submitted via
Big Medium's BM.StatusDriver JavaScript object. The HTML page
should include an empty <div> where the status bar will be displayed.

    <div class="statusbarDiv"></div>
    
    <!-- drive the status bar via iframe (in case you want to offer
         the option to do multiple actions from the same page -->
    <script type="text/javascript" language="JavaScript">
        new BM.StatusDriver(
            'statusbarDiv',
            'http://www.example.com/cgi-bin/update.cgi'
        );
    </script>

In this example, the URL C<http://www.example.com/cgi-bin/update.cgi>
should return the BigMed::Status output, which will generate HTML
to create and update the statusbar on the script by manipulating the
page's DOM layout.

An alternative method is to create a BigMed::Status object with the
inline parameter. This is appropriate only when you have already
printed to STDOUT the complete web page, including the status bar
div:

    <div class="statusbarDiv"></div>

The inline parameter tells BigMed::Status to start printing HTML
to STDOUT to control a status bar on the current page (not via iframe):

    my $status = BigMed::Status->new(
        steps     => 5,
        container => 'statusbarDiv',
        inline => 1,
    );

=head1 METHODS

=head2 C<<BigMed::Status->new( \%params )>>

Returns a new BigMed::Status object and prints the start of the HTML
response to STDOUT. The method accepts a hash-reference argument of
optional parameters:

=over 4

=item * C<< steps => 5 >>

The total number of steps in the process to be run (defaults to 0).
This affects the progress across the page. As the C<progress> parameter
gets updated, the status bar meter in the GUI moves from left to right.

=item * C<< progress => 1 >>

The starting number of the step when the status bar is first displayed
(defaults to 0).

=item * C<< message => 'Big Medium lexicon key' >>

A string corresponding to a language key to be passed to the BigMed
C<language> method. Defaults to 'STATUS_Starting...'

=item * C<< inline => 1 >>

If true, the object will behave on the assumption that the status bar
is in the same HTML page and will not use the default iframe behavior
required when called via Big Medium's BM.StatusDriver JavaScript object.

=item * C<< die => \&subroutine >>

A coderef to call if the routine dies. By default, BigMed::Status
logs the error and displays it to the GUI via the BigMed::Status
C<send_error> object method, then exits.

=item * C<< warn => \&subroutine >>

A coderef to call if the routine encounters a warning. By default,
BigMed::Status logs the warning message and continues.

=back

=head2 C<< $status->update_status( %param ) >>

Prints to STDOUT a HTML snippet containing JavaScript to update the
GUI statusbar based on the argument hash. The hash can consist of
any of these three key/value pairs (see the C<new> method for their
meaning):

=over 4

=item * C<< progress => 5 >>

=item * C<< message => 'Big Medium lexicon key' >>

=item * C<< steps => 10 >>

You'll typically want to leave this value unchanged and just stay steady
with the value used to create the object.

=back

=head2 C<< $status->ping() >>

Prints a space and line break to STDOUT. Useful in long-running processes
when you just need to keep the HTTP connection alive.

=head2 C<< $status->send_error( 'foo' ) >>

Updates the status bar with the argument string and exits. Before exiting,
calls the current BigMed::Application object's `teardown` method, if one
exists.

If no argument string is provided, the error message in the Big Medium error
queue is used, if any, or the current $! value is used.

=head2 C<< $status->mark_done() >>

Completes the GUI status bar by moving the status meter over to the right
and marking it 100% done. Also completes the HTML in the status page.

=head2 C<< $status->progress() >>

Returns the object's current progress value.

=head2 C<< $status->steps() >>

Returns the object's current steps value.

=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
