#**************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
#**************************************************************

package installer::patch::MsiTable;

=head1 NAME

    package installer::patch::MsiTable - Class that represents one table of an Msi file.

=cut
    
use installer::patch::MsiRow;

use strict;

=head new ($class, $filename, $table_name)

    Create a new MsiTable object from the output of a previous
    msidb.exe run.  The table is named $table_name, its data is read
    from $filename.

=cut
sub new ($$$)
{
    my ($class, $filename, $table_name) = @_;

    my $self = {
        'name' => $table_name,
        'is_valid' => 1
    };
    bless($self, $class);

    if ( -f $filename)
    {
        $self->ReadFile($filename);
    }
    return $self;
}




sub IsValid ($)
{
    my ($self) = @_;
    return $self->{'is_valid'};
}




sub Trim ($)
{
    my $line = shift;

    $line =~ s/(^\s+|\s+$)//g;

    return $line;
}



=head2 ReadFile($self, $filename)

    Read the content of the table from the specified .idt file.
    For each row a MsiRow object is appended to $self->{'rows'}.

=cut
sub ReadFile ($$)
{
    my ($self, $filename) = @_;

    if ( ! (-f $filename && -r $filename))
    {
        printf STDERR ("can not open idt file %s for reading\n", $filename);
        $self->{'is_valid'} = 0;
        return;
    }
    
    open my $in, "<", $filename;

    my $columns = Trim(<$in>);
    $self->{'columns'} = [split(/\t/, $columns)];

    my $column_specs = Trim(<$in>);
    $self->{'column_specs'} = [split(/\t/, $column_specs)];

    # Table name, index columns.
    my $line = Trim(<$in>);
    my @items = split(/\t/, $line);
    if (scalar @items == 3)
    {
        $self->{'codepage'} = shift @items;
    }
    my $table_name = shift @items;
    if ($table_name ne $self->{'name'})
    {
        printf STDERR ("reading wrong table data for table '%s' (got %s)\n", $self->{'name'}, $table_name);
        $self->{'is_valid'} = 0;
        return;
    }
    $self->{'index_columns'} = [@items];
    $self->{'index_column_index'} = $self->GetColumnIndex($items[0]);

    my $rows = [];
    while (<$in>)
    {
        # Remove all trailing returns and newlines.  Keep trailing spaces and tabs.
        s/[\r\n]+$//g;
        
        my @items = split(/\t/, $_);
        push @$rows, new installer::patch::MsiRow($self, @items);
    }
    $self->{'rows'} = $rows;

    return $self;
}



=head2 GetColumnCount($self)

    Return the number of columns in the table.
    
=cut
sub GetColumnCount ($)
{
    my ($self) = @_;

    return scalar @{$self->{'columns'}};
}




=head2 GetRowCount($self)

    Return the number of rows in the table.
    
=cut
sub GetRowCount ($)
{
    my ($self) = @_;

    return scalar @{$self->{'rows'}};
}




=head2 GetColumnIndx($self, $column_name)

    Return the 0 based index of the column named $column_name.  Use
    this to speed up (slightly) access to column values when accessing
    many or all rows of a table.

=cut
sub GetColumnIndex ($$)
{
    my ($self, $column_name) = @_;

    my $index = 0;
    foreach my $name (@{$self->{'columns'}})
    {
        if ($name eq $column_name)
        {
            return $index;
        }
        ++$index;
    }

    printf STDERR ("did not find column %s in %s\n", $column_name, join(" and ", @{$self->{'columns'}}));
    return -1;
}




=head2 GetValue($self, $selector_column, $selector_column_value, $value_column)

    Find the row in which the $selector_column has value
    $selector_column_value and return its value in the $value_column.

=cut
    
sub GetValue ($$$$)
{
    my ($self, $selector_column, $selector_column_value, $value_column) = @_;

    my $row = $self->GetRow($selector_column, $selector_column_value);
    if (defined $row)
    {
        return $row->GetValue($value_column);
    }
    else
    {
        return undef;
    }
}




=head2 GetRow($self, $column, $value)

    Return the (first) row which has $value in $column.
    
=cut
sub GetRow ($$$)
{
    my ($self, $column, $value) = @_;

    my $column_index = $self->GetColumnIndex($column);
    if ($column_index<0)
    {
        printf STDERR "ERROR: unknown column $column in table $self->{'name'}\n";
        return undef;
    }
    
    foreach my $row (@{$self->{'rows'}})
    {
        if ($row->GetValue($column_index) eq $value)
        {
            return $row;
        }
    }

    printf STDERR ("ERROR: did not find row for %s->%s in %s\n",
        $column,
        $value,
        table $self->{'name'});
        
    return undef;
}




=head2 GetAllRows ($self)

    Return the reference to an array that contains all rows of the table.
    
=cut
    
sub GetAllRows ($)
{
    my $self = shift;

    return $self->{'rows'};
}





1;
