package Lire::ReportOperator;

use strict;

use base qw/ Exporter /;

use Lire::I18N qw/ dgettext /;
use Lire::Utils qw/ check_param check_object_param /;

use Carp;

our @EXPORT_OK = qw/ group_data_value /;

=pod

=head1 NAME

Lire::ReportOperator - Base class for all operators that can be used to compute reports

=head1 SYNOPSIS

    print $op->name(), "\n";
    print $op->op(), "\n";
    print $op->label(), "\n";

=head1 DESCRIPTION

The Lire::ReportOperator is the base class for all operators that can
be used to compute the data appearing in Lire's reports. This class
only provides the behavior common to all operators.

Currently, the operators defined in Lire come into two main varieties:
first, there are aggregators which are operators that can split DLF
records into multiple groups, second there are group operators that
can compute data based on a group of DLF records. Aggregators are
subclasses of Lire::Aggregator and group operations are subclasses of
Lire::Aggregate.

=head1 METHODS

=head2 op()

Returns the XML element's name which refers to this operator.

=cut

sub op {
    return $_[0]{'op'};
}

=pod

=head2 report_spec()

Returns the Lire::ReportSpec object in which this operator is.

=cut

sub report_spec {
    return $_[0]{'report_spec'};
}

=pod

=head2 parent()

Returns the parent of this operator. This will be undefined for the
top-level aggregator in the report specification. Only
Lire::Aggregator can have children and as such be a parent.

=cut

sub parent {
    return $_[0]{'parent'};
}

=pod

=head2 last_parent()

Returns our top-level parent.

=cut

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

    my $parent = $self;
    while ( $parent->parent() ) {
        $parent = $parent->parent()
    }

    return $parent;
}

=pod

=head2 name( [$new_name] )

Returns the name of this operator in the current report specification.
The operator's name is an identifier that must be unique in a given
report specification. This name will be used to identify the values
generated by this operator in the generated report.

If $new_name is set, this operator's name will be changed to this new
value.

=cut

sub name {
    croak "Unimplemented name() method in ", ref $_[0];
}

=pod

=head2 has_label()

Returns true if this operator has an explicit label assigned.

=cut

sub has_label {
    return defined $_[0]->{'label'};
}

=pod

=head2 label( [$new_label] )

Returns what should be used as column's label for the data generated
by this operator.

If the second argument is set, the column label will be set to this
new value.

=cut

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

    $self->{'label'} = $label 
      if @_ == 2;

    return dgettext( 'lire-' . $self->{'report_spec'}->superservice(),
                     ( $self->{'label'} ? $self->{'label'} : $self->name() ) );
}

=pod

=head1 METHODS FOR SUBCLASSES

Subclasses must use a hash to represent their instance data. They
should call init() from their constructor method.

=head2 init( %params )

Lire::Report is an abstract class which shouldn't be instanciated
directly. Subclasses should call the init() method in their
constructor. This method initializes the attributes common to all
operators. The %params keys that should be defined are:

=over

=item op

The operator's kind. That's the XML element's name. It's a mandatory
parameter.

=item parent

This operator's parent. This must be present unless the element is the
top-level aggregator.

=item report_spec

The report specification in which this operator is added.

=item label

This operator's label. This is an optional information.

=back

This method returns the object.

=cut

sub init {
    my ( $self, %args ) = @_;

    check_param( $args{'op'}, 'op' );
    check_object_param( $args{'report_spec'}, 'report_spec',
                        'Lire::ReportSpec' );
    check_object_param( $args{'parent'}, 'parent', 'Lire::Aggregator' )
      if ( defined $args{'parent'} );

    $self->{'report_spec'} = $args{'report_spec'};
    $self->{'op'}          = $args{'op'};
    $self->{'parent'}      = $args{'parent'};
    $self->label( $args{'label'} )
      if defined $args{'label'};

    return;
}

=pod

=head2 print( $fh, $prefix )

This is the method which is called to write an XML representation of
this operator.

$fh is the file handle onto which to output the XML representation.
$indent is an integer which gives the number of spaces which should be
used as indentation when writing the XML.

=cut

sub print {
    croak( "unimplemented print() method" );
}

=pod

=head2 build_query( $query )

=cut

sub build_query {
    croak( "unimplemented build_query() method in ", ref $_[0] );
}

=pod

=head1 MERGING API

The methods should be implemented to support merging of data generated
by this operator from two reports.

=cut

=pod

=head2 init_merge()

This method is called once before merging of XML reports is started.

=cut

sub init_merge {
    croak "Unimplemented init_merge() method in ", ref $_[0], "\n";
}

=pod

=head2 end_merge()

This method is once after all the XML reports were merged.

=cut

sub end_merge {
    croak "Unimplemented end_merge() method in ", ref $_[0], "\n";
}

=pod

=head2 init_group_data()

This method should return a data structure that will be passed the
call to update_group_data() and merge_group_data(). There will be one
such data structure for every group that exists in the aggregator
containing this operator.

There are a few restrictions placed on this data structure in order to
assure that it can be sorted by aggregator that need to sort their
entries. If the data structure is

=over

=item SCALAR

That value can be used for sorting purpose.

=item SCALAR REFERENCE

The scalar value will be used as sorting key.

=item ARRAY REFERENCE

The first item in the array reference will be used as sorting key.

=item HASH REFERENCE

The C<sort_value> items in the hash will be used as sorting key.

=item OBJECT REFERENCE

The value returned by the sort_value() method will be used as sorting
key.

=back

A function group_data_value() can be used to extract the sorting key
value according to these rules. It can be imported:

    use Lire::ReportOperator qw/group_data_value/;

    my $v = group_data_value( $data );

=cut

sub init_group_data {
    croak "Unimplemented init_group_data() method in ", ref $_[0], "\n";
}

=pod

=head2 merge_group_data( $value, $data )

This method will be called once for each value generated by this
operator in the XML reports to be merged. 

The $value parameter can be either an hash reference or a
Lire::Report::Group object. It will be the same things that was
generated by this operator.

$data is the group data structure returned by init_group_data() for
the group where this $value should be merged.

=cut

sub merge_group_data {
    croak "Unimplemented merge_group_data() method in", ref $_[0], "\n";
}

=pod

=head2 end_group_data( $data )

This method will be called once for every group that was created by
the aggregator containing this operator. The $data parameter is the
data structure that was returned by init_group_data() for the current
group.

One possible use of this method would be, for example, for the avg
operator to compute the actual average since it cannot be done until
all the records are processed.

=cut

sub end_group_data {
    croak "Unimplemented end_group_data() method in", ref $_[0], "\n";
}

=pod

=head2 add_entry_value( $entry, $data )

This method will be called once for each group created in by the
aggregator. In this method, the operator should add the appropriate
name and value for this entry. $entry is the Lire::Report::Entry
object to which the names and/or values should be added. $data is the
data structure returned by init_group_data() corresponding for the
current group.

For example, its in this method that the sum operator will add the
value containing the sum for the current entry.

=cut

sub add_entry_value {
    croak "Unimplemented add_entry_value() method in ", ref $_[0], "\n";
}

=pod

=head2 group_data_value($data)

Returns the "value" from one data item created by a init_group_data()
method. This is a function not a method.

=cut

sub group_data_value {
    if ( ref $_[0] eq 'SCALAR' ) {
	return ${$_[0]};
    } elsif (ref $_[0] eq 'ARRAY' ) {
	return $_[0][0];
    } elsif (ref $_[0] eq 'HASH' ) {
	return $_[0]{'sort_value'};
    } elsif (ref $_[0]) {
	return $_[0]->sort_value;
    } else {
	return$_[0];
    }
}

# keep perl happy
1;

__END__

=head1 SEE ALSO

Lire::ReportSpec(3pm), Lire::Aggregate(3pm), Lire::Aggregator(3pm).

=head1 AUTHOR

  Francis J. Lacoste <flacoste@logreport.org>

=head1 VERSION

$Id: ReportOperator.pm,v 1.21 2006/07/23 13:16:29 vanbaal Exp $

=head1 COPYRIGHT

Copyright (C) 2001-2004 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. 

=cut
