#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::MultiStepOperations;

use IT::Globals;

use strict;


# module globals
our $BACKUP_TIMER_SECOND_LIMIT = 60;
our $RELOAD_DELAY_MS           = 0;    # 1000 ms equals 1 seconds

# user defined globals (set by Init call)
my $MAX_SECONDS_BEFORE_RELOAD = 0;

# ------------------------------------------------------------------------
# Function    : getMultiStepPlaceholders
# Description :
# Usage       : my $placeholders = &IT::MultiStepOperations::getMultiStepPlaceholders($placeholders);
# ------------------------------------------------------------------------

sub getMultiStepPlaceholders {
  my $placeholders = shift || {};
  my $template     = "admin/menus/_multiStepOperations.html";

  # get placeholders
  $placeholders->{'_startTime'}              = $FORM->{'_startTime'}      || time();
  $placeholders->{'_currentStage'}           = $FORM->{'_currentStage'}   || 0;
  $placeholders->{'_currentStep'}            = $FORM->{'_currentStep'}    || 0;
  $placeholders->{'_currentSubStep'}         = $FORM->{'_currentSubStep'} || 0;
  $placeholders->{'_elapsedSeconds'}         = &_getElapsedSeconds();
  $placeholders->{'_elapsedTime'}            = &_getElapsedTimeString( $placeholders->{'_elapsedSeconds'} );
  $placeholders->{'_backupTimerSecondLimit'} = $BACKUP_TIMER_SECOND_LIMIT;

  # get html placeholders
  $placeholders->{'_hiddenFieldsForMultiStep'}   = &templateCell($template, '_hiddenFieldsForMultiStep', $placeholders);
  $placeholders->{'_javascriptCodeForMultiStep'} = &templateCell($template, '_javascriptCodeForMultiStep', $placeholders);
  $placeholders->{'_backupTimerForMultiStep'}    = &templateCell($template, '_backupTimerForMultiStep', $placeholders);

  # for debugging
  # $placeholders->{'_hiddenFieldsForMultiStep'}  .= "CurrentStage: $placeholders->{'_currentStage'}, CurrentStep: $placeholders->{'_currentStep'}, CurrentSubStep: $placeholders->{'_currentSubStep'}<Br>\n";

  #
  return $placeholders;
}

# ------------------------------------------------------------------------
# Function    : doMultiStepOperation
# Description : Perform a large operation in many small steps, reload the
#               browser if nessicary, to avoid server timeouts that would
#               kill the cgi process after X seconds.
#
#               If you need to pass additional arguments to the "step" functions
#               you can do it by calling them from a anonymous code ref like this:
#               push @stages, sub { &_publishSinglePages($thisRule, @_)};
#               so the _publishSinglePages() function would get the following args:
#               ($thisRule, \$currentStep, \$currentSubStep).  Note that steps
#               are passed by reference so updating their value updates their
#               value in this function as well.
#
#
#               This function requires the following hidden fields be defined
#               with id="" values that match their field names.
#               _startTime - time() doMultiStepOperation was first called
#               _currentStage    - current stage
#               _currentStep     - current step
#               _currentSubStep  - current substep
#
#               This function requires the following javascript functions in the html:
#               addStatusMessage() - Add a status message to the multiStep console
#               submitForm()       - submit the multistep form to reload browser
#
# Usage       : # display alerts and progress page
#               &IT::App::alert('<font color="red"><b>DATA IMPORT IN PROGRESS</b></font>', 'keepHTML');
#               &IT::App::alert('Please, do not stop or close your browser!');
#               &callFunctionToDisplayMultiStepPageHere()
#
#               # run operation
#               &IT::MultiStepOperations::doMultiStepOperation({
#                  'stages'      => \@stageCodeRefs,
#                  'maxSeconds'  => $maxSecondsPerProcess,
#                  'displayPage' => \&displayPage,
#                  'onTimeout'   => \&onTimeout,
#                  'onComplete'  => \&onComplete,
#                });
#
# Note        : See Publish.pm for example usage.
# ------------------------------------------------------------------------

sub doMultiStepOperation {
  my $args       = shift || die "first argument must be defined and a hash ref!\n";

  # error checking
  if (@_ > 1)                               { die "doMultiStepOperation takes only ONE argument!\n"; }
  if (ref $args ne 'HASH')                  { die "first argument must be a hash ref!\n"; }
  if (!$args->{'stages'})                   { die "no stages value specified!\n"; }
  if (ref $args->{'stages'} ne 'ARRAY')     { die "stages value must be an array ref! ($args->{'stages'}\n"; }
  if (@{$args->{'stages'}} == 0)            { die "stages array must have at least one stage!\n"; }
  if (!$args->{'maxSeconds'})               { die "maxSeconds argument must be at least 1!\n"; }
  if (!$args->{'displayPage'})              { die "no 'displayPage' value specified!\n"; }
  if (ref $args->{'displayPage'} ne 'CODE') { die "displayPage must be a CODE ref!\n"; }
  if (!$args->{'onTimeout'})                { die "no 'onTimeout' value specified!\n"; }
  if (ref $args->{'onTimeout'} ne 'CODE')   { die "onTimeout must be a CODE ref!\n"; }
  if (!$args->{'onComplete'})               { die "no 'onComplete' value specified!\n"; }
  if (ref $args->{'onComplete'} ne 'CODE')  { die "onComplete must be a CODE ref!\n"; }

  ### if timed out...
  if ($FORM->{'_timedOut'}) {
    &{$args->{'onTimeout'}}();
    exit;
  }

  ### Display "Publishing in progress" page...
  &{$args->{'displayPage'}}();

  # do more steps - while time remaining...
  $MAX_SECONDS_BEFORE_RELOAD = $args->{'maxSeconds'};
  my $currentStage           = int $FORM->{'_currentStage'};
  my $currentStep            = int $FORM->{'_currentStep'};
  my $currentSubStep         = int $FORM->{'_currentSubStep'};
  my $allStagesComplete      = 0;
  my $count                  = 0;

  while (++$count == 1 || &_isTimeLeft()) { # use $count to make sure we always do at least one step!

    # perform current stage
    my $stageFunction   = $args->{'stages'}[$currentStage];
    my $isStageComplete = eval { &{$stageFunction}(\$currentStep, \$currentSubStep); };
    if ($@) {
      &addStatusMessage("Error: $@\n");
      exit;
    }

    # on stage complete: reset step counters and go to next stage
    if ($isStageComplete) {
      $currentStage++;
      $currentStep    = 0;
      $currentSubStep = 0;
    }

    # update counter fields
    &_updateHiddenFieldValue('_currentStage',   $currentStage);
    &_updateHiddenFieldValue('_currentStep',    $currentStep);
    &_updateHiddenFieldValue('_currentSubStep', $currentSubStep);

    # on all stages complete: exit while loop
    my $totalStages = scalar @{$args->{'stages'}};
    if ($currentStage >= $totalStages) {
      $allStagesComplete = 1;
      last;
    }
  }

  ### Reload when max seconds exceeded
  if (not $allStagesComplete) {
    &addStatusMessage("Reloading browser...\n");
    &_resubmitFormWithPost();
  }

  ### On all steps finished
  elsif ($allStagesComplete) {
    my $elapsedSeconds = &_getElapsedSeconds();
    my $elapsedTime    = &_getElapsedTimeString( $elapsedSeconds );
    &{$args->{'onComplete'}}($elapsedSeconds, $elapsedTime);
    exit;
  }

  exit;
}

# ----------------------------------------------------------------------------
# Function    : addStatusMessage
# Description :
# Usage       : &IT::MultiStepOperations::addStatusMessage("message");
# ----------------------------------------------------------------------------

sub addStatusMessage {
  my $message          = shift;
  my $jsEncodedMessage = &IT::Common::javascriptEncode("'", $message);

  print "\n<script>addStatusMessage('$jsEncodedMessage')</script>\n";
}


# ------------------------------------------------------------------------------
# Function    : _getElapsedSeconds
# Description :
# Usage       : my $seconds = &_getElapsedSeconds();
# ------------------------------------------------------------------------------

sub _getElapsedSeconds {

  # get elapsed publish time
  my $startTime      = $FORM->{'_startTime'} || $^T;
  my $elapsedSeconds = time() - $startTime;

  return $elapsedSeconds;
}

# ------------------------------------------------------------------------------
# Function    : _getElapsedTimeString
# Description :
# Usage       : my $timeString = &_getElapsedTimeString($elapsedSeconds);
# ------------------------------------------------------------------------------

sub _getElapsedTimeString {
  my $elapsedSeconds = int shift;

  # get time incrememnts
  my $hours   = int( $elapsedSeconds / (60*60) );
  my $minutes = int( $elapsedSeconds / 60 ) % 60;
  my $seconds = $elapsedSeconds % 60;

  # get time string in format 0:00 (M:SS) or 0:00:00 (H:MM:SS)
  my $timeString;
  if ($hours > 0) { $timeString = sprintf("%d:%02d:%02d", $hours, $minutes, $seconds); }
  else            { $timeString = sprintf("%d:%02d",              $minutes, $seconds); }

  #
  return $timeString;
}


# ------------------------------------------------------------------------------
# Function    : _isTimeLeft
# Description : Check if there is time left to do any more operations in this step
# Usage       : &_isTimeLeft();
# ------------------------------------------------------------------------------

sub _isTimeLeft {
  my $elapsedSeconds = time() - $^T;
  my $isTimeLeft     = int ($elapsedSeconds <= $MAX_SECONDS_BEFORE_RELOAD);

  return $isTimeLeft;
}


# ----------------------------------------------------------------------------
# Function    : _updateHiddenFieldValue
# Description :
# Usage       : &_updateHiddenFieldValue($fieldname, $value);
# ----------------------------------------------------------------------------

sub _updateHiddenFieldValue {
  my $fieldname = shift;
  my $value     = shift;

  # error checking
  my %validFieldNames = map { $_ => 1 } qw(_currentStage _currentStep _currentSubStep);
  unless ($validFieldNames{$fieldname}) { die "invalid field name '$fieldname'!"; }

  # update field value
  my $jsEncodedValue = &IT::Common::javascriptEncode("'", $value);
  print "\n<script>document.getElementById('$fieldname').value = '$jsEncodedValue';</script>\n";
}

# ----------------------------------------------------------------------------
# Function    : _resubmitFormWithPost
# Description :
# Usage       : &_resubmitFormWithPost();
# ----------------------------------------------------------------------------

sub _resubmitFormWithPost {
  print "\n<script>submitForm($RELOAD_DELAY_MS)</script>\n";
}

# ------------------------------------------------------------------------------
# Function    : _showPage
# Description :
# Usage       : &_showPage($pageName);
# ------------------------------------------------------------------------------

sub _showPage {
  my $templateCell = shift || die "no page or templateCell specified!";
  my @errors       = @_;
  my $template = "upgradeFromVersion1_41.html";

  # set errors
  if (@errors) {
    my $errors = join "\n", @errors;
    &IT::App::alert($errors);
  }

  # define placeholders
  my $placeholders = {};
  $placeholders->{'alerts'}                = &IT::App::alert();
  $placeholders->{'currentDatabaseEngine'} = &IT::DB::Schema::getConnectionAttributes($CURRENT_CONNECTION_NAME, 'engine');
  $placeholders->{'adminUsername'}         = $FORM->{'adminUsername'};
  $placeholders->{'adminPassword'}         = $FORM->{'adminPassword'};
  $placeholders->{'oldDataDir'}            = $FORM->{'oldDataDir'};
  $placeholders->{'oldImagesDir'}          = $FORM->{'oldImagesDir'};
  $placeholders->{'stage'}                 = $FORM->{'stage'};
  $placeholders->{'step'}                  = $FORM->{'step'};
  $placeholders->{'substep'}               = $FORM->{'substep'};
  $placeholders->{'lastStatusMessage'}     = $FORM->{'lastStatusMessage'};
  $placeholders->{'upgradeStartTime'}      = '';  # defined in importAll

  # display page
  print &IT::Common::contentType('text/html');
  print &templateCell($template, 'header',      $placeholders);
  print &templateCell($template, $templateCell, $placeholders);
  print &templateCell($template, 'footer',      $placeholders);
}

# ----------------------------------------------------------------------------
# end of file
1;

