package Lire::Report::Entry;

use strict;

use Carp;

use Lire::Utils qw/ xml_encode check_object_param /;
use Lire::Report::Group;

=pod

=head1 NAME

Lire::Report::Entry - Interface to subreport's data.

=head1 SYNOPSIS

    foreach my $name ( $entry->names() ) {
        print "Name: ", $name->{'content'}, "\n";
    }

    foreach my $value ( $entry->values() ) {
        if ( ref $value eq 'Lire::Report::Group' ) {
            # Value is a group
            foreach my $e ( $value->entries() ) {
                print_entry( $e );
            }
        } else {
            print "Value: ", $value->{'content'}, "\n";
        }
    }

=head1 DESCRIPTION

The Lire::Report::Entry objects are used to hold the subreport's data.

=head1 CONSTRUCTOR

One creates a new Entry object by using the create_entry method on a
Lire::Report::Subreport or Lire::Report::Group object.  Use the add_name(),
create_group() and add_value() methods to fill the entry.

=cut

sub new {
    my ( $class, $group ) = @_;

    check_object_param( $group, 'group', 'Lire::Report::Group' );

    return bless( {'data' => [],
                   'group' => $group},
                  $class );
}

=pod

=head1 OBJECT METHODS

=head2 row_idx()

Returns the row index in the table body where this entry's data should
be displayed. If undef, this entry shouldn't be displayed.

=cut

sub row_idx {
    $_[0]{'row_idx'} = $_[1] if @_ == 2;

    return $_[0]{'row_idx'};
}

=pod

=head2 subreport()

Returns the Lire::Report::Subreport object in which this entry is contained.

=cut

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

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

    return $parent;
}

=pod

=head2 group()

Returns the Lire::Report::Group object which contains this entry.

=cut

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

=pod

=head2 group_info()

Returns the Lire::Report::GroupInfo which contains the information
related to the group in which this entry is.

=cut

sub group_info {
    return $_[0]{'group'}->group_info();
}

=pod

=head2 data()

Returns as an array the data contained in this entry. This is a list
of hashes or Lire::Report::Group object.

=cut

sub data {
    return @{$_[0]->{'data'}};
}

=pod

=head2 data_by_name( $name )

Returns the data item contained in this Entry that was generated by
the operator named $name. Returns undef if no such data item can be
found.

=cut

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

    foreach my $d ( @{$self->{'data'}} ) {
        if ( UNIVERSAL::isa( $d, 'Lire::Report::Group' ) ) {
            return $d if $d->group_info()->name() eq $name;
        } else {
            return $d if $d->{'col_info'}->name() eq $name;
        }
    }

    return undef;
}

=pod

=head2 names()

Returns the names of the entry. This is an array of hashes. The name's
hash contains the following keys:

=over 4

=item type

Always set to 'name'.

=item content

That's the actual content of the name element. This contains the name
in a format suitable for display.

=item value

This contains the unformatted value of the name. For example, when the
name is a time string, this attribute will contains the time in
seconds since epoch.

=item missing_cases

This value contains the number of DLF records which had a undefined
value in one of the required fields to compute this statistic.

=item range

For some names, the actual content express a range (time, size, etc.).
This attribute contains the length of the range.

=item col_info

The Lire::Report::ColumnInfo object describing this name.

=back

=cut

sub names {
    return grep { $_->{'type'} eq 'name' } @{$_[0]->{'data'}};
}

=pod

=head2 add_name( $content, [$value], [$range] )

Adds a new name to this entry. Consult the documentation of the
names() method for a description of the meaning of the various
parameters which have the same meaning as the keys with the same
name.

The names, values and groups should be added in the order specified by
this entry's GroupInfo. You'll get an exception otherwise.

=cut

sub add_name {
    my ( $self, $content, $value, $range ) = @_;

    $content = '' unless defined $content;
    $value = $content unless defined $value;

    my $idx = $#{$self->{'data'}} + 1;
    my $info = $self->group_info->info_by_index( $idx );
    check_object_param( $info, "Data_$idx", 'Lire::Report::ColumnInfo' );

    my %n = ('type'     => 'name',
             'content'  => $content,
             'value'    => $value,
             'col_info' => $info);
    $n{'range'} = $range if defined $range;

    push @{$self->{'data'}}, \%n;

    return;
}

=pod

=head2 values()

Returns the values of the entry. This is an array of hashes or objects.
If the value is an hash, it has the following keys:

=over 4

=item type

Always set to 'value'.

=item content

That's the actual content of the value element. This contains the
value in a format suitable for display.

=item value

This contains the unformatted value. For example, when bytes are
displayed using "1M" or "1.1G", this will contain the value in single bytes.

=item missing_cases

The number of DLF records which had an undefined value in one of
fields required by this operator.

=item total

This is used by values that represent an average. It contains the
total which makes up the average.

=item n

This is used by values that represent an average. It contains the
total which was used in the division to compute the average.

=item col_info

The Lire::Report::ColumnInfo object describing this value.

=back

=cut

sub values {
    return grep { $_->{'type'} eq 'value' } @{$_[0]->{'data'}};
}

=pod

=head2 groups()

Returns in an array the Lire::Report::Group contained in this Entry.

=cut

sub groups {
    return grep { UNIVERSAL::isa( $_, 'Lire::Report::Group' ) }
      @{$_[0]->{'data'}};
}

=pod

=head2 create_group()

Creates a new group for this entry. This will also append it
to this entry data. If you create the group out of order compared to
the names and values that should go in that entry, you'll get an
exception.

=cut

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

    my $idx = $#{$self->{'data'}} + 1;
    my $info = $self->group_info->info_by_index( $idx );

    check_object_param( $info, "Data_$idx", 'Lire::Report::GroupInfo' );

    my $group = new Lire::Report::Group( $self, $info );
    push @{$self->{'data'}}, $group;

    return $group;
}

=pod

=head2 add_value( %value )

Adds a new value to this entry. The value hash should at least
contains the 'content' key. It can also includes values for the 'n',
'total' and 'missing_cases' keys. Consult the documentation of the
values() method for a description of the meaning of the various
parameters: these have the same meaning as the keys with the same
names.

The names, values and groups should be added in the order specified by
this entry's GroupInfo. You'll get an exception otherwise.

=cut

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

    my $value = ref( $_[1] ) ? $_[1] : { @_[1..$#_] };

    $value->{'content'} = "" unless defined $value->{'content'};
    $value->{'value'} = $value->{'content'} unless defined $value->{'value'};

    my $idx = $#{$self->{'data'}} + 1;
    my $info = $self->group_info()->info_by_index( $idx );

    check_object_param( $info, "Data_$idx", 'Lire::Report::ColumnInfo' );

    $value->{'type'} = 'value';
    $value->{'col_info'} = $info;
    $value->{'n'} = undef
      unless exists $value->{'n'};
    $value->{'total'} = undef
      unless exists $value->{'total'};
    $value->{'missing_cases'} = 0
      unless defined $value->{'missing_cases'};
    push @{$self->{'data'}}, $value;

    return;
}

#
# helper method for Lire::Report::Subreport::last_row_idx()
#
sub _last_row_idx {
    my $self = $_[0];

    return undef unless defined $self->{'row_idx'};

    my $last = $self->{'row_idx'};
    foreach my $group ( $self->groups() ) {
        my $group_last = $group->_last_row_idx();
        $last = $group_last
          if defined $group_last && $group_last > $last;
    }

    return $last;
}

#
# helper method for Lire::Report::Subreport::getrow_by_idx()
#

sub _getrow_by_idx {
    my ( $self, $idx, $row ) = @_;

    if ( $self->{'row_idx'} == $idx ) {
        foreach my $item ( @{$self->{'data'}} ) {
            my @cells = ( UNIVERSAL::isa( $item, 'Lire::Report::Group' )
                          ? $item->summary_values()
                          : $item );
            foreach my $cell ( @cells ) {
                $row->[$cell->{'col_info'}->col_start()] = $cell;
            }
        }
    } else {
        foreach my $group ( $self->groups() ) {
            $group->_getrow_by_idx( $idx, $row );
        }
    }
}

#------------------------------------------------------------------------
# Method write_report( $fh, $indent )
#
sub write_report {
    my ( $self, $fh, $indent ) = @_;

    $fh ||= *STDOUT;
    my $pfx = ' ' x $indent;

    print $fh $pfx, "<lire:entry";
    print $fh qq{ row-idx="$self->{'row_idx'}"}
      if defined $self->row_idx();
    print $fh ">\n";
    foreach my $d ( @{$self->{'data'}} ) {
        if ( $d->{'type'} eq 'name' ) {
            write_name( $fh, $pfx, $d );
        } elsif ( $d->{'type'} eq 'value' ) {
            write_value( $fh, $pfx, $d );
        } else {
            # Group
            $d->write_report( $fh, $indent + 1 );
        }
    }

    print $fh "$pfx</lire:entry>\n";

    return;
}

#------------------------------------------------------------------------
# Function write_name( $fh, $pfx, $d )
#
# Writes $d into a lire:name element.
sub write_name {
    my ( $fh, $pfx, $d ) = @_;

    print $fh $pfx, ' <lire:name col="', $d->{'col_info'}->name(), '"';
    print $fh ' value="', xml_encode( $d->{'value'} ), '"',
      if $d->{'value'} ne $d->{'content'};
    print $fh qq! range="$d->{'range'}"!
      if defined $d->{'range'};
    print $fh ">", xml_encode( $d->{'content'} ), "</lire:name>\n";

    return;
}

#------------------------------------------------------------------------
# Function write_value($fh, $pfx, $d)
#
# Writes $d into a lire:value element.
sub write_value {
    my ( $fh, $pfx, $d ) = @_;

    print $fh $pfx, ' <lire:value col="', $d->{'col_info'}->name(), '"',
      ' missing-cases="', $d->{'missing_cases'}, '"';
    print $fh qq! total="$d->{'total'}"!
      if defined $d->{'total'};
    print $fh qq! n="$d->{'n'}"!
      if defined $d->{'n'};
    print $fh qq! value="$d->{'value'}"!
      if $d->{'value'} ne $d->{'content'};
    print $fh ">$d->{'content'}</lire:value>\n";

    return;
}

#------------------------------------------------------------------------
# Method delete()
#
# Remove circular references
sub delete {
    my $self = $_[0];

    foreach my $g ( grep { $_->{'type'} eq 'group' } $self->values() ) {
        $g->delete();
    }
    %$self = ();

    return;
}

# keep perl happy
1;

=pod

=head1 SEE ALSO

Lire::ReportParser::ReportBuilder(3pm) Lire::Report(3pm)
Lire::Report::Subreport(3pm) Lire::Report::Section(3pm)
Lire::Report::Group(3pm)

=head1 VERSION

$Id: Entry.pm,v 1.32 2006/07/23 13:16:31 vanbaal Exp $

=head1 COPYRIGHT

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

=head1 AUTHOR

Francis J. Lacoste <flacoste@logreport.org>

=cut

