package XXV::MODULES::TIMERS;

use strict;
use Tools;
use POSIX ":sys_wait_h", qw(strftime mktime);
use Event::File;
use Locale::gettext;

# This module method must exist for XXV
# ------------------
sub module {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $args = {
        Name => 'TIMERS',
        Description => gettext('This module parse the timers.conf and save this in the database.'),
        Version => '0.45',
        Date => '12.09.2005',
        Author => 'xpix',
        Preferences => {
            interval => {
                description => gettext('The interval to parse timers to database in seconds'),
                default     => 300,
                type        => 'integer',
                required    => gettext("This is required!"),
            },
            prevminutes => {
                description => gettext('Buffer time in minutes before the scheduled end of the recorded program.'),
                default     => 5,
                type        => 'integer',
            },
            afterminutes => {
                description => gettext('Buffer time in minutes after the scheduled end of the recorded program.'),
                default     => 5,
                type        => 'integer',
            },
            Priority => {
                description => gettext('Defining the priority of this timer and of recordings created by this timer.'),
                default     => 50,
                type        => 'integer',
            },
            Lifetime => {
                description => gettext('The guaranteed lifetime (in days) of a recording created by this timer'),
                default     => 50,
                type        => 'integer',
            },
            DVBCards => {
                description => gettext('How much DVB cards in your system?'),
                default     => 1,
                type        => 'integer',
            },
            deactive => {
                description => gettext('Delete deactiv timer after his endtime?'),
                default     => 'n',
                type        => 'confirm',
            },
        },
        Commands => {
            tstatus => {
                description => gettext("Status from timers"),
                short       => 'ta',
                callback    => sub{ $obj->status(@_) },
                DenyClass   => 'tlist',
            },
            tlist => {
                description => gettext("List timers 'tid'"),
                short       => 'tl',
                callback    => sub{ $obj->list(@_) },
                DenyClass   => 'tlist',
            },
            tsearch => {
                description => gettext("Search timers 'text'"),
                short       => 'ts',
                callback    => sub{ $obj->search(@_) },
                DenyClass   => 'tlist',
            },
            tupdate => {
                description => gettext("Read timers and write into database"),
                short       => 'tu',
                callback    => sub{ $obj->readData(@_) },
                Level       => 'user',
                DenyClass   => 'tedit',
            },
            tnew => {
                description => gettext("Create timer 'eid'"),
                short       => 'tn',
                callback    => sub{ $obj->newTimer(@_) },
                Level       => 'user',
                DenyClass   => 'tedit',
            },
            tedit => {
                description => gettext("Edit timer 'tid'"),
                short       => 'te',
                callback    => sub{ $obj->editTimer(@_) },
                Level       => 'user',
                DenyClass   => 'tedit',
            },
            tdelete => {
                description => gettext("Delete timer 'tid'"),
                short       => 'td',
                callback    => sub{ $obj->deleteTimer(@_) },
                Level       => 'user',
                DenyClass   => 'tedit',
            },
            ttoggle => {
                description => gettext("Activate/Deactive timer 'tid'"),
                short       => 'tt',
                callback    => sub{ $obj->toggleTimer(@_) },
                Level       => 'user',
                DenyClass   => 'tedit',
            },
        },
    };
    return $args;
}

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

    my $sql = "select count(*) from TIMERS";
    my $erg = $obj->{dbh}->selectall_arrayref($sql);

    $console->message(sprintf('%d Timers exists.', $erg->[0][0]))
        if(ref $console);
}

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

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

    # Interval to read timers 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}, 'TIMERS')) {
        my $fields = fields($obj->{dbh}, 'select * from TIMERS');
        $obj->{dbh}->do('drop table TIMERS')
            unless(scalar @$fields == 16);
    }

    # Look for table or create this table
    my $version = main::getVersion;
    $obj->{dbh}->do(qq|
      CREATE TABLE IF NOT EXISTS TIMERS (
          Id int(16) unsigned NOT NULL,
          Status char(1) default 1,
          ChannelID varchar(100) NOT NULL default '',
          Day varchar(100) default '-------',
          Start int(11),
          Stop int(11),
          Priority int(11),
          Lifetime int(11),
          File varchar(100),
          Summary text default '',
          NextStartTime datetime,
          NextStopTime datetime,
          Collision varchar(100) default '0',
          NextEpgId int(16) unsigned default '0',
          AutotimerId int(16) unsigned default '0',
          addtime timestamp,
          PRIMARY KEY  (Id)
        ) COMMENT = '$version'
    |);

    $obj->{LastRefreshTime} = time;
    $obj->{newTimerFormat} = 0;

    main::after(sub{
        $obj->{svdrp} = main::getModule('SVDRP');
        $obj->readData()
    }, "TIMERS: Read Timers to Database ...");


    return 1;
}

# ------------------
sub saveTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $data = shift || return error('No Data to Save!');
    my $timerid = shift || 0;

    my $status = ($data->{Activ} eq 'y' ? 1 : 0);
       $status |= ($data->{VPS} eq 'y' ? 4 : 0);

    $data->{File} =~ s/:/|/g;
    $data->{File} =~ s/(\r|\n)//sig;

    lg sprintf('Save timer "%s"',
            $data->{File}
        );

    my $erg = $obj->{svdrp}->command(
        sprintf("%s %s:%s:%s:%s:%s:%s:%s:%s:%s",
            $timerid ? "modt $timerid" : "newt",
            $status,
            $data->{ChannelID},
            $data->{Day},
            $data->{Start},
            $data->{Stop},
            int($data->{Priority}),
            int($data->{Lifetime}),
            $data->{File},
            ($data->{Summary} || ''),
        )
    );

    # Save shortly this timer in DB if this only a new timer (at)
    if(! $timerid and my $pos = $erg->[1] =~ /250\s+(\d+)/) {
        $obj->insert([
                $status,
                $data->{ChannelID},
                $data->{Day},
                $data->{Start},
                $data->{Stop},
                int($data->{Priority}),
                int($data->{Lifetime}),
                $data->{File},
                ($data->{Summary} || ''),
                ], $pos);
    }
    return $erg;
}

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


    if($epgid and not ref $epg) {
        $epg = $obj->{dbh}->selectrow_hashref(qq|
SELECT
    eventid,
    channel_id,
    description as Summary,
    CONCAT_WS('~', title, subtitle) as File,
    DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) - ( $obj->{prevminutes} * 60 )), '%d') as Day,
    DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) - ( $obj->{prevminutes} * 60 )), '%H%i') as Start,
    DATE_FORMAT(FROM_UNIXTIME(UNIX_TIMESTAMP(starttime) + duration + ( $obj->{afterminutes} * 60 )), '%H%i') as Stop
FROM
    EPG
WHERE
    eventid = $epgid
|
        ) or return $console->message(sprintf(gettext("Event ID '%s' is not exist in database!"),$epgid));
    }
    if(not ref $epg) {
		my $t = time;
   	    $epg = {
            channel_id => '',
            File => '',
            Summary => '',
            Day => $obj->{newTimerFormat}?my_strftime("%Y-%m-%d",$t):my_strftime("%d",$t),
            Start => my_strftime("%H%M",$t),
            Stop => my_strftime("%H%M",$t)
    	};
    }

    $epg->{Status} = '1'
         if(not defined $epg->{Status});
    $epg->{Priority} = $obj->{Priority}
         if(not defined $epg->{Priority});
    $epg->{Lifetime} = $obj->{Lifetime}
         if(not defined $epg->{Lifetime});

    $obj->editTimer($watcher, $console, 0, $epg);
}

# ------------------
sub editTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $timerid = shift || 0;   # If timerid the edittimer
    my $data    = shift || 0;  # Data for defaults

    my $epg;
    if($timerid and not ref $data) {
        $epg = $obj->{dbh}->selectrow_hashref("select Id, ChannelID as channel_id, File, Summary, Start, Stop, Day, Priority, Lifetime, Status from TIMERS where Id = $timerid")
            or return $console->message(sprintf(gettext("Timer ID '%s' is not exist in database!"),$timerid));
    } elsif (ref $data eq 'HASH') {
        $epg = $data;
    }

    $epg->{Summary} =~ s/(\r|\n)//sig
        if(defined $epg->{Summary});

    my $mod = main::getModule('CHANNELS');

    my $questions = [
        'Id' => {
            typ     => 'hidden',
            def     => $epg->{Id} || 0,
        },
        'Activ' => {
            typ     => 'confirm',
            def     => (defined $epg->{Status} and ($epg->{Status} & 1) ? 'y' : 'n'),
            msg     => gettext('Switch this timer on?'),
        },
        'VPS' => {
            typ     => 'confirm',
            def     => (defined $epg->{Status} and ($epg->{Status} & 4) ? 'y' : 'n'),
            msg     => gettext('VPS for this timer on?'),
        },
        'ChannelID' => {
            typ     => 'list',
            def     => $mod->ChannelToPos($epg->{channel_id}),
            choices => $mod->ChannelArray('Name'),
            msg     => gettext('Which channel should recorded?'),
            req     => gettext("This is required!"),
            check   => sub{
                my $value = shift || return;

                if(my $ch = $mod->PosToChannel($value) || $mod->NameToChannel($value) ) {
                    $epg->{Channel} = $value;
                    return $ch;
                } elsif( ! $mod->NameToChannel($value)) {
                    return undef, sprintf(gettext("This channel '%s' is not exists!"),$value);
                } else {
                   return undef, gettext("This is required!");
                }
            },
        },
        'Day' => {
            typ     => 'string',
            def     => $epg->{Day},
            msg     => gettext("Please enter a day (1 to 31) or the weekday in format 'MDMDFSS'."),
            req     => gettext("This is required!"),
            check   => sub{
                my $value = shift || return;
                if(($value =~ /^\d+$/ and int($value) <= 31 and int($value) > 0)            # 13
                        or ($obj->{newTimerFormat} and $value =~ /^\d{4}\-\d{2}-\d{2}$/sig) # 2005-03-13
                        or $value =~ /^\S{7}\@\d{4}\-\d{2}-\d{2}$/sig                       # MTWTFSS@2005-03-13
                        or $value =~ /^\S{7}\@\d{2}$/sig                                    # MTWTFSS@13
                        or $value =~ /^\S{7}$/) {                                           # MTWTFSS
                    return $value;
                } else {
                    return undef, gettext('No right day or corrupt format!');
                }
            },
        },
        'Start' => {
            typ     => 'string',
            def     => sub{
                my $value = sprintf('%04d',$epg->{Start} || 0 );
                return substr($value, 0, 2) .':'. substr($value, 2, 2);
                },
            msg     => gettext("Starttime in format 'HH:MM'"),
            check   => sub{
                my $value = shift || return;
                my @v = split(':', $value);
                return undef, gettext('No right time!') if(scalar @v != 2);
                $value = sprintf('%02d%02d',$v[0],$v[1]);
                if(int($value) < 2400 and int($value) >= 0) {
                    return sprintf('%04d',$value);
                } else {
                    return undef, gettext('No right time!');
                }
            },
        },
        'Stop' => {
            typ     => 'string',
            def     => sub{
                my $value = sprintf('%04d',$epg->{Stop} || 0 );
                return substr($value, 0, 2) .':'. substr($value, 2, 2);
                },
            msg     => gettext("Endtime in format 'HH:MM'"),
            check   => sub{
                my $value = shift || return;
                my @v = split(':', $value);
                return undef, gettext('No right time!') if(scalar @v != 2);
                $value = sprintf('%02d%02d',$v[0],$v[1]);
                if(int($value) < 2400 and int($value) >= 0) {
                    return sprintf('%04d',$value);
                } else {
                    return undef, gettext('No right time!');
                }
            },
        },
        'Priority' => {
            typ     => 'integer',
            msg     => sprintf(gettext('Priority (0 .. %d)'),$console->{USER}->{MaxPriority} ? $console->{USER}->{MaxPriority} : 99 ),
            def     => int($epg->{Priority}),
            check   => sub{
                my $value = shift || 0;
                if($value >= 0 and $value < 100) {
                    if($console->{USER}->{MaxPriority} and $value > $console->{USER}->{MaxPriority}) {
                        return undef, sprintf(gettext('Sorry, but maximum priority is limited on %d!'), $console->{USER}->{MaxPriority});
                    }
                    return int($value);
                } else {
                    return undef, gettext('No right Value!');
                }
            },
        },
        'Lifetime' => {
            typ     => 'integer',
            msg     => sprintf(gettext('Lifetime (0 .. %d)'),$console->{USER}->{MaxLifeTime} ? $console->{USER}->{MaxLifeTime} : 99 ),
            def     => int($epg->{Lifetime}),
            check   => sub{
                my $value = shift || 0;
                if($value >= 0 and $value < 100) {
                    if($console->{USER}->{MaxLifeTime} and $value > $console->{USER}->{MaxLifeTime}) {
                        return undef, sprintf(gettext('Sorry, but maximum lifetime is limited on %d!'), $console->{USER}->{MaxLifeTime});
                    }
                    return int($value);
                } else {
                    return undef, gettext('No right Value!');
                }
            },
        },
        'File' => {
            msg     => gettext('Title of recording'),
            def     => $epg->{File},
            req     => gettext("This is required!"),
        },
        'Summary' => {
            msg   =>  gettext('Additional description'),
            def   => $epg->{Summary},
            check   => sub{
                my $value = shift || return;
                $value =~ s/(\r|\n)//sig;
                return $value;
            },
        },
    ];

    # Ask Questions
    my $datasave = $console->question(($timerid ? gettext('Edit timer')
                                                : gettext('New timer')), $questions, $data);

    if(ref $datasave eq 'HASH') {
        my $erg = $obj->saveTimer($datasave, $timerid);
        lg sprintf('%s timer with title "%s" is saved from User: %s',
            ($timerid ? 'New' : 'Changed'),
            $data->{File},
            $console->{USER}->{Name}
            );
        $obj->readData();

        $console->message($erg);
        $console->redirect({url => $console->{browser}->{Referer}, wait => 2})
            if($console->typ eq 'HTML');
    }
}

# ------------------
sub deleteTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;
    my $timerid = shift || return $console->err(gettext("No Timer ID to delete! Please use tdelete 'tid'"));   # If timerid the edittimer
    my $answer  = shift || 0;

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

    my $sql = sprintf("select * from TIMERS where Id in (%s)", join(',', @timers));
    my $data = $obj->{dbh}->selectall_hashref($sql, 'Id');

    my $mod = main::getModule('CHANNELS') or return;
    foreach my $tid (@timers) {
        unless(exists $data->{$tid}) {
            $console->err(sprintf(gettext("Timer with number '%s' is not exist in database!"), $tid));
            next;
        }

        $data->{$tid}->{Channel} = $mod->ChannelToName($data->{ChannelID});

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

        lg sprintf('Delete timer with title "%s" from User: %s',
            $data->{$tid}->{File},
            $console->{USER}->{Name}
            );

        $obj->{svdrp}->queue_cmds("delt $tid"); # Sammeln der Kommandos
    }

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

        sleep(1);

        $obj->readData();

        $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
            if(ref $console and $console->typ eq 'HTML');
    } else {
        $console->err(gettext("No timer to delete!"));
    }

    return 1;
}

# ------------------
sub toggleTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $timerid = shift || return $console->err(gettext("No Timer ID to toggle! Please use ttoggle 'id'"));   # If timerid the edittimer

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

    my $sql = sprintf("select * from TIMERS where Id in (%s)", join(',', @timers));
    my $data = $obj->{dbh}->selectall_hashref($sql, 'Id');

    for my $timer (@timers) {

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

    	my $status = (($data->{$timer}->{Status} & 1) ? 'off' : 'on');

        lg sprintf('Timer with title "%s" is %s from User: %s',
            $data->{$timer}->{File},
            ($status eq 'on' ? 'activated' : 'deactivated'),
            $data->{File},
            $console->{USER}->{Name}
            );

        $obj->{svdrp}->queue_cmds("modt $data->{$timer}->{Id} $status"); # Sammeln der Kommandos
    }

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

        my $erg = $obj->{svdrp}->queue_cmds("CALL"); # Aufrufen der Kommandos
        $console->msg($erg, $obj->{svdrp}->err)
            if(ref $console);
        $obj->readData($watcher, $console);
        $console->redirect({url => $console->{browser}->{Referer}, wait => 1})
            if(ref $console and $console->typ eq 'HTML');
        return 1;
    } else {
        $console->err(gettext('No timer to toggle!'));
        return undef;
    }
}


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

    # import only status which used from vdr and thereby exclude eventid from vdradmin
    $data->[0] &= 15;

    # change pos to channelid, because change to telnet reader
    $data->[1] = $obj->{channels}->{$data->[1]}->{Id}
        if(int($data->[1]));

    # POS
    unshift(@$data, $pos);
    $data->[8] =~ s/\|/\:/g;

    # NextTime
    my $nexttime = $obj->getNextTime( $data->[3], $data->[4], $data->[5] )
        or return error(sprintf("Can not get the nextime for this data: %s", join(' ', @$data)));
    push(@$data, $nexttime->{start}, $nexttime->{stop});

    # AutotimerId
    my $atxt = (split('~', $data->[9]))[-1];
    my $aid = $1 if(defined $atxt and $atxt =~ /AT\[(\d+)\]/);
    push(@$data, $aid || 0);

    my $sth = $obj->{dbh}->prepare('REPLACE INTO TIMERS VALUES (?,?,?,?,?,?,?,?,?,?,FROM_UNIXTIME(?), FROM_UNIXTIME(?),0,0,?,NOW())');
    $sth->execute( @$data );
}


# Read from svdrp (better for future development)
# ------------------
sub readData {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;

    # Search for old and deactivated Timers and delete this
    $obj->getOldDeactivTimer()
        if($obj->{deactive} eq 'y');

    # Search for correct times
    $obj->getCheckTimer();

    $obj->{dbh}->do('DELETE FROM TIMERS');

    # read from svdrp, because the
    # vdr edit the timers.conf to lazy ;)
    $obj->{channels} = main::getModule('CHANNELS')->ChannelHash('POS');
    my $tlist = $obj->{svdrp}->command('lstt');

    my $c = 0;
    foreach my $line (@$tlist) {
        next if(! $line or $line =~ /^22/);
        $line =~ s/^\d+[- ]+\d+\s//sig;
        $c++;
        my @data = split(':', $line, 9);

        $obj->insert(\@data, $c)
            if(scalar @data > 2);
    }

    # Search for overlapping Timers
    my $overlapping = $obj->getOverlappingTimer();

    # Search for next EpgId
    $obj->getNextEpgId();

    $console->message(sprintf(gettext("Write %d timers in database."), $c), {overlapping => $overlapping})
        if(ref $console);

    $obj->{LastRefreshTime} = time;

    return 1;
}

# ------------------
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 $search = buildsearch("t.File,t.Summary",$text);
    $obj->readData;

    my $sql = qq|
SELECT
    t.Id,
    t.Status,
    c.Name as Channel,
    c.Pos as __Pos,
    t.Day as Prg,
    t.Start,
    t.Stop,
    t.File,
    t.Priority,
    UNIX_TIMESTAMP(t.NextStartTime) as __Day,
    t.Collision as __Collision,
    t.NextEpgId as __NextEpgId
FROM
    TIMERS as t,
    CHANNELS as c
WHERE
    t.ChannelID = c.Id
    and ( $search )
ORDER BY
    t.NextStartTime|;

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

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

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

    my $sql = qq|
SELECT
    t.Id,
    t.Status,
    c.Name as Channel,
    c.Pos as __Pos,
    t.Day as Prg,
    t.Start,
    t.Stop,
    t.File,
    t.Priority,
    UNIX_TIMESTAMP(t.NextStartTime) as __Day,
    t.Collision as __Collision,
    t.NextEpgId as __NextEpgId,
    t.AutotimerId as __AutotimerId
FROM
    TIMERS as t,
    CHANNELS as c
WHERE
    t.ChannelID = c.Id
    and t.Id like '%$id%'
ORDER BY
    t.NextStartTime|;

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

    my $erg = $obj->{dbh}->selectall_arrayref($sql);
    unshift(@$erg, $fields);
    $console->table($erg, {
        runningTimer => $obj->getRunningTimer,
        cards => $obj->{DVBCards},
    });
}

# ------------------
sub getRunningTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $sql = "select * from TIMERS where NOW() between NextStartTime and NextStopTime AND (Status & 1) ";
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');
    return $erg;
}

# ------------------
sub getOldDeactivTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $sql = "select Id from TIMERS where not (Status & 1) and UNIX_TIMESTAMP(NextStopTime) > UNIX_TIMESTAMP() + (60*60*24*28)";
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');

    foreach my $t (reverse sort {$a <=> $b} keys %$erg) {
        $obj->{svdrp}->queue_cmds("delt $t");
    }
    $obj->{svdrp}->queue_cmds("CALL")
        if($obj->{svdrp}->queue_cmds("COUNT"));
    return $erg;
}

# ------------------
sub getCheckTimer {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $sql = "select * from TIMERS as t, EPG as e where (Status & 1) and NextEpgId > 0 and t.NextEpgId = e.eventid and not e.starttime between t.NextStartTime and t.NextStopTime";
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');

    foreach my $t (keys %$erg) {
        my $timer = $erg->{$t}->{Id};
        $erg->{Activ} = (($erg->{$t}->{Status} & 1) ? 'y' : 'n');
        $erg->{VPS} =   (($erg->{$t}->{Status} & 4) ? 'y' : 'n');
        $obj->saveTimer($erg, $timer);
    }
    return $erg;
}

# ------------------
sub getEpgIds {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $sql = "select Id, NextEpgId from TIMERS where NextEpgId > 0 and (Status & 1)";
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'NextEpgId');
    return $erg;
}

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

    return unless(tableExists($obj->{dbh}, 'CHANNELS'));

    my $sql = qq|
select
    TIMERS.Id,
    TIMERS.Priority,
    TIMERS.NextStartTime,
    TIMERS.NextStopTime,
    CHANNELS.TID as transponderid,
    LEFT(CHANNELS.Source,1) as source
from TIMERS,
    CHANNELS
where TIMERS.ChannelID = CHANNELS.Id
|;
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');
    my $return;

    foreach my $tid (keys %$erg) {
        my $result = $obj->checkOverlapping($erg->{$tid});
        if(ref $result eq 'ARRAY' and scalar @$result) {
            $obj->{dbh}->do(sprintf('UPDATE TIMERS SET Collision = "%s" WHERE Id = %lu', join(',',@$result), $tid));
            $return->{"timer_$tid"} = join(',',@$result);
        }


    }
    return $return;
}

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

    my $NextStartTime =  $data->{NextStartTime};
    my $NextStopTime  =  $data->{NextStopTime};
    my $transponder   =  $data->{transponderid};
    my $source        =  $data->{source};
    my $Priority      =  $data->{Priority} || $obj->{Priority};
    my $tid           =  $data->{Id};

        my $sql = sprintf(
qq|
select
    t.Id,
    t.Priority,
    c.TID
from
    TIMERS as t, CHANNELS as c
where
    (('%s' between t.NextStartTime
        and t.NextStopTime)
        OR ('%s' between t.NextStartTime
        and t.NextStopTime)
    OR (t.NextStartTime between '%s'
        and '%s')
        OR (t.NextStopTime between '%s'
        and '%s'))
    and
        t.Id != %lu
    and
        (t.Status & 1)
    and
        t.ChannelID = c.Id
    and
        c.TID != %lu
    and
        LEFT(c.Source,1) = '%s'
    ORDER BY
        t.Priority desc
|,
    $NextStartTime,
    $NextStopTime,
    $NextStartTime,
    $NextStopTime,
    $NextStartTime,
    $NextStopTime,
    $tid,
    $transponder,
    $source
    );

    my $result = $obj->{dbh}->selectall_arrayref($sql);

    if(scalar @{$result}) {
            my $coltext = [];
            foreach my $probant (@{$result}) {

               if(defined $probant->[0]) {

               # current timer has higher Priority
               last
                  if($Priority > $probant->[1]);


               # Store conflict line at line
               my $col = sprintf('%d:%d',
                                    $probant->[0],
                                    $probant->[1]);

               # insert double transponder, on same line
               my $n = 0;
               foreach my $trans (@{$result}) {

                    if(defined $trans->[0]
                         && $probant->[0] != $trans->[0]
                         && $probant->[2] == $trans->[2]) {

                        $col .= sprintf('|%d:%d',
                                    $trans->[0],
                                    $trans->[1]);

                       undef @{$result}[$n]->[0];
                    }
                    ++$n;
               }
               # Add line
               push(@$coltext,$col);
           }
        }
        if(scalar(@$coltext) > $obj->{DVBCards} - 1) {
            return $coltext;
        }
    }
}


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

    return unless(tableExists($obj->{dbh}, 'EPG'));

    my $sql = 'select * from TIMERS';
    my $erg = $obj->{dbh}->selectall_hashref($sql, 'Id');

    foreach my $tid (keys %$erg) {
        my $title = $erg->{$tid}->{File};
           $title =~ s/\'/\\\'/sg;
        my @file = split('~', $title);

        my $sql;
        if(scalar @file >= 2) { # title and subtitle defined
            $sql = sprintf(qq|
                select * from EPG
                WHERE
                    channel_id = '%s' AND
                    starttime between '%s' AND '%s' AND
                    (title like '%%%s%%' or title like '%%%s%%')
                |,
                $erg->{$tid}->{ChannelID},
                $erg->{$tid}->{NextStartTime},
                $erg->{$tid}->{NextStopTime},
                $file[-1],
                $file[-2]
            );
        } else {
                $sql = sprintf(qq|
                select * from EPG
                WHERE
                    channel_id = '%s' AND
                    starttime between '%s' AND '%s' AND
                    (title like '%%%s%%')
                |,
                $erg->{$tid}->{ChannelID},
                $erg->{$tid}->{NextStartTime},
                $erg->{$tid}->{NextStopTime},
                $title
            );
        }

        my $e = $obj->{dbh}->selectrow_hashref($sql);
        if(exists $e->{eventid}) {
            $obj->{dbh}->do(sprintf('UPDATE TIMERS SET NextEpgId = %lu WHERE Id = %lu', $e->{eventid}, $tid));
        }
    }
}

# The following subroutines is stolen from vdradmind and vdradmin-0.97-am
# Thanks on Cooper and Thomas for this great work!
# $obj->getNextTime('MDMDFSS', 1300, 1200)
# ------------------
sub getNextTime {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $dor = shift || return error ('No Day!' );
    my $start = shift || return error ('No Starttime!' );
    my $stop =  shift || return error ('No Stoptime!' );

    $start = sprintf('%04d', $start);
    $stop = sprintf('%04d', $stop);

    my ($startsse, $stopsse);
    if(length($dor) == 7) { # repeating timer => MTWTFSS
        $startsse = my_mktime(substr($start, 2, 2), substr($start, 0, 2),
                       my_strftime("%d"), (my_strftime("%m") - 1), my_strftime("%Y"));
        $stopsse  = my_mktime(substr($stop, 2, 2), substr($stop, 0, 2),
                       my_strftime("%d"), (my_strftime("%m") - 1), my_strftime("%Y"));
        $stopsse += 86400 if($stopsse < $startsse);

        my $weekday = ((localtime(time))[6] + 6) % 7;
        my $perrec = join("", substr($dor, $weekday), substr($dor, 0, $weekday));
        $perrec =~ m/^-+/g;

        my $off = (pos $perrec || 0) * 86400;
        if($off == 0 && $stopsse < time) {
            #$weekday = ($weekday + 1) % 7;
            $perrec = join("", substr($dor, ($weekday + 1) % 7), substr($dor, 0, ($weekday + 1) % 7));
            $perrec =~ m/^-+/g;
            $off = ((pos $perrec || 0) + 1) * 86400;
        }
        $startsse += $off;
        $stopsse += $off;
    } elsif(length($dor) == 18) { # first-day timer => MTWTFSS@2005-03-13
        $dor =~ /.{7}\@(\d{4})-(\d{2})-(\d{2})/;
        $startsse = my_mktime(substr($start, 2, 2),
                substr($start, 0, 2), $3, ($2 - 1), $1);
        # 31 + 1 = ??
        $stopsse = my_mktime(substr($stop, 2, 2),
                substr($stop, 0, 2), $stop > $start ? $3 : $3 + 1,
                ($2 - 1), $1);
    } else { # regular timer
      if ($dor =~ /(\d{4})-(\d{2})-(\d{2})/) { # vdr >= 1.3.23 => 2005-03-13
        $startsse = my_mktime(substr($start, 2, 2),
                              substr($start, 0, 2), $3, ($2 - 1), $1);

        $stopsse = my_mktime(substr($stop, 2, 2),
                             substr($stop, 0, 2), $stop > $start ? $3 : $3 + 1, ($2 - 1), $1);
        $obj->{newTimerFormat} = 1;
      }
      else { # vdr < 1.3.23 => 13
        $startsse = my_mktime(substr($start, 2, 2),
                substr($start, 0, 2), $dor, (my_strftime("%m") - 1),
                my_strftime("%Y"));

        $stopsse = my_mktime(substr($stop, 2, 2),
                substr($stop, 0, 2), $stop > $start ? $dor : $dor + 1,
                (my_strftime("%m") - 1), my_strftime("%Y"));

        # move timers which have expired one month into the future
        if(length($dor) != 7 && $stopsse < time) {
          $startsse = my_mktime(substr($start, 2, 2),
                                substr($start, 0, 2), $dor, (my_strftime("%m") % 12),
                                (my_strftime("%Y") + (my_strftime("%m") == 12 ? 1 : 0)));

          $stopsse = my_mktime(substr($stop, 2, 2),
                               substr($stop, 0, 2), $stop > $start ? $dor : $dor + 1,
                               (my_strftime("%m") % 12),
                               (my_strftime("%Y") + (my_strftime("%m") == 12 ? 1 : 0)));
        }
      }
    }

    my $ret = {
        start => $startsse,
        stop => $stopsse,
    };
    return $ret;
}

sub my_mktime {
    my $sec  = 0;
    my $min = shift;
    my $hour = shift;
    my $mday = shift;
    my $mon  = shift;
    my $year = shift() - 1900;

    return mktime($sec, $min, $hour, $mday, $mon, $year, 0, 0, -1);
}

sub my_strftime {
    my $format = shift;
    my $time = shift || time;
    return(strftime($format, localtime($time)));
}


1;
