#!/usr/bin/perl
#
# $Header: /Users/claude/fuzz/lib/Genezzo/SpaceMan/RCS/SMFile.pm,v 7.4 2005/09/07 08:26:09 claude Exp claude $
#
# copyright (c) 2003,2004,2005 Jeffrey I Cohen, all rights reserved, worldwide
#
#
package Genezzo::SpaceMan::SMFile;  # assumes Some/Module.pm

use strict;
use warnings;

use Carp;
use Genezzo::Util;
use Genezzo::Row::RSBlock;

BEGIN {
    use Exporter   ();
    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);

    # set the version for version checking
#    $VERSION     = 1.00;
    # if using RCS/CVS, this may be preferred
    $VERSION = do { my @r = (q$Revision: 7.4 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker

    @ISA         = qw(Exporter);
    @EXPORT      = ( ); # qw(&NumVal);
    %EXPORT_TAGS = ( );     # eg: TAG => [ qw!name1 name2! ],

    # your exported package globals go here,
    # as well as any optionally exported functions
#    @EXPORT_OK   = qw($Var1 %Hashit &func3 &func5);
    @EXPORT_OK   = (); 

}

sub new 
{
    my $invocant = shift;
    my $class = ref($invocant) || $invocant ; 
    my $self = { };
    
#    whoami @_;

    $self->{filename}  = shift;
    $self->{numbytes}  = shift;
    $self->{numblocks} = shift;

    # buffer cache
    $self->{realbc} = shift;

    $self->{filenumber} = shift;

    $self->{read_only} = 0; # TODO: set for read-only database
    
    $self->{bc} = {};
    my $bc = $self->{bc};
    $bc->{realbcfileno} = 
        $self->{realbc}->FileReg(FileName => $self->{filename},
                                 FileNumber => $self->{filenumber});

    unless (defined($bc->{realbcfileno}))
    {
        whoami;
        my $fn = $self->{filename};
        whisper "failed to register $fn!!";
        print   "\nfailed to register $fn!!\n";
        return undef;
    }


    # read the fileheader
    $bc->{fileheader} = {};

    my $blessref = bless $self, $class;

    unless (_tiefh($self, $bc))
    {
        whisper "bad tie for file header";
        return undef;
    }
    my $fileheader = $bc->{fileheader};

    # get the index for the freelist row
    my $flidx = $fileheader->{RealTie}->_fetchmeta("FL");

    {
        # currently have a free extent starting at block _1_
        # of _numblocks_ [i.e. all] blocks

        # block 0 for file header

        unless (defined($flidx))
        {

            # fileheader - last col indicates that a single extent
            # (block 0, length 1 block) is used for the header 
            # XXX XXX: extend to support additional blocks of file header
            # information

            my $packstr = 
                PackRow(
                        [
                         'HEADER',
                         $self->{filename},
                         $self->{numbytes},
                         $self->{numblocks},
                         '0:1'
                         ]
                        );

            $fileheader->{RealTie}->HPush($packstr);

            # block 0 is in use for fileheader
            my $freelist = '1:' . ($self->{numblocks}-1);
            
            $packstr = 
                PackRow(
                        [
                         'FreeExtents',
                         $freelist
                         ]
                        );

            # set the metadata for the free list index
            $flidx = $fileheader->{RealTie}->HPush($packstr);

            return undef
                unless (defined($flidx));

            my $stat = 
                $fileheader->{RealTie}->_update_meta_zero("FL", $flidx);

            return undef
                unless (defined($stat));

            # flush not necessary unless update block 

            return undef
                unless ($self->flush());
        }
        $self->{freelist_idx} = $flidx;

    }

    return $blessref;

} # end new

sub _file_info
{
    my $self = shift;
    my %args = @_;

    my @outarr;

    my $bc = $self->{bc};

    push @outarr, $bc->{realbcfileno};
    push @outarr, $self->{filename};
    push @outarr, $self->{numbytes};
    push @outarr, $self->{numblocks};

    if (exists($args{tablename})
        && exists($args{object_id}))
    { # add the latest extent information for the specified object
        my $tablename = $args{tablename};
        my $object_id = $args{object_id};

        my $spacelist = $self->UnPackSpaceList($tablename);
        if (_is_valid_spacelist($spacelist))
        {
            my ($currBlocknum, @currExtent) = _get_current_info($spacelist);  
            
            push @outarr, @currExtent
                if (scalar(@currExtent > 1));
        }
    }
    return @outarr;
}

sub PackSpaceList
{
    my ($self, $tablename, $object_id, $desc) = @_;

#    whoami;
#        local $Genezzo::Util::QUIETWHISPER = 0; # XXX: unquiet the whispering

    unless (defined($object_id))
    {
        use Carp; # XXX XXX XXX XXX

        croak "no object id for $tablename";

    }

    my $bc = $self->{bc};
    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (0);
    }
    my $fileheader = $bc->{fileheader};

    my $reftb = $fileheader->{RefTie};

    my @outarr;    

    my ($kk, $vv);

#    my $cnt = scalar(keys(%{$reftb}));
    while (($kk, $vv) = each (%{$reftb}))
#    while ($icount < scalar (@{$reftb}))
    {

        @outarr = UnPackRow($vv);

#        greet $kk, @outarr;
        my $rtyp = shift @outarr;
        unless ($rtyp =~ m/TABLE/)
        {
            @outarr = ();
            next;
        }
        last if ($outarr[0] eq $tablename); 
        
        @outarr = ();
    }

    if ((defined($object_id))
        && (scalar(@{$desc}))
        && ($desc->[0] =~ /TABLE/))
    {
        $desc->[0] = 'TABLE_' . $object_id;
    }

    my $packstr = 
        PackRow($desc);

    unless (scalar(@outarr))
#    if ($icount >= scalar (@{$reftb}))
    {
        
#        whisper "no match! push new row";
        
        my $stat = $fileheader->{RealTie}->HPush($packstr);

        unless ($stat)
        {
            whisper "out of space in block header!";
            return 0;
        }
#        push (@{$reftb}, $packstr);
        
        return 1;
    }

#    whisper "match!";

    $reftb->{$kk} = $packstr;

    return 1;

}

sub UnPackSpaceList
{
    # XXX: setting pluskey prepends hash key for table to outarr
    my ($self, $tablename, $pluskey) = @_;

#    whoami;

    my $bc = $self->{bc};
    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (0);
    }
    my $fileheader = $bc->{fileheader};
#    greet $bc;

    my $reftb = $fileheader->{RefTie};

    my @outarr;    

#    my $cnt = scalar(keys(%{$reftb}));
    my ($kk, $vv);
    while (($kk, $vv) = each (%{$reftb}))
#    while ($icount < scalar (@{$reftb}))
    {
        @outarr = UnPackRow($vv);
#        greet $kk, @outarr;
        my $rtyp = shift @outarr;
        unless ($rtyp =~ m/TABLE/)
        {
            @outarr = ();
            next;
        }
        if ($outarr[0] eq $tablename)
        {
            # prepend the key value if requested
            unshift (@outarr, $kk)
                if ($pluskey);
                    
            last ;
        }

        @outarr = ();
    }

    return undef unless (scalar(@outarr));
    return undef if (scalar(@outarr) < 2);

#    whisper "match!";

#    greet @outarr; 

    return \@outarr;
}

sub _tiefh
{
    my ($self, $bc) = @_;

#    whoami;

    my $fileheader = $bc->{fileheader};

    # NOTE: always in block zero for now
    my $blocknum = 0;

    $fileheader->{bceref} = 
        $self->{realbc}->ReadBlock(filenum  => $bc->{realbcfileno},
                                   blocknum => $blocknum);

    unless ( $fileheader->{bceref} )
    {
        whisper "failed to read block!";
        return (0);
    }

    my $bce = ${$fileheader->{bceref}};
#    greet $bce;

    # BCE to RDBlock - please respond
    my $mailbag = Genezzo::Util::AddMail(To => 'Genezzo::Block::RDBlock',
                                         From => $bce,
                                         Msg => 'RSVP');

    # build a separate tie for the file header
    my %filetiebufa;
    
    # tie array to buffer
    $fileheader->{RealTie} = 
        tie %filetiebufa, 'Genezzo::Row::RSBlock', 
        (refbufstr => $bce->{bigbuf}, 
         blocksize => $bce->{blocksize},
         blocknum  => $blocknum,
         MailBag   => $mailbag
         );
    
    $fileheader->{RefTie} = \%filetiebufa;

    return 1;
}

# XXX: note - not a class or instance method
sub _is_valid_spacelist
{
    my $spacelist = shift;

    return 0
        unless (defined($spacelist)
                && (scalar(@{$spacelist}) > 3));

    return 1;
}
# XXX: note - not a class or instance method
sub _get_current_info
{
    my $spacelist = shift;

    # NOTE: assume valid spacelist

    my ($currBlocknum, $freeBlocknum);

    if ($spacelist->[1] =~ m/\d+/)
    {
        # no free list
        $currBlocknum = $spacelist->[1];
    }
#    else
#    {
#        # have free list
#        my @foo =  split('F', $spacelist->[1]);
#        $currBlocknum = shift @foo;
#        $freeBlocknum = shift @foo;
#    }
        
    my @currExtent = split(':', $spacelist->[2]);

    return $currBlocknum, @currExtent;
}


sub SMGrowFile
{
    whoami;

    my $self = shift;
    my %required = (
                    filenumber => "no filenumber!",
                    blocksize  => "no blocksize!",
                    );

    my %optional = (
                    numblocks   => 0,
#                    pctincrease => 50
                    );

    my %args = (%optional,
                @_);

    my @newinfo;

    return undef
        unless (Validate(\%args, \%required));

    # get free extent info from fileheader
    my $bc = $self->{bc};
    my $freelist_idx = $self->{freelist_idx};

    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (undef);
    }
    my $fileheader = $bc->{fileheader};
    
    my $reftb = $fileheader->{RefTie};
    
#    greet $reftb;

    my @freelist = UnPackRow($reftb->{$freelist_idx});
            
    greet @freelist;
            
    shift @freelist;

    if ($args{numblocks})
    {
        # last blockno is current block total
        my $lastblockno = $self->{numblocks}; 
        my $numblocks   = $args{numblocks};

        # divide max file size/blocksize to get max block number
        my $maxblockno = $Genezzo::Util::MAXDBSIZE/$args{blocksize};

        # XXX: off by 1?
        if (($lastblockno + $numblocks) > $maxblockno)
        {
            $numblocks = $maxblockno - $lastblockno;
            return @newinfo
                unless ($numblocks > 0);
        }

        my ($startblockno, $new_numblocks) = 
            $self->{realbc}->BCGrowFile($args{filenumber}, $lastblockno,
                                        $numblocks);

        # Note: issue of multiple clients trying to grow same file in parallel

        greet "match!" if (($startblockno == $lastblockno)
                           && (defined($new_numblocks))
                           && ($new_numblocks == $numblocks));

        return @newinfo # make sure got something...
            unless (defined($new_numblocks) && ($new_numblocks > 0));

        $self->{numblocks} += $new_numblocks;
        $self->{numbytes}  += ($new_numblocks * $args{blocksize});

        my $free_ext = $startblockno . ':' . $new_numblocks;
        greet $free_ext;

        push @freelist, $free_ext;
        unshift (@freelist, 'FreeExtents');

        # XXX: fragile - need a STORE with a check to see if succeeds
        $reftb->{$freelist_idx} =
            PackRow(\@freelist);

        @newinfo = ($startblockno, $new_numblocks);
    }


    return @newinfo;
} # end SMGrowFile


sub make_new_extent
{
    return 0;
}

sub make_new_block
{
    my $self = shift;
    return $self->nextfreeblock(@_);
}

# get next free block from current extent, else allocate new extent...
sub nextfreeblock
{
#    whoami;
    my $self = shift;
    
    my %optional = (
                    extentsize  => 2, # 2 blocks
                    pctincrease => 0, # make next extent 0% larger, i.e
                                      # the same size as previous
                    );

    my %required = (
                    tablename  => "no tablename !",
                    );

    my %args = (%optional,
		@_);

    return undef
        unless (Validate(\%args, \%required));

#    local $Genezzo::Util::QUIETWHISPER = 1; # XXX: quiet the whispering
    
    my $gotnewextent = 0; # true if get new extent

   # get free extent info from fileheader
    my $bc = $self->{bc};
    my $freelist_idx = $self->{freelist_idx};

    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (undef);
    }
    my $fileheader = $bc->{fileheader};
    
    my $reftb = $fileheader->{RefTie};
    
#    greet $reftb;

    my $extsize = $args{extentsize};
    my $tablename = $args{tablename};
    my $object_id = $args{object_id};

    my ($currBlocknum, @currExtent );

    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $spacelist;

    if (!(_is_valid_spacelist($spacelist)))
    {
        $spacelist = [ $tablename ];
    }
    else
    {
        
        ($currBlocknum, @currExtent) = _get_current_info($spacelist);  

        if (scalar(@currExtent) < 2)
        {
            whisper "bad curr extent";
            return (undef);
        }

#        for my $i (3..(scalar(@{$spacelist})-1))
#        {
#            whisper "used extent $i : $spacelist->[$i]";
#        }

        if ($args{pctincrease})
        {
            use POSIX ; #  need some rounding

            # convert the pctincrease to a real multiplication factor,
            # e.g. pct=50 -> 1.5 to get 50% larger (which is really
            # 150% of the previous size)
            my $pctincrease = ($args{pctincrease}/100) + 1;

            # XXX XXX: this won't work if add freelist at end of extent list...
            my @LastExtent = split(':', $spacelist->[-1]);
            greet @LastExtent;
            my $lastsize = pop @LastExtent;

            # reset extent size to the calculated increased of the
            # last extent size
            $extsize = POSIX::floor($lastsize * $pctincrease);
            greet $extsize;
        }        

    }

  L_bigloop:
    while (1)
    {
        # allocate an extent if necessary
        unless (scalar(@currExtent))
        {

            # current algorithm works if start with one large extent and
            # always allocate fixed size extents of same size [even
            # divisors of numblocks, though could add a round-up to fix
            # that].

            # pop off a free extent -- if has enough space subdivide it
            # and use $extsize for the currextent and return the rest.

            # if free extent is exact extsize don't have to return anything.

            # $$$ $$$ variable extsize requires a mechanism to search the
            # free extents for best fit.

            # $$$ note locking issues for multithreaded allocations --
            # contention over free extent

            my @freelist = UnPackRow($reftb->{$freelist_idx});
            
###         greet @freelist;
            
            shift @freelist;

            unless (scalar (@freelist))
            {
                whisper "failed to allocate extent - none left!! ";
                return undef;           
            }
            
            # $$$ $$$ make this work for list of free extents -- do
            # "best fit", etc
            
            my %bysize; # hash by size
            for (my $flidx = 0; $flidx < scalar(@freelist); $flidx++)
            {
                my @splitextent = ($freelist[$flidx] =~ m/(\w.*)\:(\w.*)/);

                unless (2 == scalar (@splitextent))
                {
                  Carp::croak("badly formed extent - $flidx!! ");
                    return undef;
                }
                
                # hash by size of indexes of freelist entries
                push (@{$bysize{$splitextent[1]}}, $flidx);
            }
            
            my $free_ext = ();
            if (exists($bysize{$extsize}))
            {
                whisper "exact match for extent of size $extsize";

                my $flidx = pop(@{$bysize{$extsize}});
                my @splitextent = ($freelist[$flidx] =~ m/(\w.*)\:(\w.*)/);
                $free_ext = \@splitextent;
                # remove this entry from free list
                splice(@freelist, $flidx, 1);
            }
            else # inexact match - look for best fit
            {
                # foreach key of free list extentsize...
              L_flx:
                foreach my $flxsize (sort {$a <=> $b}(keys(%bysize)))
                {
                    next if ($extsize > $flxsize);

                    my $flidx = pop(@{$bysize{$flxsize}});
                    my @splitextent = ($freelist[$flidx] =~ m/(\w.*)\:(\w.*)/);

                    # splitextent should always have 2 entries - we
                    # checked when bysize was loaded...
                    $free_ext = \@splitextent;
                    # return remainder (if any) of free extent to free list 
                    my $startblock = ($free_ext->[0] + $extsize);
                    my $sizeleft   = ($free_ext->[1] - $extsize);

                    # sizeleft is always nonzero
                    $freelist[$flidx] = 
                        ( $startblock . ':' . $sizeleft) ;

                    $free_ext->[1] = $extsize;
                    last L_flx;
                }
                unless (defined($free_ext)
                        && (scalar(@{$free_ext})))
                {
                    whisper "failed to allocate extent - insufficient space!! ";
                    return undef;
                }
            } # end look for best fit

            unless (defined($free_ext)
                    && (scalar (@{$free_ext})))
            {
                whisper "failed to allocate extent - none left!! ";
                return undef;
            }
            
            if ($free_ext->[1] < $extsize)
            {
                whisper "failed to allocate extent - insufficient space!! ";
                return undef;
            }

            # set the current block number to the first block of the
            # new extent
            $currBlocknum  = $currExtent[0] = $free_ext->[0];
            $currExtent[1] = $free_ext->[1];

            $gotnewextent = 1;

            unshift (@freelist, 'FreeExtents');

            # XXX: fragile - need a STORE with a check to see if succeeds
            $reftb->{$freelist_idx} =
                PackRow(\@freelist);

            return undef 
                unless ($self->flush());

            # we are done
            last L_bigloop;
        } # end allocate extent

        whisper "current block : $currBlocknum";

        if (($currExtent[1] + $currExtent[0]) > $currBlocknum + 1)
        {
            # still room in this extent
            $currBlocknum += 1;
            last L_bigloop;
        }
        else
        {

            whisper "need new extent";

            @currExtent = ();
            next L_bigloop;

        }
    } # end L_bigloop while(1)

    $spacelist->[1] = $currBlocknum;
    $spacelist->[2] = join (':', @currExtent);

    # extend the used extent list if got a new one
    push @{$spacelist}, $spacelist->[2]
        if ($gotnewextent);

    # XXX XXX: why not 'TABLE_' . $object_id ??  Because packspacelist
    # fixes this up for us...
    unshift @{$spacelist}, 'TABLE'; 

    return undef
        unless ($self->PackSpaceList($tablename, $object_id, $spacelist));

    my @outi;
    push @outi, $currBlocknum;
    push @outi, @currExtent  # new extent info
        if ($gotnewextent);

    return @outi;
}

sub get_current_block
{
    my $self = shift;
    return $self->currblock(@_);
}

sub currblock
{
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !",
                    );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $tablename, $spacelist;

    return (undef)
        unless (_is_valid_spacelist($spacelist));
 
    my ($currBlocknum, @currExtent) = _get_current_info($spacelist);       

    if (scalar(@currExtent) < 2)
    {
        whisper "bad curr extent";
        return (undef);
    }

    return ($currBlocknum);
}

sub firstblock
{
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !",
                    );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $spacelist;

    return (undef)
        unless (_is_valid_spacelist($spacelist));
        
    my ($currBlocknum, @currExtent) = _get_current_info($spacelist);       
        
    my @firstExtent = split(':', $spacelist->[3]);

    if (scalar(@firstExtent) < 2)
    {
        whisper "bad first extent";
        return (undef);
    }

    return ($firstExtent[0]);

}

sub nextblock
{
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !", 
                    prevblock  => "no previous block !",
                   );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    my $prevblock = $args{prevblock};
    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $spacelist;

    unless (defined($prevblock))
    {
        whisper "no previous block";
        return (undef);
    }

    return (undef)
        unless (_is_valid_spacelist($spacelist));

    my $currBlocknum = $spacelist->[1];        

    # XXX XXX XXX XXX XXX XXX XXX 
    # stop if we reached the current block (insertion point)
    return undef
        if ($prevblock == $currBlocknum);

    # shift off the current block info
    splice (@{$spacelist}, 0, 3);

#    greet @{$spacelist};

    my $getnext = 0; # true if next block is in next extent

    foreach my $xt (@{$spacelist})
    {
        my @nextExtent = split(':', $xt);

#        greet @nextExtent;

        if (scalar(@nextExtent) < 2)
        {
            return (undef);
        }

        return ($nextExtent[0])  # first block of this extent is next block
            if $getnext;         # if getnext was set

        # last block of this extent
        my $lastblock = ($nextExtent[0] + ($nextExtent[1] - 1)) ;
        # return the nextblock if it would still be in current extent
        if (($prevblock >= $nextExtent[0]) &&
            ($prevblock < $lastblock))
        {
            $prevblock++;
            return ($prevblock);
        }

        # if prevblock = last block of this extent, then next block is
        # first block of next extent, so set $getnext.  Works
        # correctly (returns undef) if no next extent

        $getnext = 1
            if ($prevblock == $lastblock)
    }
    return (undef);

}

# count of all blocks allocated for a table.  
# NOTE: Allocated block count is possibly greater than the current
# number of blocks in use -- it doesn't take into account deleted
# blocks.
sub countblock
{
#    whoami;
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !", 
                   );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $spacelist;

    return (undef)
        unless (_is_valid_spacelist($spacelist));

    my ($currBlocknum, @currExtent) = _get_current_info($spacelist);       

    # shift off the current block info
    splice (@{$spacelist}, 0, 3);

    my $total = 0;

    foreach my $xt (@{$spacelist})
    {
        my @nextExtent = split(':', $xt);

#        greet $xt, @nextExtent;

        if (scalar(@nextExtent) < 2)
        {
            return $total; # XXX XXX : ? what is this case? no extents?
        }

        if ($currExtent[0] == $nextExtent[0])
        {
            # get an accurate count of the number of blocks in use in
            # the current extent to try to give decent estimates if
            # have a single enormous extent
            $total += 1 + ($currBlocknum - $currExtent[0]);
        }
        else
        {
            # assume entire extent is in use.  
            # XXX XXX: Could have metadata in the extent header to
            # provide more accurate usage statistics
            $total += $nextExtent[1] ;
        }
    }

    return $total;
} # countblock

sub hasblock
{
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !", 
                    blocknum   => "no block number !",
                   );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    my $blocknum  = $args{blocknum};
    my $spacelist = $self->UnPackSpaceList($tablename);
#    greet $spacelist;

    return 0
        unless (_is_valid_spacelist($spacelist));

    # shift off the current block info
    splice (@{$spacelist}, 0, 3);

    foreach my $xt (@{$spacelist})
    {
        my @nextExtent = split(':', $xt);

#        greet @nextExtent;

        if (scalar(@nextExtent) < 2)
        {
            return (0);
        }
        # last block of this extent
        my $lastblock = ($nextExtent[0] + ($nextExtent[1] - 1)) ;

        # return 1 if block in current extent
        if (($blocknum >= $nextExtent[0]) &&
            ($blocknum <= $lastblock))
        {
            return (1);
        }
    }
    return 0;
}

sub freetable # drop table/drop index/destroy any object
{
    my $self = shift;

    my %required = (
                    tablename  => "no tablename !",
                    );

    my %args = (
		@_);

    return undef
        unless (Validate(\%args, \%required));

    my $tablename = $args{tablename};
    # XXX: unpack the space list with the key prepended
    my $spacelist = $self->UnPackSpaceList($tablename, 1);
#    greet $spacelist;

    unless (defined($spacelist))
    {
        return (undef);
    }

    my $tablekey = shift @{$spacelist};
    my $bc = $self->{bc};
    my $freelist_idx = $self->{freelist_idx};

    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (undef);
    }
    my $fileheader = $bc->{fileheader};
    
    my $reftb = $fileheader->{RefTie};

    # blow the table out of the main list
    delete $reftb->{$tablekey};

    return (0) 
        unless $self->flush();

    # check for valid table data
    return 1
        unless (_is_valid_spacelist($spacelist));
        
    # shift off the current block info
    splice (@{$spacelist}, 0, 3);

#    greet @{$spacelist};

    my @freelist = UnPackRow($reftb->{$freelist_idx});
            
    unless (scalar (@freelist))
    {
        return (0);
    }

    # return the space used
    push @freelist, @{$spacelist};

    # XXX: fragile - need a STORE with a check to see if succeeds
    $reftb->{$freelist_idx} =
        PackRow(\@freelist);
    
    return ($self->flush());
}

sub flush
{
    my $self = shift;

    return 1
        if ($self->{read_only});

    my $bc = $self->{bc};

    # XXX XXX: need to support flush of all blocks associated with
    # space management if stored in more places than block zero.

    # NOTE: always in block zero for now
    my $blocknum = 0;

    unless (
            $self->{realbc}->WriteBlock(filenum  => $bc->{realbcfileno},
                                        blocknum => $blocknum))
    {
        whisper "failed to write block!";
        return (0);
    }
    return 1;
}

sub dump
{
    my $self = shift;
    my $bc = $self->{bc};
    unless ($self->_tiefh($bc))
    {
        whisper "bad fh tie";
        return (undef);
    }
    my $fileheader = $bc->{fileheader};
    
    my $reftb = $fileheader->{RefTie};

    my ($kk, $vv);

#    my $cnt = scalar(keys(%{$reftb}));
    while (($kk, $vv) = each (%{$reftb}))
    {
        my @outarr = UnPackRow($vv);

        print join(" ",@outarr), "\n";
    }
}


END {

}


1;  # don't forget to return a true value from the file

__END__

=head1 NAME

Genezzo::SpaceMan::SMFile.pm - File Space Management

=head1 SYNOPSIS


=head1 DESCRIPTION

Maintain a block header for each file with information on space usage.
Each file is composed of *extents*, groups of contiguous blocks.  The
free extent list is composed of a number of blocknumber/extent length
pairs, e.g. the row:

  FreeExtents 39:12 19:2  

indicates that the current file has free space starting at block 39 of
12 contiguous blocks, plus space of 2 contiguous block starting at
block 19.

When an object requests space, SMFile tries to find an exact match in
the free list.  If it cannot, it carves the requested size from a
larger free extent.

=head1 FUNCTIONS

=over 4

=item currblock

return the current active block (insert high water mark) for an object

=item firstblock, nextblock

iterate over the set of *used* blocks for an object.  Ignores unused
blocks in last extent

=item countblock

count of all blocks associated with the object.  Includes allocated,
*unused* blocks, plus empty blocks (i.e. blocks with no rows).

=item hasblock 

check if block is associated with an object

=item freetable 

return all of an object's blocks to the freelist

=item flush 

write the contents of block zero to disk.  Need to handle case of
extent lists spread over multiple blocks.


=back


=head2 EXPORT

=head1 TODO

=over 4

=item  read_only database support

=item  support for non-table objects like indexes - done? 

=item  freetable: when last object is freed, need to update _tsfiles as
       UNUSED

=item  need to coalesce adjacent free extents

=item  maintain multiple free lists for performance

=item  better indexing scheme - maybe a btree

=item  chain the block header if necessary -- allocate a new block to
       hold additional free list information, append extent allocation
       to HEADER row (after 0:1)

=item  check status everywhere where update rows 

=item  maintain free extents list for each object, so can re-use
       extents (especially important for updates of large multi-block rows)

=back



=head1 AUTHOR

Jeffrey I. Cohen, jcohen@genezzo.com

=head1 SEE ALSO

perl(1).

Copyright (c) 2003, 2004 Jeffrey I Cohen.  All rights reserved.

    This program 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
    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; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

Address bug reports and comments to: jcohen@genezzo.com

For more information, please visit the Genezzo homepage 
at L<http://www.genezzo.com>

=cut
