package Lire::AsciiDlf::GroupOp;

use strict;

use Lire::GroupOp;

use Lire::DataTypes qw/ format_numeric_type /;
use vars qw( $VERSION @ISA );

BEGIN {
    ($VERSION)	= '$Revision: 1.14 $' =~ m!Revision: ([.\d]+)!;
}

#
# OPTIMIZATION OF THE DATA STRUCTURE
#.
# Scalar ref takes less place then array refs which takes less spec
# then hash ref. We use only scalar or array refs.
#
# As a side effects, we also get a little speed optimization :
# perl -MBenchmark=cmpthese -e '
#   my $dummy = 0;
#   my $scalar = \$dummy;
#   my $array = [0]; 
#   my $hash = { value => 0};
#   cmpthese( 500000, {
#		SCALAR => sub { $$scalar++ },
#		ARRAY  => sub { $array->[0]++ },
#		HASH   => sub { $hash->{value}++ },
#	     });
# Benchmark: timing 500000 iterations of ARRAY, HASH, SCALAR...
#     ARRAY:  0 wallclock secs ( 0.57 usr +  0.00 sys =  0.57 CPU) @ 877192.98/s (n=500000)
#     HASH:  1 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 746268.66/s (n=500000)
#    SCALAR:  1 wallclock secs ( 0.41 usr +  0.00 sys =  0.41 CPU) @ 1219512.20/s (n=500000)
#             Rate   HASH  ARRAY SCALAR
# HASH    746269/s     --   -15%   -39%
# ARRAY   877193/s    18%     --   -28%
# SCALAR 1219512/s    63%    39%     --
#
sub init_report {}

sub dlf_sort_fields { return (); }

sub init_group_data {
    my $scalar = 0;
    return \$scalar;
}

sub update_group_data {}

sub end_group_data {}

sub add_entry_value {
    my ( $self, $entry, $data ) = @_;

    $self->add_content_value( $entry, $$data );
}

sub add_content_value {
    my ( $self, $entry, $value ) = @_;

    my $type	    = $self->{field_type} || "number";
    my $content   = format_numeric_type( $value, $type );

    $entry->add_value( $content, $value );
}

package Lire::AsciiDlf::GroupOp::Count;

use vars qw/ @ISA /;

use Lire::Config;

use Carp;

BEGIN {
    @ISA = qw/ Lire::GroupOp::Count Lire::AsciiDlf::GroupOp /;
}

sub init_report {
    my ($self, $dlf_info ) = @_;

    $self->{min_memory} = 0;

    if ( $self->fields() ) {
	$self->{key_maker} =
	  $self->{report_spec}->schema->make_key_access_func( @{$self->fields} );

	my $keys = 1;
	foreach my $field ( @{$self->fields} ) {
	    $keys *= $dlf_info->field_keys( $field );
	}

	my $threshold = defined $self->{parent}{parent} ? 
	  $Lire::Config::KEYS_THRESHOLD_NESTED : 
	    $Lire::Config::KEYS_THRESHOLD_TOP;

	$self->{min_memory} = $keys > $threshold;
    }

    $self;
}

sub dlf_sort_fields {
    my ( $self ) = shift;

    if ( $self->{min_memory} ) {
	return @{$self->{fields}};
    } else {
	return ();
    }
}

sub init_group_data {
    my ( $self ) = @_;

    if ( $self->{key_maker} ) {
	if ( $self->{min_memory} ) {
	    return [ 0, "" ];
	} else {
	    return [ 0, {} ];
	}
    } else {
	my $scalar = 0;
	return \$scalar;
    }
}

sub update_group_data {
    my ( $self, $dlf, $data ) = @_;

    if ( $self->{key_maker} ) {
	my $key = $self->{key_maker}->( $dlf );
	if ( $self->{min_memory} ) {
	    if ( $data->[1] ne $key ) {
		$data->[1] = $key;
		$data->[0]++;
	    }
	} elsif ( ! exists $data->[1]{$key} ) {
	    $data->[1]{$key} = 1;
	    $data->[0]++;
	}
    } else {
	$$data++;
    }
}

sub end_group_data {
    my ( $self, $data ) = @_;

    $data->[1] = undef
      if ( $self->{key_maker} );
}

sub add_entry_value {
    my ( $self, $entry, $data ) = @_;

    if ( $self->{key_maker}) {
	$self->add_content_value( $entry, $data->[0] );
    } else {
	$self->add_content_value( $entry, $$data );
    }
}

package Lire::AsciiDlf::GroupOp::SimpleStat;

use vars qw/ @ISA /;

BEGIN {
    @ISA = qw/ Lire::GroupOp::SimpleStat Lire::AsciiDlf::GroupOp /;
}

sub init_report {
    my ($self, $dlf_info ) = @_;

    my $field = $self->{report_spec}->schema()->field( $self->field );
    $self->{field_idx}  = $field->pos;
    $self->{field_type} = $field->type;

    $self;
}

package Lire::AsciiDlf::GroupOp::Sum;

use vars qw/ @ISA /;

BEGIN {
    @ISA = qw/ Lire::GroupOp::Sum Lire::AsciiDlf::GroupOp::SimpleStat /;
}

sub update_group_data {
    my ( $self, $dlf, $data ) = @_;

    $$data += $dlf->[$self->{field_idx}];
}

package Lire::AsciiDlf::GroupOp::Avg;

use Lire::Config;
use Lire::DataTypes qw/ is_numeric_type format_numeric_type/;
use Carp;

use vars qw/ @ISA /;

use constant AVG_VALUE => 0;
use constant AVG_SUM   => 1;
use constant AVG_N     => 2;
use constant AVG_KEY_CACHE => 3;
use constant AVG_BY_KEY_CACHE => 4;

BEGIN {
    @ISA = qw/ Lire::GroupOp::Avg Lire::AsciiDlf::GroupOp /;
}

sub init_report {
    my ( $self, $dlf_info ) = @_;

    if ( $self->field ) {
	my $f = $self->{report_spec}->schema()->field( $self->field );
	$self->{field_idx}  = $f->pos;
	$self->{is_numeric} = is_numeric_type( $f->type );
	$self->{field_type} = $f->type;
    }
    $self->{min_memory} = 0;

    if ( $self->by_fields() ) {
	$self->{key_maker} =
	  $self->{report_spec}->schema->make_key_access_func( @{$self->by_fields} );

	my $keys = 1;
	foreach my $field ( @{$self->by_fields} ) {
	    $keys *= $dlf_info->field_keys( $field );
	}

	my $threshold = defined $self->{parent}{parent} ? 
	  $Lire::Config::KEYS_THRESHOLD_NESTED : 
	    $Lire::Config::KEYS_THRESHOLD_TOP;

	$self->{min_memory} = $keys > $threshold;
    }

    $self;
}

sub dlf_sort_fields {
    my ( $self ) = @_;

    if ( $self->{min_memory} && $self->{key_maker} ) {
	return @{$self->by_fields};
    } else {
	return ();
    }
}

sub init_group_data {
    my ( $self ) = @_;
    # Array layout
    # Value, sum, n, key-cache, by-key-cache
    if ( $self->{min_memory} ) {
	# We can't avoid the two caches, we can only remove one
	# since they are independant
	return [ 0, 0, 0, {}, "" ];
    } else {
	return [ 0, 0, 0, {}, {} ];
    }
}

sub update_group_data {
    my ( $self, $dlf, $data ) = @_;

    if ( exists $self->{key_maker} ) {
	my $key = $self->{key_maker}->( $dlf );
	if ( $self->{min_memory} ) {
	    if ( $data->[AVG_BY_KEY_CACHE] ne $key ) {
		$data->[AVG_BY_KEY_CACHE] = $key;
		$data->[AVG_N]++;
	    }
	} elsif ( ! exists $data->[AVG_BY_KEY_CACHE]{$key} ) {
	    $data->[AVG_BY_KEY_CACHE]{$key} = 1;
	    $data->[AVG_N]++;
	}
    } else {
	$data->[AVG_N]++;
    }

    if ( $self->{is_numeric} ) {
	$data->[AVG_SUM] += $dlf->[$self->{field_idx}];
    } elsif ( exists $self->{field_idx} ) {
	my $key = $dlf->[$self->{field_idx}];
	unless ( exists $data->[AVG_KEY_CACHE]{$key} ) {
	    $data->[AVG_KEY_CACHE]{$key} = 1;
	    $data->[AVG_SUM]++;
	}
    } else {
	$data->[AVG_SUM]++;
    }
}

sub end_group_data {
    my ( $self, $data ) = @_;

    if ( $data->[AVG_N]) {
	$data->[AVG_VALUE] = $data->[AVG_SUM] / $data->[AVG_N];
	$data->[AVG_VALUE] = sprintf "%.2f", $data->[AVG_VALUE];
    } else {
	$data->[AVG_VALUE] = "n/a";
    }
    $data->[AVG_KEY_CACHE] = undef;
    $data->[AVG_BY_KEY_CACHE] = undef;
}

sub add_entry_value {
    my ( $self, $entry, $data ) = @_;

    my $type	 = $self->{field_type} || "number";
    my $content  = format_numeric_type( $data->[AVG_VALUE], $type );

    $entry->add_value( $content, $data->[AVG_VALUE], $data->[AVG_SUM], 
		       $data->[AVG_N] );
}

package Lire::AsciiDlf::GroupOp::Min;

use vars qw/ @ISA /;

BEGIN {
    @ISA = qw/ Lire::GroupOp::Min Lire::AsciiDlf::GroupOp::SimpleStat /;
}

sub init_group_data {
    my $scalar = undef;
    return \$scalar;
}

sub update_group_data {
    my ( $self, $dlf, $data ) = @_;

    my $value = $dlf->[$self->{field_idx}];
    $$data = $value
      unless ( defined $$data );

    $$data = $value
      if $value < $$data;
}

sub end_group_data {
    my ( $self, $data ) = @_;

    $$data = "n/a" unless defined $$data;
}

package Lire::AsciiDlf::GroupOp::Max;

use vars qw/ @ISA /;

BEGIN {
    @ISA = qw/ Lire::GroupOp::Max Lire::AsciiDlf::GroupOp::SimpleStat /;
}

sub init_group_data {
    my $scalar = undef;
    return \$scalar;
}

sub update_group_data {
    my ( $self, $dlf, $data ) = @_;

    my $value = $dlf->[$self->{field_idx}];

    $$data = $value
      unless ( defined $$data );

    $$data = $value
      if $value > $$data;
}

sub end_group_data {
    my ( $self, $data ) = @_;

    $$data = "n/a" unless defined $$data;
}

# keep perl happy
1;

__END__

=pod

=head1 NAME

Lire::AsciiDlf::GroupOp -

=head1 SYNOPSIS


=head1 DESCRIPTION

=head1 VERSION

$Id: GroupOp.pm,v 1.14 2002/05/21 18:37:05 flacoste Exp $

=head1 COPYRIGHT

Copyright (C) 2001 Stichting LogReport Foundation LogReport@LogReport.org

This file is part of Lire.

Lire is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html or write to the Free Software 
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.

=head1 AUTHOR

Francis J. Lacoste <flacoste@logreport.org>

=cut
