package tests::TimegroupTest;

use strict;

use base qw/Lire::Test::TestCase tests::TestStoreFixture/;

use Lire::Timegroup;
use Lire::ReportSpec;
use Lire::Param;
use Lire::Report::Group;
use Lire::Report::TableInfo;
use Lire::DlfQuery;
use Lire::Test::Mock;

use Time::Local;

#our @TESTS = qw/ /;

sub new {
    my $self = shift()->SUPER::new( @_ );

    $self->init();

    return $self;
}

sub set_up {
    my $self = $_[0];
    $self->SUPER::set_up();

    $self->set_up_test_schema();

    $self->set_up_tz( 'UTC' );

    $self->{'spec'} = new Lire::ReportSpec();
    $self->{'spec'}->superservice( 'test' );
    $self->{'spec'}->id( 'timegroup-test' );

    foreach my $p ( qw/4h 1d 1w 3M 1y/ ) {
        $self->{ $p . '_timegroup'} =
          new Lire::Timegroup( 'report_spec' => $self->{'spec'},
                               'period' =>  $p,
                               'label' => 'Period',
                             );
    }

    return;
}

sub tear_down {
    my $self = $_[0];
    $self->SUPER::tear_down();

    return;
}

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

    $self->assert_equals( 'timegroup:time_start',
                          $self->{'1d_timegroup'}->name() );
}

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

    my $info = new Lire::Report::TableInfo();
    $self->{'1d_timegroup'}->create_categorical_info( $info );

    my @cols = $info->children();
    $self->assert_equals( 1, scalar @cols );

    my $col = $cols[0];
    $self->assert_equals( 'categorical', $col->class() );
    $self->assert_equals( 'timegroup:time_start', $col->name() );
    $self->assert_equals( 'timestamp', $col->type() );
    $self->assert_equals( 'Period', $col->label() );
}

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

    $self->{'spec'}->param( 'dur', new Lire::Param( 'name' => 'dur',
                                                    'type' => 'duration',
                                                    'default' => '1d' ) );
    $self->{'spec'}->param( 'int', new Lire::Param( 'name' => 'int',
                                                    'type' => 'int',
                                                    'default' => '100' ) );

    my $timegroup = $self->{'1d_timegroup'};
    $self->assert_equals( '1d', $timegroup->period() );

    $self->assert_died( sub { $timegroup->period( undef ) },
                        qr/missing 'period' parameter/ );
    $self->assert_died( sub { $timegroup->period( 'wawa' ) },
                        qr/'period' parameter isn't a valid duration: 'wawa'/);
    $self->assert_died( sub { $timegroup->period( '$wawa' ) },
                        qr/parameter 'wawa' isn't defined/ );
    $self->assert_died( sub { $timegroup->period( '2d' ) },
                        qr/can't use multiple with period of type 'd'/ );
    $self->assert_died( sub { $timegroup->period( '$int' ) },
                        qr/parameter 'int' isn't a 'duration' parameter: 'int'/ );

    $timegroup->period( '$dur' );
    $self->assert_equals( '$dur', $timegroup->{'period'} );

    $timegroup->period( '2h' );
    $self->assert_equals( '2h', $timegroup->{'period'} );
}

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

    foreach my $t ( [ '4h', 'lr_timegroup_sec("time-start",14400)' ],
                    [ '1d', 'lr_timegroup_day("time-start")' ],
                    [ '1w', 'lr_timegroup_week("time-start",1)' ],
                    [ '3M', 'lr_timegroup_month("time-start",3)' ],
                    [ '1y', 'lr_timegroup_year("time-start",1)' ] )
    {
        my $timegroup = $self->{ $t->[0] . '_timegroup' };
        $timegroup->{'field'} = 'time-start';
        my $e_query = new Lire::DlfQuery( 'test' );
        $e_query->add_aggr_field( '_lr_nrecords', 'count(*)' );
        $e_query->add_group_field( "timegroup:time-start", $t->[1] );
        $e_query->set_sort_spec( "timegroup:time-start" );

        my $query = new Lire::DlfQuery( 'test' );
        $timegroup->build_query( $query );
        $self->assert_deep_equals( $e_query, $query );
    }
}

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

    $self->{'cfg'}{'lr_week_numbering'} = 'ISO';

    my $info = new Lire::Report::TableInfo();
    $self->{'1d_timegroup'}->create_categorical_info( $info );

    foreach my $t ( [ '4h', '2003-10-14 20:00',  1066161600, 4*3600 ],
                    [ '1d', '2003-10-14', 1066089600, 24*3600 ],
                    [ '1w', '2003-W42', 1064966400, 7*24*3600 ],
                    [ '3M', 'April 2003', 1049155200, 30*24*3600*3 ],
                    [ '1y', '2003', 1041379200, 365*24*3600 ], )
    {
        my ( $period, $fmt_value, $value, $period_sec ) = @$t;

        my $timegroup = $self->{$period . '_timegroup'};
        my $group = new Lire::Report::Group( bless( {}, 'Lire::Report::Entry'),
                                             $info );
        my $dlf = { 'timegroup:time_start' => $value };
        my $entry = $timegroup->create_entry( $group, $dlf );

        $self->assert_not_null( $entry, "create_entry() returned undef" );
        my @data = $entry->data();
        $self->assert_equals( 1, scalar @data );
        $self->assert_equals( $fmt_value, $data[0]->{'content'} );
        $self->assert_equals( $value, $data[0]->{'value'} );
        $self->assert_equals( $period_sec, $data[0]->{'range'} );
    }
}

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

    my $timegroup = $self->{'1d_timegroup'};
    my $info = new Lire::Report::TableInfo();
    $self->{'1d_timegroup'}->create_categorical_info( $info );
    my $group = new Lire::Report::Group( bless( {}, 'Lire::Report::Entry'),
                                         $info );

    my $entry = $timegroup->create_entry( $group,
                                          { 'timegroup:time_start' => undef,
                                            '_lr_nrecords' => 24 } );
    $self->assert_null( $entry, "create_entry() should have returned undef" );
    $self->assert_equals( 24, $group->missing_cases() );
}

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

    my $info = new Lire::Report::TableInfo();
    $self->{'1d_timegroup'}->create_categorical_info( $info );
    my $group = new Lire::Report::Group( bless( {}, 'Lire::Report::Entry'),
                                         $info );
    foreach my $t ( [ '2003-10-14 16:00', 1066147200 ],
                    [ '           20:00', 1066161600 ],
                    [ '2003-10-15 00:00', 1066176000 ],
                  )
    {
        my $timegroup = $self->{'4h_timegroup'};
        my $dlf = { 'timegroup:time_start' => $t->[1] };
        my $entry = $timegroup->create_entry( $group, $dlf );


        $self->assert_not_null( $entry, "create_entry() returned undef" );
        $self->assert_str_equals( $t->[0], ($entry->data())[0]->{'content'} );
    }
}

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

    my $spec = new Lire::ReportSpec();
    $spec->superservice( 'test' );
    $spec->param( 'period', new Lire::Param( 'name' => 'period',
                                             'type' => 'duration',
                                             'value' => '1d' ) );
    $self->{'timegroup'} = new Lire::Timegroup( 'report_spec' => $spec,
                                                'period' => '$period' );
    $self->{'aggregate'} = new Lire::Test::Mock( 'Lire::Aggregate',
                                                 'name' => 'aggr' );
    $self->{'timegroup'}->ops( [ $self->{'aggregate'} ] );

    return;
}

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

    $self->set_up_merge_fixture();

    $self->{'timegroup'}->init_merge();
    $self->assert_num_equals( 86400, $self->{'timegroup'}{'period_sec'} );
    $self->assert_isa( 'Lire::Timegroup::SecHelper',
                       $self->{'timegroup'}{'helper'} );
    $self->assert_num_equals( 86400, $self->{'timegroup'}{'helper'}{'period'});
    $self->assert_num_equals( 1, $self->{'timegroup'}{'helper'}{'multiple'} );
    $self->assert_num_equals( 1, $self->{'aggregate'}->invocation_count( 'init_merge' ) );
    $self->assert_num_equals( 0, $self->{'timegroup'}{'_merge_started'} );

    $self->{'timegroup'}{'period'} = '4w';
    $self->{'timegroup'}->init_merge();
    $self->assert_isa( 'Lire::Timegroup::WeekHelper',
                       $self->{'timegroup'}{'helper'} );
    $self->assert_num_equals( 4, $self->{'timegroup'}{'helper'}{'multiple'} );

    $self->{'timegroup'}{'period'} = '1M';
    $self->{'timegroup'}->init_merge();
    $self->assert_isa( 'Lire::Timegroup::MonthHelper',
                       $self->{'timegroup'}{'helper'} );

    $self->{'timegroup'}{'period'} = '4y';
    $self->{'timegroup'}->init_merge();
    $self->assert_isa( 'Lire::Timegroup::YearHelper',
                       $self->{'timegroup'}{'helper'} );
}

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

    $self->set_up_merge_fixture();
    my $entry = new Lire::Test::Mock( 'Lire::Report::Entry' );
    my $group = new Lire::Test::Mock( 'Lire::Report::Group',

                                      'entries' => sub { return ( $entry ) } );
    $self->{'timegroup'}->init_merge();

    $self->_test_merge_aggregator_data_errors( $group, $entry );

    my $timeslices = [];
    $self->{'aggregate'}->set_result( 'init_group_data' => [] );
    my $data = { 'aggr' => {} };
    $entry->set_result( 'names' =>
                        { 'range' => 3600,
                          'value' => timelocal( 0, 0, 11, 1, 0, 2004 ) },
                        'data_by_name' => sub { $data->{$_[1]} } );
    $self->{'timegroup'}->merge_aggregator_data( $group, $timeslices );
    $self->assert_num_equals( 1, $self->{'timegroup'}{'_merge_started'} );
    $self->assert_deep_equals( [ $self->{'aggregate'}, {}, [] ],
                               $self->{'aggregate'}->get_invocation( 'merge_group_data' ) );
    $self->assert_deep_equals( [ [ timelocal( 0, 0, 0, 1, 0, 2004 ), [] ] ],
                               $timeslices );

    $entry->set_result( 'names' =>
                        { 'range' => 3600,
                          'value' => timelocal( 0, 0, 11, 3, 0, 2004 ) } );
    $self->{'timegroup'}->merge_aggregator_data( $group, $timeslices );
    $self->assert_deep_equals( [ [ timelocal( 0, 0, 0, 1, 0, 2004 ), [] ],
                                 undef,
                                 [ timelocal( 0, 0, 0, 3, 0, 2004 ), [] ] ],
                               $timeslices );
    $self->assert_num_equals( 2, $self->{'aggregate'}->invocation_count( 'merge_group_data' ) );

    $entry->set_result( 'names' =>
                        { 'range' => 3600,
                          'value' => timelocal( 0, 0, 11, 30, 11, 2003 ) } );
    $self->{'timegroup'}->merge_aggregator_data( $group, $timeslices );
    $self->assert_deep_equals( [ [ timelocal( 0, 0, 0, 30, 11, 2003 ), [] ],
                                 undef,
                                 [ timelocal( 0, 0, 0, 1, 0, 2004 ), [] ],
                                 undef,
                                 [ timelocal( 0, 0, 0, 3, 0, 2004 ), [] ] ],
                               $timeslices );
    $entry->set_result( 'names' =>
                        { 'range' => 3600,
                          'value' => timelocal( 0, 0, 11, 31, 11, 2003 ) } );
    $self->{'timegroup'}->merge_aggregator_data( $group, $timeslices );
    $self->assert_deep_equals( [ [ timelocal( 0, 0, 0, 30, 11, 2003 ), [] ],
                                 [ timelocal( 0, 0, 0, 31, 11, 2003 ), [] ],
                                 [ timelocal( 0, 0, 0, 1, 0, 2004 ), [] ],
                                 undef,
                                 [ timelocal( 0, 0, 0, 3, 0, 2004 ), [] ] ],
                               $timeslices );
}

sub _test_merge_aggregator_data_errors {
    my ( $self, $group, $entry ) = @_;

    $entry->set_result( 'names' => { 'range' => 86400 * 2, 'value' => 0 } );
    $self->assert_dies( qr/incompatible merge:/,
                        sub { $self->{'timegroup'}->merge_aggregator_data( $group, [] ) } );

    $entry->set_result( 'names' => { 'range' => 3601, 'value' => 0 } );
    $self->assert_dies( qr/incompatible merge:/,
                        sub { $self->{'timegroup'}->merge_aggregator_data( $group, [] ) } );
}

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

    $self->{'cfg'}{'lr_week_numbering'} = 'ISO';
    my $helper = new Lire::Timegroup::WeekHelper( 7*86400, 1 );

    $helper->init( timelocal( 0, 0, 0, 1, 0, 2004 ) );
    $self->assert_num_equals( 0, $helper->{'week_start'} );
    $self->assert_num_equals( 2004, $helper->{'year_start'} );

    $helper->init( timelocal( 0, 0, 0, 31, 11, 2003 ) );
    $self->assert_num_equals( 0, $helper->{'week_start'} );
    $self->assert_num_equals( 2004, $helper->{'year_start'} );

    $helper->init( timelocal( 0, 0, 0, 1, 1, 2004 ) );
    $self->assert_num_equals( 4, $helper->{'week_start'} );
    $self->assert_num_equals( 2004, $helper->{'year_start'} );

    $helper->init( timelocal( 0, 0, 0, 1, 0, 2005 ) );
    $self->assert_num_equals( 52, $helper->{'week_start'} );
    $self->assert_num_equals( 2004, $helper->{'year_start'} );

    $self->{'cfg'}{'lr_week_numbering'} = 'U';
    $helper->init( timelocal( 0, 0, 0, 1, 0, 2004 ) );
    $self->assert_num_equals( 51, $helper->{'week_start'} );
    $self->assert_num_equals( 2003, $helper->{'year_start'} );

    $helper->init( timelocal( 0, 0, 0, 1, 1, 2004 ) );
    $self->assert_num_equals( 4, $helper->{'week_start'} );
    $self->assert_num_equals( 2004, $helper->{'year_start'} );
}

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

    $self->{'cfg'}{'lr_week_numbering'} = 'ISO';
    my $helper = new Lire::Timegroup::WeekHelper( 7*86400, 1 );
    $helper->init( timelocal( 0, 0, 0, 1, 0, 2004 ) );

    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,1,0,2004)));
    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,31,11,2003)));
    $self->assert_num_equals( 4, $helper->find_idx( timelocal(0,0,0,1,1,2004)));
    $self->assert_num_equals( 52, $helper->find_idx( timelocal(0,0,0,1,0,2005)));
    $self->assert_num_equals( -1, $helper->find_idx( timelocal(0,0,0,25,11,2003)));
    $self->assert_num_equals( -52, $helper->find_idx( timelocal(0,0,0,1,0,2003)));
    $self->assert_num_equals( -53, $helper->find_idx( timelocal(0,0,0,25,11,2002)));

    $helper->{'week_start'} = 4;
    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,1,1,2004)));
    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,31,0,2004)));
    $self->assert_num_equals( 1, $helper->find_idx( timelocal(0,0,0,2,1,2004)));
    $self->assert_num_equals( 48, $helper->find_idx( timelocal(0,0,0,1,0,2005)));
    $self->assert_num_equals( -4, $helper->find_idx( timelocal(0,0,0,1,0,2004)));
    $self->assert_num_equals( -5, $helper->find_idx( timelocal(0,0,0,25,11,2003)));

    $helper->{'multiple'} = 4;
    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,1,1,2004)));
    $self->assert_num_equals( 0, $helper->find_idx( timelocal(0,0,0,2,1,2004)));
    $self->assert_num_equals( 12, $helper->find_idx( timelocal(0,0,0,1,0,2005)));
    $self->assert_num_equals( -2, $helper->find_idx( timelocal(0,0,0,25,11,2003)));
    $self->assert_num_equals( -13, $helper->find_idx( timelocal(0,0,0,1,1,2003)));
    $self->assert_num_equals( -14, $helper->find_idx( timelocal(0,0,0,1,0,2003)));
}

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

    $self->{'cfg'}{'lr_week_numbering'} = 'ISO';
    my $helper = new Lire::Timegroup::WeekHelper( 7*86400, 1 );
    $helper->init( timelocal( 0, 0, 0, 1, 0, 2004 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 29, 11, 2003 ),
                              $helper->slice_start( 0 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 26, 0, 2004 ),
                              $helper->slice_start( 4 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 27, 11, 2004 ),
                              $helper->slice_start( 52 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 3, 0, 2005 ),
                              $helper->slice_start( 53 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 31, 0, 2005 ),
                              $helper->slice_start( 57 ) );

    $helper->{'week_start'} = 4;
    $self->assert_num_equals( timelocal( 0, 0, 0, 26, 0, 2004 ),
                              $helper->slice_start( 0 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 20, 11, 2004 ),
                              $helper->slice_start( 47 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 27, 11, 2004 ),
                              $helper->slice_start( 48 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 24, 0, 2005 ),
                              $helper->slice_start( 52 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 26, 11, 2005 ),
                              $helper->slice_start( 100 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 23, 0, 2006 ),
                              $helper->slice_start( 104 ) );

    $helper->{'multiple'} = 4;
    $self->assert_num_equals( timelocal( 0, 0, 0, 26, 0, 2004 ),
                              $helper->slice_start( 0 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 23, 1, 2004 ),
                              $helper->slice_start( 1 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 27, 11, 2004 ),
                              $helper->slice_start( 12 ) );
}

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

    my $helper = new Lire::Timegroup::MonthHelper( 7*86400, 1 );
    $helper->init( timelocal( 0, 0, 0, 1, 0, 2004 ) );
    $self->assert_num_equals( 104, $helper->{'year_start'} );
    $self->assert_num_equals( 0, $helper->{'month_start'} );

    $self->assert_num_equals( 0, $helper->find_idx( timelocal( 0, 0, 0, 15, 0, 2004 ) ) );
    $self->assert_num_equals( 11, $helper->find_idx( timelocal( 0, 0, 0, 15, 11, 2004 ) ) );
    $self->assert_num_equals( 12, $helper->find_idx( timelocal( 0, 0, 0, 15, 0, 2005 ) ) );
    $self->assert_num_equals( -1, $helper->find_idx( timelocal( 0, 0, 0, 31, 11, 2003 ) ) );
    $self->assert_num_equals( -13, $helper->find_idx( timelocal( 0, 0, 0, 31, 11, 2002 ) ) );

    $helper->{'month_start'} = 2;
    $self->assert_num_equals( 9, $helper->find_idx( timelocal( 0, 0, 0, 15, 11, 2004 ) ) );
    $self->assert_num_equals( -2, $helper->find_idx( timelocal( 0, 0, 0, 15, 0, 2004 ) ) );

    $helper->{'multiple'} = 3;
    $self->assert_num_equals( -1, $helper->find_idx( timelocal( 0, 0, 0, 15, 0, 2004 ) ) );
    $self->assert_num_equals( 0, $helper->find_idx( timelocal( 0, 0, 0, 15, 2, 2004 ) ) );
    $self->assert_num_equals( 3, $helper->find_idx( timelocal( 0, 0, 0, 15, 0, 2005 ) ) );
    $self->assert_num_equals( -1, $helper->find_idx( timelocal( 0, 0, 0, 31, 11, 2003 ) ) );
    $self->assert_num_equals( -5, $helper->find_idx( timelocal( 0, 0, 0, 31, 11, 2002 ) ) );
}

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

    my $helper = new Lire::Timegroup::MonthHelper( 7*86400, 1 );
    $helper->{'year_start'} = 104;
    $helper->{'month_start'} = 2;

    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 2, 2004 ),
                              $helper->slice_start( 0 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 11, 2004 ),
                              $helper->slice_start( 9 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 2, 2005 ),
                              $helper->slice_start( 12 ) );
    $helper->{'multiple'} = 3 ;
    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 2, 2004 ),
                              $helper->slice_start( 0 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 11, 2004 ),
                              $helper->slice_start( 3 ) );
    $self->assert_num_equals( timelocal( 0, 0, 0, 1, 2, 2005 ),
                              $helper->slice_start( 4 ) );
}

1;


syntax highlighted by Code2HTML, v. 0.9.1