package XXV::MODULES::RECORDS;

use strict;

use Tools;
use Time::Local;
use File::Find;
use File::Copy;
use File::Path;
use File::Basename;
use File::stat;
use Locale::gettext;

$SIG{CHLD} = 'IGNORE';

# This module method must exist for XXV
# ------------------
sub module {
# ------------------
    my $obj = shift || return error ('No Object!' );

    my $args = {
        Name => 'RECORDS',
        Description => gettext('This module managed recordings.'),
        Version => '0.45',
        Date => '04.09.2005',
        Author => 'xpix',
        Status => sub{ $obj->status(@_) },
        Preferences => {
            commandfile => {
                description => gettext('Location of reccmds.conf on your system.'),
                default     => '/var/lib/vdr/reccmds.conf',
                type        => 'file',
                required    => gettext("This is required!"),
            },
            interval => {
                description => gettext('The interval to read the recordings from svdrp'),
                default     => 300,
                type        => 'integer',
                required    => gettext("This is required!"),
            },
            videodir => {
                description => gettext('Directory, where vdr recordings are stored.'),
                default     => '/video',
                type        => 'dir',
                required    => gettext("This is required!"),
            },
            previewbinary => {
                description => gettext('Location of used program to produce preview images on your system.'),
                default     => '/usr/bin/mplayer',
                type        => 'file',
                required    => gettext("This is required!"),
            },
            previewcommand => {
                description => gettext('Please choose the used program to produce preview images.'),
                type        => 'list',
                choices     => [
                    [gettext('Nothing'), 'Nothing'],
                    ['MPlayer1.0pre5', 'MPlayer1.0pre5'],
                    ['MPlayer1.0pre6', 'MPlayer1.0pre6'],
                    ['vdr2jpeg',       'vdr2jpeg'],
                ],
                default     => 'Nothing',
                required    => gettext("This is required!"),
            },
            previewlog => {
                description => gettext('Log file of produce preview images.'),
                default     => '/var/log/xxvd_mplayer.log',
                type        => 'string',
                required    => gettext("This is required!"),
            },
            previewcount => {
                description => gettext('How many preview images produce?'),
                default     => 3,
                type        => 'integer',
            },
            previewlistthumbs => {
                description => gettext('Display list records with thumbnails?'),
                default     => 'n',
                type        => 'confirm',
            },
            previewimages => {
                description => gettext('common directory for preview images'),
                default     => '/var/cache/xxv/preview',
                type        => 'dir',
                required    => gettext('This is required!'),
            },
            vfat => {
                description => gettext('Set this if your filename encoded for vfat filesystems'),
                default     => 'y',
                type        => 'confirm',
            },
        },
        Commands => {
            rdisplay => {
                description => gettext("Display recording 'rid'"),
                short       => 'rd',
                callback    => sub{ $obj->display(@_) },
                DenyClass   => 'rlist',
            },
            rlist => {
                description => gettext('List recordings'),
                short       => 'rl',
                callback    => sub{ $obj->list(@_) },
                DenyClass   => 'rlist',
            },
            rsearch => {
                description => gettext("Search recordings 'text'"),
                short       => 'rs',
                callback    => sub{ $obj->search(@_) },
                DenyClass   => 'rlist',
            },
            rupdate => {
                description => gettext('Update recordings'),
                short       => 'ru',
                callback    => sub{ $obj->refresh(@_) },
                Level       => 'user',
                DenyClass   => 'redit',
            },
            rdelete => {
                description => gettext("Delete recording 'rid'"),
                short       => 'rr',
                callback    => sub{ $obj->delete(@_) },
                Level       => 'user',
                DenyClass   => 'redit',
            },
            redit => {
                description => gettext("Edit recording 'rid'"),
                short       => 're',
                callback    => sub{ $obj->redit(@_) },
                Level       => 'user',
                DenyClass   => 'redit',
            },
            rconvert => {
                description => gettext("Convert recording 'rid'"),
                short       => 'rc',
                callback    => sub{ $obj->conv(@_) },
                Level       => 'user',
                DenyClass   => 'redit',
            },
        },
    };
    return $args;
}

# ------------------
sub new {
# ------------------
	my($class, %attr) = @_;
	my $self = {};
	bless($self, $class);

    # paths
    $self->{paths} = delete $attr{'-paths'};

	# who am I
    $self->{MOD} = $self->module;

    # all configvalues to $self without parents (important for ConfigModule)
    map {
        $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_} || $self->{MOD}->{Preferences}->{$_}->{default}
    } keys %{$self->{MOD}->{Preferences}};

    # read the DB Handle
    $self->{dbh} = delete $attr{'-dbh'};

    # i need the svdrp module
    $self->{svdrp} = main::getModule('SVDRP');

    # The Initprocess
    my $erg = $self->init or return error('Problem to initialze Module');

    # Interval to read channels and put to DB
    Event->timer(
        interval => $self->{interval},
        prio => 6,  # -1 very hard ... 6 very low
        cb => sub{
            $self->readData();
        },
    );

	return $self;
}

# ------------------
sub init {
# ------------------
    my $obj = shift || return error ('No Object!' );

    # Old Version
    if(tableExists($obj->{dbh}, 'RECORDS')) {
		my $fields = fields($obj->{dbh}, 'select * from RECORDS');
        $obj->{dbh}->do('drop table RECORDS')
            unless(scalar @$fields == 9);
    }

    # Look for table or create this table
    my $version = main::getVersion;
    $obj->{dbh}->do(qq|
      CREATE TABLE IF NOT EXISTS RECORDS (
          eventid int(16) unsigned NOT NULL,
          RecordId int(16) unsigned not NULL,
          Duration int(11) NOT NULL default '0',
          Start int(11) NOT NULL default '0',
          Path text NOT NULL,
          Prio tinyint NOT NULL,
          Lifetime tinyint NOT NULL,
          State tinyint NOT NULL,
          Marks text,
          addtime timestamp,
          PRIMARY KEY  (eventid),
          UNIQUE KEY  (eventid)
        ) COMMENT = '$version'
    |);

    # Load Reccmds's
    $obj->{reccmds} = [];
    if(-r $obj->{commandfile} and my $text = load_file($obj->{commandfile})) {
        foreach my $zeile (split(/\n/, $text)) {
            if($zeile !~ /^\#/ and $zeile !~ /^$/ and $zeile !~ /true/) {
                push(@{$obj->{reccmds}}, $zeile);
            }
        }
    }

    main::after(sub{
        $obj->readData()
    }, "RECORDS: Read Records to Database ...");

    1;
}

# ------------------
sub dot1000 {
# ------------------
    my $t = reverse shift;
    $t =~ s/(\d{3})(?=\d)(?!\d*\.)/$1./g;
    return scalar reverse $t;
}

# ------------------
sub readData {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;
    my $waiter = shift;

    my $c = 0;
    my $e = 0;


    $obj->{JOBS} = [];

    # Read VideoDir
    $obj->{FILES} = [];
    find(
            {
                wanted => sub{
                    if(-r $File::Find::name) {
                        push(@{$obj->{FILES}},$File::Find::name)
                            if($File::Find::name =~ /\d{3}.vdr$/sig);
                    } else {
                        warn "Permissions deny, can't read : ",$File::Find::name;
                    }
                },
                follow => 1,
                follow_skip => 2,
            },
        $obj->{videodir}
    );

    # Delete old Records
    $obj->{dbh}->do('DELETE FROM RECORDS');

    # Get state from used harddrive (/video)
    my $stat = $obj->{svdrp}->command('stat disk');
    if($stat->[1] and $stat->[1] =~ /^250/s) {
        #250 473807MB 98028MB 79%
        my ($total, $totalUnit, $free, $freeUnit, $percent)
            = $stat->[1] =~ /^250[\-|\s](\d+)(\S+)\s+(\d+)(\S+)\s+(\S+)/s;

        $obj->{usage} = sprintf(gettext("Used %s, Total %s%s, Free %s%s"),$percent, dot1000($total), $totalUnit,  dot1000($free), $freeUnit);

    } else {
        error("Can't get disc state : ".join("\n", @$stat));
        $obj->{usage} = gettext("Unknown disc capacity!");
    }

    # Records einlesen
    my $erg = $obj->{svdrp}->command('lstr');

    if(ref $waiter) {
        $waiter->min(0);
        $waiter->max(scalar @$erg);
        $waiter->screen('yes');
    }

    my @merkIds;
    foreach my $record (@$erg) {
        ++$c;
        $waiter->next($c)
          if(ref $waiter);
        next if($record =~ /^22/);
        my $anahash = $obj->analyze($record);
        if(ref $anahash eq 'HASH') {
            push(@merkIds, $anahash->{eventid});
            if( ! $obj->insert($anahash)) {
                ++$e;
            }
        } else {
            ++$e;
        }
    }

   # last call of waiter
   $waiter->next(scalar @$erg,1)
      if(ref $waiter);

    # Packen des contents am Ende verhindern
    $console->{nopack} = 1 if(ref $waiter);

    # Previews im fork erzeugen
    if(scalar @{$obj->{JOBS}}) {
        defined(my $child = fork()) or return error("Can't fork: $!");
        if($child == 0) {
            $obj->{dbh}->{InactiveDestroy} = 1;

            foreach my $command (@{$obj->{JOBS}}) {
                lg sprintf('Call cmd "%s" now',
                        $command,
                    );
                my $erg = system("nice -19 $command");
            }
            exit 0;
        }
    }

    # alte PreviewDirs loeschen
    foreach my $dir (glob(sprintf('%s/*_shot', $obj->{previewimages}))) {
        my $oldEventNumber = (split('/', $dir))[-1];
        unless(grep(sprintf('%lu_shot',$_) eq $oldEventNumber, @merkIds)) {
            deleteDir($dir);
        }
    }

    # Delete all EpgOld Entrys without the RecordIds
    if(scalar @merkIds) {
        my $sql = sprintf('delete from OLDEPG where eventid not in (%s)', join(',', @merkIds));
        $obj->{dbh}->do($sql);
    }

    if(ref $console) {
        $console->start() if(ref $waiter);
        if($e == 0) {
            $console->message(sprintf(gettext("Write %d recordings in Database."), $c));
        } else {
            $console->err(sprintf(gettext("Write only %d recordings in Database. Can\'t assign %d recordings."), $c , $e));
        }

        $console->redirect({url => '?cmd=rlist', wait => 1})
            if($console->typ eq 'HTML');
        $console->footer() if(ref $waiter);
    }
    return 1;
}

# ------------------
sub refresh {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;

    my $waiter;
    if(ref $console) {
        $waiter = $console->wait(gettext("Get informations from recordings ..."),0,1000,'yes');
        $waiter->next(1);
    }

    return $obj->readData($watcher,$console,$waiter);
}

# ------------------
sub insert {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $attr = shift || return;

    my $sth = $obj->{dbh}->prepare(
    qq|
     REPLACE INTO RECORDS
        (eventid, RecordId, Duration, Start, Path, Prio, Lifetime, State, Marks )
     VALUES (?,?,?,?,?,?,?,?,?)
    |);

    $attr->{Marks} = ""
        if(not $attr->{Marks});

    return $sth->execute(
        $attr->{eventid},
        $attr->{RecordId},
        int($attr->{Duration}),
        $attr->{Start},
        $attr->{Path},
        $attr->{Prio},
        $attr->{Lifetime},
        $attr->{State},
        $attr->{Marks}
    );
}

# ------------------
sub analyze {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $record = shift || return;
    my ($id, $date, $hour, $minute, $state, $duration, $title);
    my ($day,$month,$year);

    if($record =~ /\s+\d+´\s+/) { # VDR is patched with recording length patch
        # 250-1  01.11 15:14* 50´ Discovery~Die Rose von Kerrymore Spielfilm D/2000
        ($id, $date, $hour, $minute, $state, $duration, $title)
            = $record =~ /^250[\-|\s](\d+)\s+([\d|\.]+)\s+(\d+)\:(\d+)(.?)\s*(\d*).*?\s+(.+)/si;
    } else { # Vanilla VDR
        # 250-1  01.11 15:14* Discovery~Die Rose von Kerrymore Spielfilm D/2000
        ($id, $date, $hour, $minute, $state, $title)
            = $record =~ /^250[\-|\s](\d+)\s+([\d|\.]+)\s+(\d+)\:(\d+)(.?).*?\s+(.+)/si;
    }

    lg sprintf('Analyze record "%s" from system',
            $title,
        );

    return error("Can't analyze record : ".$record)
        unless($id);

    # Split date
    if($date =~ /^(\d+)\.(\d+)$/) { # on DD.MM before 1.3.26
        ($day,$month) = $date =~ /^(\d+)\.(\d+)$/;
    } else { # on DD.MM.YY on 1.3.26
        ($day,$month,$year) = $date =~ /^(\d+)\.(\d+)\.(\d+)$/;
    }

    my $info = $obj->videoInfo($day, $month, $hour, $minute, $title)
        or return warn("Can not find recording for Id $id!");

    return unless(ref $info eq 'HASH');

    # Path to Record
    my $Path;
    if(scalar $info->{files} > 0 and $info->{files}->[0] ne "" ) {
        $Path = $info->{files}->[0];
        $Path = dirname($Path)
            if(! -d $Path);
    } else {
      warn ("couldn't found path for recording : $title");
      $Path = '<undef>';
    }

    my @t = split('~', $title);
    my $subtitle;
    if(scalar @t > 1) { # Splitt genre~title | subtitle
        my @p = split('/', $Path);
        $subtitle = delete $t[-1]
            if(scalar @p > 3 && $p[-2] ne '_');
        $subtitle = '' if(defined $subtitle and $subtitle eq ' ');
        $title = join('~',@t);
    }

    $year = ($info->{mtime}->[5] || 2005)
        if(!defined $year);
    $year += 100
        if($year < 70); # Adjust year, 0-69 => 100-169 (2000-2069)
    $year += 1900
        if($year < 1900); # Adjust year, 70-99 => 1977-1999 ... 2000-2069
    my $start = timelocal(0,$minute,$hour,$day,$month-1, $year);
    my $dur = ($duration) ? $duration : (exists $info->{duration}) ? $info->{duration} : 0;
    my $event = $obj->SearchEpgId( $start, $dur, $title, $subtitle );

    # XXXX
    # Sollte kein Event gefunden werden so muss dieser in die EPGOLD mit
    # den vorhandenen Daten (lstr nummer) eingetragen werden und eine PseudoEventId (min(eventid)-1)
    # erfunden werden ;)
    unless($event) {
        $event = $obj->createOldEventId($id, $start, $dur, $title, $subtitle, $info->{summary})
            || return error('Can not create OldEvent!');
    }

    # Make Preview
    my $command = $obj->videoPreview( $event, $info, $dur );
    push(@{$obj->{JOBS}}, $command)
        if($command);

    my $ret = {
        RecordId => $id,
        Duration => $dur,
        Start => $start,
        Path  => $Path,
        Prio  => $info->{Prio},
        Lifetime  => $info->{Lifetime},
        eventid => $event->{eventid},
        State => $state eq '*' ? 1 : 0
    };
    $ret->{Marks} = join(',', @{$info->{marks}})
        if(ref $info->{marks} eq 'ARRAY');
    return $ret;
}

# ------------------
sub videoInfo {
# ------------------
    my $obj     = shift || return error ('No object!' );
    my $day     = shift || return error ('No day!' );
    my $month   = shift || return error ('No month!' );
    my $hour    = shift || return error ('No hour!' );
    my $minute  = shift || return error ('No minute!' );
    my $title   = shift || return error ('No title!' );

    lg sprintf('Get videoInfo from record "%s"',
            $title,
        );

    my @files;
    # Teste beide Moeglichkeiten aus (vfat=$obj->{vfat} und das Gegenteil
    foreach my $vfat ( ($obj->{vfat}, ($obj->{vfat} eq 'y' ? 'n' : 'y') ) ) {
        my $t = $obj->translate($title, $vfat);
        $t =~ s/([\)\(\-\?\+\*\[\]\{\}])/\\$1/g; # Replace regex groupsymbols "),(,-,?,+,*,[,],{,}"
        $t =~ s/([\/])/\./g; # Replace splash
        @files = grep(/\/$t.*?\d{4}\-$month\-$day\.$hour[\:|\.]$minute.+?\d{3}\.vdr/, @{$obj->{FILES}});
        last
            if(scalar @files);
    }

    return 0, warn sprintf('Can\'t assign recording with title: %s', $title)
         unless(scalar @files);

    my $status = {};
    foreach my $file (@files) {
    	#Splitt 2005-01-16.04:35.88.99.rec
    	my ($year, $month, $day, $hour, $minute, $prio, $lifetime)
             = (basename(dirname($file))) =~ /^(\d+)\-(\d+)\-(\d+)\.(\d+)[\:|\.](\d+)\.(\d+)\.(\d+)\.rec/si;
    	if($year && $month && $day && $hour && $minute && $year >= 1970 && $year < 2038 ) {
    		@{$status->{mtime}} = localtime(timelocal(0,int($minute),int($hour),$day,$month-1,$year-1900));
    	} else {
            my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        	    $atime,$mtime,$ctime,$blksize,$blocks) = stat $file;
    		@{$status->{mtime}} = localtime $mtime;
    	}
        $status->{Prio} = $prio;
        $status->{Lifetime} = $lifetime;
        last;
    }

    # Dateigröße von index.vdr für Aufnahmedauer ermitteln
    if($files[0] && -e $files[0]) {
        my $index = sprintf("%s/index.vdr", dirname($files[0]));
        if(-r $index) {
            my $bytes = stat($index)->size;
            $status->{duration} = ($bytes / 8) / 25;
        } else {
            warn "Couldn't read : ", $index;
        }

        # Schnittmarken ermitteln
        my $marks = sprintf("%s/marks.vdr", dirname($files[0]));
        if(-r $marks) {
            my $data = load_file($marks)
                or error("I cannot read file $marks");
            if($data) {
                foreach my $zeile (split("\n", $data)) {
                    # 0:35:07.09 moved from [0:35:13.24 Logo start] by checkBlackFrameOnMark
                    my ($mark, $text) = $zeile =~ /^(\d+\:\d+\:\d+\.\d+)\s+(.+)/sg;
                    push(@{$status->{marks}}, $mark)
                        if(defined $mark);
                }
            }
        }

        # Summary ermitteln
        my $file = sprintf("%s/info.vdr", dirname($files[0]));
           $file = sprintf("%s/summary.vdr", dirname($files[0]) ) unless(-e $file);

        if(-r $file) {
            my $text = load_file($file);

            # Neue Vdr Version 1.3.25!
            if(-s $file and $file =~ /info\.vdr/si) {
                foreach my $zeile (split("\n", $text)) {
                    if($zeile =~ /^D\s+(.+)/) {
                        $text = $1;
                        last;
                    }
                }
            }
            $status->{summary} = $text;
        }
    }


    $status->{files} = \@files;
    return $status;
}

# ------------------
sub videoPreview {
# ------------------
    my $obj     = shift || return error ('No Object!' );
    my $event   = shift || return error ('No EventHash!');
    my $info    = shift || return error ('No InfoHash!');
    my $duration    = shift;

    if ($obj->{previewcommand} eq 'Nothing') {
        return ();
    }

    # Videodir
    my $vdir = dirname($info->{files}->[0]);
    if(! -d $vdir ) {
        error($info->{files}->[0]);
        return ();
    }

    # Save dir
    my $frames = $obj->{previewcount};
    my $outdir = sprintf('%s/%lu_shot', $obj->{previewimages}, $event->{eventid});
    my @files = glob("$outdir/*.jpg");

    if(scalar @files >= $frames) {
        return 0;
    } else {
        unless(-d $outdir) {
            mkpath($outdir) or warn "Can't mkpath $outdir : $!";
        }
    }

    # Mplayer
    return error("I can't find executable file as usable preview command !")
            unless(-x $obj->{previewbinary});

    my $scalex = 180;
    my $startseconds = main::getModule('TIMERS')->{prevminutes} * 60;
    my $stepseconds = 120;
	# reduced interval on short movies
	if($duration && ($startseconds + ($frames * $stepseconds)) > $duration) {
		$stepseconds = $duration / ( $frames + 2 ) ;
		$startseconds = $stepseconds;
	}
    my @steps;
    my $s = $startseconds * 25;
    while (scalar @steps < $frames) {
        push(@steps, $s);
        $s += ( $stepseconds * 25 );
    }

    my $mversions = {
        'MPlayer1.0pre5' => sprintf('%s -noautosub -noconsolecontrols -nosound -nolirc -nojoystick -quiet -vo jpeg -jpeg outdir=\'%s\' -ni -ss %d -sstep %d -vf scale -zoom -xy %d -frames %d \'%s\' >> %s 2>&1',
                                $obj->{previewbinary}, $outdir, $startseconds / 5, $stepseconds / 5, $scalex, $frames, join("\' \'",@{$info->{files}}), $obj->{previewlog}),
        'MPlayer1.0pre6' => sprintf('%s -noautosub -noconsolecontrols -nosound -nolirc -nojoystick -quiet -vo jpeg:outdir=\'%s\' -ni -ss %d -sstep %d -vf scale -zoom -xy %d -frames %d \'%s\' >> %s 2>&1',
                                $obj->{previewbinary}, $outdir, $startseconds / 5, $stepseconds / 5, $scalex, $frames, join("\' \'",@{$info->{files}}), $obj->{previewlog}),
        'vdr2jpeg'       => sprintf("%s -r '%s' -f %s -x %d -o '%s' >> %s 2>&1",
                                $obj->{previewbinary}, $vdir, join(" -f ", @steps), $scalex, $outdir, $obj->{previewlog}),
    };

    return $mversions->{$obj->{previewcommand}};
}



# ------------------
sub SearchEpgId {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $start = shift || return error ('No Start!' );
    my $dur = shift || return;
    my $title = shift || return error ('No Title!' );
    my $subtitle = shift || "";

    my $bis = $start + $dur;
    $title =~ s/\'/\\\'/sg;
    $subtitle =~ s/\'/\\\'/sg if($subtitle);
    $title =~ s/%/\%/sg;
    $subtitle =~ s/%/\%/sg if($subtitle);

    # Search in DB
    my $sql = "SELECT * FROM OLDEPG WHERE UNIX_TIMESTAMP(starttime) BETWEEN '$start' and '$bis' AND title = '$title'";
    $sql .= " AND subtitle = '$subtitle'" if($subtitle);
    my $erg = $obj->{dbh}->selectrow_hashref($sql);
    return $erg
		if($erg->{eventid}
		  and ( # check for equal subtitle
			(not $subtitle and not $erg->{subtitle})
			 or (($subtitle and $erg->{subtitle}) and ($subtitle eq $erg->{subtitle}))
		      )
		  );
}

# ------------------
sub createOldEventId {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $id = shift || return error ('No Id!' );
    my $start = shift || return error ('No Starttime!' );
    my $duration = shift || 0;
    my $title = shift || return error ('No Title!' );
    my $subtitle = shift || "";
    my $summary = shift || "";

    my $attr = {
        title => $title,
        subtitle => $subtitle,
        description => $summary,
        channel => '<undef>',
        duration => $duration,
        starttime => $start,
    };

    $attr->{eventid} = $obj->{dbh}->selectall_arrayref('select max(eventid)+1 from OLDEPG')->[0][0];
    $attr->{eventid} = 1000000000 if(not defined $attr->{eventid} or $attr->{eventid} < 1000000000 );

    # dumper($attr);

    lg sprintf('Create OldEventId from event "%s" - "%s"',
            $title,
            $subtitle,
        );

    my $sth = $obj->{dbh}->prepare('REPLACE INTO OLDEPG(eventid, title, subtitle, description, channel_id, duration, tableid, starttime, addtime) VALUES (?,?,?,?,?,?,?,FROM_UNIXTIME(?),FROM_UNIXTIME(?))');
    $sth->execute(
        $attr->{eventid},
        $attr->{title},
        $attr->{subtitle},
        $attr->{description},
        $attr->{channel},
        int($attr->{duration}),
        $attr->{tableid},
        $attr->{starttime},
        time
    );

    return $attr;
}

# ------------------
sub display {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $recordid = shift || return $console->message(gettext("No RecordID to display the recording! Please use rdisplay 'rid'"));

    my $sql = qq|
select
    RECORDS.RecordId,
    RECORDS.eventid,
    RECORDS.Duration,
    RECORDS.Marks,
    RECORDS.Prio,
    RECORDS.Lifetime,
    DATE_FORMAT(FROM_UNIXTIME(RECORDS.Start), '%d.%m.%Y') as Day,
    DATE_FORMAT(FROM_UNIXTIME(RECORDS.Start), '%H:%i') as StartTime,
    DATE_FORMAT(FROM_UNIXTIME(RECORDS.Start + RECORDS.Duration), '%H:%i') as StopTime,
    OLDEPG.title as Title,
    OLDEPG.subtitle as SubTitle,
    OLDEPG.description as Description,
    RECORDS.State as New
from
    RECORDS,OLDEPG
where
    RECORDS.eventid = OLDEPG.eventid
    and RecordId = '$recordid'
|;

    my $fields = fields($obj->{dbh}, $sql);

    my $erg = $obj->{dbh}->selectrow_hashref($sql);


    my $param = {
        previews => $obj->getPreviewFiles($erg->{eventid}),
        reccmds => $obj->{reccmds},
    };
    $console->table($erg, $param);
}


# ------------------
sub list {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $text    = shift || "";
    my $params  = shift;

    my $deep = 1;
    my $folder = scalar (my @a = split('/',$obj->{videodir})) + 1;

    my $select = "e.eventid = r.eventid";
    if($text) {
        $deep   = scalar (my @c = split('~',$text));
        $folder += $deep;
        $deep += 1;

        $text =~ s/\'/\\\'/sg;
        $text =~ s/%/\\%/sg;
        $select .= qq|
AND (
      SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text'
      OR
      SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) LIKE '$text~%'
)
|;

    }
    my $sql = qq|
SELECT
    r.RecordId as Id,
    r.eventid as __EventId,
    e.title,
    e.subtitle,
    r.duration,
    from_unixtime(r.Start) as __RecordStart,
    SUM(State) as __New,
    COUNT(*) as __Group,
    SUBSTRING_INDEX(CONCAT_WS('~',e.title,e.subtitle), '~', $deep) as __fulltitle,
    IF(COUNT(*)>1,0,1) as __IsRecording
FROM
    RECORDS as r,
    OLDEPG as e
WHERE
    $select
GROUP BY
    SUBSTRING_INDEX(r.Path, '/', IF(Length(e.subtitle)<=0, $folder + 1, $folder))
|;
    my $fields = fields($obj->{dbh}, $sql);

    my $sortby = "__fulltitle";
    $sortby = $params->{sortby}
        if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
    $sql .= "order by __IsRecording asc, $sortby";
    if(exists $params->{desc} && $params->{desc} == 1) {
        $sql .= " desc"; }
    else {
        $sql .= " asc"; }

    my $erg = $obj->{dbh}->selectall_arrayref($sql);
    unshift(@$erg, $fields);

    my $param = {
        sortable => 1,
        usage => $obj->{usage},
        previewcommand => $obj->{previewlistthumbs},
        getPreview => sub{ return $obj->getPreviewFiles(@_) },
    };
    return $console->table($erg, $param);
}

# ------------------
sub search {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $text    = shift || return $obj->list($watcher,$console);
    my $params  = shift;

    my $search = buildsearch("e.title,e.subtitle,e.description",$text);

    my $sql = qq|
SELECT
    r.RecordId as Id,
    r.eventid as __EventId,
    e.title,
    e.subtitle,
    r.duration,
    from_unixtime(r.Start) as __RecordStart,
    r.State as __New
FROM
    RECORDS as r,
    OLDEPG as e
WHERE
    e.eventid = r.eventid
	AND ( $search )
|;

    my $fields = fields($obj->{dbh}, $sql);

    my $sortby = "r.Start";
    $sortby = $params->{sortby}
        if(exists $params->{sortby} && grep(/^$params->{sortby}$/i,@{$fields}));
    $sql .= "order by $sortby";
    if(exists $params->{desc} && $params->{desc} == 1) {
        $sql .= " desc"; }
    else {
        $sql .= " asc"; }

    my $erg = $obj->{dbh}->selectall_arrayref($sql);
    unshift(@$erg, $fields);

    my $param = {
        sortable => 1,
        usage => $obj->{usage},
        previewcommand => $obj->{previewcommand},
        getPreview => sub{ return $obj->getPreviewFiles(@_) },
    };
    return $console->table($erg, $param);
}

# ------------------
sub delete {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $record  = shift || return $console->err(gettext("No Recording ID to delete! Please use rdelete 'id'"));
    my $answer  = shift || 0;

    my @recordings  = reverse sort{ $a <=> $b } split(/[^0-9]/, $record);
    my @todelete;

    my $sql = sprintf("select r.RecordId as Id,e.title,e.subtitle from RECORDS as r,OLDEPG as e where e.eventid = r.eventid and r.RecordId in (%s)", join(',', @recordings));
    my $data = $obj->{dbh}->selectall_hashref($sql, 'Id');

    foreach my $rid (@recordings) {

        unless(exists $data->{$rid}) {
            $console->err(sprintf(gettext("Recording with number '%s' is not exist in database!"), $rid));
            next;
        }

        if(ref $console and $console->{TYP} eq 'CONSOLE') {
            $console->table($data->{$rid});
            my $confirm = $console->confirm({
                typ   => 'confirm',
                def   => 'y',
                msg   => gettext('Are you sure to delete this recording?'),
            }, $answer);
            next if(! $answer eq 'y');
        }

        lg sprintf('Call delete record with title "%s" from User: %s',
            $data->{$rid}->{title},
            $console->{USER}->{Name}
            );


        $obj->{svdrp}->queue_cmds("delr $rid");
        push(@todelete,$rid);
    }

    if($obj->{svdrp}->queue_cmds('COUNT')) {

        $console->msg(sprintf(gettext("Recording with number '%s' to delete ..."),join(',', @todelete)));

        my $erg = $obj->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
        $console->msg($erg, $obj->{svdrp}->err)
            if(ref $console);

        sleep(2);
        $obj->readData($watcher, $console);

    } else {
        $console->err(gettext("No recording to delete!"));
    }

    return 1;
}


sub is_empty_dir {
    my $dir    = shift;
    local (*DIR);
    return unless opendir DIR, $dir;
    while (defined($_ = readdir DIR)) {
        next if /^\.\.?$/;
        closedir DIR;
        return 0;
    }
    closedir DIR;
    return 1;
}

sub touch {
    my $files    = shift;
    my $now = time;
    local (*TMP);
    foreach my $file ($files) {
            lg sprintf('Call touch file "%s"',
                $file,
                );
            utime ($now, $now, $file)
            || open (TMP, ">>$file")
            || warn ("Couldn't touch $file: $!\n");
    }
}

# ------------------
sub redit {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $recordid  = shift || return $console->message(gettext("No RecordID to edit!"));
    my $data    = shift || 0;

    my $rec;
    if($recordid) {
    my $sql = qq|
SELECT
    CONCAT_WS('~',e.title,e.subtitle) as title,
    e.eventid as EventId,
    r.Path,
    r.Prio,
    r.Lifetime
FROM
    RECORDS as r,
    OLDEPG as e
WHERE
    e.eventid = r.eventid
	AND ( r.RecordId = '$recordid' )
|;
        $rec = $obj->{dbh}->selectrow_hashref($sql)
            or return $console->err(sprintf(gettext("RecordID '%s' is not exist in Database!"),$recordid));
    }

    my $summaryfile = sprintf("%s/info.vdr", $rec->{Path});
       $summaryfile = sprintf("%s/summary.vdr", $rec->{Path} )
            unless(-e $summaryfile);

    my $summary = load_file($summaryfile)
        or $console->err(sprintf(gettext("can't open file '%s' : %s"),$summaryfile,$!));

    my $marksfile = sprintf('%s/%s', $rec->{Path}, 'marks.vdr');
    my $marks = (-r $marksfile ? load_file($marksfile) : '');

	$rec->{title} =~s#~+#~#g;
	$rec->{title} =~s#^~##g;
    $rec->{title} =~s#~$##g;

    my $questions = [
        'title' => {
            msg     => gettext('Title of recording'),
            def     => $rec->{title},
            req     => gettext("This is required!"),
        },
        'lifetime' => {
            typ     => 'integer',
            msg     => gettext('Lifetime (0 .. 99)'),
            def     => int($rec->{Lifetime}),
            check   => sub{
                my $value = shift || 0;
                if($value >= 0 and $value < 100) {
                    return int($value);
                } else {
                    return undef, gettext('No right Value!');
                }
            },
            req     => gettext("This is required!"),
         },
        'priority' => {
            typ     => 'integer',
            msg     => gettext('Priority (0 .. 99)'),
            def     => int($rec->{Prio}),
            check   => sub{
                my $value = shift || 0;
                if($value >= 0 and $value < 100) {
                    return int($value);
                } else {
                    return undef, gettext('No right Value!');
                }
            },
            req     => gettext("This is required!"),
        },
        'summary' => {
            msg   => gettext("Summary"),
            def   => $summary || '',
        },
        'marks' => {
            param => {type => 'text'},
            msg   => gettext("Marks"),
            def   => $marks || '',
        },
    ];

    $data = $console->question(gettext("Edit recording"), $questions, $data);

    if(ref $data eq 'HASH') {
        my $touchVDR = 0;
        my $dropEPGEntry = 0;

        lg sprintf('Record "%s" is changed from User: %s',
            $rec->{title},
            $console->{USER}->{Name}
            );

        if($data->{summary} ne $summary) {
            save_file($summaryfile, $data->{summary})
               or return $console->err(sprintf(gettext("can't write file '%s' : %s"),$summaryfile,$!));
            $dropEPGEntry = 1;
        }

        if($data->{marks} ne $marks) {
            save_file($marksfile, $data->{marks})
               or return $console->err(sprintf(gettext("can't write file '%s' : %s"),$marksfile,$!));
        }


        if($data->{lifetime} ne $rec->{Lifetime}
            or $data->{priority} ne $rec->{Prio}) {

            my @options = split('\.', $rec->{Path});

            $options[-2] = sprintf("%02d",$data->{lifetime})
                if($data->{lifetime} ne $rec->{Lifetime});

            $options[-3] = sprintf("%02d",$data->{priority})
                if($data->{priority} ne $rec->{Prio});

            my $newPath = join('.', @options);

            move($rec->{Path}, $newPath)
                 or return $console->err(sprintf(gettext("Recording: '%s', can't move to '%s' : %s"),$rec->{title},$newPath,$!));


            $touchVDR = 1;
        }

	    $data->{title} =~s#~+#~#g;
	    $data->{title} =~s#^~##g;
        $data->{title} =~s#~$##g;

        if($data->{title} ne $rec->{title}) {

            # Rename auf der Platte
            my ($old) = $rec->{Path} =~ /^(.+?)\/\d{4}\-\d{2}\-\d{2}.+?rec/;
            my $newPath = sprintf('%s/%s', $obj->{videodir}, $obj->translate($data->{title}));

            my $parentnew = dirname($newPath);
            unless( -d $parentnew) {
                mkpath($parentnew)
                    or return $console->err(sprintf(gettext("Recording: '%s', can't mkpath: '%s' : %s"),$rec->{title},$parentnew,$!));
            }

            move($old,$newPath)
                    or return $console->err(sprintf(gettext("Recording: '%s', can't move to '%s' : %s"),$rec->{title},$data->{title},$!));

            my $parentold = dirname($old);
            if($obj->{videodir} ne $parentold
                and -d $parentold
                and is_empty_dir($parentold)) {
                rmdir($parentold)
                    or return $console->err(sprintf(gettext("Recording: '%s', can't remove '%s' : %s"),$rec->{title},$parentold,$!));
            }

            $dropEPGEntry = 1;
            $touchVDR = 1;
        }

        if($dropEPGEntry) { # Delete EpgOld Entrys
            my $sql = sprintf("delete from OLDEPG where eventid = '%s'", $rec->{eventid});
            $obj->{dbh}->do($sql);
        }

        if($touchVDR) { #Ab 1.3.11 resync with touch /video/.update
            touch($obj->{videodir}."/.update");
        }

        $obj->readData();
        $console->message(gettext('Recording is edited!'));

        $console->redirect({url => $data->{refe}, wait => 2})
            if($console->typ eq 'HTML');
    }

    return 1;
}

# ------------------
sub conv {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $data = shift || 0;


    unless(scalar @{$obj->{reccmds}}) {
        $console->message(gettext('No reccmds.conf on your System!'));
        return 1;
    }

    unless($data) {
        $console->message(gettext("Please use rconvert 'cmdid_rid'"));
        unshift(@{$obj->{reccmds}}, ['Descr.', 'Command']);
        $console->table($obj->{reccmds});
        $obj->list($watcher, $console);
    }

    my ($cmdid, $recid) = split(/[\s_]/, $data);
    my $cmd = (split(':', $obj->{reccmds}->[$cmdid-1]))[-1] || return $console->message(gettext("I can't find this CommandID"));
    my $path = $obj->IdToPath($recid) || return $console->message(gettext("I can't find this RecordID"));

    lg sprintf('Call command "%s" on record "%s" from User: %s',
        $cmd,
        $path,
        $console->{USER}->{Name}
        );

    my $call = "$cmd \"$path\"";
    my $output = `$call`;
    if( $? >> 8 > 0) {
        $console->message(sprintf(gettext("Sorry! Call %s %s Error output: %s"), $cmd, $path, $output));
    } else {
        $console->message(sprintf(gettext("Call %s %s With output: %s"), $cmd, $path, $output));
    }
    $console->link({
        text => gettext("Back to recordings list"),
        url => "?cmd=rlist",
    }) if($console->typ eq 'HTML');
    return 1;
}

# ------------------
sub status {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;
    my $lastReportTime = shift || 0;

    my $sql = qq|
SELECT
    r.RecordId as __Id,
    r.eventid as __EventId,
    e.title,
    e.subtitle,
    FROM_UNIXTIME(r.Duration,'%h:%i:%s') as Duration,
    from_unixtime(r.Start) as __RecordStart
FROM
    RECORDS as r,
    OLDEPG as e
WHERE
    e.eventid = r.eventid
    and r.Start > $lastReportTime
ORDER BY
    r.Start asc
|;

    my $fields = fields($obj->{dbh}, $sql);
    my $erg = $obj->{dbh}->selectall_arrayref($sql);
    unshift(@$erg, $fields);
    return {
        message => sprintf(gettext('%d new recordings since last report time %s'), (scalar @$erg -1), scalar localtime($lastReportTime)),
        table   => $erg,
    };
}


# ------------------
sub IdToPath {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $id = shift || return undef;

    my $sql = sprintf('select Path from RECORDS where RecordId = "%lu"', $id);
    my $erg = $obj->{dbh}->selectall_arrayref($sql);
    return $erg->[0][0];
}

# ------------------
sub getPreviewFiles {
# ------------------
    my $obj = shift  || return error ('No Object!' );
    my $id = shift || return error ('No EventID!' );

    # look for pictures
    my $outdir = sprintf('%s/%lu_shot', $obj->{previewimages}, $id);
    if(my @previews = glob("$outdir/*.jpg")) {
        splice(@previews,$obj->{previewcount},scalar(@previews));
        map {
            $_ =~ s/^$obj->{previewimages}/previewimages/
        } @previews;
        return \@previews;
    } else {
        return undef;
    }
}


# ------------------
sub translate {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $title = shift || return error ('No Title in translate!');
    my $vfat = shift || $obj->{vfat};

    if($vfat eq 'y')
    {
        $title =~ s/([^üäößa-z0-9\&\!\-\s\.\@\~\,\(\)\%\+])/sprintf('#%X', ord($1))/seig;
        $title =~ s/[^üäößa-z0-9\!\&\-\#\.\@\~\,\(\)\%\+]/_/sig;
    } else {
        $title =~ s/\'/\x01/g;
        $title =~ s/\\/\x02/g;
        $title =~ s/ /_/g;
    }
    $title =~ s/~/\//g;

    return $title;
}

1;
