#!/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", 1,
   "adjust", 0,
   "round", 0,
   "year", 1,
   "name", 1,
   "nameadd", 1,
   "debug", 0,
   "lang", 1,
   "startday", 1,
   "numdays", 1,
   "projwidth", 1,
   "screenwidth", 1,
   "includeall", 0,
   "datestyle", 1,
   "minutes", 0,
   "extradir", 1,
);

$usage = "Usage: $0 [-year nn ] [-adjust] [-round] [-lang nn] [-startday day]
	[-numdays nn] [-projwidth nn] [-screenwidth nn] [-includeall]
	[-datestyle nn] [-minutes] [week]
Default 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;

# Find which week to sum for
$week = shift(ARGV);
$oneday = 24 * 60 * 60;
if (!$week) {
   # Default is last week.
   $week = &DATE'weekno($timenow) - 1;
} elsif ($week eq "now") {
   # now is this week
   $week = &DATE'weekno($timenow);
}
   
$timefetch = &DATE'firstinweek($week, $year);
push(@dirlist, $TimeTrackerdir);
if ($extradir) {
   push(@dirlist, $extradir);
}

for $wday ($startday..$endday) {
   @dayfetch = localtime($timefetch);
   if ($datestyle eq "us") {
       $weekdate[$wday] = sprintf("%d/%d", $dayfetch[4] + 1, $dayfetch[3]);
   } elsif ($datestyle eq "de") {
       $weekdate[$wday] = sprintf("%d.%d.", $dayfetch[3], $dayfetch[4] + 1);
   } else {
       $weekdate[$wday] = sprintf("%d/%d", $dayfetch[3], $dayfetch[4] + 1);
   }
   $filename = sprintf("%04d-%02d-%02d",
       1900 + $dayfetch[5], 1 + $dayfetch[4], $dayfetch[3]);
   for $dir (@dirlist) {
      if (open(FILE, "$dir/$filename")) {
         while (<FILE>) {
            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;
      } else {
          print STDERR "$word{'nofile'} $filename",
             $dir eq $TimeTrackerdir?"":" $word{'indir'} $dir", "\n";
      };
   }
   $timefetch = $timefetch + $oneday;
}

&resum;
if ($hours == 0) {
   print STDERR "$word{'nohours'} $week!\n";
   exit 1;
}

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;
}

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} == 0) {
         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....
);

   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;
   }
   if ($name2lang{$lang}) {
      $lang = $name2lang{$lang};
   } elsif ($lang =~ /\./) { # Attempt to drop charset trailer if present
      $lang = $`;
      $lang = $name2lang{$lang} if $name2lang{$lang};
   }

# 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",
);

%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",
);


   %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"});
}
