#/*
# *  Copyright 2007-2010 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed 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.
# */
#
# $Id: CDB.pm 1901 2009-12-31 02:40:20Z hikarin $
#

package Img0ch::Maple::CDB;

use strict;
use CDB_File qw();

sub new {
    my ( $iClass, $iKernel, $path ) = @_;
    bless {
        __cdb      => undef,
        __data     => {},
        __filename => $path,
        __kernel   => $iKernel,
        __remove   => {},
    }, $iClass;
}

sub DESTROY { }

sub load {
    my ( $iRepos, $path ) = @_;
    my $iKernel = $iRepos->{__kernel};

    $path ||= $iRepos->{__filename};
    -r $path or return 1;

    $iRepos->{__cdb} = CDB_File->TIEHASH($path)
        or $iKernel->throw_io_exception($path);
    return 1;
}

sub save {
    my ( $iRepos, $path ) = @_;
    my $iKernel = $iRepos->{__kernel};

    $path ||= $iRepos->{__filename};
    my $temp = $path . '.tmp';

    eval {
        if ( -w $path )
        {
            unlink $path or $iKernel->throw_io_exception($path);
        }
        my $new_cdb = CDB_File->new( $path, $temp )
            or $iKernel->throw_io_exception($path);
        my ( $key, $value );
        my $data   = $iRepos->{__data};
        my $remove = $iRepos->{__remove};
        map { $data->{$_} } keys %$remove;
        while ( ( $key, $value ) = each %{$data} ) {
            $new_cdb->insert( $key, $value );
        }
        if ( my $cdb = $iRepos->{__cdb} ) {
            for ( $key = $cdb->FIRSTKEY(); $key; $key = $cdb->NEXTKEY($key) )
            {
                ( exists $data->{$key} or exists $remove->{$key} ) and next;
                $new_cdb->insert( $key, $cdb->FETCH($key) );
            }
        }
        $new_cdb->finish() or $iKernel->throw_io_exception($path);
        $iRepos->{__cdb} = CDB_File->TIEHASH($path)
            or $iKernel->throw_io_exception($path);
        %{ $iRepos->{__data} }   = ();
        %{ $iRepos->{__remove} } = ();
    };
    $@ and die $@, "\n";

    return 1;
}

sub set {
    my ( $iRepos, $key, $value ) = @_;
    $iRepos->{__data}->{$key} = $value;
    return;
}

*set_binary = \&set;

sub get {
    my ( $iRepos, $key ) = @_;
    my $cdb   = $iRepos->{__cdb};
    my $dirty = $iRepos->{__data}->{$key};
    $dirty and return $dirty;
    $cdb or return '';
    return $cdb->FETCH($key) || '';
}

*get_binary = \&get;

sub decode_binary { shift; return $_[0] }

sub is_binary_safe {1}

sub get_int { Img0ch::Kernel::intval( $_[0]->get( $_[1] ) ) }

sub remove {
    my ( $iRepos, $key ) = @_;
    my $old_value = delete $iRepos->{__data}->{$key};
    $iRepos->{__remove}->{$key} = 1;
    return $old_value || $iRepos->get($key);
}

sub iterate {
    my ( $iRepos, $code_ref, @args ) = @_;
    my ( $key, $value );

    if ( my $cdb = $iRepos->{__cdb} ) {
        for ( $key = $cdb->FIRSTKEY(); $key; $key = $cdb->NEXTKEY($key) ) {
            my $value = $cdb->FETCH($key);
            my $status = $code_ref->( $key, \$value, @args );
            if ( $status > 0 ) {
                $iRepos->set( $key, $value );
            }
            elsif ( $status < 0 ) {
                $iRepos->remove($key);
            }
        }
    }

    my $data = $iRepos->{__data};
    while ( ( $key, $value ) = each %{$data} ) {
        my $status = $code_ref->( $key, \$value, @args );
        if ( $status > 0 ) {
            $iRepos->set( $key, $value );
        }
        elsif ( $status < 0 ) {
            $iRepos->remove($key);
        }
    }
    return 1;
}

1;
__END__
