#!/local/bin/perl
# PERL hours list writer for TimeTracker
#
# Copyright (c) Harald T. Alvestrand
#
# If you know the GNU copyleft, you are at least allowed to do anything
# you can do with it under GNU copyleft. I am trying to think of what
# copyright I *want* on this stuff.
#
do "weekno.perl" || die "Could not do weekno.perl\n";
# Prefixes for some types of project
$privateprefix = "-";
$adjustprefix = "\\+";
# Possible arguments
%args = (
   "fudge", "0.n",
   "adjust", "",
   "round", "",
   "year", "YY",
   "name", "Argument",
   "nameadd", "string",
   "debug", "",
   "lang", "xx",
   "startday", "weekdaynum",
   "numdays", "n",
   "projwidth", "nn",
   "screenwidth", "nn",
   "includeall", "",
   "datestyle", "us/de",
   "minutes", "",
   "extradir", "directory",
   "nonofile", "",
   "user", "userid",
);

$usage = "Usage: $0 ";
for $arg (sort(keys(%args))) {
    $usage .= "\n        " if (length($usage) % 80 > 65);
    if ($args{$arg} eq "") {
	$usage .= " [-$arg]";
    } else {
	$usage .= " [-$arg $args{$arg}]";
    }
}
$usage .= "\nDefault week=last week\n";

# Set default values for variables
$fudge = 0.5;  # Dividing point for ROUND. Fudgeable.
@pwent = getpwuid($<);
$name = $pwent[6];
$name =~ s/\,.*//;
$timenow = time;
@daynow = localtime($timenow);
$year = $daynow[5];
$lang = $ENV{"LANG"};
$startday = 1;
$numdays = 7;
$projwidth = undef;
$screenwidth = undef;

if ($ENV{"TIMETRACKDIR"}) {
    $TimeTrackerdir = $ENV{"TIMETRACKDIR"};
} elsif ($ENV{"TIMEXDIR"}) {
    $TimeTrackerdir = $ENV{"TIMEXDIR"};
} elsif ( -d "$ENV{\"HOME\"}/.timex" && ! -d "$ENV{\"HOME\"}/.TimeTracker") {
    # Compatibility mode for old versions
    $TimeTrackerdir = "$ENV{\"HOME\"}/.timex";
} else {
    $TimeTrackerdir = "$ENV{\"HOME\"}/.TimeTracker";
}

# Set variables from command line and defaults file
&getdefaults("$TimeTrackerdir/sumdefaults");
&setlanguage();

if (! $screenwidth) {
    $screenwidth = 79;
}
elsif ($projwidth) {
    die "$0: Can't specify both -projwidth and -screenwidth.\n";
}

$projwidth = $screenwidth - 7 - 6 * $numdays if (! $projwidth);

if ($projwidth <= 0) {
    warn "$0: Project width too small; using `1'.\n";
    $projwidth = 1;
}

$DATE'startday = $startday;
$endday = $startday + $numdays - 1;
$oneday = 24 * 60 * 60;

# Find which week to sum for
if (!$ARGV[0]) {
   # Default is last week.
   $week = &DATE'weekno($timenow) - 1;
   unshift(@ARGV, $week);
   print STDERR "Selecting week $week\n" if $debug;
}
undef @dirlist;
push(@dirlist, $TimeTrackerdir);
if ($extradir) {
    # Allow extradir to be a colon-separated list
    push(@dirlist, split(":", $extradir));
}
week:
for $week (@ARGV) {
    print STDERR "Week $week\n" if $debug;
    last if $week eq "";
    if ($week eq "now") {
	# now is this week
	$week = &DATE'weekno($timenow);
    }

   
    $timefetch = &DATE'firstinweek($week, $year);

    for $wday ($startday..$endday) {
	@dayfetch = localtime($timefetch);
	$month = $dayfetch[4] + 1;
	if ($datestyle eq "us") {
	    $weekdate[$wday] = sprintf("%d/%d", $month, $dayfetch[3]);
	} elsif ($datestyle eq "de") {
	    $weekdate[$wday] = sprintf("%d.%d.", $dayfetch[3], $month);
	} else {
	    $weekdate[$wday] = sprintf("%d/%d", $dayfetch[3], $month);
	}
	$foundfile = 0;
	$foundfile = &addoneday($dayfetch[5] + 1900, $month, $dayfetch[3]);
	if ($foundfile == 0) {
	    print STDERR "$word{'nofile'} $filename\n",
		    if (!$nonofile);
	}
	$timefetch = $timefetch + $oneday;
    }
	
    &resum;
    if ($hours == 0) {
	print STDERR "$word{'nohours'} $week!\n";
	next week;
    }
	
    if ($adjust) {
	&adjust();
    }
	
    if ($round) {
	&round;
    }

# Print the result
    if ($nameadd) {
	print "$nameadd $name\n";
    } else {
	print "$name\n";
    }
    print $word{"banner"}, " $week 19$year\n";

    printf "%-${projwidth}.${projwidth}s%6s!", $word{"project"}, $word{"tot"};
    for ($startday..$endday) {
	printf "%6s", $days[$_ % 7];
    }
    print "\n";

    printf "%-${projwidth}.${projwidth}s%6s!", $word{"date"}, "";
    for ($startday..$endday) {
	printf "%6s", $weekdate[$_];
    }
    print "\n";

    print '=' x ($projwidth + 7 + 6 * $numdays) . "\n";

    for $pro (sort(keys(%hourstot))) {
	if ($hourstot{$pro}) {
	    if ($minutes) {
		printf("%-${projwidth}.${projwidth}s%3d:%02d!", $pro,
		       int($hourstot{$pro}), 
		       int(($hourstot{$pro}-int($hourstot{$pro}))*60));
	    }
	    else {
		printf "%-${projwidth}.${projwidth}s%6.1f!", $pro, $hourstot{$pro};
	    }
	    for $day ($startday..$endday) {
		if ($hours{"$pro $day"}) {
		    if ($minutes) {
			printf "%3d:%02d", 
			int($hours{"$pro $day"}), 
			int(($hours{"$pro $day"}-int($hours{"$pro $day"}))*60 + 0.1);
		    } else {
			printf "%6.1f", $hours{"$pro $day"};
		    }
		} else {
		    printf "%6s", "";
		}
	    }
	    print "\n";
	}
    }

    print '=' x ($projwidth + 7 + 6 * $numdays) . "\n";

    if ($minutes) {
	printf("%-${projwidth}.${projwidth}s%3d:%02d!", $word{"total"},
	       int($hours), int(($hours-int($hours))*60));
	for $day ($startday..$endday) {
	    printf("%3d:%02d", int(@hours[$day]), 
		   int((@hours[$day]-int(@hours[$day]))*60));
	}
	printf "\n";
	printf("%-${projwidth}.${projwidth}s%3d:%02d\n", $word{"private"},
	       int($privhours), int(($privhours-int($privhours))*60));
    } else {
	printf "%-${projwidth}.${projwidth}s%6.1f!", $word{"total"}, $hours;
	for ($startday..$endday) {
	    printf "%6.1f", $hours[$_];
	}
	print "\n";
	printf "%-${projwidth}.${projwidth}s%6.1f\n",
                  $word{"private"}, $privhours if $privhours;
    }
    print "\n\n";
    # Undef things that need it for next week
    undef %worked;
    undef %hours;
    undef %hourstot;
    undef @hours;
    undef $hours;
} # end of Week loop

sub addoneday {
    local($year, $month, $day) = @_;
    local($foundfile) = 0;
    local($filename);
    print STDERR "Looking for $year/$month/$day\n" if $debug;
    for $dir (@dirlist) {
	# Add both 1.9x and 2.00 versions of TimeTracker filenames
	$filename = sprintf("%04d-%02d-%02d",
			    $year, $month, $day);
	$foundfile |= addonefile("$dir/$filename");
	$filename = sprintf("%04d/%02d-%02d.ttx",
			    $year, $month, $day);
	$foundfile |= addonefile("$dir/$filename");
    }
    return $foundfile;
}

sub addonefile {
    local($filename) = shift;
    if (open(FILE, $filename)) {
	print STDERR "Found file $filename\n" if $debug;
	while (<FILE>) {
	    s/[\r\n]+$//; # DOS-compatible chop
	    if (/^\s*(\d*):(\d*) (.*)/) {
		$project = $3;
		$spent = $1 + $2 / 60;
		$worked{$project} = 1;  # Mark as worked-on this week
		$hours{"$project $wday"} += $spent;
	    }
	}
	close FILE;
	return 1;
    } else {
	print STDERR "Did not find $filename\n" if $debug;
	return 0;
    }
}

sub adjust {
   # Adjust - spread + projects across the board

   for $pro (keys(%hours)) {
     if ($pro =~ /^$adjustprefix/) {
        $tospread += $hours{$pro};
     }
   }
   $factor = ($tospread/($hours - $tospread)) + 1; 
   printf STDERR "Distributing %5.1f hours across %5.1f hours, factor %5.2f\n",
	$tospread, $hours, $factor;
   # 1) Distribute across projects
   for $pro (keys(%hours)) {
      if ($pro =~ /^$adjustprefix/) {
         $hours{$pro} = 0;
      } elsif ($pro !~ /^$privateprefix/) { # do not spread on private pros
         $hours{$pro} *= $factor;
      }
   }
   &resum("adjust");
}

sub round {
# Round all numbers to half-hours
   for $pro (keys(%hours)) {
      $hours{$pro} =  int(($hours{$pro} * 2) + $fudge) / 2; 
   }
   &resum("round");
}

sub resum {
   local($why) = @_;
   local($oldhours) = $hours;
   undef %hourstot;
   undef @hours;
   $hours = 0;
   $privhours = 0;
   for $pro (keys(%hours)) {
      # Do NOT sum - marked projects
      if (($pro =~ /^$privateprefix/) && !$includeall) {
         $privhours += $hours{$pro};
      } elsif ($pro =~ /^(.*) (\d+)$/) {
         $project = $1; $wday = $2;
         $hourstot{$project} += $hours{$pro};
         $hours[$wday] += $hours{$pro};
         $hours += $hours{$pro};
      } else {
         print STDERR "Bad projectday: $pro\n";
      }
   }
   if ($oldhours && (($hours - $oldhours) ** 2 > 0.1)) {
      print STDERR "$why: Changed total from $oldhours to $hours\n";
   }
}

sub getdefaults {
   local($deffile) = @_;
   local($opt, $arg);
   $status = open(DEFAULTS, $deffile);
   if ($status) { # Defaults file found
      while (<DEFAULTS>) {
         chop;
         s/#.*//;
         if (/^(\S+)\s*/) {
            $opt = $1;
            $arg = $';
            &setvar($opt, $arg, "file");
         }
      }
      close(DEFAULTS);
   }
   while ($ARGV[0] =~ /^-(\S+)/) {
      $opt = $1;
      shift(@ARGV);
      $ret = &setvar($opt, $ARGV[0], "ARGV");
      if ($ret == 0) {
         die $usage;
      } elsif ($ret == 2) { # Argument consumed
         shift(@ARGV);
      }
   }
}

sub setvar {
   local($opt, $arg, $where) = @_;
   local($ret);
   if (defined($args{$opt})) {
      if ($args{$opt} eq "") {
         eval "\$$opt = 1";
         print STDERR "$where:$opt (set)\n" if $debug;
         $ret = 1;
      } else {
         eval "\$$opt = \"$arg\"";
         print STDERR "$where:$opt = $arg\n" if $debug;
         $ret = 2;
      }
   } else {
     $ret = 0;
   }
   $ret;
}

sub setlanguage {

# Transform some common language names into ISO 639 language codes
%name2lang = (
"norsk", "no",
"norwegian", "no",
"svenska", "sv", # Yes, the language is "sv" and the country is "se"....
"swedish", "sv",
"se", "sv",
"dansk", "da",
"danish", "da",
"dk", "da", # Domain name mapping for country DK to language da
"eng", "en",
"english", "en",
"fre", "fr",
"ger", "de",
"ita", "it",
# The Big Six (but not int and net) top level domains are assumed to be English
"com", "en",
"edu", "en",
"mil", "en",
"gov", "en",
# US English?
"us", "en",
"au", "en", # Australians mostly speek Englis, ya'know....
"finnish", "fi",
"fi", "fi",
"fi_FI", "fi",
);

   if (!$lang) { 
      $lang = "nolanguage";
      $host = `hostname`;
      chop($host);
      print STDERR "Host(hostname) is $host\n" if $debug;
      if ($host =~ /\.([a-z]{2,3})$/) {
         $lang = $1;
      } else {
         @host = gethostbyname($host);
         $host = $host[0];
         print STDERR "Host(gethostbyname) is $host\n" if $debug;
         if ($host =~ /\.([a-z]{2,3})$/) {
            $lang = $1;
         } else {
            $host = `domainname`; chop $host;
            print STDERR "Domainname is $host\n" if $debug;
            if ($host =~ /\.([a-z]{2,3})$/) {
               $lang = $1;
            }
         }
      }
      print STDERR "Chose language $lang\n" if $debug;
   }

# Set the strings used in various languages
%lang_no = (
"days", "Sn,Man,Tir,Ons,Tor,Fre,Lr,Sn,Man,Tir,Ons,Tor,Fre",
"banner", "Timeliste for uke",
"total", "Total",
"private", "Private",
"project", "Prosjekt",
"nofile", "Ingen fil for dag",
"date", "Dato",
"tot", "TOT",
"nohours", "Ingen timer registrert i uke",
"indir", "i katalog",
);

%lang_sv = (
"days", "Sn,Mn,Tis,Ons,Tor,Fre,Lr,Sn,Mn,Tis,Ons,Tor,Fre",
"banner", "Timlista fr vecka",
"total", "Total",
"private", "Private",
"project", "Projekt",
"nofile", "Ingen projektfil fr dag",
"date", "Datum",
"tot", "TOT",
"nohours", "Inga timmar registrerade i vecka",
);

%lang_en = (
"days", "Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun,Mon,Tue,Wed,Thu,Fri",
"banner", "Hours worked for week",
"total", "Total",
"private", "Private",
"project", "Project",
"nofile", "No file for day",
"date", "Date",
"tot", "TOT",
"nohours", "No hours worked in week",
"indir", "in directory",
);

%lang_fr = (
"days", "Dim,Lun,Mar,Mer,Jeu,Ven,Sam,Dim,Lun,Mar,Mer,Jeu,Ven",
"banner", "Horaire de semaine",
"total", "Total",
"private", "Private",
"project", "Projet",
"date", "Date",
"nofile", "Pas trouve de fichier pour le",
"tot", "TOTAL",
"nohours", "Aucune heure travaille dans la semaine",
"indir", "dans le rpertoire",
);

%lang_de = (
"days", "So,Mo,Di,Mi,Do,Fr,Sa,So,Mo,Di,Mi,Do,Fr",
"banner", "Wochenzeitplan Woche",
"total", "Summe",
"private", "Private",
"project", "Projekt",
"date", "Datum",
"tot", "SUM",
"nofile", "Keine Informationen fr das Datum",
"nohours", "Keine Informationen fr die Woche",
);

%lang_it = (
"days", "Dom,Lun,Mar,Mer,Gio,Ven,Sab,Dom,Lun,Mar,Mer,Gio,Ven",
"banner", "Il tempo lista per settimana",
"total", "Total",
"private", "Private",
"project", "Project",
);

%lang_da = (
"days", "Sn,Man,Tir,Ons,Tor,Fre,Lr,Sn,Man,Tir,Ons,Tor,Fre",
"banner", "Timeliste for uge",
"total", "Total",
"private", "Private",
"project", "Projekt",
"nofile", "Ingen fil for dag",
"date", "Dato",
"tot", "Total",
"nohours", "Ingen timer registreret i uge",
"indir", "i katalog",
);

%lang_fi = (
"days", "Su,Ma,Ti,Ke,To,Pe,La,Su,Ma,Ti,Ke,To,Pe",
"banner", "Tunnit viikolla",
"total", "Yhteens",
"private", "Yksityist",
"project", "Projekti",
"nofile", "Ei tiedostoa pivlle",
"date", "Pivys",
"tot", "Yht.",
"nohours", "Ei tunteja viikolla",
"indir", "Hakemistossa",
);

%word = (
  "days", "Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun,Mon,Tue,Wed,Thu,Fri",
  "banner", "*Hours worked week",
  "nofile", "*No file for day",
  "project", "*Project",
  "total", "*TOTAL",
  "private", "Private",
  "tot", "*TOT",
  "date", "*Date",
  "nohours", "*No hours worked in week",
  "indir", "*in directory",
);

   if ($name2lang{$lang}) {
      $lang = $name2lang{$lang};
   } elsif ($lang =~ /[\._]/) { # Attempt to drop charset trailer if present
      $lang = $`;
      $lang = $name2lang{$lang} if $name2lang{$lang};
   }

   %langword = eval "\%lang_$lang";
   print STDERR "$@\n" if $@;
   if (!%langword) {
      print STDERR "Language $lang unknown, using English (en)\n",
                   "Use command line switch -lang xx to force another\n";
      $lang = "en";
      %langword = %lang_en;
   }
   #if (!%langword) {
   for $word (keys(%word)) {
      if ($langword{$word}) {
         print STDERR "$word = $langword{$word}\n" if $debug;
         $word{$word} = $langword{$word};
      } else {
         print STDERR "\$lang_$lang{$word} not found\n" if $debug;
      }
   }
   @days = split(/,/, $word{"days"});
}
