#
# little helper routines 
#       written by ...: geronimo at vdrportal dot de
# 
# COPYRIGHT:   (c) 2006 Creative Commons by-nc-sa
#               some rights reserved. For details on copyright see:
#               http://creativecommons.org/licenses/by-nc-sa
#
# DISCLAIMER:   NO WARANTY AT ALL. Use at your own risk.
# 
# vi: ts=3
#
package V2I::utils;
use strict;
use Carp qw(carp croak);
use Data::Dumper;
use File::Basename;

BEGIN {
   use Exporter    ();
   our($VERSION, @ISA, @EXPORT);
   @ISA = qw(Exporter);
   $VERSION = '0.0.1';
   @EXPORT  = qw(&min &max &localizeNumber &localizeDate &localizeTime &localizeTS
                 &readIdx &readMarks &writeMarks &getCmType &checkMedia);
}

my $indent = 0;
my $txtIndent = "\t";
my $debug = 0;

sub localizeNumber {
   my $number = shift;

   $number =~ s/(?<=\d)(?=(\d\d\d)+$)/./g if($main::appLang eq 'de');
   $number =~ s/(?<=\d)(?=(\d\d\d)+$)/,/g if(not $main::appLang eq 'de');

   return $number;                
}

sub localizeDate {
   my $raw = shift;
   my $rv = $raw; 

   if($raw =~ /^(\d+)-(\d+)-(\d+)/){
      if(   $main::appLang eq 'de') { $rv = sprintf('%2d.%02d.%04d', $3,$2,$1); }
      elsif($main::appLang eq 'en') { $rv = sprintf('%2d/%02d/%04d', $2,$3,$1); }
      }
   return $rv;
}

sub localizeTime {
   my $raw = shift;
   my $rv = $raw;

   if($raw =~ /^(\d+)[\:\.](\d+)/){
      if(   $main::appLang eq 'de') { $rv = sprintf('%2d:%02d', $1, $2); }
      elsif($main::appLang eq 'en') { $rv = sprintf('%2d:%02d', $1, $2); }
      else                          { $rv = sprintf('%2d:%02d', $1, $2); }
      } 
   return $rv;
}

sub localizeTS {
   my $raw = shift;
   my $rv = localizeDate(substr($raw, 0, 10));

   $rv .= ' '.localizeTime(substr($raw, 11)) if length($raw) > 10;

   return $rv;
}

sub max {
   my $a = shift;
   my $b = shift;
   my $c = shift;
   my $rv = $a;

   $rv = $b if $b > $a;
   $rv = $c if $c and $c > $rv;
   
   return $rv;
}

sub min {
   my $a = shift;
   my $b = shift;
   my $rv = $a;
   
   $rv = $b if $b < $a;

   return $rv;
}

sub readIdx {
   my $path = shift;
   my $buffer = '';
   my $bytesRead = 0;
   my $fp;
   my @pcm = ();
   my $debug = 0;

   $path .= '/' if not $path =~ /\/$/;
   open($fp, $path.'index.vdr') or die('Oups, could not open index file!'."\n");
   $bytesRead = sysread($fp, $buffer, -s $path.'index.vdr', 0);
   die('Oups, something went wrong on reading index: '.$!."\n") if($bytesRead != -s $path.'index.vdr');
   close($fp);        

   my $mx = $bytesRead / 8;
   for(my $i=0; $i < $mx; $i++){
       my($off,$type,$mx,undef) = unpack('LCCS', substr($buffer, $i*8, 8));
       
       push(@pcm, {offset=>$off, type=>$type, file=>$mx});
       }
   return \@pcm;
}

sub findMark {
   my $pcmRef = shift;
   my $off = shift;
   my $filno = shift;
   my $startFrame = 0;
   my $oldOff = 0;
   my $i = 0;
   my %rv;

   foreach my $cm (@$pcmRef){
           $i++;
           next if($cm->{file} < $filno);
           $startFrame = $i-1 if not $startFrame;
           if($cm->{offset} > $off){
              return ($i-1, $oldOff, $filno, $startFrame, $cm->{type});
              }
           $oldOff = $cm->{offset};
           }
   return ($#{$pcmRef}, $oldOff, $pcmRef->[$#{$pcmRef}]{file}, $startFrame, $pcmRef->[$#{$pcmRef}]{type});
}

sub recLength {
   my $path = shift;        
   my $total = 0;

   $path .= '/' if not $path =~ /\/$/;
   for my $i (1..999){
       my $file = $path.sprintf('%03d.vdr', $i);

       last if not -s $file;
       $total  += -s $file;
       }
   return $total;
}

sub getCmType {
   my $fnMarks = shift;
   my $type    = 'vdrsync';

   if($fnMarks =~ /\.([^.]+)$/){
      my $xt = $1;

      $type = 'projectX' if $xt =~ /pjx/i;
      }
   return $type;   
}

sub bp2offset {
   my $path   = shift;
   my $offset = shift;
   my $total  = 0;
   my $oldSum = 0;
   my $filno  = 0;
   my $nDelta = 0;
   my $nOffset= 0;

   $path .= '/' if not $path =~ /\/$/;
   for my $i (1..999){
       my $file = $path.sprintf('%03d.vdr', $i);

       $total += -s $file;
       if(not -s $file){
          if(not $filno and $total <= $offset){
             $filno = $i-1;     
             $nDelta  = $oldSum - (-s $path.sprintf('%03d.vdr', $filno));        
             $nOffset = $offset - $nDelta;
             $filno = $i-1;
             }
          last;     
          }
       if($total > $offset and $oldSum <= $offset){
          $nDelta  = $oldSum;        
          $nOffset = $offset - $nDelta;
          $filno = $i;
          }
       $oldSum = $total;
       }
   return ($total, $nOffset, $nDelta, $filno);
}

sub bp2idx {
   my $bp = shift;
   my $path = shift;
   my $pcmRef = shift;
   my(undef, $off, $delta, $filno) = bp2offset($path, $bp);
   my($mIdx, $mOff, $mFilno, $startFrame, $mType) = findMark($pcmRef, $off, $filno);

   return ($mOff, $mOff+$delta, $mIdx-1, $mFilno);
}

sub tc2idx {
   my $tc = shift;        
   my $frame = 0;
   my $hour = 0;
   my $min = 0;
   my $sec = 0;

   if($tc =~ /(\d+):(\d+):(\d+)\.(\d+)/){
      $hour = $1;
      $min  = $2;
      $sec  = $3;
      $frame= $4;
      }
   return int(($hour * 3600 + $min * 60 + $sec) * 25 + $frame -1);
}

sub idx2offset {
   my $path   = shift;
   my $idx    = shift;
   my $pcmRef = shift;
   my $total  = 0;
   my $offset = $pcmRef->[$idx]{offset};

   $path .= '/' if not $path =~ /\/$/;
   for my $i (1..999){
       my $file = $path.sprintf('%03d.vdr', $i);

       last if not -s $file;
       $total  += -s $file;
       $offset += -s $file if $i < $pcmRef->[$idx]{file};
       }
   return ($pcmRef->[$idx]{offset}, $offset, $idx, $pcmRef->[$idx]{file});        
}

sub tc2offset {
   my $tc = shift;
   my $path = shift;
   my $pcmRef = shift;
   my $idx = tc2idx($tc);
   my($offS, $offT, undef, $filno) = idx2offset($path, $idx, $pcmRef);

   return ($offS, $offT, $idx, $filno);
}

sub idx2tc {
   my $idx = shift;        
   my $frame  = int($idx % 25) +1;
   my $tm     = int($idx / 25);
   my $hour   = int($tm / 3600);
   my $min    = int(($tm - 3600 * $hour) / 60);
   my $sec    = $tm - 3600 * $hour - 60 * $min;

   return sprintf('%u:%02u:%02u.%02u', $hour, $min, $sec, $frame);
}

sub readMarks {
   my $path = shift;
   my $fnUser = shift;
   my $pcmRef = shift;
   my $marks = {};
   my $fnMarks;
   my $fp;

   $marks->{base} = ();
   $marks->{name} = '';
   $marks->{idx}  = ();
   $pcmRef = readIdx($path) if not $pcmRef;
   $path .= '/' if not $path =~ /\/$/;
   $fnMarks = $path.$fnUser if("$fnUser" and not $fnMarks =~ /\//);
   $fnMarks = $fnUser if("$fnUser" and $fnMarks =~ /\//);

   if(not "$fnUser" or not -s $fnMarks){
      undef($fnMarks);
      $fnMarks = $path.'marks.pjx' if(-s $path.'marks.pjx');
      $fnMarks = $path.'marks.vdr' if(not $fnMarks and -s $path.'marks.vdr');
   }

   if($fnMarks and open($fp, $fnMarks)){
      $marks->{'name'} = $fnMarks;     
      print('processing markfile <'.basename($fnMarks).">\n");     
      while(my $line = <$fp>){
            if($line =~ /^(\d+:\d+:\d+\.\d+)/){
               my($offS, $offT, $idx, undef) = tc2offset($1, $path, $pcmRef);     

               push(@{$marks->{'idx'}}, $idx);
               push(@{$marks->{'base'}}, $offT);
               }     
            elsif($line =~ /^(\d+)/){
               my($offS, $offT, $idx, undef) = bp2idx($1, $path, $pcmRef);     
   
               push(@{$marks->{'idx'}}, $idx);
               push(@{$marks->{'base'}}, $offT);
               }
            }
      close($fp);
      }
   if(not $marks->{base}){
      print('Oups, no marks found. Create fake ones ...'."\n");     
      push(@{$marks->{'idx'}}, 0);
      push(@{$marks->{'base'}}, 0);
      my($offS, $offT, $idx, undef) = bp2idx(recLength($path), $path, $pcmRef);     

      push(@{$marks->{'idx'}}, $idx);
      push(@{$marks->{'base'}}, $offT);
      }
   elsif($#{$marks->{base}} < 1){
      print('Oups, only one mark found. Gonna terminate recording ...'."\n");     
      my($offS, $offT, $idx, undef) = bp2idx(recLength($path), $path, $pcmRef);     

      push(@{$marks->{'idx'}}, $idx);
      push(@{$marks->{'base'}}, $offT);
      }
   return $marks;
}

sub writeMarks {
   my $fnMarks = shift;
   my $marks   = shift;
   my $pcmRef  = shift;
   my $type    = getCmType($fnMarks);
   my $fp;
   
   $pcmRef = readIdx(dirname($marks->{file})) if not $pcmRef;
   open($fp, '> '.$fnMarks) or die('could not create new marksfile.'."\n");
   print($fp 'CollectionPanel.CutMode=0'."\n") if $type eq 'projectX';
   if($type eq 'projectX'){
      foreach my $mark (@{$marks->{base}}){
              print($fp $mark."\n");     
              }
      }
   else{
      foreach my $mark (@{$marks->{idx}}){
              print($fp idx2tc($mark)."\n");     
              }
      }
   close($fp);
}

sub skalarToXml {
   my $value = shift;
   my $name = shift;
   my $rv = '';
   
   $indent++;
   for my $i (2 .. $indent){ $rv .= $txtIndent; }
   $rv .= '<'.$name.' value="';
   if(defined($value)){
      $value =~ s/"/'/g;     
      $value =~ s/\n/|/g;     
      $rv .= $value; 
      } 
   $rv .= '"/>'."\n";
   $indent--;

   return $rv;
}

sub hashToXml {
   my $hRef = shift;
   my $name = shift;
   my $rv = '';

   $indent++;
   for my $i (2 .. $indent){ $rv .= $txtIndent; }
   $rv .= '<'.$name.' type="HASH">'."\n";

   while(my($subKey, $subValue) = each(%{$hRef})){
         if(ref($subValue) eq 'HASH')      { $rv .= hashToXml($subValue, $subKey); }
         elsif(ref($subValue) eq 'ARRAY')  { $rv .= arrayToXml($subValue, $subKey); }
         elsif(ref($subValue) eq 'SCALAR') { $rv .= skalarToXml($$subValue, $subKey); }
         elsif(not ref($subValue))         { $rv .= skalarToXml($subValue, $subKey); }
         else{ 
            if(UNIVERSAL::isa($subValue, 'HASH'))    { $rv .= hashToXml($subValue, $subKey); }
            elsif(UNIVERSAL::isa($subValue, 'ARRAY')){ $rv .= arrayToXml($subValue, $subKey); }
            else{ die('unknown data-type ('.ref($subValue).') to transform! - GIVE UP!'); }
            }
         }
   for my $i (2 .. $indent){ $rv .= $txtIndent; }
   $rv .= '</'.$name.'>'."\n";
   $indent--;

   return $rv;
}

sub arrayToXml {
   my $aRef = shift;
   my $name = shift;
   my $rv = '';

   $indent++;
   for my $i (2 .. $indent){ $rv .= $txtIndent; }
   $rv .= '<'.$name.' type="ARRAY">'."\n";

   foreach my $entry (@{$aRef}){
           if(ref($entry) eq 'HASH')      { $rv .= hashToXml($entry, 'entry'); }
           elsif(ref($entry) eq 'ARRAY')  { $rv .= arrayToXml($entry, 'entry'); }
           elsif(ref($entry) eq 'SCALAR') { $rv .= skalarToXml($$entry, 'entry'); }
           elsif(not ref($entry))         { $rv .= skalarToXml($entry, 'entry'); }
           else{ die('unknown data-type ('.ref($entry).') to transform! - GIVE UP!'); }
           }
   
   for my $i (2 .. $indent){ $rv .= $txtIndent; }
   $rv .= '</'.$name.'>'."\n";
   $indent--;

   return $rv;
}

sub toXml($$) {
   my $dataRef = shift;
   my $name = shift;
   my $result = '';

   if(ref($dataRef) eq 'HASH')      { $result .= hashToXml($dataRef, $name); }
   elsif(ref($dataRef) eq 'ARRAY')  { $result .= arrayToXml($dataRef, $name); }
   elsif(ref($dataRef) eq 'SCALAR') { $result .= skalarToXml($$dataRef, $name); }
   elsif(not ref($dataRef))         { $result .= skalarToXml($dataRef, $name); }
   else{ die('unknown data-type ('.ref($dataRef).') to transform! - GIVE UP!'); }

   return $result;
}

sub createHash {
   my $tagsRef = shift;
   my $hashId = shift;
   my $rv;

   while(my $tag = shift(@$tagsRef)){
         if($tag =~ /^(\/?)([^ \/]+)\s*(?:([^=]+)="([^"]+)")?(\/)?$/){
            my $end = $1;        
            my $name = $2;
            my $id = $3;
            my $value = $4;
            my $single = $5;
            
            print('createHash(): end="'.$end.'", name=<'.$name.'>, id=['.$id.'], value={'.$value.'}, single="'.$single."\"\n") if $debug;
            last if $name eq $hashId and $end eq '/';
            if($id eq 'type'){
               if($value eq 'HASH')    { $rv->{$name} = createHash($tagsRef, $name); }
               elsif($value eq 'ARRAY'){ $rv->{$name} = createArray($tagsRef, $name); }
               else{ die('unsupported data-type ('.$value.'). GIVE UP!'); }
               }
            elsif($id eq 'value'){ 
               $value =~ s/\|/\n/g;     
               $rv->{$name} = $value; 
               }
            elsif(not defined($id) and $end eq '/'){ $rv->{$name} = undef; }
            else{ die('unknown data <'.$tag.'>. GIVE UP!'); }
            }
         }
   return $rv;
}

sub createArray {
   my $tagsRef = shift;
   my $arrayId = shift;
   my $rv;

   while(my $tag = shift(@$tagsRef)){
         if($tag =~ /^(\/?)([^ \/]+)\s*(?:([^=]+)="([^"]+)")?(\/)?$/){
            my $end = $1;     
            my $name = $2;
            my $id = $3;
            my $value = $4;
            my $single = $5;
            
            last if $name eq $arrayId and $end eq '/';
            if($id eq 'type'){
               if($value eq 'HASH')    { push(@$rv, createHash($tagsRef, $name)); }
               elsif($value eq 'ARRAY'){ push(@$rv, createArray($tagsRef, $name)); }
               else{ die('unsupported data-type ('.$value.'). GIVE UP!'); }
               }
            elsif($id eq 'value'){ 
               $value =~ s/\|/\n/g;     
               push(@$rv, $value); 
               }
            elsif(not defined($id) and $end eq '/'){ push(@$rv, undef); }
            else{ die('unknown data. GIVE UP!'); }
            }
         }
   return $rv;
}

sub fromXmlFile {
   my $fn = shift;
   my $dom = shift;
   my $result = undef;
   my $rawData = '';
   my $fp;

   if(open($fp, $fn)){
      while(my $line = <$fp>){ $rawData .= $line; }
      close($fp);
      $result = fromXml($rawData, $dom);
      }
   return $result;
}

sub fromXml {
   my $xmlData = shift;
   my $dom = shift;
   my @allTags;

   while($xmlData =~ /<([^>]+)>/g){ push(@allTags, $1); }
   croak('INVALID input-file! TOP domain should be <'.$dom.">\n") if not $allTags[0] =~ /^$dom/;
      
   return tags2Data(\@allTags);
}

sub tags2Data {
   my $tagsRef = shift;
   my $result;

   while(my $tag = shift(@$tagsRef)){
         if($tag =~ /^([^ \/]+)\s*(?:([^=]+)="([^"]+)")?(\/)?$/){
            my $name = $1;
            my $id = $2;
            my $value = $3;
            my $end = $4;
            
            if($id eq 'type'){
               if($value eq 'HASH')    { $result = createHash($tagsRef, $name); }
               elsif($value eq 'ARRAY'){ $result = createArray($tagsRef, $name);   }
               else{ die('unsupported data-type ('.$value.'). GIVE UP!'); }
               }
            elsif($id eq 'value'){ 
               $value =~ s/\|/\n/g;     
               $result = $value; 
               }
            elsif(not defined($id) and $end eq '/'){ $result = undef; }
            else{ die('unknown data. GIVE UP!'); }
            }
         }
   return $result;
}

1;
