The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

package IO::Uncompress::Base ;
 
use warnings;
use bytes;

our (@ISA, $VERSION, @EXPORT_OK, %EXPORT_TAGS);
#@ISA    = qw(Exporter IO::File);
@ISA    = qw(Exporter );


$VERSION = '2.006';

use constant G_EOF => 0 ;
use constant G_ERR => -1 ;

use IO::Compress::Base::Common v2.006 ;
#use Parse::Parameters ;

use IO::File ;
use Symbol;
use Scalar::Util < qw(readonly);
use List::Util < qw(min);
use Carp ;

%EXPORT_TAGS = %( );
push @{ %EXPORT_TAGS{+all} }, < @EXPORT_OK ;
#Exporter::export_ok_tags('all') ;


sub smartRead
{
    my $self = @_[0];
    my $out = @_[1];
    my $size = @_[2];
    $$out = "" ;

    my $offset = 0 ;


    if (defined $self->{?InputLength}) {
        return 0
            if $self->{?InputLengthRemaining} +<= 0 ;
        $size = min($size, *$self->{?InputLengthRemaining});
    }

    if ( length $self->{?Prime} ) {
        #$$out = substr($self->{Prime}, 0, $size, '') ;
        $$out = substr($self->{?Prime}, 0, $size) ;
        substr($self->{Prime}, 0, $size,  '') ;
        if (length $$out == $size) {
            $self->{+InputLengthRemaining} -= length $$out
                if defined $self->{?InputLength};

            return length $$out ;
        }
        $offset = length $$out ;
    }

    my $get_size = $size - $offset ;

    #if ( defined $self->{InputLength} ) {
    #    $get_size = min($get_size, $self->{InputLengthRemaining});
    #}

    if (defined $self->{?FH})
      { read($self->{?FH}, $$out, $get_size, $offset) }
    elsif (defined $self->{?InputEvent}) {
        my $got = 1 ;
        while (length $$out +< $size) {
            last 
                if ($got = $self->{?InputEvent}->($$out, $get_size)) +<= 0;
        }

        if (length $$out +> $size ) {
            #$self->{Prime} = substr($$out, $size, length($$out), '');
            $self->{+Prime} = substr($$out, $size, length($$out));
            substr($$out, $size, length($$out),  '');
        }

       $self->{+EventEof} = 1 if $got +<= 0 ;
    }
    else {
       no warnings 'uninitialized';
       my $buf = $self->{?Buffer} ;
       $$buf = '' unless defined $$buf ;
       #$$out = '' unless defined $$out ;
       substr($$out, $offset, undef, substr($$buf, $self->{?BufferOffset}, $get_size));
       if ($self->{?ConsumeInput})
         { substr($$buf, 0, $get_size, '') }
       else  
         { $self->{+BufferOffset} += length($$out) - $offset }
    }

    $self->{+InputLengthRemaining} -= length($$out) #- $offset 
        if defined $self->{?InputLength};
        
    $self->saveStatus(length $$out +< 0 ?? STATUS_ERROR !! STATUS_OK) ;

    return length $$out;
}

sub pushBack
{
    my $self = shift ;

    return if ! defined @_[0] || length @_[0] == 0 ;

    if (defined $self->{?FH} || defined $self->{?InputEvent} ) {
        $self->{+Prime} = @_[0] . $self->{?Prime} ;
        $self->{+InputLengthRemaining} += length(@_[0]);
    }
    else {
        my $len = length @_[0];

        if($len +> $self->{?BufferOffset}) {
            $self->{+Prime} = substr(@_[0], 0, $len - $self->{?BufferOffset}) . $self->{?Prime} ;
            $self->{+InputLengthRemaining} = $self->{?InputLength};
            $self->{+BufferOffset} = 0
        }
        else {
            $self->{+InputLengthRemaining} += length(@_[0]);
            $self->{+BufferOffset} -= length(@_[0]) ;
        }
    }
}

sub smartSeek
{
    my $self   = shift ;
    my $offset = shift ;
    my $truncate = shift;
    #print "smartSeek to $offset\n";

    # TODO -- need to take prime into account
    if (defined $self->{?FH})
      { $self->{?FH}->seek($offset, SEEK_SET) }
    else {
        $self->{+BufferOffset} = $offset ;
        substr(${ $self->{?Buffer} }, $self->{?BufferOffset}, undef, '')
            if $truncate;
        return 1;
    }
}

sub smartWrite
{
    my $self   = shift ;
    my $out_data = shift ;

    if (defined $self->{?FH}) {
        # flush needed for 5.8.0 
        defined $self->{?FH}->write($out_data, length $out_data) &&
        defined $self->{FH}->flush() ;
    }
    else {
       my $buf = $self->{?Buffer} ;
       substr($$buf, $self->{?BufferOffset}, length $out_data, $out_data) ;
       $self->{+BufferOffset} += length($out_data) ;
       return 1;
    }
}

sub smartReadExact
{
    return @_[0]->smartRead(@_[1], @_[2]) == @_[2];
}

sub smartEof
{
    my $self = @_[0];

    return 0 if length $self->{?Prime} || $self->{?PushMode};

    if (defined $self->{?FH})
     { eof($self->{?FH}) }
    elsif (defined $self->{?InputEvent})
     { $self->{?EventEof} }
    else 
     { $self->{?BufferOffset} +>= length(${ $self->{?Buffer} }) }
}

sub clearError
{
    my $self   = shift ;

    $self->{+ErrorNo}  =  0 ;
    ${ $self->{Error} } = '' ;
}

sub saveStatus
{
    my $self   = shift ;
    my $errno = shift() + 0 ;
    #return $errno unless $errno || ! defined $self->{ErrorNo};
    #return $errno unless $errno ;

    $self->{+ErrorNo}  = $errno;
    ${ $self->{Error} } = '' ;

    return $self->{?ErrorNo} ;
}


sub saveErrorString
{
    my $self   = shift ;
    my $retval = shift ;

    #return $retval if ${ $self->{Error} };

    ${ $self->{Error} } = shift ;
    $self->{+ErrorNo} = shift() + 0 if (nelems @_) ;

    #warn "saveErrorString: " . ${ $self->{Error} } . " " . $self->{Error} . "\n" ;
    return $retval;
}

sub croakError
{
    my $self   = shift ;
    $self->saveErrorString(0, @_[0]);
    croak @_[0];
}


sub closeError
{
    my $self = shift ;
    my $retval = shift ;

    my $errno = $self->{?ErrorNo};
    my $error = ${ $self->{?Error} };

    $self->close();

    $self->{+ErrorNo} = $errno ;
    ${ $self->{Error} } = $error ;

    return $retval;
}

sub error
{
    my $self   = shift ;
    return ${ $self->{?Error} } ;
}

sub errorNo
{
    my $self   = shift ;
    return $self->{?ErrorNo};
}

sub HeaderError
{
    my @($self) =@( shift);
    return $self->saveErrorString(undef, "Header Error: @_[0]", STATUS_ERROR);
}

sub TrailerError
{
    my @($self) =@( shift);
    return $self->saveErrorString( <G_ERR, "Trailer Error: @_[0]", STATUS_ERROR);
}

sub TruncatedHeader
{
    my @($self) =@( shift);
    return $self->HeaderError("Truncated in @_[0] Section");
}

sub TruncatedTrailer
{
    my @($self) =@( shift);
    return $self->TrailerError("Truncated in @_[0] Section");
}

sub postCheckParams
{
    return 1;
}

sub checkParams
{
    my $self = shift ;
    my $class = shift ;

    my $got = shift || IO::Compress::Base::Parameters::new();
    
    my $Valid = \%(
                    'BlockSize'     => \@(1, 1, Parse_unsigned, 16 * 1024),
                    'AutoClose'     => \@(1, 1, Parse_boolean,  0),
                    'Strict'        => \@(1, 1, Parse_boolean,  0),
                    'Append'        => \@(1, 1, Parse_boolean,  0),
                    'Prime'         => \@(1, 1, Parse_any,      undef),
                    'MultiStream'   => \@(1, 1, Parse_boolean,  0),
                    'Transparent'   => \@(1, 1, Parse_any,      1),
                    'Scan'          => \@(1, 1, Parse_boolean,  0),
                    'InputLength'   => \@(1, 1, Parse_unsigned, undef),
                    'BinModeOut'    => \@(1, 1, Parse_boolean,  0), <
                    #'Encode'        => [1, 1, Parse_any,       undef],

                   #'ConsumeInput'  => [1, 1, Parse_boolean,  0],

                    $self->getExtraParams(),

                    #'Todo - Revert to ordinary file on end Z_STREAM_END'=> 0,
                    # ContinueAfterEof
                ) ;

    $Valid->{+TrailingData} = \@(1, 1, Parse_writable_scalar, undef)
        if  $self->{?OneShot} ;
        
    $got->parse($Valid, < @_ ) 
        or $self->croakError("$($class): $got->{Error}")  ;

    $self->postCheckParams($got) 
        or $self->croakError("$($class): " . $self->error())  ;

    return $got;
}

sub _create
{
    my $obj = shift;
    my $got = shift;
    my $append_mode = shift ;

    my $class = ref $obj;
    $obj->croakError("$class: Missing Input parameter")
        if ! nelems @_ && ! $got ;

    my $inValue = shift ;

    $obj->{+OneShot}           = 0 ;

    if (! $got)
    {
        $got = $obj->checkParams($class, undef, < @_)
          or die < $obj->croakError("$class: Failed checkParams");
    }

    my $inType  = whatIsInput($inValue, 1);

    $obj->ckInputParam($class, $inValue, 1) 
      or return undef;

    $obj->{+InNew} = 1;

    $obj->ckParams($got)
        or $obj->croakError("$($class): " . *$obj->{Error});

    if ($inType eq 'buffer' || $inType eq 'code') {
        *$obj->{+Buffer} = $inValue ;        
        *$obj->{+InputEvent} = $inValue 
           if $inType eq 'code' ;
    }
    else {
        if ($inType eq 'handle') {
            *$obj->{+FH} = $inValue ;
            *$obj->{+Handle} = 1 ;

            # Need to rewind for Scan
            *$obj->{?FH}->seek(0, SEEK_SET) 
                if $got->value('Scan');
        }  
        else {    
            my $mode = '<';
            $mode = '+<' if $got->value('Scan');
            $obj->{+StdIO} = ($inValue eq '-');
            $obj->{+FH} = IO::File->new( "$inValue", $mode)
                or return $obj->saveErrorString(undef, "cannot open file '$inValue': $^OS_ERROR", $^OS_ERROR) ;
        }
        
        #*$obj->{LineNo} = $. = 0;
        setBinModeInput($obj->{?FH}) ;

        my $buff = "" ;
        $obj->{+Buffer} = \$buff ;
    }

    if ($got->parsed('Encode')) { 
        my $want_encoding = $got->value('Encode');
        $obj->{+Encoding} = getEncoding($obj, $class, $want_encoding);
    }


    $obj->{+InputLength}       = $got->parsed('InputLength') 
                                    ?? $got->value('InputLength')
                                    !! undef ;
    $obj->{+InputLengthRemaining} = $got->value('InputLength');
    $obj->{+BufferOffset}      = 0 ;
    $obj->{+AutoClose}         = $got->value('AutoClose');
    $obj->{+Strict}            = $got->value('Strict');
    $obj->{+BlockSize}         = $got->value('BlockSize');
    $obj->{+Append}            = $got->value('Append');
    $obj->{+AppendOutput}      = $append_mode || $got->value('Append');
    $obj->{+ConsumeInput}      = $got->value('ConsumeInput');
    $obj->{+Transparent}       = $got->value('Transparent');
    $obj->{+MultiStream}       = $got->value('MultiStream');

    # TODO - move these two into RawDeflate
    $obj->{+Scan}              = $got->value('Scan');
    $obj->{+ParseExtra}        = $got->value('ParseExtra') 
                                  || $got->value('Strict')  ;
    $obj->{+Type}              = '';
    $obj->{+Prime}             = $got->value('Prime') || '' ;
    $obj->{+Pending}           = '';
    $obj->{+Plain}             = 0;
    $obj->{+PlainBytesRead}    = 0;
    $obj->{+InflatedBytesRead} = 0;
    $obj->{+UnCompSize}        = U64->new();
    $obj->{+CompSize}          = U64->new();
    $obj->{+TotalInflatedBytesRead} = 0;
    $obj->{+NewStream}         = 0 ;
    $obj->{+EventEof}          = 0 ;
    $obj->{+ClassName}         = $class ;
    $obj->{+Params}            = $got ;

    if ($obj->{?ConsumeInput}) {
        $obj->{+InNew} = 0;
        $obj->{+Closed} = 0;
        return $obj
    }

    my $status = $obj->mkUncomp($class, $got);

    return undef
        unless defined $status;

    if ( !  $status) {
        return undef 
            unless $obj->{?Transparent};

        $obj->clearError();
        $obj->{+Type} = 'plain';
        $obj->{+Plain} = 1;
        #$status = $obj->mkIdentityUncomp($class, $got);
        $obj->pushBack($obj->{HeaderPending})  ;
    }

    push @{ $obj->{+InfoList} }, $obj->{?Info} ;

    $obj->saveStatus(STATUS_OK) ;
    $obj->{+InNew} = 0;
    $obj->{+Closed} = 0;

    return $obj;
}

sub ckInputParam
{
    my $self = shift ;
    my $from = shift ;
    my $inType = whatIsInput(@_[0], @_[1]);

    $self->croakError("$from: input parameter not a filename, filehandle, array ref or scalar ref")
        if ! $inType ;

    if ($inType  eq 'filename' )
    {
        $self->croakError("$from: input filename is undef or null string")
            if ! defined @_[0] || @_[0] eq ''  ;

        if (@_[0] ne '-' && ! -e @_[0] )
        {
            return $self->saveErrorString(undef, 
                            "input file '@_[0]' does not exist", STATUS_ERROR);
        }
    }

    return 1;
}


sub _inf
{
    my $obj = shift ;

    my $class = (caller)[[0]] ;
    my $name = (caller(1))[[3]] ;

    $obj->croakError("$name: expected at least 1 parameters\n")
        unless (nelems @_) +>= 1 ;

    my $input = shift ;
    my $haveOut = (nelems @_) ;
    my $output = shift ;


    my $x = Validator->new($class, $obj->{?Error}, $name, $input, $output)
        or return undef ;
    
    push @_, $output if $haveOut && $x->{?Hash};

    $obj->{+OneShot} = 1 ;
    
    my $got = $obj->checkParams($name, undef, < @_)
        or return undef ;

    if ($got->parsed('TrailingData'))
    {
        $obj->{+TrailingData} = $got->value('TrailingData');
    }

    $obj->{+MultiStream} = $got->value('MultiStream');
    $got->value('MultiStream', 0);

    $x->{+Got} = $got ;

#    if ($x->{Hash})
#    {
#        while (my($k, $v) = each %$input)
#        {
#            $v = \$input->{$k} 
#                unless defined $v ;
#
#            $obj->_singleTarget($x, $k, $v, @_)
#                or return undef ;
#        }
#
#        return keys %$input ;
#    }
    
    if ($x->{?GlobMap})
    {
        $x->{+oneInput} = 1 ;
        foreach my $pair ( @{ $x->{Pairs} })
        {
            my @($from, $to) =  @$pair ;
            $obj->_singleTarget($x, $from, $to, < @_)
                or return undef ;
        }

        return scalar nelems @{ $x->{?Pairs} } ;
    }

    if (! $x->{?oneOutput} )
    {
        my $inFile = ($x->{?inType} eq 'filenames' 
                        || $x->{?inType} eq 'filename');

        $x->{+inType} = $inFile ?? 'filename' !! 'buffer';
        
        foreach my $in (@($x->{?oneInput} ?? $input !! < @$input))
        {
            my $out ;
            $x->{+oneInput} = 1 ;

            $obj->_singleTarget($x, $in, $output, < @_)
                or return undef ;
        }

        return 1 ;
    }

    # finally the 1 to 1 and n to 1
    return $obj->_singleTarget($x, $input, $output, < @_);

    croak "should not be here" ;
}

sub retErr
{
    my $x = shift ;
    my $string = shift ;

    ${ $x->{Error} } = $string ;

    return undef ;
}

sub _singleTarget
{
    my $self      = shift ;
    my $x         = shift ;
    my $input     = shift;
    my $output    = shift;
    
    my $buff = '';
    $x->{+buff} = \$buff ;

    my $fh ;
    if ($x->{?outType} eq 'filename') {
        my $mode = '>' ;
        $mode = '>>'
            if $x->{?Got}->value('Append') ;
        $x->{+fh} = IO::File->new( "$output", $mode) 
            or return retErr($x, "cannot open file '$output': $^OS_ERROR") ;
        binmode $x->{?fh} if $x->{?Got}->valueOrDefault('BinModeOut');

    }

    elsif ($x->{?outType} eq 'handle') {
        $x->{+fh} = $output;
        binmode $x->{?fh} if $x->{?Got}->valueOrDefault('BinModeOut');
        if ($x->{?Got}->value('Append')) {
                seek($x->{?fh}, 0, SEEK_END)
                    or return retErr($x, "Cannot seek to end of output filehandle: $^OS_ERROR") ;
            }
    }

    
    elsif ($x->{?outType} eq 'buffer' )
    {
        $$output = '' 
            unless $x->{?Got}->value('Append');
        $x->{+buff} = $output ;
    }

    if ($x->{?oneInput})
    {
        defined $self->_rd2($x, $input, $output)
            or return undef; 
    }
    else
    {
        for my $element (@( ($x->{?inType} eq 'hash') ?? < keys %$input !! < @$input))
        {
            defined $self->_rd2($x, $element, $output) 
                or return undef ;
        }
    }


    if ( ($x->{?outType} eq 'filename' && $output ne '-') || 
         ($x->{?outType} eq 'handle' && $x->{?Got}->value('AutoClose'))) {
        $x->{fh}->close() 
            or return retErr($x, $^OS_ERROR); 
        delete $x->{fh};
    }

    return 1 ;
}

sub _rd2
{
    my $self      = shift ;
    my $x         = shift ;
    my $input     = shift;
    my $output    = shift;
        
    my $z = createSelfTiedObject($x->{?Class}, $self->{?Error});
    
    $z->_create($x->{?Got}, 1, $input, < @_)
        or return undef ;

    my $status ;
    my $fh = $x->{?fh};
    
    while (1) {

        while (($status = $z->read($x->{buff})) +> 0) {
            if ($fh) {
                print $fh, ${ $x->{?buff} }
                    or return $z->saveErrorString(undef, "Error writing to output file: $^OS_ERROR", $^OS_ERROR);
                ${ $x->{buff} } = '' ;
            }
        }

        if (! $x->{?oneOutput} ) {
            my $ot = $x->{?outType} ;

            if ($ot eq 'array') 
              { push @$output, $x->{?buff} }
            elsif ($ot eq 'hash') 
              { $output->{+$input} = $x->{?buff} }

            my $buff = '';
            $x->{+buff} = \$buff;
        }

        last 
            unless $self->{?MultiStream};

        $status = $z->nextStream();

        last 
            unless $status == 1 ;
    }

    return $z->closeError(undef)
        if $status +< 0 ;

    ${ $self->{TrailingData} } = $z->trailingData()
        if defined $self->{?TrailingData} ;

    $z->close() 
        or return undef ;

    return 1 ;
}

sub TIEHANDLE
{
    return @_[0] if ref(@_[0]);
    die "OOPS\n" ;

}
  
sub UNTIE
{
    my $self = shift ;
}


sub readBlock
{
    my $self = shift ;
    my $buff = shift ;
    my $size = shift ;

    if (defined $self->{?CompressedInputLength}) {
        if ($self->{?CompressedInputLengthRemaining} == 0) {
            delete $self->{CompressedInputLength};
            $self->{+CompressedInputLengthDone} = 1;
            return STATUS_OK ;
        }
        $size = min($size, $self->{?CompressedInputLengthRemaining} );
        $self->{+CompressedInputLengthRemaining} -= $size ;
    }
    
    my $status = $self->smartRead($buff, $size) ;
    return $self->saveErrorString(STATUS_ERROR, "Error Reading Data")
        if $status +< 0  ;

    if ($status == 0 ) {
        $self->{+Closed} = 1 ;
        $self->{+EndStream} = 1 ;
        return $self->saveErrorString(STATUS_ERROR, "unexpected end of file", STATUS_ERROR);
    }

    return STATUS_OK;
}

sub postBlockChk
{
    return STATUS_OK;
}

sub _raw_read
{
    # return codes
    # >0 - ok, number of bytes read
    # =0 - ok, eof
    # <0 - not ok
    
    my $self = shift ;

    return G_EOF if $self->{?Closed} ;
    #return G_EOF if !length $self->{Pending} && $self->{EndStream} ;
    return G_EOF if $self->{?EndStream} ;

    my $buffer = shift ;
    my $scan_mode = shift ;

    if ($self->{?Plain}) {
        my $tmp_buff ;
        my $len = $self->smartRead(\$tmp_buff, $self->{BlockSize}) ;
        
        return $self->saveErrorString( <G_ERR, "Error reading data: $^OS_ERROR", $^OS_ERROR) 
                if $len +< 0 ;

        if ($len == 0 ) {
            $self->{+EndStream} = 1 ;
        }
        else {
            $self->{+PlainBytesRead} += $len ;
            $$buffer .= $tmp_buff;
        }

        return $len ;
    }

    if ($self->{?NewStream}) {

        $self->gotoNextStream() +> 0
            or return G_ERR;

        # For the headers that actually uncompressed data, put the
        # uncompressed data into the output buffer.
        $$buffer .=  $self->{?Pending} ;
        my $len = length  $self->{?Pending} ;
        $self->{+Pending} = '';
        return $len; 
    }

    my $temp_buf = '';
    my $outSize = 0;
    my $status = $self->readBlock(\$temp_buf, $self->{?BlockSize}, $outSize) ;
    return G_ERR
        if $status == STATUS_ERROR  ;

    my $buf_len = 0;
    if ($status == STATUS_OK) {
        my $beforeC_len = length $temp_buf;
        my $before_len = defined $$buffer ?? length $$buffer !! 0 ;
        $status = $self->{?Uncomp}->uncompr(\$temp_buf, $buffer,
                                    defined $self->{?CompressedInputLengthDone} ||
                                                $self->smartEof(), $outSize);

        return $self->saveErrorString(G_ERR, $self->{Uncomp}->{?Error}, $self->{Uncomp}->{ErrorNo})
            if $self->saveStatus($status) == STATUS_ERROR;

        $self->postBlockChk($buffer, $before_len) == STATUS_OK
            or return G_ERR;

        $buf_len = length($$buffer) - $before_len;
    
        $self->{?CompSize}->add($beforeC_len - length $temp_buf) ;

        $self->{+InflatedBytesRead} += $buf_len ;
        $self->{+TotalInflatedBytesRead} += $buf_len ;
        $self->{?UnCompSize}->add($buf_len) ;

        $self->filterUncompressed($buffer);

        if ($self->{?Encoding}) {
            $$buffer = $self->{?Encoding}->decode($$buffer);
        }
    }

    if ($status == STATUS_ENDSTREAM) {

        $self->{+EndStream} = 1 ;
        $self->pushBack($temp_buf)  ;
        $temp_buf = '';

        my $trailer;
        my $trailer_size = $self->{Info}->{?TrailerLength} ;
        my $got = 0;
        if ($self->{Info}->{?TrailerLength})
        {
            $got = $self->smartRead(\$trailer, $trailer_size) ;
        }

        if ($got == $trailer_size) {
            $self->chkTrailer($trailer) == STATUS_OK
                or return G_ERR;
        }
        else {
            return $self->TrailerError("trailer truncated. Expected " . 
                                      "$trailer_size bytes, got $got")
                if $self->{?Strict};
            $self->pushBack($trailer)  ;
        }

        # TODO - if want to file file pointer, do it here

        if (! $self->smartEof()) {
            $self->{+NewStream} = 1 ;

            if ($self->{?MultiStream}) {
                $self->{+EndStream} = 0 ;
                return $buf_len ;
            }
        }

    }
    

    # return the number of uncompressed bytes read
    return $buf_len ;
}

sub reset
{
    my $self = shift ;

    return $self->{Uncomp}->reset();
}

sub filterUncompressed
{
}

#sub isEndStream
#{
#    my $self = shift ;
#    return $self->{NewStream} ||
#           $self->{EndStream} ;
#}

sub nextStream
{
    my $self = shift ;

    my $status = $self->gotoNextStream();
    $status == 1
        or return $status ;

    $self->{+TotalInflatedBytesRead} = 0 ;
    #$self->{LineNo} = $. = 0;

    return 1;
}

sub gotoNextStream
{
    my $self = shift ;

    if (! $self->{?NewStream}) {
        my $status = 1;
        my $buffer ;

        # TODO - make this more efficient if know the offset for the end of
        # the stream and seekable
        $status = $self->read($buffer) 
            while $status +> 0 ;

        return $status
            if $status +< 0;
    }

    $self->{+NewStream} = 0 ;
    $self->{+EndStream} = 0 ;
    $self->reset();
    $self->{UnCompSize}->reset();
    $self->{CompSize}->reset();

    my $magic = $self->ckMagic();
    #$self->{EndStream} = 0 ;

    if ( ! $magic) {
        if (! $self->{?Transparent} )
        {
            $self->{+EndStream} = 1 ;
            return 0;
        }

        $self->clearError();
        $self->{+Type} = 'plain';
        $self->{+Plain} = 1;
        $self->pushBack($self->{HeaderPending})  ;
    }
    else
    {
        $self->{+Info} = $self->readHeader($magic);

        if ( ! defined $self->{?Info} ) {
            $self->{+EndStream} = 1 ;
            return -1;
        }
    }

    push @{ $self->{InfoList} }, $self->{?Info} ;

    return 1; 
}

sub streamCount
{
    my $self = shift ;
    return 1 if ! defined $self->{?InfoList};
    return scalar nelems @{ $self->{?InfoList} }  ;
}

sub read
{
    # return codes
    # >0 - ok, number of bytes read
    # =0 - ok, eof
    # <0 - not ok
    
    my $self = shift ;

    return G_EOF if $self->{?Closed} ;
    return G_EOF if !length $self->{?Pending} && $self->{?EndStream} ;

    my $buffer ;

    #$self->croakError($self->{ClassName} . 
    #            "::read: buffer parameter is read-only")
    #    if Compress::Raw::Zlib::_readonly_ref($_[0]);

    if (ref @_[0] ) {
        $self->croakError($self->{?ClassName} . "::read: buffer parameter is read-only")
            if readonly(${ @_[0] });

        $self->croakError($self->{?ClassName} . "::read: not a scalar reference @_[0]" )
            unless ref @_[0] eq 'SCALAR' ;
        $buffer = @_[0] ;
    }
    else {
        $self->croakError($self->{?ClassName} . "::read: buffer parameter is read-only")
            if readonly(@_[0]);

        $buffer = \@_[0] ;
    }

    my $length = @_[?1] ;
    my $offset = @_[?2] || 0;

    # the core read will return 0 if asked for 0 bytes
    return 0 if defined $length && $length == 0 ;

    $length = $length || 0;

    $self->croakError($self->{?ClassName} . "::read: length parameter is negative")
        if $length +< 0 ;

    $$buffer = '' unless $self->{?AppendOutput}  || $offset ;

    # Short-circuit if this is a simple read, with no length
    # or offset specified.
    unless ( $length || $offset) {
        if (length $self->{?Pending}) {
            $$buffer .= $self->{?Pending} ;
            my $len = length $self->{?Pending};
            $self->{+Pending} = '' ;
            return $len ;
        }
        else {
            my $len = 0;
            $len = $self->_raw_read($buffer) 
                while ! $self->{?EndStream} && $len == 0 ;
            return $len ;
        }
    }

    # Need to jump through more hoops - either length or offset 
    # or both are specified.
    my $out_buffer = $self->{?Pending} ;

    while (! $self->{?EndStream} && length($out_buffer) +< $length)
    {
        my $buf_len = $self->_raw_read(\$out_buffer);
        return $buf_len 
            if $buf_len +< 0 ;
    }

    $length = length $out_buffer 
        if length($out_buffer) +< $length ;

    return 0 
        if $length == 0 ;

    $self->{+Pending} = $out_buffer;
    $out_buffer = \$self->{+Pending} ;

    if ($offset) { 
        $$buffer .= "\0" x ($offset - length($$buffer))
            if $offset +> length($$buffer) ;
        #substr($$buffer, $offset) = substr($$out_buffer, 0, $length, '') ;
        substr($$buffer, $offset, undef, substr($$out_buffer, 0, $length)) ;
        substr($$out_buffer, 0, $length,  '') ;
    }
    else {
        #$$buffer .= substr($$out_buffer, 0, $length, '') ;
        $$buffer .= substr($$out_buffer, 0, $length) ;
        substr($$out_buffer, 0, $length,  '') ;
    }

    return $length ;
}

sub _getline
{
    my $self = shift ;

    # Slurp Mode
    if ( ! defined $^INPUT_RECORD_SEPARATOR ) {
        my $data ;
        1 while $self->read($data) +> 0 ;
        return \$data ;
    }

    # Record Mode
    if ( ref $^INPUT_RECORD_SEPARATOR eq 'SCALAR' && ${$^INPUT_RECORD_SEPARATOR} =~ m/^\d+$/ && ${$^INPUT_RECORD_SEPARATOR} +> 0) {
        my $reclen = ${$^INPUT_RECORD_SEPARATOR} ;
        my $data ;
        $self->read($data, $reclen) ;
        return \$data ;
    }

    # Paragraph Mode
    if ( ! length $^INPUT_RECORD_SEPARATOR ) {
        my $paragraph ;    
        while ($self->read($paragraph) +> 0 ) {
            if ($paragraph =~ s/^(.*?\n\n+)//s) {
                $self->{+Pending}  = $paragraph ;
                my $par = $1 ;
                return \$par ;
            }
        }
        return \$paragraph;
    }

    # $/ isn't empty, or a reference, so it's Line Mode.
    do {
        my $line ;    
        my $offset;
        my $p = \$self->{+Pending}  ;

        if (length($self->{?Pending}) && 
                    ($offset = index($self->{?Pending}, $^INPUT_RECORD_SEPARATOR)) +>=0) {
            my $l = substr($self->{?Pending}, 0, $offset + length $^INPUT_RECORD_SEPARATOR );
            substr($self->{Pending}, 0, $offset + length $^INPUT_RECORD_SEPARATOR, '');    
            return \$l;
        }

        while ($self->read($line) +> 0 ) {
            my $offset = index($line, $^INPUT_RECORD_SEPARATOR);
            if ($offset +>= 0) {
                my $l = substr($line, 0, $offset + length $^INPUT_RECORD_SEPARATOR );
                substr($line, 0, $offset + length $^INPUT_RECORD_SEPARATOR, '');    
                $$p = $line;
                return \$l;
            }
        }

        return \$line;
    };
}

sub getline
{
    my $self = shift;
    my $current_append = $self->{?AppendOutput} ;
    $self->{+AppendOutput} = 1;
    my $lineref = $self->_getline();
    #$. = ++ $self->{LineNo} if defined $$lineref ;
    $self->{+AppendOutput} = $current_append;
    return $$lineref ;
}

sub getlines
{
    my $self = shift;
    my($line, @lines);
    push(@lines, $line) 
        while defined($line = $self->getline);
    return @lines;
}

sub getc
{
    my $self = shift;
    my $buf;
    return $buf if $self->read($buf, 1);
    return undef;
}

sub ungetc
{
    my $self = shift;
    $self->{+Pending} = ""  unless defined $self->{?Pending} ;    
    $self->{+Pending} = @_[0] . $self->{?Pending} ;    
}


sub trailingData
{
    my $self = shift ;

    if (defined $self->{?FH} || defined $self->{?InputEvent} ) {
        return $self->{?Prime} ;
    }
    else {
        my $buf = $self->{?Buffer} ;
        my $offset = $self->{?BufferOffset} ;
        return substr($$buf, $offset) ;
    }
}


sub eof
{
    my $self = shift ;

    return  @($self->{?Closed} ||
               @(!length $self->{?Pending} 
                &&  @( $self->smartEof() || $self->{?EndStream}))) ;
}

sub tell
{
    my $self = shift ;

    my $in ;
    if ($self->{?Plain}) {
        $in = $self->{?PlainBytesRead} ;
    }
    else {
        $in = $self->{?TotalInflatedBytesRead} ;
    }

    my $pending = length $self->{?Pending} ;

    return 0 if $pending +> $in ;
    return $in - $pending ;
}

sub close
{
    # todo - what to do if close is called before the end of the gzip file
    #        do we remember any trailing data?
    my $self = shift ;

    return 1 if $self->{?Closed} ;

    my $status = 1 ;

    if (defined $self->{?FH}) {
        if ((! $self->{?Handle} || $self->{?AutoClose}) && ! $self->{?StdIO}) {
        #if ( $self->{AutoClose}) {
            #local $.; 
            $^OS_ERROR = 0 ;
            $status = close($self->{?FH});
            return $self->saveErrorString(0, $^OS_ERROR, $^OS_ERROR)
                if !$self->{?InNew} && $self->saveStatus($^OS_ERROR) != 0 ;
        }
        delete $self->{FH} ;
        $^OS_ERROR = 0 ;
    }
    $self->{+Closed} = 1 ;

    return 1;
}

sub DESTROY
{
    my $self = shift ;
    $self->close() ;
}

sub seek
{
    my $self     = shift ;
    my $position = shift;
    my $whence   = shift ;

    my $here = $self->tell() ;
    my $target = 0 ;


    if ($whence == SEEK_SET) {
        $target = $position ;
    }
    elsif ($whence == SEEK_CUR) {
        $target = $here + $position ;
    }
    elsif ($whence == SEEK_END) {
        $target = $position ;
        $self->croakError($self->{?ClassName} . "::seek: SEEK_END not allowed") ;
    }
    else {
        $self->croakError($self->{?ClassName} ."::seek: unknown value, $whence, for whence parameter");
    }

    # short circuit if seeking to current offset
    return 1 if $target == $here ;    

    # Outlaw any attempt to seek backwards
    $self->croakError( $self->{?ClassName} ."::seek: cannot seek backwards")
        if $target +< $here ;

    # Walk the file to the new offset
    my $offset = $target - $here ;

    my $got;
    while (($got = $self->read(my $buffer, min($offset, $self->{?BlockSize})) ) +> 0)
    {
        $offset -= $got;
        last if $offset == 0 ;
    }

    return $offset == 0 ?? 1 !! 0 ;
}

sub fileno
{
    my $self = shift ;
    return defined $self->{?FH} 
           ?? fileno $self->{?FH} 
           !! undef ;
}

sub binmode
{
    1;
#    my $self     = shift ;
#    return defined $self->{FH} 
#            ? binmode $self->{FH} 
#            : 1 ;
}

sub opened
{
    my $self     = shift ;
    return ! $self->{?Closed} ;
}

sub autoflush
{
    my $self     = shift ;
    return defined $self->{?FH} 
            ?? $self->{?FH}->autoflush(< @_) 
            !! undef ;
}

sub input_line_number
{
    my $self = shift ;
    my $last = $self->{?LineNo};
    #$. = $self->{LineNo} = @_[1] if (nelems @_) ;
    return $last;
}


*BINMODE  = \&binmode;
*SEEK     = \&seek; 
*READ     = \&read;
*sysread  = \&read;
*TELL     = \&tell;
*EOF      = \&eof;

*FILENO   = \&fileno;
*CLOSE    = \&close;

sub _notAvailable
{
    my $name = shift ;
    #return sub { croak "$name Not Available" ; } ;
    return sub { croak "$name Not Available: File opened only for intput" ; } ;
}


*print    = _notAvailable('print');
*PRINT    = _notAvailable('print');
*printf   = _notAvailable('printf');
*PRINTF   = _notAvailable('printf');
*write    = _notAvailable('write');
*WRITE    = _notAvailable('write');

#*sysread  = \&read;
#*syswrite = \&_notAvailable;



package IO::Uncompress::Base ;


1 ;
__END__

=head1 NAME


IO::Uncompress::Base - Base Class for IO::Uncompress modules 


=head1 SYNOPSIS

    use IO::Uncompress::Base ;

=head1 DESCRIPTION


This module is not intended for direct use in application code. Its sole
purpose if to to be sub-classed by IO::Unompress modules.




=head1 SEE ALSO

L<Compress::Zlib>, L<IO::Compress::Gzip>, L<IO::Uncompress::Gunzip>, L<IO::Compress::Deflate>, L<IO::Uncompress::Inflate>, L<IO::Compress::RawDeflate>, L<IO::Uncompress::RawInflate>, L<IO::Compress::Bzip2>, L<IO::Uncompress::Bunzip2>, L<IO::Compress::Lzop>, L<IO::Uncompress::UnLzop>, L<IO::Compress::Lzf>, L<IO::Uncompress::UnLzf>, L<IO::Uncompress::AnyInflate>, L<IO::Uncompress::AnyUncompress>

L<Compress::Zlib::FAQ|Compress::Zlib::FAQ>

L<File::GlobMapper|File::GlobMapper>, L<Archive::Zip|Archive::Zip>,
L<Archive::Tar|Archive::Tar>,
L<IO::Zlib|IO::Zlib>





=head1 AUTHOR

This module was written by Paul Marquess, F<pmqs@cpan.org>. 



=head1 MODIFICATION HISTORY

See the Changes file.

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2005-2007 Paul Marquess. All rights reserved.

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.