#!/usr/bin/perl -w

use POSIX qw(strftime);
use lib '/etc/lilalo';
use l3config;
use utf8;
use DBI;
#no warnings;

our @Fields=qw/id time prompt cline output err last_command tab diff l3cd/;
our $Fields=join(", ",@Fields);
our $db;

=cut
Множество команд, с которыми выполняется операция,
определяется текущим контекстом и текущей строкой,
а также параметрами:
    * фильтр;
    * регулярное выражение;
    * интервал.
=cut 
our $Filter;
our $Grep;
our $Interval;
our $Command;

# Если мы используем режим -f, то в этой переменной запоминается
# до какой точки мы дошли
our $Follow=0;

our $Context;

our @Commands = qw/head tail cat history/;

sub main();
sub connect_database();

main();

sub main()
{
    $| = 1;

    init_config_without_command_line_processing();
    $Context=$Config{l3cd};
    connect_database();
# Обработка всех grep'ов
    $Grep="";
    my $i=0;
    my $arg;
    my @argv=();
    while ($i<=$#ARGV) {
        $arg=$ARGV[$i];
        #print "arg=$arg\n";
        if ($arg eq "grep") {
            my $grep_keys="";
            my $grep_regexp="";
            $i++;
            while(defined ($ARGV[$i]) and $ARGV[$i] =~ /^-/) {
                $grep_keys .= " ".$ARGV[$i];
                $i++;
            }
            if (defined($ARGV[$i])) {
                $grep_regexp = $ARGV[$i];
                $i++;
            }
            else {
                die "ERROR: grep argument is not specified";
            }
            $Grep .= grep_options($grep_regexp,$grep_keys);
        }
        else {
            $i++;
            push(@new_argv,$arg);
        }
    }
    @argv=@new_argv;

    if (grep(/^-f$/, @argv)) {
        $follow=1;
    }
    if (grep(/^history$/, @argv)) {
        my $interval=$argv[0]||"%";
        $interval="%" if $interval eq "history";
        my $exit=0;
        while (not $exit) {
            print_commands(select_interval($interval, $Grep));
            if (not $follow) {
                $exit=1;
            } else {
                sleep(1);
            }
        }
    } else {
        my $exit=0;
        while (not $exit) {
            print_all(select_interval($argv[0]||"%", $Grep));
            if (not $follow) {
                $exit=1;
            } else {
                sleep(1);
            }
        }
    }
    #print_all(select_by_cline("*privet*"));
}

sub connect_database()
{
    $db = DBI->connect("dbi:SQLite:".$Config{cache_sqlite}, "", "",
                {RaiseError => 1, AutoCommit => 1});
    $db->func('regexp', 2, sub {
            my ($regex, $string) = @_;
                return $string =~ /$regex/;
    }, 'create_function');
}

sub select_all() {
    $l3cd=$Config{l3cd};
    return $db->selectall_arrayref("SELECT $Fields FROM commands WHERE l3cd='$l3cd'");
}

sub select_last_n($) {
    my $last=$_[0];
    $count = $db->selectall_arrayref("SELECT COUNT(*) FROM commands");
    my $offset=$$count[0][0]-$last;
    return $db->selectall_arrayref("SELECT $Fields FROM commands LIMIT $last OFFSET $offset");
}

sub select_interval($) {
    my $interval = shift;
    my $grep = shift;

    my $q;  # Рабочая переменная для запросов
# %
    $interval='1,$' if ($interval eq "%");
#        return $db->selectall_arrayref(
#                "SELECT $Fields FROM commands WHERE l3cd='$Context'");

# Вычисляем номера начальной и конечной строки
# на основе интервала
    my ($start,$stop);
    my ($start_n, $stop_n, $curr_n) = ("","",""); # номера начальной и конечной строк
    if ($interval =~ /,/) {
        ($start,$stop) = split(/,/,$interval);
    }
    else {
        $start = $interval;
        $stop = $interval;
    }

    $q = $db->selectall_arrayref("SELECT COUNT(*) FROM commands");
    my $count = $$q[0][0];

# Номер текущей строки
# По умолчанию текущая строка указывает на последнюю
    $curr_n = $count;
    #$curr_n = 2;

    if ($start =~ /^[0-9]*$/) { $start_n = $start; }
    if ($stop =~ /^[0-9]*$/)  { $stop_n  = $stop; }
    if ($start =~ /^-[0-9]*$/) { $start_n = $curr_n + $start; }
    if ($stop =~ /^-[0-9]*$/)  { $stop_n  = $curr_n + $stop; }
    if ($start =~ /^\+[0-9]*$/) { $start_n = $curr_n + $start; }
    if ($stop =~ /^\+[0-9]*$/)  { $stop_n  = $curr_n + $stop; }
    if ($start eq '.') { $start_n = $curr_n; }
    if ($stop eq '.')  { $stop_n  = $curr_n; }
    if ($start eq '$') { $start_n = $count; }
    if ($stop eq '$')  { $stop_n  = $count; }
    if ($start =~ m@^/(.*)/@ ) {
        $q = $db->selectall_arrayref(
                "SELECT id FROM commands WHERE cline REGEXP '$1' LIMIT 1");
        $start_n=$$q[0][0];
    }
    if ($stop =~ m@^/(.*)/@ ) {
        $q = $db->selectall_arrayref(
                "SELECT id FROM commands 
                 WHERE cline REGEXP '$1' 
                        AND (id >= $start_n) LIMIT 1");
        $stop_n=$$q[0][0];
    }
    if ($start =~ m@^o/(.*)/@ ) {
        $q = $db->selectall_arrayref(
                "SELECT id FROM commands WHERE output REGEXP '$1' LIMIT 1");
        $start_n=$$q[0][0];
    }
    if ($stop =~ m@^o/(.*)/@ && $start_n) {
        $q = $db->selectall_arrayref(
                "SELECT id FROM commands 
                 WHERE output REGEXP '$1' AND (id >= $start_n) LIMIT 1");
        $stop_n=$$q[0][0];
    }

    if (not $start_n or not $stop_n) {return ""};

    $stop_n = $count if $count < $stop_n;
    $start_n =1 if 1 > $start_n;

    my $offset=$start_n-1;
    my $limit = $stop_n - $offset;

    my $pre_follow=$Follow;
    $q = $db->selectall_arrayref(
            "SELECT id FROM commands 
            WHERE ((id>=$start_n AND id<=$stop_n AND id>$pre_follow) $grep)
            ORDER BY id DESC");
    $Follow = $$q[0][0] if $$q[0][0];
    return $db->selectall_arrayref(
            "SELECT $Fields FROM commands 
            WHERE ((id>=$start_n AND id<=$stop_n AND id>$pre_follow) $grep)");
}

sub grep_options($$) {
=cut
    -c ищем только в комадной строке    # по умолчанию
    -o ищем только в выводе
    -f ищем и там, и там
    -v инверсия
=cut
    my $regexp=shift; return "" if $regexp eq '';
    my $options=shift;
    my @options = split //, $options;
    my $not = "";
    $not = "NOT" if grep(/v/,@options);

    my $grep ="";
    if (grep(/f/,@options)) {
        $grep = "AND $not ((cline REGEXP '$regexp') OR (output REGEXP '$regexp'))";
    }
    elsif (grep(/o/,@options)) {
        $grep = "AND $not (output REGEXP '$regexp')";
    }
    elsif (1 || grep(/c/,@options)) {
        $grep = "AND $not (cline REGEXP '$regexp')";
    }

    return $grep;
}

sub select_by_cline($) {
    my $cline = $_[0];
    return $db->selectall_arrayref("SELECT $Fields FROM commands WHERE cline GLOB '$cline'");
}

sub print_commands($)
{
    my @fields=@Fields;
    my $fields=join(", ",@fields);
    my $all=$_[0];
    foreach my $row (@$all) {
        my $cl;       #Каждая строка
        my $i=0;
        for my $f (@fields) {
            $cl->{$fields[$i]}=$$row[$i];
            $i++;
        }
        print $cl->{id}." ".strdequot($cl->{cline})."\n";
    }
}
sub print_all($) {
    my @fields=@Fields;
    my $fields=join(", ",@fields);
    my $all=$_[0];
    my $prev_cl; # Предыдущая строка
    my $tab_seq; # Номер в табуляционной последовательности
    foreach my $row (@$all) {
        my $cl;       #Каждая строка
        my @anchor;   #Начальная строка, якорь, по которому привязывается команда
        my $res="";   #Буфер, в который выводится результат вывода каждой строки
        $i=0;
        for my $f (@fields) {
            $cl->{$fields[$i]}=$$row[$i];
            $i++;
        }
        next    if ($Config{"skip_empty"} =~ /^y/i     && $cl->{"cline"} =~ /^\s*$/ )
                || ($Config{"skip_wrong"} =~ /^y/i     && $cl->{"err"} != 0)
                || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);

        # Время
        # Добавляем спереди 0 для удобочитаемости
        my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time});
        $min  = "0".$min  if $min  =~ /^.$/;
        $hour = "0".$hour if $hour =~ /^.$/;
        $sec  = "0".$sec  if $sec  =~ /^.$/;

        if ($Config{"show_time"} =~ /^y/i) {
            push @anchor,"$hour:$min:$sec ";
        }

        if ($cl->{"err"}) {
            push @anchor, "err=".$cl->{'err'};
        }
        if ($cl->{"tab"}) {
            push @anchor, "tab=".$cl->{'tab'};
        }
        $res.="#=".join("",@anchor)."\n";
        $res.=$cl->{prompt}.strdequot($cl->{cline})."\n";

        my $output = ""; # фомируем вывод команды
        my $last_command = $cl->{"last_command"};
        if (!( $Config{"suppress_editors"} =~ /^y/i 
               && grep ($_ eq $last_command, @{$Config{"editors"}}) 
            || $Config{"suppress_pagers"}  =~ /^y/i 
               && grep ($_ eq $last_command, @{$Config{"pagers"}})
            || $Config{"suppress_terminal"}=~ /^y/i 
               && grep ($_ eq $last_command, @{$Config{"terminal"}})
            )) {
            $output = strdequot($cl->{output});

            # Вырезаем слишком длинные выводы
            my @lines = split '\n', $output;
            if (!$Config{"command_id"} 
                && ( $Config{"head_lines"} || $Config{"tail_lines"})
                && $#lines >  $Config{"head_lines"} + $Config{"tail_lines"}) {
                    $output="";
                    for (my $i=0; $i<= $#lines && $i < $Config{"head_lines"}; $i++) {
                        $output .= $lines[$i]."\n";
                    }
                    $output .= $Config{"skip_text"}."\n";

                    my $start_line=$#lines-$Config{"tail_lines"}+1;
                    for (my $i=$start_line; $i<= $#lines; $i++) {
                        $output .= $lines[$i]."\n";
                    }
                } 
            }
        $res.=$output;
        if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"}) {
            $res.= $cl->{"diff"}
        }
        #open(OUTPUT, '|sendxmpp nata_samoylenko@jabber.ru');
        #print OUTPUT $res;
        #close(OUTPUT);
        print $res;
        $res="";
        $prev_cl=$cl;
    }
}

sub strdequot($)
{
    my $text = join "", @_;
    $text =~ s/&amp;/&/g;
    $text =~ s/&lt;/</g;
    $text =~ s/&gt;/>/g;
    $text =~ s/&quot;/"/g;
    return $text;
}

sub show_usage()
{
    print <<EOF;
Usage:
    l3 [INTERVAL] [grep [KEYS] REGEXP] [filter [KEYS] FILTER] [COMMAND [KEYS]]

Commands:
    history
    tail
    head
    cat (default)

    If COMMAND is not specified, cat assumed.

EOF
}