use strict;

my %config = ();
my %block = ();

# XXX Should parse the virtual size from size from the XML document.
# Instead we estimate the size here, and the user can override
# it by setting the optional size=... parameter.
my $estimated_size;

# Does XVA define this?  We might need to read it from the file
# somehow.
my $block_size = 1048576;

sub config
{
    my $key = shift;
    my $value = shift;
    $config{$key} = $value;
}

sub config_complete
{
    die "no file=... parameter given" unless exists $config{file};

    my $file = $config{file};
    $estimated_size = 0;

    # Parse the XVA file for offsets.
    # XXX Does not work if the XVA contains multiple disk images.  We
    # should parse the 'Ref:NN' field too.
    open FILE, "tar tRvf $file |" or die "$file: $!";
    while (<FILE>) {
        if (m{^block (\d+): \S+ \d+/\d+\s+(\d+)\s.*/0*(\d+)$}) {
            my $offset = $1;    # block offset in tar file
            my $size = $2;      # size in bytes in tar file
            my $filename = $3;  # filename in tar file, without leading 0s
            my $byte_offset = (1 + $offset) * 512; # byte offset in tar file
            $block{$filename} = { size => $size, offset => $byte_offset };
            #printf ("%s %d %d\n", $filename, $byte_offset, $size);

            if ($byte_offset + $size > $estimated_size) {
                $estimated_size = $byte_offset + $size;
            }
        }
    }
    close FILE;
}

sub open
{
    my $readonly = shift;
    my $file = $config{file};
    open (my $fh, "<", $file) or die "$file: $!";
    my $h = { fh => $fh };
    return $h;
}

sub close
{
    my $h = shift;
    close ($h->{fh});
}

sub get_size
{
    my $h = shift;
    my $i = $estimated_size;
    # Allow user to override the estimate by setting size= parameter.
    $i = $config{size} if exists $config{size};
    return $i;
}

sub read_block
{
    my $h = shift;
    my $bnum = shift;
    my $offset = $block{$bnum}{offset};
    my $size = $block{$bnum}{size};
    seek ($h->{fh}, $offset, 0);
    my $ret = "";
    my $i = 0;
    while ($size > 0) {
        my $n = read ($h->{fh}, $ret, $size, $i);
        if ($n == 0) {
            die "unexpected end of file: block number $bnum (offset $offset, size $size)"
        }
        $i += $n;
        $size -= $n;
    }
    return $ret;
}

sub pread
{
    my $h = shift;
    my $count = shift;
    my $offset = shift;

    my $buf = "";
    my $bufi = 0;

    while ($count > 0) {
        # Which block, if any, contains $offset?
        my $bnum = int ($offset / $block_size);
        my $boffs = $offset % $block_size;
        #print "offset = $offset, bnum = $bnum, boffs = $boffs\n";

        # n = number of bytes that can be read from this block.
        my $n = $block_size - $boffs;
        $n = $count if $n > $count;

        if (exists $block{$bnum}) {
            my $bdata = read_block ($h, $bnum);
            $buf .= substr $bdata, $boffs, $n;
        }
        else {
            # Virtual zero block.
            $buf .= "\0" x $n;
        }

        $count -= $n;
    }

    return $buf;
}
