#
# This class handles jobfiles for vdr2iso and the jobeditor.
#       this module is written by ...: geronimo at vdrportal dot de
# 
# COPYRIGHT:   (c) 2005-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
#      
# DESCRIPTION: see README on a detailled description
#
package V2I::Job;

use strict;
use Data::Dumper;
use Carp qw(carp croak);
use Storable qw(dclone);
use V2I::Theme;
use V2I::utils;

my $debug = 0;

sub init {
   my $self = shift;     
           
   if(ref($_[0]) eq 'V2I::Config'){ $self->initFromAppConfig($_[0]); }
   elsif(ref($_[0]) eq 'HASH'){ $self->initFromHash($_[0]); }
   else{ $self->populate(@_); }
   $self->addDisk();
}

sub initFromAppConfig {
   my $self = shift;
   my $cfg = shift;

   $self->{'_'}{'appConfig'}   = $cfg;
   $self->{'_'}{'base'}        = $cfg->getRecBase;
   $self->{'_'}{'language'}    = $cfg->getConfig->{'language'};
   $self->{'_'}{'demux'}       = $cfg->getConfig->{'demux'};
   $self->{'_'}{'chapLen'}     = $cfg->getConfig->{'chapLen'};
   $self->{'_'}{'cutMarks'}    = $cfg->getConfig->{'cutMarks'};
   $self->{'_'}{'header'}      = $cfg->getConfig->{'header'};
   $self->{'_'}{'theme'}       = $cfg->getConfig->{'theme'};
   $self->{'_'}{'filename'}    = '';
   $self->{'_'}{'currentDisk'} = 0;
   $self->{'_'}{'currentRec'}  = 0;
   $self->{'_'}{'currentPage'} = 0;
   foreach (keys(%{$cfg->getConfig->{'themes'}})){
           $self->{'themeDefaults'}{$_} = $cfg->getConfig->{'themes'}{$_};
           }
}

sub new {
   my $class = shift;
   my $self = {
      _ => {
         header => 'cinema'
       , language => 0
       , chapLen => 10
       , demuxer => 'vdrsync'
       , cutmarks => ''
       , stdtheme => 'Default'
       , currentDisk => 0
       , currentRec => 0
       , currentPage => 0
      },
      themeDefaults => {},
      job => {
         defaultvalues => {}
       , disks => []
      }};
   bless($self, $class);
   $self->init(@_);

   return $self;
}

sub setTitle {
   my $self = shift;
   my $number = shift;

   $self->currentDisk->{'title'} = $self->{'_'}{'appConfig'}->getDiskTitle($number);
}

sub addDisk {
   my $self = shift;
   my $unum = shift;
   my $disk = { 
      title=>'disk #'
    , theme=>'Default'
    , settings => {}
    , recordings => []
    , special => { settings => {}
                 , pages => []} 
   };
   
   $unum = $#{$self->{'job'}{'disks'}}+1 if not $unum;
   $disk->{'title'} = $self->{'_'}{'appConfig'}->getDiskTitle($unum);
   $disk->{'theme'} = $self->{'_'}{'theme'};

   foreach (qw(language demux chapLen cutMarks header theme)){
           $disk->{'settings'}{$_} = $self->{'_'}{$_};     
           }
   $self->{'_'}{'appConfig'}->getThemeManager->getTheme($disk->{'theme'})->copyDefaults($disk->{'settings'});
   push(@{$self->{'job'}{'disks'}}, $disk);
   $self->{'_'}{'currentDisk'} = $#{$self->{'job'}{'disks'}};
}

sub addRecording {
   my $self = shift;
   my $rec = shift;
   my $disk = $self->currentDisk;
   my $recording = dclone($rec);

   $recording->{'settings'} = dclone($disk->{'settings'});
   $recording->{'title'} = $rec->{'name'};
   push(@{$disk->{'recordings'}}, $recording);   
}

sub addSpecials {
   my $self = shift;
   my $disk = shift;
   my $page = {
      title=>'Special page #1'
   ,  settings=>undef      
   };
   
   $disk = $self->currentDisk if not $disk;
   $disk->{'special'} = { title=>$self->{'_'}{'appConfig'}->getLangToken('special')
                        , settings=>undef
                        , pages=>undef };
   $disk->{'special'}{'settings'} = dclone($disk->{'settings'});
   $page->{'settings'} = dclone($disk->{'settings'});
   $self->{'_'}{'currentPage'} = 0;
   push(@{$disk->{'special'}{'pages'}}, $page);
}

sub addPage {
   my $self = shift;
   my $disk = shift;
   my $page = shift;

   $disk = $self->currentDisk if not $disk;
   if(not $page){
      $page = { title=>undef, settings=>undef };
      $page->{'title'} = 'Special page #'.($#{$disk->{'special'}{'pages'}}+2);
      $page->{'settings'} = dclone($disk->{'special'}{'settings'});
      }
   push(@{$disk->{'special'}{'pages'}}, $page);
   $self->{'_'}{'currentPage'} = $#{$disk->{'special'}{'pages'}};
}

sub getSimpleRecList {
   my $cfg = shift;
   my $cmd = 'find -L \''.$cfg->getRecBase.'\' -name index.vdr -print 2>/dev/null ';
   my $fp;
   my @rv;
   
   print('getSimpleRecList() - command: '.$cmd."\n") if $debug;
   open($fp, $cmd.' |') or die 'could not scan recordings! '.$!."\n";
   while(my $rec = <$fp>){
         $rec =~ s/index.vdr\n//;     
         my @parts = split(/\//, $rec);
         my $ts = pop(@parts);
         my $title = pop(@parts);
         
         if($title =~ /\#(.{2})/){
            $title =~ s/\#5F/ /ig;
            $title =~ s/\#2F/\//ig;
            $title =~ s/\#3A/:/ig;
            $title =~ s/\#E8/é/ig;
            }
         $title =~ s/_/ /g;
   
         push(@rv, {name=>$title, ts=>$ts, path=>$rec});
         }
   close($fp);

   return @rv;
}

sub createSimple {
   my $class = shift;
   my $cfg = shift;
   my $fnSortRef = shift;
   my $recsPerDisk = shift;
   my $firstRec = shift;
   my $maxDisks = shift;
   my $job = $class->new($cfg);
   my $fp;

   $fnSortRef = sub{ $a->{'ts'} cmp $b->{'ts'}} if(not ref($fnSortRef) eq 'CODE');
   my @recs = sort($fnSortRef getSimpleRecList($cfg));
   my $n = $firstRec;

   $n-- if $firstRec > 0;
   my $last = $n + $maxDisks * $recsPerDisk;

   die('NOT so many recordings to satisfy firstRecording! ( '.$firstRec.' <> '.$#recs.' )'."\n") if $firstRec > $#recs;
   $last = $#recs if $last > $#recs;
   print($class.'::createSimple(#'.$#recs.'): first='.$firstRec.', count='.$recsPerDisk.', max='.$maxDisks.', last='.$last."\n") if $debug;
   while($n <= $last){
         $job->setTitle(int($n / $recsPerDisk));     
         for my $i (1 .. $recsPerDisk) {     
             my $rec = $recs[$n++];
             my $summary = $rec->{name};
    
             if(-s $rec->{'path'}.'summary.vdr'){
                if(open($fp, $rec->{'path'}.'summary.vdr')){
                   $summary = '';     
                   while(my $line = <$fp>){ $summary .= $line; }
                   close($fp);
                   }
                }
             elsif(-s $rec->{'path'}.'info.vdr'){
                if(open($fp, $rec->{'path'}.'info.vdr')){
                   $summary = '';
                   while(my $line = <$fp>){
                         if($line =~ /^T (.+)$/){ $rec->{'title'} = $1; }
                         elsif($line =~ /^D (.+)$/){ $summary = $1; }
                         }
                   close($fp);
                   } 
                } 
             $summary =~ s/\n/|/g;
             $summary =~ s/;/./g;
             $rec->{'summary'} = $summary;
             $rec->{'title'} = $rec->{'name'} if not defined($rec->{'title'});
             $job->addRecording($rec);
             last if $n >= $last;
             }
         last if $n >= $last;
         $job->addDisk(int($n / $recsPerDisk));
         }

   return $job;
}

sub removeCurrentDisk {
   my $self = shift;

   splice(@{$self->{'job'}{'disks'}}, $self->getCurrentDisk, 1);
   $self->{'_'}{'currentDisk'}-- if $self->getCurrentDisk >= 0;
}

sub removeCurrentRecording {
   my $self = shift;

   splice(@{$self->getRecordings($self->getCurrentDisk)}, $self->getCurrentRecording, 1);
   $self->{'_'}{'currentRec'}-- if $self->getCurrentRecording >= 0;
}

sub removeCurrentPage {
   my $self = shift;

   splice(@{$self->getPages}, $self->getCurrentPage, 1);
   $self->{'_'}{'currentPage'}-- if $self->getCurrentPage >= 0;
}

sub themeChanged {
   my $self = shift;     

   $self->{'_'}{'appConfig'}->getThemeManager->getTheme($self->getCurrentTheme)->copyDefaults($self->currentDisk->{'settings'});
}

sub dump {
   my $self = shift;

   print(ref($self->{'job'}).' looks like ...:'."\n".V2I::utils::toXml($self->{'job'}, 'job')."\n");
}

sub copySettings {
   my $self = shift;
   my $dst = shift;
   my $src = shift;

   foreach (keys(%{$src})){
           if(ref($src->{$_})){ $dst->{$_} = dclone($src->{$_}); }
           else{ $dst->{$_} = $src->{$_}; }
           }
}

sub propagateJob {
   my $self = shift;
   my $src  = $self->{'_'}{'appConfig'}->getConfig;
   my $dst  = $self->{'job'}{'defaultvalues'};
   my $tSrc = $src->{'themes'}{$src->{'theme'}};
   my @tP   = $self->{'_'}{'appConfig'}->getThemeManager->getTheme($src->{'theme'})->getProperties;
   my $test = {};

   foreach (qw(language demux cutmarks chapLen niceLevel header)){
           $dst->{$_} = $src->{$_};
           }
   foreach (@tP){
           if(ref($tSrc->{$_})){ $dst->{$_} = dclone($tSrc->{$_}); }
           else{ $dst->{$_} = $tSrc->{$_}; } 
           }
   $src = $self->{'job'}{'defaultvalues'};
   foreach (@{$self->getDisks}){
           $self->copySettings($_->{'settings'}, $src);
           $self->propagateDisk($_);
           }
}

sub usedDemuxers {
   my $self = shift;
   my %used;
   my @rv;

   if($#{$self->getDisks} > (-1)){
      foreach my $disk (@{$self->getDisks}){
              if(defined($disk->{'recordings'}) and $#{$disk->{'recordings'}} > (-1)){
                 foreach my $rec (@{$disk->{'recordings'}}){
                         push(@rv, $rec->{'settings'}{'demux'}) if not $used{$rec->{'settings'}{'demux'}};
                         $used{$rec->{'settings'}{'demux'}} = 1;
                         }
                 }
              }
      }
   return @rv;      
}

sub propagateDisk {
   my $self = shift;     
   my $disk = shift;
   
   $disk = $self->currentDisk if not $disk;
   if(defined($disk->{'recordings'}) and $#{$disk->{'recordings'}} > (-1)){
      foreach (@{$disk->{'recordings'}}){
              $self->copySettings($_->{'settings'}, $disk->{'settings'});     
              }
      }
   $self->propagateSpecs($disk);
}

sub propagateSpecs {
   my $self = shift;
   my $disk = shift;

   $disk = $self->currentDisk if not $disk;
   if($disk->{'special'}{'title'}){
      $self->copySettings($disk->{'special'}{'settings'}, $disk->{'settings'});
      foreach (@{$disk->{'special'}{'pages'}}){
              $self->copySettings($_->{'settings'}, $disk->{'settings'});
              }
      }
}

sub removeSpecials {
   my $self = shift;
   my $disk = shift;

   $disk = $self->currentDisk if not $disk;
   delete($disk->{'special'});
}

sub createJobSettings {
   my $self = shift;
   my $frLeft = shift;
   my $frRight = shift;
   my @themes = $self->{'_'}{'appConfig'}->getThemeManager->getActiveThemes;

   main::guiElem($frLeft,  17, 30, 'header',  'Entry', \$self->{'_'}{'appConfig'}->getConfig->{'header'});
   main::guiElem($frLeft,  17, 30, 'demux',   'Radio', \$self->{'_'}{'appConfig'}->getConfig->{'demux'}, ['vdrsync', 'projectX'], 'H');
   main::guiElem($frLeft,  17, 30, 'cutmarks','Path' , \$self->{'_'}{'appConfig'}->getConfig->{'cutmarks'}, undef, 'emptyOk');
   main::guiElem($frLeft,  17, 30, 'theme',   'Option',\$self->{'_'}{'appConfig'}->getConfig->{'theme'}, \@themes);
   main::guiElem($frRight, 14,  5, 'lang',    'Number',\$self->{'_'}{'appConfig'}->getConfig->{'language'},[0,9],  1);
   main::guiElem($frRight, 14,  5, 'chapLen', 'Number',\$self->{'_'}{'appConfig'}->getConfig->{'chapLen'}, [1,20], 1);
}

sub createDVDMenu {
   my $self = shift;
   my $args = \@_;

   print(ref($self).'::createDVDMenu() with theme <'.$self->getCurrentTheme.'> ...:'."\n".Dumper($args)."\n") if $debug;
   $self->{'_'}{'appConfig'}->getThemeManager->getTheme($self->getCurrentTheme)->createDVDMenu($self->currentDisk, $args, $self->{'_'}{'appConfig'});
}

sub getFilename { return $_[0]->{'_'}{'filename'}; }
sub setFilename {
   my $self = shift;
   my $nfn = shift;

   $self->{'_'}{'filename'} = $nfn;
}
sub getDisks              { return $_[0]->{'job'}{'disks'}; }
sub getCurrentDisk      { return $_[0]->{'_'}{'currentDisk'}; }
sub currentDisk         { return $_[0]->{'job'}{'disks'}[$_[0]->getCurrentDisk]; }
sub currentPage         { return $_[0]->specials->{'pages'}[$_[0]->getCurrentPage]; }
sub currentDiskSettings { return $_[0]->{'job'}{'disks'}[$_[0]->getCurrentDisk]{'settings'}; }
sub getCurrentRecording { return $_[0]->{'_'}{'currentRec'}; }
sub getCurrentPage      { return $_[0]->{'_'}{'currentPage'}; }
sub currentRecording    { return $_[0]->currentDisk->{'recordings'}[$_[0]->getCurrentRecording]; }
sub specials            { return $_[0]->currentDisk->{'special'}; }
sub getCurrentTheme     { return $_[0]->currentDisk->{'theme'}; }
sub setCurrentTheme {
   my $self = shift;
   my $nt = shift;

   if($self->{'_'}{'appConfig'}->getThemeManager->isActiveTheme($nt)){
      $self->currentDiskSettings->{'theme'}{'value'} = $nt;     
      }
}

sub getRecordings { 
   my $self = shift;
   my $disk = shift;
   my $disks = $self->getDisks;
   my $diskRef;

   if(not $disk){ $diskRef = $self->currentDisk; }
   elsif(not ref($disk)){
      if($disk =~ /^\d+$/){
         $disk = 0 if $disk < 0;
         $disk = $#{$disks} if $disk > $#{$disks};
         $diskRef = $disks->[$disk];
         }
      }
   else{ $diskRef = $disk; }

   return $diskRef->{'recordings'};
}

sub getPages {
   my $self = shift;
   my $disk = shift;
   my $disks = $self->getDisks;
   my $diskRef;

   if(not $disk){ $diskRef = $self->currentDisk; }
   elsif(not ref($disk)){
      if($disk =~ /^\d+$/){
         $disk = 0 if $disk < 0;
         $disk = $#{$disks} if $disk > $#{$disks};
         $diskRef = $disks->[$disk];
         }
      }
   else{ $diskRef = $disk; }

   return $diskRef->{'special'}{'pages'};
}

sub movePageUp {
   my $self = shift;
   my $pgNr = shift;
   
   $pgNr = $self->getCurrentPage if not defined($pgNr);

   return if $pgNr > ($#{$self->getPages}-1);
   my $tmp = $self->getPages->[$pgNr +1];

   $self->getPages->[$pgNr +1] = $self->getPages->[$pgNr];
   $self->getPages->[$pgNr] = $tmp;
   $self->setCurrentPage($pgNr +1);
}

sub movePageDown {
   my $self = shift;
   my $pgNr = shift;
   
   $pgNr = $self->getCurrentPage if not defined($pgNr);

   return if $pgNr < 1;
   my $tmp = $self->getPages->[$pgNr -1];

   $self->getPages->[$pgNr -1] = $self->getPages->[$pgNr];
   $self->getPages->[$pgNr] = $tmp;
   $self->setCurrentPage($pgNr -1);
}

sub setCurrentDisk {
   my $self = shift;
   my $ndn = shift;

   $ndn = 0 if $ndn < 0;
   $ndn = $#{$self->getDisks} if $ndn > $#{$self->getDisks};
   $self->{'_'}{'currentDisk'} = $ndn;
   $self->{'_'}{'currentRec'} = 0;
}

sub setCurrentRecording {
   my $self = shift;     
   my $nrn = shift;
   my $nor = $#{$self->getRecordings};

   $nrn = 0 if $nrn < 0;
   $nrn = $nor if $nrn > $nor;
   $self->{'_'}{'currentRec'} = $nrn;   
}

sub setCurrentPage {
   my $self = shift;
   my $npn = shift;
   my $nop = $#{$self->getPages};

   $npn = 0 if $nop < 0;
   $npn = $nop if $npn > $nop;
   $self->{'_'}{'currentPage'} = $npn;
}

sub toXml {
   my $self = shift;

   return V2I::utils::toXml($self->{'job'}, 'job');
}

sub fromXml {
   my $class = shift;
   my $xmlData = shift;
   my $config = shift;
   my $rv = $class->new($config);

   $rv->importFromXml($xmlData);
   $rv->validateBase;

   return $rv;
}

sub importFromFile {
   my $self = shift;
   my $fileName = shift;

   $self->{'job'} = V2I::utils::fromXmlFile($fileName, 'job');
}

sub importFromXml {
   my $self = shift;
   my $xmlData = shift;

   $self->removeCurrentDisk;
   $self->{'job'} = V2I::utils::fromXml($xmlData, 'job');
}

sub load {
   my $class = shift;
   my $fileName = shift;
   my $config = shift;
   my $rv = $class->new($config);

   $rv->importFromFile($fileName);
   $rv->validateBase;
   $rv->setCurrentDisk(0);
   $rv->setCurrentRecording(0);

   return $rv;
}

sub validateBase {
   my $self = shift;
   my $base = $self->{'job'}{'defaultvalues'}{'base'};
   my $realBase = $self->{'_'}{'appConfig'}->getRecBase;


   print(ref($self).'::validateBase() - check 4 patch ['.$base.'] with <'.$realBase.">\n") if $debug;
   return if $base eq $realBase;
   print(ref($self).'::validateBase() - have to patch ['.$base.'] to <'.$realBase.">\n");

   foreach my $disk (@{$self->getDisks}){
           foreach my $rec (@{$disk->{'recordings'}}){
                   my $path = $rec->{'path'};

                   print('validateBase() - old path: '.$path."\n") if $debug;
                   $path =~ s/^$base(.*)$/$realBase$1/;
                   print('validateBase() - new path: '.$path."\n") if $debug;
                   }
           }
}

sub save {
   my $self = shift;
   my $fp;

   return -1 if not $self->getFilename;
   foreach (qw(base language demux chapLen cutMarks header theme)){
           $self->{'job'}{'defaultvalues'}{$_} = $self->{'_'}{$_};     
           }
   $self->{'_'}{'appConfig'}->getThemeManager->getTheme($self->{'_'}{'theme'})->copyDefaults($self->{'job'}{'defaultvalues'});
   if(open($fp, '> '.$self->getFilename)){
      print($fp V2I::utils::toXml($self->{'job'}, 'job'));
      close($fp);     
      return 0;
      }
   return -2;
}

1;
