package Object::InsideOut::Util; {

require 5.006;

use strict;
use warnings;

our $VERSION = '3.56';
$VERSION = eval $VERSION;

use Object::InsideOut::Metadata 3.56;

### Module Initialization ###

BEGIN {
    # 1. Install our own 'no-op' version of Internals::SvREADONLY for Perl < 5.8
    if (! Internals->can('SvREADONLY')) {
        *Internals::SvREADONLY = sub (\$;$) { return; };
    }

    # Import 'share' and 'bless' if threads::shared
    if ($threads::shared::threads_shared) {
        import threads::shared;
    }
}


# 2. Export requested subroutines
sub import
{
    my $class = shift;   # Not used

    # Exportable subroutines
    my %EXPORT_OK;
    @EXPORT_OK{qw(create_object hash_re is_it make_shared shared_copy)} = undef;

    # Handle entries in the import list
    my $caller = caller();
    my %meta;
    while (my $sym = shift) {
        if (exists($EXPORT_OK{lc($sym)})) {
            # Export subroutine name
            no strict 'refs';
            *{$caller.'::'.$sym} = \&{lc($sym)};
            $meta{$sym}{'hidden'} = 1;
        } else {
            OIO::Code->die(
                'message' => "Symbol '$sym' is not exported by Object::InsideOut::Util",
                'Info'    => 'Exportable symbols: ' . join(' ', keys(%EXPORT_OK)),
                'ignore_package' => 'Object::InsideOut::Util');
        }
    }
    if (%meta) {
        add_meta($caller, \%meta);
    }
}


### Subroutines ###

# Returns a blessed (optional), readonly (Perl 5.8) anonymous scalar reference
# containing either:
#   the value returned by a user-specified subroutine; or
#   a user-supplied scalar
sub create_object
{
    my ($class, $id) = @_;

    # Create the object from an anonymous scalar reference
    my $obj = \do{ my $scalar; };

    # Set the scalar equal to ...
    if (my $ref_type = ref($id)) {
        if ($ref_type eq 'CODE') {
            # ... the value returned by the user-specified subroutine
            local $SIG{__DIE__} = 'OIO::trap';
            $$obj = $id->($class);
        } else {
            # Complain if something other than code ref
            OIO::Args->die(
                'message' => q/2nd argument to create_object() is not a code ref or scalar/,
                'Usage'   => 'create_object($class, $scalar) or create_object($class, $code_ref, ...)',
                'ignore_package' => 'Object::InsideOut::Util');
        }

    } else {
        # ... the user-supplied scalar
        $$obj = $id;
    }

    # Bless the object into the specified class (optional)
    if ($class) {
        bless($obj, $class);
    }

    # Make the object 'readonly' (Perl 5.8)
    Internals::SvREADONLY($$obj, 1) if ($] >= 5.008003);

    # Done - return the object
    return ($obj);
}


# Make a thread-shared version of a complex data structure or object
sub make_shared
{
    my $in = shift;
    my $cloned = shift || {};

    # If not sharing or already thread-shared, then just return the input
    if (! ref($in) ||
        ! $threads::threads ||
        ! $threads::shared::threads_shared ||
        threads::shared::_id($in))
    {
        return ($in);
    }

    # Check for previously cloned references
    #   (this takes care of circular refs as well)
    my $addr = Scalar::Util::refaddr($in);
    if (exists($cloned->{$addr})) {
        # Return the already existing clone
        return $cloned->{$addr};
    }

    # Make copies of array, hash and scalar refs
    my $out;
    my $ref_type = Scalar::Util::reftype($in);

    # Copy an array ref
    if ($ref_type eq 'ARRAY') {
        # Make empty shared array ref
        $out = &threads::shared::share([]);
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        push(@$out, map { make_shared($_, $cloned) } @$in);
    }

    # Copy a hash ref
    elsif ($ref_type eq 'HASH') {
        # Make empty shared hash ref
        $out = &threads::shared::share({});
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        foreach my $key (keys(%{$in})) {
            $out->{$key} = make_shared($in->{$key}, $cloned);
        }
    }

    # Copy a scalar ref
    elsif ($ref_type eq 'SCALAR') {
        $out = \do{ my $scalar = $$in; };
        threads::shared::share($out);
        # Add to clone checking hash
        $cloned->{$addr} = $out;
    }

    # Copy of a ref of a ref
    elsif ($ref_type eq 'REF') {
        # Special handling for $x = \$x
        if ($addr == Scalar::Util::refaddr($$in)) {
            $out = \$out;
            threads::shared::share($out);
            $cloned->{$addr} = $out;
        } else {
            my $tmp;
            $out = \$tmp;
            threads::shared::share($out);
            # Add to clone checking hash
            $cloned->{$addr} = $out;
            # Recursively copy and add contents
            $tmp = make_shared($$in, $cloned);
        }

    } else {
        # Just return anything else
        # NOTE: This will end up generating an error
        return ($in);
    }

    # Return blessed copy, if applicable
    if (my $class = Scalar::Util::blessed($in)) {
        bless($out, $class);
    }

    # Clone READONLY flag
    if ($ref_type eq 'SCALAR') {
        if (Internals::SvREADONLY($$in)) {
            Internals::SvREADONLY($$out, 1) if ($] >= 5.008003);
        }
    }
    if (Internals::SvREADONLY($in)) {
        Internals::SvREADONLY($out, 1) if ($] >= 5.008003);
    }

    # Return clone
    return ($out);
}


# Make a copy of a complex data structure or object.
# If thread-sharing, then make the copy thread-shared.
sub shared_copy
{
    return (($threads::shared::threads_shared) ? clone_shared(@_) : clone(@_));
}


# Recursively make a copy of a complex data structure or object that is
# thread-shared
sub clone_shared
{
    my $in = shift;
    my $cloned = shift || {};

    # Just return the item if not a ref or if it's an object
    return $in if (! ref($in) || Scalar::Util::blessed($in));

    # Check for previously cloned references
    #   (this takes care of circular refs as well)
    my $addr = Scalar::Util::refaddr($in);
    if (exists($cloned->{$addr})) {
        # Return the already existing clone
        return $cloned->{$addr};
    }

    # Make copies of array, hash and scalar refs
    my $out;
    my $ref_type = Scalar::Util::reftype($in);

    # Copy an array ref
    if ($ref_type eq 'ARRAY') {
        # Make empty shared array ref
        $out = &threads::shared::share([]);
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        push(@$out, map { clone_shared($_, $cloned) } @$in);
    }

    # Copy a hash ref
    elsif ($ref_type eq 'HASH') {
        # Make empty shared hash ref
        $out = &threads::shared::share({});
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        foreach my $key (keys(%{$in})) {
            $out->{$key} = clone_shared($in->{$key}, $cloned);
        }
    }

    # Copy a scalar ref
    elsif ($ref_type eq 'SCALAR') {
        $out = \do{ my $scalar = $$in; };
        threads::shared::share($out);
        # Add to clone checking hash
        $cloned->{$addr} = $out;
    }

    # Copy of a ref of a ref
    elsif ($ref_type eq 'REF') {
        # Special handling for $x = \$x
        if ($addr == Scalar::Util::refaddr($$in)) {
            $out = \$out;
            threads::shared::share($out);
            $cloned->{$addr} = $out;
        } else {
            my $tmp;
            $out = \$tmp;
            threads::shared::share($out);
            # Add to clone checking hash
            $cloned->{$addr} = $out;
            # Recursively copy and add contents
            $tmp = clone_shared($$in, $cloned);
        }

    } else {
        # Just return anything else
        # NOTE: This will end up generating an error
        return ($in);
    }

    # Return blessed copy, if applicable
    if (my $class = Scalar::Util::blessed($in)) {
        bless($out, $class);
    }

    # Clone READONLY flag
    if ($ref_type eq 'SCALAR') {
        if (Internals::SvREADONLY($$in)) {
            Internals::SvREADONLY($$out, 1) if ($] >= 5.008003);
        }
    }
    if (Internals::SvREADONLY($in)) {
        Internals::SvREADONLY($out, 1) if ($] >= 5.008003);
    }

    # Return clone
    return ($out);
}


# Recursively make a copy of a complex data structure or object
sub clone
{
    my $in = shift;
    my $cloned = shift || {};

    # Just return the item if not a ref or if it's an object
    return $in if (! ref($in) || Scalar::Util::blessed($in));

    # Check for previously cloned references
    #   (this takes care of circular refs as well)
    my $addr = Scalar::Util::refaddr($in);
    if (exists($cloned->{$addr})) {
        # Return the already existing clone
        return $cloned->{$addr};
    }

    # Make copies of array, hash and scalar refs
    my $out;
    my $ref_type = Scalar::Util::reftype($in);

    # Copy an array ref
    if ($ref_type eq 'ARRAY') {
        # Make empty shared array ref
        $out = [];
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        push(@$out, map { clone($_, $cloned) } @$in);
    }

    # Copy a hash ref
    elsif ($ref_type eq 'HASH') {
        # Make empty shared hash ref
        $out = {};
        # Add to clone checking hash
        $cloned->{$addr} = $out;
        # Recursively copy and add contents
        foreach my $key (keys(%{$in})) {
            $out->{$key} = clone($in->{$key}, $cloned);
        }
    }

    # Copy a scalar ref
    elsif ($ref_type eq 'SCALAR') {
        $out = \do{ my $scalar = $$in; };
        # Add to clone checking hash
        $cloned->{$addr} = $out;
    }

    # Copy of a ref of a ref
    elsif ($ref_type eq 'REF') {
        # Special handling for $x = \$x
        if ($addr == Scalar::Util::refaddr($$in)) {
            $out = \$out;
            $cloned->{$addr} = $out;
        } else {
            my $tmp;
            $out = \$tmp;
            # Add to clone checking hash
            $cloned->{$addr} = $out;
            # Recursively copy and add contents
            $tmp = clone($$in, $cloned);
        }

    } else {
        # Just return anything else
        # NOTE: This will end up generating an error
        return ($in);
    }

    # Clone READONLY flag
    if ($ref_type eq 'SCALAR') {
        if (Internals::SvREADONLY($$in)) {
            Internals::SvREADONLY($$out, 1) if ($] >= 5.008003);
        }
    }
    if (Internals::SvREADONLY($in)) {
        Internals::SvREADONLY($out, 1) if ($] >= 5.008003);
    }

    # Return clone
    return ($out);
}


# Access hash value using regex
sub hash_re
{
    my $hash = $_[0];   # Hash ref to search through
    my $re   = $_[1];   # Regex to match keys against

    foreach (keys(%{$hash})) {
        if (/$re/) {
            return ($hash->{$_}, $_) if wantarray();
            return ($hash->{$_});
        }
    }
    return;
}


# Checks if a scalar is a specified type
sub is_it
{
    my ($thing, $what) = @_;

    return ((Scalar::Util::blessed($thing))
                ? $thing->isa($what)
                : (ref($thing) eq $what));
}

}  # End of package's lexical scope

1;
