package tests::DlfConverterProcessTest;

use strict;

use base qw/ tests::DlfConverterProcessFixture /;

use Lire::DlfConverterProcess;
use Lire::Utils qw/tempfile/;
use Lire::I18N qw/set_fh_encoding/;

use Class::Inner;

my %schemas =
(
 'test1' => <<EOF,
<lire:dlf-schema superservice="test1" timestamp="time"
 xmlns:lire="http://www.logreport.org/LDSML/">
<lire:field name="time" type="timestamp"/>
<lire:field name="code" type="string"/>
<lire:field name="message" type="string"/>
</lire:dlf-schema>
EOF
 'test2' => <<EOF,
<lire:dlf-schema superservice="test2" timestamp="time_start"
 xmlns:lire="http://www.logreport.org/LDSML/">
<lire:field name="time_start" type="timestamp"/>
<lire:field name="file" type="filename"/>
<lire:field name="result" type="string"/>
</lire:dlf-schema>
EOF
);

my %logs =
  (
   'test_line' =>
   {
    'log' => <<EOF,
schema=test1;time=90000;code=500;message=This is a long message
schema=test1;
schema=test2;time_start=1023456;file=/var/tmp;result=OK
ignore=1;key=another line to be ignored
schema=test1;time=
schema=test1;time=100000;message=Another message
ignore=1;key=this line will be ignored
EOF
    'error_count' => 1,
    'line_count' => 7,
    'ignored_count' => 2,
    'saved_count' => 0,
    'dlf_count' => 4,
    'dlf' =>
    {
     'test1' =>
     [
      {
       'dlf_id'  => 1,
       'dlf_source' => 'myjob-20040311',
       'time'    => 90000,
       'code'    => 500,
       'message' => "This is a long message",
      },
      {
       'dlf_id'  => 2,
       'dlf_source' => 'myjob-20040311',
       'time'    => undef,
       'code'    => undef,
       'message' => undef,
      },
      {
       'dlf_id'  => 3,
       'dlf_source' => 'myjob-20040311',
       'time'    => 100000,
       'code'    => undef,
       'message' => "Another message",
      },
     ],
     'test2' =>
     [
      {
       'dlf_id'  => 1,
       'dlf_source' => 'myjob-20040311',
       'time_start' => 1023456,
       'file' => "/var/tmp",
       'result' => "OK",
      },
     ],
    },
   },

   'continuation' =>
   {
    'log' => <<EOF,
schema=test1;time=90000;code=500;message=This is a long message;save=1
schema=test2;time_start=1023456;file=/var/tmp;result=OK;save=1
EOF
    'error_count' => 0,
    'line_count' => 2,
    'ignored_count' => 0,
    'saved_count' => 2,
    'dlf_count' => 0,
    'dlf' => {},
   },
  );

# Modify for file test
$logs{'test_file'} = { %{$logs{'test_line'}} };
$logs{'test_file'}{'name'} = "test_file";
$logs{'test_file'}{'line_count'} = 0;
$logs{'test_file'}{'ignored_count'} = undef;
$logs{'test_file'}{'saved_count'} = undef;

# Continuation results
my $cont_results =
  {
    'log' => "",
    'error_count' => 0,
    'line_count' => 4, # 2 saved + 2 of original log
    'ignored_count' => 0,
    'saved_count' => 2, # 2 of the original log
    'dlf_count' => 2, # 2 saved
    'dlf' =>
    {
     'test1' => [
               {
                'dlf_id'  => 1,
                'dlf_source' => 'myjob-20040311',
                'time'    => 90000,
                'code'    => 500,
                'message' => "This is a long message",
               },
              ],
     'test2' => [
               {
                'dlf_id'  => 1,
                'dlf_source' => 'myjob-20040311',
                'time_start' => 1023456,
                'file' => "/var/tmp",
                'result' => "OK",
               },
              ],
    },
  };

sub schema_fixtures {
    \%schemas;
}

sub import_job_fixtures {
    my $r = { map { $_ => $logs{$_}{'log'} } keys %logs };

    return $r;
}

my @converters =
  (
   new tests::DlfConverterProcessTest::DlfConverter( "line", 1 ),
   new tests::DlfConverterProcessTest::DlfConverter( "file", 0 )
  );

sub converter_fixtures {
    \@converters;
}

sub test_new {
    my $self = $_[0];

    my $p = new Lire::DlfConverterProcess( $self->import_job( "test_line"),
                                           $self->dlf_store() );
    $self->assert_isa( 'Lire::DlfConverterProcess', $p );
    $self->assert_str_equals( $self->import_job( "test_line" ),
                              $p->import_job() );
    $self->assert_str_equals( $self->dlf_store(), $p->dlf_store() );
}

sub test_run_import_job_line {
    my $self = $_[0];

    $self->import_job( "test_line" )->converter( "line" );
    my $p = new Lire::DlfConverterProcess( $self->import_job( "test_line" ),
                                           $self->dlf_store() );
    $self->assert_isa( 'Lire::DlfConverterProcess', $p );
    $p->{'_job_id'} = 'myjob-20040311';
    $p->run_import_job();

    $self->assert_dlf_converter_match_results( $logs{'test_line'}, $p );

    eval { $p->run_import_job() };
    $self->assert_not_null( $@, "calling run_import_job twice should fail" );
}

sub test_run_import_job_file {
    my $self = $_[0];

    $self->import_job( "test_file" )->converter( "file" );
    my $p = new Lire::DlfConverterProcess( $self->import_job( "test_file" ),
                                           $self->dlf_store() );
    $self->assert_isa( 'Lire::DlfConverterProcess', $p );

    $p->{'_job_id'} = 'myjob-20040311';
    $p->run_import_job();
    $self->assert_dlf_converter_match_results( $logs{'test_file'}, $p );
}

sub test_convert_continuation {
    my $self = $_[0];

    $self->import_job( "continuation" )->converter( "line" );
    my $p = new Lire::DlfConverterProcess( $self->import_job( "continuation" ),
                                           $self->dlf_store() );
    $self->assert_isa( 'Lire::DlfConverterProcess', $p );

    $p->{'_job_id'} = 'myjob-20040311';
    $p->run_import_job();
    $self->assert_dlf_converter_match_results( $logs{'continuation'}, $p );

    # Should process saved lines now
    $p = new Lire::DlfConverterProcess( $self->import_job( "continuation" ),
                                        $self->dlf_store() );
    $p->{'_job_id'} = 'myjob-20040311';
    $p->run_import_job();
    $self->assert_dlf_converter_match_results( $cont_results, $p );
}

sub set_up_converter_process {
    my $self = $_[0];

    $self->{'process'} =
      new Lire::DlfConverterProcess( $self->import_job( "test_line" ),
                                     $self->dlf_store() );

    $self->import_job( "test_line" )->converter( "line" );
    $self->{'process'}->_init_converter();

    return;
}

sub test_init_streams {
    my $self = $_[0];

    $self->set_up_converter_process();
    $self->{'process'}->_init_streams();
    $self->assert_isa( 'Lire::DlfStream', $self->{'process'}{'_streams'}{'test1'} );
    $self->assert_isa( 'Lire::DlfStream', $self->{'process'}{'_streams'}{'test2'} );
    $self->assert_isa( 'Lire::DlfStream', $self->{'process'}{'_log_stream'} );
}

sub test_error {
    my $self = $_[0];

    $self->set_up_converter_process();
    $self->{'process'}{'_log_stream'} =
      $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'w' );

    $self->{'process'}{'_job_id'} = 'My ID';
    $self->{'process'}{'_line_count'} = 54;
    $self->{'process'}{'_error_count'} = 0;

    $self->assert_dies( qr/missing 'error_msg' parameter/,
                        sub { $self->{'process'}->error() } );

    $self->{'process'}->error( 'Bad line', 'Test line' );
    $self->{'process'}{'_line_count'} = 55;
    $self->{'process'}->error( 'Unknown error' );
    $self->assert_num_equals( 2, $self->{'process'}{'_error_count'} );
    $self->{'process'}{'_log_stream'}->close();

    my $log = $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'r',
                                                   'time' );
    my $dlf1 = $log->read_dlf();
    my $dlf2 = $log->read_dlf();
    $self->assert_null( $log->read_dlf(), "unexpected DLF" );
    $self->assert_not_null(  $dlf1->{'time'}, 'Time should be set' );
    $self->assert_str_equals( 'My ID', $dlf1->{'job_id'} );
    $self->assert_str_equals( 'test_line', $dlf1->{'job_name'} );
    $self->assert_num_equals( 54, $dlf1->{'line_no'} );
    $self->assert_str_equals(  'error', $dlf1->{'type'} );
    $self->assert_str_equals(  'Test line', $dlf1->{'line'} );
    $self->assert_str_equals(  'Bad line', $dlf1->{'msg'} );

    $self->assert_str_equals( '', $dlf2->{'line'} );
    $self->assert_str_equals(  'Unknown error', $dlf2->{'msg'} );
}

sub test_save_log_line_not_supported {
    my $self = $_[0];

    my $process =
      new Lire::DlfConverterProcess( $self->import_job( 'test_file' ),
                                     $self->dlf_store() );
    $self->import_job( "test_file" )->converter( "file" );
    $process->_init_converter();

    $self->assert_dies( qr/only DLF converter handling log lines can save log line/,
                        sub { $process->save_log_line( 'A line' ) } );
}

sub test_save_log_line {
    my $self = $_[0];

    $self->set_up_converter_process();
    $self->{'process'}{'_log_stream'} =
      $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'w' );

    $self->{'process'}{'_job_id'} = 'My ID';
    $self->{'process'}{'_line_count'} = 54;
    $self->{'process'}{'_saved_count'} = 0;

    $self->assert_dies( qr/missing 'line' parameter/,
                        sub { $self->{'process'}->save_log_line() } );

    $self->{'process'}->save_log_line( 'My line');
    $self->{'process'}{'_line_count'} = 55;
    $self->assert_num_equals( 1, $self->{'process'}{'_saved_count'} );
    $self->{'process'}{'_log_stream'}->close();

    my $log = $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'r',
                                                   'time' );
    my $dlf = $log->read_dlf();
    $self->assert_null( $log->read_dlf(), "unexpected DLF" );
    $self->assert_not_null(  $dlf->{'time'}, 'Time should be set' );
    $self->assert_str_equals( 'My ID', $dlf->{'job_id'} );
    $self->assert_str_equals( 'test_line', $dlf->{'job_name'} );
    $self->assert_num_equals( 54, $dlf->{'line_no'} );
    $self->assert_str_equals(  'continuation', $dlf->{'type'} );
    $self->assert_str_equals(  'My line', $dlf->{'line'} );
    $self->assert_null( $dlf->{'msg'}, 'msg should be null' );
}

sub test_ignore_log_line {
    my $self = $_[0];

    $self->set_up_converter_process();
    $self->{'process'}{'_log_stream'} =
      $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'w' );

    $self->{'process'}{'_job_id'} = 'My ID';
    $self->{'process'}{'_line_count'} = 54;
    $self->{'process'}{'_ignored_count'} = 0;

    $self->assert_dies( qr/missing 'line' parameter/,
                        sub { $self->{'process'}->ignore_log_line() } );

    $self->{'process'}->ignore_log_line( 'My line' );
    $self->{'process'}{'_line_count'} = 55;
    $self->{'process'}->ignore_log_line( 'Another line', 'Whatever' );
    $self->{'process'}{'_line_count'} = 55;
    $self->assert_num_equals( 2, $self->{'process'}{'_ignored_count'} );
    $self->{'process'}{'_log_stream'}->close();

    my $log = $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'r',
                                                   'time' );
    my @dlf = ( $log->read_dlf(), $log->read_dlf() );
    $self->assert_null( $log->read_dlf(), "unexpected DLF" );
    $self->assert_not_null(  $dlf[0]{'time'}, 'Time should be set' );
    $self->assert_str_equals( 'My ID', $dlf[0]{'job_id'} );
    $self->assert_str_equals( 'test_line', $dlf[0]{'job_name'} );
    $self->assert_num_equals( 54, $dlf[0]{'line_no'} );
    $self->assert_str_equals(  'ignored', $dlf[0]{'type'} );
    $self->assert_str_equals(  'My line', $dlf[0]{'line'} );
    $self->assert_str_equals( 'Unknown reason', $dlf[0]{'msg'} );

    $self->assert_str_equals(  'Another line', $dlf[1]{'line'} );
    $self->assert_str_equals( 'Whatever', $dlf[1]{'msg'} );
}

sub test_save_import_stats {
    my $self = $_[0];

    $self->set_up_converter_process();

    $self->{'process'}{'_job_id'} = 'My ID';
    $self->{'process'}{'_time_start'} = time - 1000;
    $self->{'process'}{'_line_count'} = 300;
    $self->{'process'}{'_dlf_count'} = 294;
    $self->{'process'}{'_ignored_count'} = 3;
    $self->{'process'}{'_saved_count'} = 1;
    $self->{'process'}{'_error_count'} = 2;

    $self->{'process'}->_save_import_stats();

    my $stats = $self->dlf_store()->open_dlf_stream( 'lire_import_stats', 'r');
    my $dlf = $stats->read_dlf();
    $self->assert_null( $stats->read_dlf(), "unexpected DLF" );
    $stats->close();

    $self->assert_str_equals(  $dlf->{'time_start'},
                               $self->{'process'}{'_time_start'} );
    $self->assert_str_equals( 'My ID', $dlf->{'job_id'} );
    $self->assert_str_equals( 'test_line', $dlf->{'job_name'} );
    $self->assert_num_equals( 300, $dlf->{'line_count'} );
    $self->assert_num_equals(  294, $dlf->{'dlf_count'} );
    $self->assert_num_equals(  3, $dlf->{'ignored_count'} );
    $self->assert_num_equals( 1, $dlf->{'saved_count'} );
    $self->assert_num_equals( 2, $dlf->{'error_count'} );

    $self->assert( $dlf->{'elapsed'} >= 1000, 
                   "elapsed should be 1000 or more");
}

sub test_handle_continuation {
    my $self = $_[0];

    my $process = new Lire::DlfConverterProcess($self->import_job('test_line'),
                                                $self->dlf_store() );
    $process->{'_time_start'} = time;
    $process->{'_line_count'} = 0;
    $process->{'_log_stream'} =
      $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'w' );
    $process->{'_converter'} =
      new Class::Inner( 'parent' => 'Lire::DlfConverter',
                        'methods' =>
                        {
                         'new' => sub { return bless [], shift },
                         'name' => sub { return 'mock' },
                         'handle_log_lines' => sub { return 1 },
                         'process_log_line' => sub {
                             push @{$_[0]}, $_[2];
                         }
                        } );
    my $stream = $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'w' );
    $stream->write_dlf( { 'time' => time - 100,
                          'type' => 'continuation',
                          'line_no' => 1,
                          'line' => 'My line 1',
                          'job_name' => 'test_line',
                          } );
    $stream->write_dlf( { 'time' => time - 50,
                          'type' => 'continuation',
                          'line_no' => 2,
                          'line' => 'My line 2',
                          'job_name' => 'test_line',
                          } );
    $stream->write_dlf( { 'line_no' => 1,
                          'type' => 'ignored',
                          'line' => 'An ignored line',
                          'job_name' => 'test_line',
                          } );
    $stream->write_dlf( {'line_no' => 2,
                          'type' => 'continuation',
                          'line' => 'A line',
                          'job_name' => 'another_job',
                          } );
    $stream->close();

    $process->_handle_continuation();
    $self->assert_num_equals( 2, $process->{'_line_count'} );
    my @lines = @{$process->{'_converter'}};
    $self->assert_deep_equals( [ 'My line 1', 'My line 2' ],
                               \@lines  );
    $stream = $self->dlf_store()->open_dlf_stream( 'lire_import_log', 'r',
                                                   'line_no' );
    my @dlf = ( $stream->read_dlf(), $stream->read_dlf() );
    $self->assert_null( $stream->read_dlf(), 'Stream should be empty' );
    $self->assert_str_equals( 'An ignored line', $dlf[0]{'line'} );
    $self->assert_str_equals( 'A line', $dlf[1]{'line'} );
    $stream->close();
}

sub test_init_converter {
    my $self = $_[0];

    my $process =
      new Lire::DlfConverterProcess( $self->import_job( "test_line" ),
                                     $self->dlf_store() );

    $self->import_job( "test_line" )->converter( "line" );
    my $cfg = { 'myconfig' => 1 };
    $self->import_job( "test_line" )->converter_config( $cfg  );

    $self->assert_null( $process->{'_converter'},
                        '_converter should be undef');
    $process->_init_converter();

    $self->assert_isa( 'Lire::DlfConverter', $process->{'_converter'} );
    $self->assert_str_equals( $process, $process->{'_converter'}{'process'} );
    $self->assert_str_equals( 'line', $process->{'_converter'}->name() );
    $self->assert_str_equals( $cfg, $process->{'_converter'}{'config'} );

    return;
}

package tests::DlfConverterProcessTest::DlfConverter;

use base qw/Lire::DlfConverter/;

sub new {
    bless { 'name' => $_[1],
            'handle_lines' => $_[2],
          }, $_[0];
}

sub name { $_[0]{'name'} };

sub handle_log_lines { $_[0]{'handle_lines'} }

sub schemas { return ("test1", "test2") };

sub init_dlf_converter {
    $_[0]{'process'} = $_[1];
    $_[0]{'config'} = $_[2];
    return;
}

sub process_log_file {
    my ( $self, $process, $fh ) = @_;
    while ( <$fh> ) {
        chomp $_;
        $self->process_log_line( $process, $_ );
    }
}

sub process_log_line {
    my ( $self, $process, $line ) = @_;

    my %r = ();
    my @fields = split /;/, $line;
    foreach my $f ( @fields ) {
        my ( $key, $value ) = $f =~ /^(\w+)=(.+)$/;

        if ( defined $key && defined $value ) {
            $r{$key} = $value;
        } else {
            $process->error( "contains an invalid field", $line );
            return;
        }
    }

    if ( $r{'ignore'}) {
        $process->ignore_log_line( $line )
          if $self->{'handle_lines'};
    } elsif ( $r{'save'}) {
        $line =~ s/save=1/save=0/;
        $process->save_log_line( $line )
          if $self->{'handle_lines'};
    } else {
        $process->write_dlf( $r{'schema'}, \%r );
    }
}

sub finish_conversion {
    delete $_[0]{'process'};
    delete $_[0]{'config'};
    return;
}


1;


syntax highlighted by Code2HTML, v. 0.9.1