use strict;
use warnings;

package Data::ParseBinary::Stream::BitReader;
our @ISA = qw{Data::ParseBinary::Stream::Reader Data::ParseBinary::Stream::WrapperBase};

__PACKAGE__->_registerStreamType("Bit");

sub new {
    my ($class, $byteStream) = @_;
    my $self = bless { buffer => '' }, $class;
    $self->_warping($byteStream);
    return $self;
}

sub ReadBytes {
    my ($self, $count) = @_;
    return $self->_readBytesForBitStream($count);
}

sub ReadBits {
    my ($self, $bitcount) = @_;
    my $current = $self->{buffer};
    my $moreBitsNeeded = $bitcount - length($current);
    $moreBitsNeeded = 0 if $moreBitsNeeded < 0;
    my $moreBytesNeeded = int($moreBitsNeeded / 8) + ($moreBitsNeeded % 8 ? 1 : 0);
    #print "BitStream: $bitcount bits requested, $moreBytesNeeded bytes read\n";
    my $string = $self->{ss}->ReadBytes($moreBytesNeeded);
    $current .= unpack "B*", $string;
    my $data = substr($current, 0, $bitcount, '');
    $self->{buffer} = $current;
    return $data;
}

sub tell {
    my $self = shift;
    #die "A bit stream is not seekable";
    if ($self->{buffer}) {
        return "Bit ". (8 - length($self->{buffer}))
    } else {
        return "Bit 0";
    }
}

sub seek {
    my ($self, $newpos) = @_;
    die "A bit stream is not seekable";
}

sub isBitStream { return 1 };


package Data::ParseBinary::Stream::BitWriter;
our @ISA = qw{Data::ParseBinary::Stream::Writer Data::ParseBinary::Stream::WrapperBase};

__PACKAGE__->_registerStreamType("Bit");

sub new {
    my ($class, $byteStream) = @_;
    my $self = bless { buffer => '' }, $class;
    $self->_warping($byteStream);
    return $self;
}

sub WriteBytes {
    my ($self, $data) = @_;
    return $self->_writeBytesForBitStream($data);
}

sub WriteBits {
    my ($self, $bitdata) = @_;
    my $current = $self->{buffer};
    my $new_buffer = $current . $bitdata;
    my $numof_bytesToWrite = int(length($new_buffer) / 8);
    my $bytesToWrite = substr($new_buffer, 0, $numof_bytesToWrite * 8, '');
    my $binaryToWrite = pack "B".($numof_bytesToWrite * 8), $bytesToWrite;
    $self->{buffer} = $new_buffer;
    return $self->{ss}->WriteBytes($binaryToWrite);
}

sub Flush {
    my $self = shift;
    my $write_size = (-length($self->{buffer})) % 8;
    $self->WriteBits('0'x$write_size);
    return $self->{ss}->Flush();
}

sub tell {
    my $self = shift;
    return "Bit ". length($self->{buffer});
    #die "A bit stream is not seekable";
}

sub seek {
    my ($self, $newpos) = @_;
    die "A bit stream is not seekable";
}

sub isBitStream { return 1 };

package Data::ParseBinary::Stream::ReversedBitStreamReader;
our @ISA = qw{Data::ParseBinary::Stream::BitReader};

__PACKAGE__->_registerStreamType("ReversedBit");

sub ReadBits {
    my ($self, $bitcount) = @_;
    my $current = $self->{buffer};
    my $moreBitsNeeded = $bitcount - length($current);
    if ($moreBitsNeeded > 0) {
        my $moreBytesNeeded = int($moreBitsNeeded / 8) + ($moreBitsNeeded % 8 ? 1 : 0);
        my $string = $self->{ss}->ReadBytes($moreBytesNeeded);
        $string = join '', reverse split '', $string if $moreBytesNeeded > 1;
        $current = unpack("B*", $string) . $current;
    }
    my $data = substr($current, -$bitcount, $bitcount, '');
    $data = join '', reverse split '', $data if length($data) > 1;
    $self->{buffer} = $current;
    return $data;
}

package Data::ParseBinary::Stream::ReversedBitStreamWriter;
our @ISA = qw{Data::ParseBinary::Stream::BitWriter};

__PACKAGE__->_registerStreamType("ReversedBit");

sub WriteBits {
    my ($self, $bitdata) = @_;
    $bitdata = join '', reverse split '', $bitdata if length($bitdata) > 1;
    $self->{buffer} = $bitdata . $self->{buffer};
    my $numof_bytesToWrite = int(length($self->{buffer}) / 8);    
    my $num_of_bits_to_cut = $numof_bytesToWrite * 8;
    my $bytesToWrite = substr($self->{buffer}, -$num_of_bits_to_cut, $num_of_bits_to_cut, '');
    my $binaryToWrite = pack "B".($numof_bytesToWrite * 8), $bytesToWrite;
    $binaryToWrite = join '', reverse split '', $binaryToWrite if $numof_bytesToWrite > 1;
    return $self->{ss}->WriteBytes($binaryToWrite);
}

1;