##----------------------------------------------------------------------------
## Module Generic - ~/lib/Module/Generic/TieHash.pm
## Version v1.1.0
## Copyright(c) 2021 DEGUEST Pte. Ltd.
## Author: Jacques Deguest <jack@deguest.jp>
## Created 2021/03/20
## Modified 2022/02/27
## All rights reserved
## 
## This program is free software; you can redistribute  it  and/or  modify  it
## under the same terms as Perl itself.
##----------------------------------------------------------------------------
package Module::Generic::TieHash;
BEGIN
{
    use strict;
    use warnings::register;
    use warnings;
    use parent qw( Module::Generic );
    use Scalar::Util ();
    our $VERSION = 'v1.1.0';
};

use strict;
no warnings 'redefine';

sub TIEHASH
{
    my $self  = shift( @_ );
    my $opts  = {};
    $opts = shift( @_ ) if( @_ );
    if( Scalar::Util::reftype( $opts ) ne 'HASH' )
    {
        warn( "Parameters provided ($opts) is not an hash reference.\n" ) if( $self->_warnings_is_enabled );
        return;
    }
    my $disable = [];
    $disable = $opts->{disable} if( Scalar::Util::reftype( $opts->{disable} ) );
    my $list = {};
    @$list{ @$disable } = ( 1 ) x scalar( @$disable );
    my $hash =
    {
    ## The caller sets this to its class, so we can differentiate calls from inside and outside our caller's package
    disable => $list,
    debug => $opts->{debug},
    ## When disabled, the Tie::Hash system will return hash key values directly under $self instead of $self->{data}
    ## Disabled by default so the new() method can access its setup data directly under $self
    ## Then new() can call enable to active it
    enable => 0,
    ## Where to store the actual hash data
    data  => {},
    };
    my $class = ref( $self ) || $self;
    return( bless( $hash => $class ) );
}

sub CLEAR
{
    my $self = shift( @_ );
    my $data = $self->{data};
    %$data = ();
}

sub DELETE
{
    my $self = shift( @_ );
    my $data = $self->{data};
    my $key  = shift( @_ );
    my $caller = caller;
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        CORE::delete( $self->{ $key } );
    }
    else
    {
        CORE::delete( $data->{ $key } );
    }
}

sub EXISTS
{
    my $self = shift( @_ );
    my $data = $self->{data};
    my $key  = shift( @_ );
    my $caller = caller;
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        CORE::exists( $self->{ $key } );
    }
    else
    {
        CORE::exists( $data->{ $key } );
    }
}

sub FETCH
{
    my $self = shift( @_ );
    my $data = $self->{data};
    my $key  = shift( @_ );
    my $caller = caller;
    ## print( STDERR "FETCH($caller)[enable=$self->{enable}] <- '$key''\n" );
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        #print( STDERR "FETCH($caller)[owner calling, enable=$self->{enable}] <- '$key' <- '$self->{$key}'\n" );
        return( $self->{ $key } )
    }
    else
    {
        #print( STDERR "FETCH($caller)[enable=$self->{enable}] <- '$key' <- '$data->{$key}'\n" );
        return( $data->{ $key } );
    }
}

sub FIRSTKEY
{
    my $self = shift( @_ );
    my $data = $self->{data};
    my @keys = ();
    my $caller = caller;
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        @keys = keys( %$self );
    }
    else
    {
        @keys = keys( %$data );
    }
    $self->{ITERATOR} = \@keys;
    return( shift( @keys ) );
}

sub NEXTKEY
{
    my $self = shift( @_ );
    my $data = $self->{data};
    my $keys = ref( $self->{ITERATOR} ) ? $self->{ITERATOR} : [];
    return( shift( @$keys ) );
}

sub SCALAR
{
    my $self  = shift( @_ );
    my $data = $self->{data};
    my $caller = caller;
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        return( scalar( keys( %$self ) ) );
    }
    else
    {
        return( scalar( keys( %$data ) ) );
    }
}

sub STORE
{
    my $self  = shift( @_ );
    my $data = $self->{data};
    my( $key, $val ) = @_;
    my $caller = caller;
    if( $self->_exclude( $caller ) || !$self->{enable} )
    {
        #print( STDERR "STORE($caller)[owner calling] <- '$key' -> '$val'\n" );
        $self->{ $key } = $val;
    }
    else
    {
        #print( STDERR "STORE($caller)[enable=$self->{enable}] <- '$key' -> '$val'\n" );
        $data->{ $key } = $val;
    }
}

sub enable { return( shift->_set_get_boolean( 'enable', @_ ) ); }

sub _exclude
{
    my $self = shift( @_ );
    my $caller = shift( @_ );
    ## $self->message( 3, "Disable hash contains: ", sub{ $self->dump( $self->{disable} ) });
    return( CORE::exists( $self->{disable}->{ $caller } ) );
}

1;

__END__

=encoding utf-8

=head1 NAME

Module::Generic - Generic Tie Hash Mechanism for Object Oriented Hashes

=head1 SYNOPSIS

    my $tie = tie( %hash, 'Module::Generic::TieHash' );

=head1 VERSION

    v1.1.0

=head1 AUTHOR

Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>

=head1 COPYRIGHT & LICENSE

Copyright (c) 2020-2021 DEGUEST Pte. Ltd.

You can use, copy, modify and redistribute this package and associated
files under the same terms as Perl itself.

=cut