The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package ProgressMonitor::Stringify::Fields::ETA;

use warnings;
use strict;

use ProgressMonitor::State;
require ProgressMonitor::Stringify::Fields::AbstractField if 0;

use Time::HiRes qw(time);

use constant MINUTE => 60;
use constant HOUR   => 60 * MINUTE;
use constant DAY    => 24 * HOUR;

no strict 'refs';
use classes
  extends  => 'ProgressMonitor::Stringify::Fields::AbstractField',
  new      => 'new',
  attrs_pr => ['start', 'index', 'lastHH', 'lastMM', 'lastSS', 'lastDelim', 'lastTime', 'lastLeft'],
  ;

sub new
{
	my $class = shift;
	my $cfg   = shift;

	my $self = $class->SUPER::_new($cfg, $CLASS);

	$cfg = $self->_get_cfg;

	# we only wish to portray up to 23:59:49 at this point
	#
	my $delim    = $cfg->get_mainDelimiter;
	my $delimLen = length($delim);
	$self->_set_width(2 + $delimLen + 2 + $delimLen + 2);

	my $ofc = $cfg->get_unknownCharacter;
	$self->{$ATTR_start}     = 0;
	$self->{$ATTR_index}     = 0;
	$self->{$ATTR_lastHH}    = $self->{$ATTR_lastMM} = $self->{$ATTR_lastSS} = "$ofc$ofc";
	$self->{$ATTR_lastDelim} = $delim;
	$self->{$ATTR_lastTime}  = 0;
	$self->{$ATTR_lastLeft}  = 0;

	return $self;
}

sub render
{
	my $self       = shift;
	my $state      = shift;
	my $ticks      = shift;
	my $totalTicks = shift;
	my $clean      = shift;

	my $now            = time;
	my $timeSinceStart = $now - $self->{$ATTR_start};
	my $cfg            = $self->_get_cfg;
	my $hh             = $self->{$ATTR_lastHH};
	my $mm             = $self->{$ATTR_lastMM};
	my $ss             = $self->{$ATTR_lastSS};
	my $delim          = $self->{$ATTR_lastDelim};

	if (!$self->{$ATTR_start})
	{
		# this is the first call - just render 'unknown'
		#
		$self->{$ATTR_start} = $self->{$ATTR_lastTime} = $now;
	}
	else
	{

		if ($state == STATE_DONE)
		{
			my $ofc = $cfg->get_unknownCharacter;
			$hh = $mm = $ss = "$ofc$ofc";
			($hh, $mm, $ss) = $self->__fmtHMS($timeSinceStart) if $timeSinceStart < DAY;
		}
		else
		{
			# to avoid too much flickering, only update at the given rate
			#
			if ($now >= $self->{$ATTR_lastTime} + $cfg->get_maxUpdateRate)
			{
				# flicker delimiter
				#
				my $seq = $cfg->get_idleDelimiterSequence;
				$delim = $seq->[$self->{$ATTR_index}++ % @$seq];

				# try to ensure we have some information to predict on
				#
				my $ratio = defined($totalTicks) && $totalTicks > 0 ? $ticks / $totalTicks : 0;
				if ($ratio > $cfg->get_waitForRatio)
				{
					my $left = int($timeSinceStart * ((1 - $ratio) / $ratio));
					$left = DAY if $left > DAY;
					if ($clean || $left != $self->{$ATTR_lastLeft})
					{
						($hh, $mm, $ss) = $self->__fmtHMS($left);
						$delim = $cfg->get_mainDelimiter;
					}
					$self->{$ATTR_lastLeft} = $left;
				}
				$self->{$ATTR_lastTime} = $now;
			}
		}
	}

	$self->{$ATTR_lastHH}    = $hh;
	$self->{$ATTR_lastMM}    = $mm;
	$self->{$ATTR_lastSS}    = $ss;
	$self->{$ATTR_lastDelim} = $delim;
	return sprintf("%s%s%s%s%s", $hh, $delim, $mm, $delim, $ss);
}

sub completed
{
	my $self = shift;

	# We're done - report the actual time it took
	# but check for overflow
	#
	my $cfg = $self->_get_cfg;
	my $ofc = $cfg->get_unknownCharacter;
	my ($hh, $mm, $ss);
	$hh = $mm = $ss = "$ofc$ofc";
	my $timeSinceStart = time - $self->{$ATTR_start};
	($hh, $mm, $ss) = $self->__fmtHMS($timeSinceStart) if $timeSinceStart < DAY;
	my $delim = $cfg->get_mainDelimiter;

	return sprintf("%s%s%s%s%s", $hh, $delim, $mm, $delim, $ss);
}

sub __fmtHMS
{
	my $self = shift;
	my $time = shift;

	my $fmt = '%02u';
	my $hh  = sprintf($fmt, int($time / DAY));
	my $mm  = sprintf("%02u", int(($time % DAY) / MINUTE));
	my $ss  = sprintf("%02u", $time % MINUTE);

	return ($hh, $mm, $ss);
}

###

package ProgressMonitor::Stringify::Fields::ETAConfiguration;

use strict;
use warnings;

no strict 'refs';
use classes
  extends => 'ProgressMonitor::Stringify::Fields::AbstractFieldConfiguration',
  attrs   => ['unknownCharacter', 'mainDelimiter', 'idleDelimiterSequence', 'waitForRatio', 'maxUpdateRate'],
  ;

sub defaultAttributeValues
{
	my $self = shift;

	return {
			%{$self->SUPER::defaultAttributeValues()},
			unknownCharacter      => '-',
			mainDelimiter         => ':',
			idleDelimiterSequence => [' ', ':'],
			waitForRatio          => 0.01,
			maxUpdateRate         => 1,
		   };
}

sub checkAttributeValues
{
	my $self = shift;

	$self->SUPER::checkAttributeValues;

	X::Usage->throw("unknownCharacter should have a length of 1") unless length($self->get_unknownCharacter) == 1;
	my $seq = $self->get_idleDelimiterSequence;
	X::Usage->throw("idleDelimiterSequence must be an array") unless ref($seq) eq 'ARRAY';
	my $len = length($self->get_mainDelimiter);
	for (@$seq)
	{
		X::Usage->throw("all idleDelimiterSequence elements must have same length as mainDelimiter") if length($_) != $len;
	}
	X::Usage->throw("0 < waitForRatio <= 1") if ($self->get_waitForRatio < 0 || $self->get_waitForRatio > 1);
	X::Usage->throw("maxUpdateRate can not be negative") if $self->get_waitForRatio < 0;

	return;
}

############################

=head1 NAME

ProgressMonitor::Stringify::Field::ETA - a field implementation that renders progress
as a time-to-completion.

=head1 VERSION

Version 0.01

=head1 DESCRIPTION

	@@TODO@@

=head1 AUTHOR

Kenneth Olwing, C<< <knth at cpan.org> >>

=head1 BUGS

I wouldn't be surprised! If you can come up with a minimal test that shows the
problem I might be able to take a look. Even better, send me a patch.

Please report any bugs or feature requests to
C<bug-progressmonitor at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ProgressMonitor>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ProgressMonitor

You can also look for information at:

=over 4

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ProgressMonitor>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ProgressMonitor>

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ProgressMonitor>

=item * Search CPAN

L<http://search.cpan.org/dist/ProgressMonitor>

=back

=head1 ACKNOWLEDGEMENTS

Thanks to my family. I'm deeply grateful for you!

=head1 COPYRIGHT & LICENSE

Copyright 2006,2007 Kenneth Olwing, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1;    # End of ProgressMonitor::Stringify::Fields::ETA