#!/usr/bin/perl
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2004-2007 Jan Engelhardt
#
#	wktimer
#	A program which impelements time() based time counters.
#
use Getopt::Long;
use constant {
	OP_RESTAMP => 1 << 0,
	OP_VERBOSE => 1 << 1,
};
use strict;
no strict "subs"; # OP_START/OP_STOP

my $do_show     = 1;
my $g_flags     = OP_VERBOSE;
my $flag_iStart = 0;
my $flag_hdr    = 1;
my $flag_sec    = 0;
my $timer_file  = $ENV{HOME}."/.timers";
my @ARGV2;
my %TIMER;

sub repush {
	push(@ARGV2, "-".shift(@_), @_);
	return;
}
&Getopt::Long::Configure(qw(bundling pass_through));
&GetOptions(
	"A|add=s"           => \&repush,
	"D|del|delete=s"    => \&repush,
	"F|flush"           => \&repush,
	"H|nohdr"           => sub { $flag_hdr = 0; },
	"L|list:s"          => \&repush,
	"S|stop=s"          => \&repush,
	"X=s"               => \&repush,
	"W|switch=s"        => \&repush,
	"i|istart"          => \$flag_iStart,
	"q|quiet"           => sub { $g_flags &= ~OP_VERBOSE; },
	"s|sec"             => \$flag_sec,
	"t|file=s"          => \$timer_file,
);

&read_timers($g_flags);
@ARGV = @ARGV2;

&GetOptions(
	"A|add=s" => sub {
		$do_show = 0;
		foreach my $name (split(/,/so, $_[1])) {
			&start_timer($name, OP_RESTAMP | $g_flags);
		}
	},
	"D|del|delete=s" => sub {
		$do_show = 0;
		foreach my $name (split(/,/so, $_[1])) {
			&delete_timer($name, OP_RESTAMP | $g_flags);
		}
	},
	"F|flush" => sub {
		$do_show = 0;
		&flush_timers($g_flags);
	},
	"L|list:s" => sub {
		$do_show = 0;
		&show_timers(split(/,/so, $_[1]));
	},
	"S|stop=s" => sub {
		$do_show = 0;
		foreach my $name (split(/,/so, $_[1])) {
			&stop_timer($name, $g_flags);
		}
	},
	"X=s" => sub {
		foreach my $name (split(/,/so, $_[1])) {
			&stop_timer($name, OP_RESTAMP | $g_flags);
		}
	},
	"W|switch=s" => sub {
		$do_show = 0;
		foreach my $name (split(/,/so, $_[1])) {
			&switch_timer($name, $g_flags);
		}
	},
);

if ($do_show) {
	&show_timers();
}
&save_timers($g_flags);

sub start_timer ()
{
	my $name  = lc shift @_;
	my $flags = shift @_;

	if (!exists($TIMER{$name})) {
		$TIMER{$name} = {};
		my $t = $TIMER{$name};
		$t->{InitialStart} = $t->{Start} = time();
		$t->{Stop}         = -1;
		if ($flags & OP_VERBOSE) {
			print "Created timer \"$name\"\n";
		}
		return 1;
	}

	my $t = $TIMER{$name};
	if ($t->{Stop} < 0 && $flags & OP_RESTAMP) {
		# still running
		$t->{Stop}        = time();
		$t->{Cumulative} += $t->{Stop} - $t->{Start};
		$t->{Start}       = $t->{Stop};
		$t->{Stop}        = -1;
		if ($flags & OP_VERBOSE) {
			print "Updated START mark for timer \"$name\"\n";
		}
	} elsif ($t->{Stop} >= 0) {
		# was a stopped timer
		$t->{Start} = time();
		$t->{Stop}  = -1;
		if ($flags & OP_VERBOSE) {
			print "Restarted timer \"$name\"\n";
		}
	}
	return 1;
}

sub stop_timer ()
{
	my $name  = lc shift @_;
	my $flags = shift @_;

	if (!exists($TIMER{$name})) {
		if ($flags & OP_VERBOSE) {
			print "Timer \"$name\" does not exist\n";
		}
		return;
	}

	my $t = $TIMER{$name};
	if ($t->{Stop} >= 0 && $flags & OP_RESTAMP) {
		$t->{Cumulative} -= $t->{Stop} - $t->{Start};
		$t->{Stop}        = time();
		$t->{Cumulative} += $t->{Stop} - $t->{Start};
		if ($flags & OP_VERBOSE) {
			print "Updated STOP mark for timer \"$name\"\n";
		}
	} elsif ($t->{Stop} < 0) {
		$t->{Stop}        = time();
		$t->{Cumulative} += $t->{Stop} - $t->{Start};
		if ($flags & OP_VERBOSE) {
			print "Stopped timer \"$name\"\n";
		}
	}
	return;
}

sub switch_timer ()
{
	my $name  = lc shift @_;
	my $flags = shift @_;

	if (!exists($TIMER{$name})) {
		if ($flags & OP_VERBOSE) {
			print "Timer \"$name\" does not exist\n";
		}
		return 1;
	}

	foreach my $subname (keys %TIMER) {
		my $t = $TIMER{$subname};
		if ($subname ne $name) {
			&stop_timer($subname, OP_STOP);
		} else {
			&start_timer($name, OP_START);
		}
	}
	if ($flags & OP_VERBOSE) {
		print "Switched to timer \"$name\"\n";
	}
	&save_timers();
	return;
}

sub delete_timer ()
{
	my $name  = lc shift @_;
	my $flags = shift @_;

	if (exists $TIMER{$name}) {
		if ($flags & OP_VERBOSE) {
			print "Timer \"$name\" does not exist\n";
		}
		return;
	}
	delete $TIMER{$name};
	if ($flags & OP_VERBOSE) {
		print "Deleted timer \"$name\"\n";
	}
	return;
}

sub flush_timers ()
{
	my $flags = shift @_;
	undef %TIMER;
	if ($flags & OP_VERBOSE) {
		print "Deleted all timers\n";
	}
	return;
}

sub read_timers()
{
	local *IN;

	if (!-e $timer_file) {
		return;
	}

	open(IN, "< $timer_file") || die "Could not open $timer_file: $!\n";

	while (defined(my $ln = <IN>)) {
		my($name, @data);
		my $t;

		chomp($ln);
		$ln =~ s/^\s+//giso;
		$ln =~ s/\s+$//giso;
		($name, @data)     = split(/\s+/, $ln);
		$TIMER{$name}      = {};
		$t                 = $TIMER{$name};
		$t->{InitialStart} = shift @data;
		$t->{Cumulative}   = shift @data;
		$t->{Start}        = shift @data;
		$t->{Stop}         = shift @data;
	}

	close IN;
	return;
}

sub show_timers ()
{
	if ($flag_hdr) {
		printf "%-10s", "Name";
		if ($flag_iStart) {
			printf "  %11s", "I-Start";
		}
		printf "  %12s  %11s  %11s  %12s\n",
		       "Cumulative", "L-Start", "L-Stop", "Elapsed";
	}

	if ($#_ >= 0) {
		foreach my $name (@_) {
			$name = lc $name;
			if (exists $TIMER{$name}) {
				&print_timer($name);
			}
		}
	} else {
		foreach my $name (sort keys %TIMER) {
			&print_timer($name);
		}
	}
	return;
}

sub print_timer ()
{
	my $name = shift @_;
	my $t    = $TIMER{$name};
	my $cum  = ($t->{Cumulative} + (($t->{Stop} == -1) ?
	           time() - $t->{Start} : 0));
	my $elap = time() - $t->{Start};

	if ($flag_sec) {
		$cum .= "s";
		$elap .= "s";
	} else {
		$cum  = &sec2dur($cum);
		$elap = &sec2dur($elap);
	}

	printf "%-10s", $name;
	if ($flag_iStart) {
		printf "  %11s", &sec2date($t->{InitialStart});
	}
	printf "  %12s  %11s  %11s  %12s\n",
	       $cum, &sec2date($t->{Start}), &sec2date($t->{Stop}), $elap;
	return;
}

sub save_timers ()
{
	local *OUT;

	if (scalar(keys %TIMER) == 0) {
		unlink $timer_file;
		return;
	}

	open(OUT, "> $timer_file") || die "Could not open $timer_file: $!\n";

	foreach my $name (sort keys %TIMER) {
		my %t = %{$TIMER{$name}};
		printf OUT "%s %u %u %u %d\n",
		       $name, @t{InitialStart,Cumulative,Start,Stop};
	}
	close OUT;
	return;
}

sub is_running ()
{
	my $name = lc shift @_;
	if ($TIMER{$name}{Stop} < 0) {
		return 1;
	}
	return 0;
}

sub sec2date ()
{
	my $in = shift @_;
	if ($in < 0) {
		return "(running)";
	}
	my @d = localtime $in;
	return sprintf "%02d%02d-%02d%02d%02d", $d[3], $d[4] + 1, @d[2,1,0];
}

sub sec2dur ()
{
	my($S_hr, $S_min, $S_sec) = (0, 0, 0);
	my $in   = shift @_;
	my $sec  = $in % 60;
	my $min  = int($in / 60) % 60;
	my $hr   = int($in / 3600) % 24;
	my $days = int($in / 86400);
	my $ret;

	if ($days != 0) {
		$ret .= sprintf "%dd", $days;
		$S_hr = $S_min = $S_sec = 2;
	}

	if ($hr != 0 || $S_hr) {
		if ($S_hr == 2) {
			$ret .= sprintf "%02dh", $hr;
		} else {
			$ret .= sprintf "%2dh", $hr;
		}
		$S_min = $S_sec = 2;
	}

	if ($min != 0 || $S_min) {
		if ($S_min == 2) {
			$ret .= sprintf "%02dm", $min;
		} else {
			$ret .= sprintf "%2dm", $min;
		}
		$S_sec = 2;
	}

	if ($S_sec == 2) {
		$ret .= sprintf "%02ds", $sec;
	} else {
		$ret .= sprintf "%2ds", $sec;
	}

	return $ret;
}
