package ADBGUI::Timetools;

use strict;
use warnings;

BEGIN {
   use Exporter;
   our @ISA = qw/Exporter/;
   our @EXPORT = qw//;
   our @EXPORT_OK = qw/padSize calcTimeInRanges getStartEndFromTimedefNoDB getRangesOnDay addDays timeDefToTime splitTimeRange verifyAndAddDayTest $validdow $REVERSE/;
}

use ADBGUI::BasicVariables;
use Time::HiRes qw(gettimeofday);
use Time::Local;
use Date::Calc qw(Add_Delta_Days Day_of_Week);

our $validdow = ["mo", "di", "mi", "do", "fr", "sa", "so"];
my $validstrs = [(map { ($_, "pre".$_) } ("normal", "we", "feiertag")), @$validdow];

our $REVERSE     = 1**2;

sub padSize {
   my $value = shift || '';
   my $char = shift;
   return $value unless defined($char);
   my $size = shift || 0;
   return $value if (length($value) >= $size);
   $value = ($char x ($size-length($value))).$value;
}

sub calcTimeInRanges {
   my $ranges = shift;
   my $starttime = shift;
   my $endtime = shift;
   my $name = shift;
   my $return = 0;
   #print "BEGIN TYPE=".$name."\t\t\t".localtime($starttime)."-".localtime($endtime)."\n" if $name;
   foreach my $range (@$ranges) {
      unless (ref($range) eq "ARRAY") {
         Log("Range ist keine ARRAY sondern ".ref($range), $ERROR);
         next;
      }
      if (($starttime <= $range->[1]) &&
          ($endtime   >  $range->[0])) {
         my $begin = (($starttime > $range->[0]) ? $starttime : $range->[0]);
         my $end   = (($endtime   < $range->[1]) ? $endtime : $range->[1]);
         my $diff = $end-$begin;
         #print "FOUND TYPE=".$name." START=".localtime($begin)." END=".localtime($end)." DIFF=".$diff."\n" if $name;
         $return += $diff if ($diff > 0);
      } else {
         #print "SKIPPING TYPE=".$name." ".localtime($range->[0])."-".localtime($range->[1])."\n" if $name;
      }
   }
   return $return;
}

sub splitTimeRange {
   my $line = shift;
   my $time = $line;
   my $days = [];
   if ($line =~ /^\s*(.*\;)?([^\;]*)$/) {
      $days = [split(";", $1)] if $1;
      $time = $2;
   }
   if (my ($start, $end) =  split(/\s*\-\s*/, $time)) {
      $start            = [split(/:/, $start)];
      $end              = [split(/:/, $end  )];
      push(@$start, 0) if (scalar(@$start) == 0);
      push(@$end,   0) if (scalar(@$end  ) == 0);
      push(@$start, 0) if (scalar(@$start) == 1);
      push(@$end,   0) if (scalar(@$end  ) == 1);
      return {start => $start, end => $end, days => $days};
   } else {
      return $days ? {days => $days} : undef;
   }
}

sub timeDefToTime {
   my $timedef = shift;
   my $now = shift;
   my $result = undef;
   my $add = 0;
   my $hour = $timedef->[0];
   if (defined($hour) &&
               $hour &&
              ($hour == 24)) {
      $add += 1;
      $hour = 0;
   }
   eval { $result = timelocal(0, $timedef->[1], $hour, $now->[3], $now->[4], $now->[5]); };
   $result = addDays($result, $add) if $add;
   Log("zeiterfassung::Tools::timeDefToTime: timelocal: params=0,".$timedef->[1].",".$timedef->[0].",".$now->[3].",".$now->[4].",".$now->[5]." ERROR=".$@, $ERROR) if ($@);
   return $result;
}

sub getStartEndFromTimedefNoDB {
   my $timedefs = shift; # Ist z.B. MO/Di/Mi/Do/Fr;8-40,prewe/Sa/So;8-9:30
   my $now = shift;
   my $nowtocalc = shift;
   my $todayisfeiertag = shift;
   my $tomorrowisfeiertag = shift;
   my @now = localtime($now);
   my $tmp = [localtime($nowtocalc)];
   my $return = [];
   my $dow = undef;
   eval { $dow = Day_of_Week($tmp->[5]+1900, $tmp->[4]+1, $tmp->[3]); };
   if ($@) {
      Log("doDay: Day_of_Week: params=".($tmp->[5]+1900).",".($tmp->[4]+1).",".$tmp->[3]." error=".$@, $ERROR);
      return $return;
   }
   my $iswe = (($dow == 6) || ($dow == 7)) ? 1 : 0;
   my $iswetomorrow = (($dow == 5) || ($dow == 6)) ? 1 : 0;
   my $given = [];
   my $line = undef;
   push(@$given, ("feiertag"))
      if (defined($line = $todayisfeiertag));
   push(@$given, ("we")) if $iswe;
      #print "LOCALTIME:".localtime(addDays($nowtocalc,1)).":".join(";", map { $_."=".$line->{$_} } keys %$line).":\n";
   push(@$given, ("normal")) unless scalar(@$given);
   my $pregiven = [];
   push(@$pregiven, ("prefeiertag"))
      if (defined($line = $tomorrowisfeiertag));
   push(@$pregiven, ("prewe")) if $iswetomorrow;
   push(@$pregiven, ("prenormal")) unless scalar(@$pregiven);
   push(@$given, @$pregiven);
   push(@$given, $validdow->[$dow-1]);
   #print "WANTED=".((defined($wanted) && (ref($wanted) eq "ARRAY")) ? join(",", @$wanted) : $wanted)."\n";
   my $thecurday = undef;
   foreach my $timedef (map { splitTimeRange($_) } split (",", $timedefs)) {
      next unless defined($timedef);
      my $tmp = '';
      my $curday = undef;
      if (exists($timedef->{days}) &&
         defined($timedef->{days}) &&
                 $timedef->{days} &&
            (ref($timedef->{days}) eq "ARRAY") &&
        scalar(@{$timedef->{days}})) {
         next unless (defined($dow) && $dow && grep {
            $curday = $_;
            (defined($thecurday) ?
                    ($thecurday eq $curday) :
          checkGivenWanted($given, $curday))
         } @{$timedef->{days}});
         $tmp = join(";", @{$timedef->{days}});
      } else {
         next if (defined($thecurday) && ($thecurday ne ""));
         #print "DAY:EMPTYDAY ";
      }
      #print time_format_day($nowtocalc)." WANTED=".((defined($wanted) && (ref($wanted) eq "ARRAY")) ? join(",", @$wanted) : $wanted)."\n";
      #print "-> SELECTED".$tmp.":".time_format_hours($timedef->{"start"}).":".time_format_hours($timedef->{"end"})."\n";
      $thecurday = $curday
         if (!defined($thecurday) && defined($curday));
      #Log("zeiterfassung::DBManager::getStartEndFromTimedef: ".'$thecurday != $curday:'.$thecurday.";".$curday, $ERROR) if (defined($thecurday) && (!defined($curday) || ($curday ne $thecurday)));
      if (exists($timedef->{"start"}) && exists($timedef->{"end"})) {
         #Log("zeiterfassung::DBManager::getStartEndFromTimedef: Using ".($curday||"undef").":".$timedef->{"start"}->[0].":".$timedef->{"start"}->[1]."->".$timedef->{"end"}->[0].":".$timedef->{"end"}->[1], $DEBUG);
         push(@$return, {start => timeDefToTime($timedef->{"start"}, [@now]),
                         end   => timeDefToTime($timedef->{"end"},   [@now]),
                         day   => $thecurday || ""});
      } else {
         #Log("zeiterfassung::DBManager::getStartEndFromTimedef: Using ".($curday||"undef"), $DEBUG);
      }
   }
   #Log("zeiterfassung::DBManager::getStartEndFromTimedef: No getStartEndFromTimedef for ".time_format_day($nowtocalc)." WANTED=".((defined($wanted) && (ref($wanted) eq "ARRAY")) ? join(",", @$wanted) : $wanted), $DEBUG);
   return $return;
}

sub getRangesOnDay {
   my $yesterday = shift;
   my $today = shift;
   my $now = shift;
   my $flags = shift || 0;
   my @now = localtime($now);
   my $return = [];
   my $daystart = timeDefToTime([0,0], [@now]);
   my $dayend   = addDays($daystart,1);
   my $yesterdayend = undef;
   foreach my $calcday (["yesterday", $yesterday],
                        ["today"    , $today]) {
      foreach my $currange (@{$calcday->[1]}) {
         Log("Sommer -> Winterzeit > 3600 Sekunden(".($currange->{end}-$dayend).") ?!", $ERROR)
            if ($currange->{end} > ($dayend+3600)); # Bei Sommer->Winterzeit ist 3600 Sekunden (= 1 Stunde) möglich.
         if ($currange->{start} >= $currange->{end}) {
            if ($calcday->[0] eq "yesterday") {
               if ($flags & $REVERSE) {
                  $yesterdayend = $currange->{end};
               } else {
                  push(@$return, [$daystart, $currange->{end}, $currange->{day}]);
                  #print "  Y Bereitschaft: ".localtime($daystart)." -> ".localtime($currange->{end})."\n";
               }
            } elsif ($calcday->[0] eq "today") {
               if ($flags & $REVERSE) {
                  push(@$return, [defined($yesterdayend) ? $yesterdayend : $daystart, $currange->{start}, $currange->{day}]);
                  #print "  TRBereitschaft: ".localtime(defined($yesterdayend) ? $yesterdayend : $daystart)." -> ".localtime($currange->{start})."\n";
               } else {
                  #print "  T Bereitschaft: ".localtime($currange->{start})." -> ".localtime($dayend)."\n";
                  push(@$return, [$currange->{start}, $dayend, $currange->{day}]);
               }
            } else {
               Log("CANNOT BE 1", $ERROR);
               return $return;
            }
         } else {
            if ($calcday->[0] eq "yesterday") {
            } elsif ($calcday->[0] eq "today") {
               if ($flags & $REVERSE) {
                  #print "  TRBereitschaft1: ".localtime(defined($yesterdayend) ? $yesterdayend : $daystart)." -> ".localtime($currange->{start})."\n";
                  push(@$return, [defined($yesterdayend) ? $yesterdayend : $daystart, $currange->{start}, $currange->{day}]);
                  #print "  TRBereitschaft2: ".localtime($currange->{end})." -> ".localtime($dayend)."\n";
                  push(@$return, [$currange->{end}, $dayend, $currange->{day}]);
               } else {
                  #print "  T Bereitschaft: ".localtime($currange->{start})." -> ".localtime($currange->{end})."\n";
                  push(@$return, [$currange->{start}, $currange->{end}, $currange->{day}]);
               }
            } else {
               Log("CANNOT BE 2", $ERROR);
               return $return;
            }
         }
      }
   }
   $return = [[$daystart, $dayend]] if (!scalar(@$return) && ($flags & $REVERSE));
   return $return;
}

sub checkGivenWanted {
   my $given = shift;
   my $wanted = shift;
   return 1 unless $wanted;
   if (defined($given) &&
          (ref($given) eq "ARRAY")) {
      my $wanteddef = [];
      while (1) {
         my $and = index($wanted, "&");
         my $or  = index($wanted, "/");
         if (($and != -1) || ($or != -1)) {
            my $type = ($or > $and) ? "||" : "&&";
            my $str = substr($wanted, 0, (($type eq "||") ? $or : $and)+1, "");
            chop($str);
            return 0 if verifyAndAddDayTest($wanteddef, $str, $type);
         } else {
            return 0 if verifyAndAddDayTest($wanteddef, $wanted);
            last;
         }
      }
      return 0 unless @$wanteddef;
      my $cmd = 'my @given = ('.join(",", map { '"'.$_.'"' } grep { defined($_) && $_ } @$given).'); '.join(" ", map { $_->[2].$_->[0].($_->[1] ? " ".$_->[1] : '').$_->[3] } @$wanteddef);
      #Log("zeiterfassung::DBManager::checkGivenWanted:cmd: ".$cmd, $DEBUG);
      my $ret = eval($cmd);
      if ($@) {
         Log("zeiterfassung::DBManager::checkGivenWanted: eval error: ".$@, $ERROR);
         return 0;
      }
      return $ret;
   }
   return 0;
}

sub addDays {
   my $time = shift;
   my $days = shift;
   my @cur = localtime($time);
   my $day = $cur[3];
   my $month = ($cur[4]+1);
   my $year = ($cur[5]+1900);
   my $return = undef;
   eval { ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $days); };
   if ($@) {
      Log("addDays: Add_Delta_Days: error=".$@, $ERROR);
      return undef;
   }
   eval { $return = timelocal(@cur[0..2],$day,($month-1),($year-1900)) };
   if ($@) {
      Log("addDays: timelocal: error=".$@, $ERROR);
      return undef;
   }
   return $return;
}

sub verifyAndAddDayTest {
   my $wanteddef = shift;
   my $str = shift;
   my $type = shift || undef;
   my $pre = '';
   my $post = '';
   if ($str =~ s,^([\(\)]+),,gi) {
      $pre = $1;
   }
   if ($str =~ s,([\(\)]+)$,,gi) {
      $post = $1;
   }
   if (grep { $_ eq lc($str) } @$validstrs) {
      if($str =~ m,\',) {
         Log("CANNOT BE", $ERROR);
         return 1;
      } else {
         push(@$wanteddef, ['(grep { $_ eq "'.lc($str).'" } @given)', $type, $pre, $post])
            if ($str);
      }
   } else {
      return $str ? $str : 1;
   }
   return 0;
}

1;

