###################################################
## Interval.pm		(Time::Interval)
## Andrew N. Hicox	<andrew@hicox.com>
## http://www.hicox.com
##
## a module for dealing with time intervals
###################################################


## Global Stuff ###################################
package	Time::Interval;
use		strict;
require	Exporter;

#class global vars ...
use vars qw($VERSION @EXPORT @ISA %intervals);
@ISA 		= qw(Exporter);
@EXPORT		= qw(&parseInterval &convertInterval &getInterval &coalesce);
$VERSION	= '1.0.5';
#what everything is worth in seconds
%intervals 	= (
	'days'		=> ((60**2) * 24),
	'hours'		=> (60 **2),
	'minutes'	=> 60,
	'seconds'	=> 1
);


## getInterval ####################################
sub getInterval {
	my $date1 = shift();
	my $date2 = shift();
	my $string = shift();
	if (! $string){ $string = 0; }
	if ( (! $date1) || (! $date2) ){
		warn ("two dates are required for the getInterval method");
		return (undef);
	}
	require Date::Parse;
	foreach ($date1, $date2){
		$_ = Date::Parse::str2time($_) || do {
			warn ("failed to parse date: $!\n");
			return (undef);
		};
	}
	my $data = parseInterval(
		seconds	=> abs($date1 - $date2),
		String	=> $string
	);
	return ($data);
}


## convertInterval ################################
#'days'		=> $num,
#'hours'	=> $num,
#'minutes'	=> $num,
#'seconds'	=> $num,
#'ConvertTo'	=> 'days'|'hours'|'minutes'|'seconds'
sub convertInterval {
	my %p = @_;
	#ConvertTo, default is seconds
	exists($p{'ConvertTo'}) || do {
		$p{'ConvertTo'} = "seconds";
		warn ("convertInterval: using default ConvertTo (seconds)") if $p{'Debug'};
	};
	#convert everything to seconds
	my $seconds = 0;
	foreach ("days","hours","minutes","seconds"){
		if (exists($p{$_})){ $seconds += ($intervals{$_} * $p{$_}); }
	}
	#send it back out into the desired output
	return (($seconds/$intervals{$p{'ConvertTo'}}));
}


## parseInterval ##################################
#'days'		=> $num,
#'hours'	=> $num,
#'minutes'	=> $num,
#'seconds'	=> $num,
sub parseInterval {
	my %p = @_;
	#convert everything to seconds
	my $seconds = convertInterval(%p);
	#do the thang
	my %time = (
		'days'		=> 0,
		'hours'		=> 0,
		'minutes'	=> 0,
		'seconds'	=> 0
	);
	while ($seconds > 0){
		foreach ("days","hours","minutes","seconds"){
			if ($seconds >= $intervals{$_}){
				$time{$_} ++;
				$seconds  -= $intervals{$_};
				last;
			}
		}
	}
	#return data
	if ($p{'String'} != 0){
		#return a string?
		my @temp = ();
		foreach ("days","hours","minutes","seconds"){
			if ($time{$_} > 0){
				push (@temp, "$time{$_} $_");
			}
		}
		return (join (", ", @temp));
	}else{
		#return a data structure
		return (\%time);
	}	
}


## coalesce #######################################
#coalesce([ [$start1, $end1], [$start2, $end2] ... ])
sub coalesce {
	require Date::Parse;
	my $intervals = shift();
	my (%epoch_map) = ();
	my ($flag, $repeat) = (0,1);
	
	#convert each start / end to an epoch pair and stash 'em in epoch_map
	foreach my $int (@{$intervals}) {
		foreach (@{$int}){
			my $epoch = Date::Parse::str2time($_);
			$epoch_map{$epoch} = $_;
			$_ = $epoch;
		}
	}

	#sort 'em by start time
	@{$intervals} = sort { $a->[0] <=> $b->[0] } @{$intervals};
	
	#flatten 'em
	while ($repeat == 1) {
		@{$intervals} = sort {
			#if it's not an array ref, it's been destructo'd
			if ( (ref($a) eq "ARRAY") && (ref($b) eq "ARRAY") ){
			
				#if b is inside a
				if (($b->[0] >= $a->[0]) && ($b->[0] <= $a->[1])){
					#if b's end time is greater than a's, update a's end time
					if ($b->[1] > $a->[1]){ $a->[1] = $b->[1]; }
					#destructo b
					$b = ();
					$flag = 1;
					return (1);
				#if a is inside b
				}elsif (($a->[0] >= $b->[0]) && ($a->[0] <= $b->[1])){
					#if a's end time is greater than b's, update b's end time
					if ($a->[1] > $b->[1]){ $b->[1] = $a->[1]; }
					#destructo a
					$a = ();
					$flag = 1;
					return (0);
				}else{
					return (0);
				}
		
			}else{
				return (1);
			}
	
		} @{$intervals};
	
		#weed out null elements
		my $i = 0;
		foreach (@{$intervals}){
			if ( ref($_) ne "ARRAY" ){ splice (@{$intervals}, $i, 1); }
			$i ++;
		}
		
		#decide wether or not to repeat
		if ($flag == 1){ 
			$repeat = 1;  
			$flag = 0;
		}else{ 
			$repeat = 0;
		}
	}
	
	#replace the epoch's with their time string equivalents
	foreach my $pair (@{$intervals}){
		if ( ref($pair) eq "ARRAY"){
			foreach (@{$pair}){ $_ = $epoch_map{$_}; }
		}
	}
	
	#weed out any remaining bum elements
	my $i = 0;
	foreach (@{$intervals}){
		unless (ref($_) eq "ARRAY"){ splice (@{$intervals}, $i, 1); }
		$i ++;
	}
	return ($intervals);
}
