# Net::ZooKeeper - Perl extension for Apache ZooKeeper
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.

use File::Spec;
use Test::More tests => 47;

BEGIN { use_ok('Net::ZooKeeper', qw(:all)) };


my $test_dir;
(undef, $test_dir, undef) = File::Spec->splitpath($0);
require File::Spec->catfile($test_dir, 'util.pl');

my($hosts, $root_path, $node_path) = zk_test_setup(0);


SKIP: {
    my $zkh = Net::ZooKeeper->new($hosts);

    skip 'no valid handle', 15 unless (defined($zkh));

    my $stat = $zkh->stat();
    my $watch = $zkh->watch();


    ## DESTROY() on reblessed handle

    bless($zkh, 'My::ZooKeeper');
    is(ref($zkh), 'My::ZooKeeper',
       'bless(): reblessed handle');

    eval {
        $zkh->EXISTS();
    };
    like($@, qr/Can't locate object method "EXISTS" via package "My::ZooKeeper"/,
         'EXISTS(): not defined on reblessed handle');

    my $attr = tied(%{$zkh});

    my $ret = $attr->DESTROY();
    ok($ret,
       'DESTROY(): destroyed inner hash of reblessed handle');

    $ret = $attr->DESTROY();
    ok(!$ret,
       'DESTROY(): no action on destroyed inner hash of reblessed handle');

    undef $zkh;
    ok(!defined($zkh),
       'undef: released reblessed handle');


    ## DESTROY() on reblessed stat handle

    bless($stat, 'My::ZooKeeper::Stat');
    is(ref($stat), 'My::ZooKeeper::Stat',
       'bless(): reblessed stat handle');

    eval {
        $stat->EXISTS(1);
    };
    like($@, qr/Can't locate object method "EXISTS" via package "My::ZooKeeper::Stat"/,
         'stat EXISTS(): not defined on reblessed stat handle');

    $attr = tied(%{$stat});

    $ret = $attr->DESTROY();
    ok($ret,
       'stat DESTROY(): destroyed inner hash of reblessed stat handle');

    $ret = $attr->DESTROY();
    ok(!$ret,
       'stat DESTROY(): no action on destroyed inner hash of ' .
       'reblessed stat handle');

    undef $stat;
    ok(!defined($stat),
       'undef: released reblessed stat handle');


    ## DESTROY() on reblessed watch handle

    bless($watch, 'My::ZooKeeper::Watch');
    is(ref($watch), 'My::ZooKeeper::Watch',
       'bless(): reblessed watch handle');

    eval {
        $watch->EXISTS(1);
    };
    like($@, qr/Can't locate object method "EXISTS" via package "My::ZooKeeper::Watch"/,
         'watch EXISTS(): not defined on reblessed watch handle');

    $attr = tied(%{$watch});

    $ret = $attr->DESTROY();
    ok($ret,
       'watch DESTROY(): destroyed inner hash of reblessed watch handle');

    $ret = $attr->DESTROY();
    ok(!$ret,
       'watch DESTROY(): no action on destroyed inner hash of ' .
       'reblessed watch handle');

    undef $watch;
    ok(!defined($watch),
       'undef: released reblessed watch handle');
}

SKIP: {
    my $zkh = Net::ZooKeeper->new($hosts);

    skip 'no valid handle', 9 unless (defined($zkh));

    my $stat = $zkh->stat();
    my $watch = $zkh->watch();


    ## UNTIE() on reblessed handle

    bless($zkh, 'My::ZooKeeper');
    is(ref($zkh), 'My::ZooKeeper',
       'bless(): reblessed handle');

    eval {
        untie(%{$zkh});
    };
    like($@, qr/untying hashes of class Net::ZooKeeper not supported/,
         'untie(): untying hashes from reblessed handle not supported');

    my $attr = tied(%{$zkh});

    eval {
        $attr->UNTIE(0);
    };
    like($@, qr/untying hashes of class Net::ZooKeeper not supported/,
         'UNTIE(): untying hashes from reblessed handle not supported');


    ## UNTIE() on reblessed stat handle

    bless($stat, 'My::ZooKeeper::Stat');
    is(ref($stat), 'My::ZooKeeper::Stat',
       'bless(): reblessed stat handle');

    eval {
        untie(%{$stat});
    };
    like($@, qr/untying hashes of class Net::ZooKeeper::Stat not supported/,
         'untie(): untying hashes from reblessed stat handle not supported');

    $attr = tied(%{$stat});

    eval {
        $attr->UNTIE(0);
    };
    like($@, qr/untying hashes of class Net::ZooKeeper::Stat not supported/,
         'stat UNTIE(): untying hashes from reblessed stat handle ' .
         'not supported');


    ## UNTIE() on reblessed watch handle

    bless($watch, 'My::ZooKeeper::Watch');
    is(ref($watch), 'My::ZooKeeper::Watch',
       'bless(): reblessed watch handle');

    eval {
        untie(%{$watch});
    };
    like($@, qr/untying hashes of class Net::ZooKeeper::Watch not supported/,
         'untie(): untying hashes from reblessed watch handle not supported');

    $attr = tied(%{$watch});

    eval {
        $attr->UNTIE(0);
    };
    like($@, qr/untying hashes of class Net::ZooKeeper::Watch not supported/,
         'watch UNTIE(): untying hashes from reblessed watch handle ' .
         'not supported');
}


package Net::ZooKeeper::Test;

use Net::ZooKeeper qw(:acls);

our @ISA = qw(Net::ZooKeeper);

sub create
{
    my($self, $path, $buf) = @_;

    return $self->SUPER::create($path, $buf,
                                'path_read_len' => length($path),
                                'acl' => ZOO_OPEN_ACL_UNSAFE);
}

sub get_children_absolute
{
    my($self, $path) = @_;

    my @child_paths = $self->get_children($path);

    my $prefix = $path . (($path =~ /\/$/) ? '' : '/');
    return map {$prefix . $_} @child_paths;
}

sub stat
{
    my $self = shift;

    my $stat = $self->SUPER::stat();

    return bless($stat, 'Net::ZooKeeper::Test::Stat');
}


sub watch
{
    my $self = shift;

    my $watch = $self->SUPER::watch();

    return bless($watch, 'Net::ZooKeeper::Test::Watch');
}


package Net::ZooKeeper::Test::Stat;

our @ISA = qw(Net::ZooKeeper::Stat);

sub get_ctime
{
    my $self = shift;

    return $self->{'ctime'};
}


package Net::ZooKeeper::Test::Watch;

our @ISA = qw(Net::ZooKeeper::Watch);

sub get_timeout
{
    my $self = shift;

    return $self->{'timeout'};
}


package main;

my $sub_zkh = Net::ZooKeeper::Test->new($hosts);
isa_ok($sub_zkh, 'Net::ZooKeeper::Test',
       'new(): created subclassed handle');

SKIP: {
    skip 'no valid subclassed handle', 21 unless (defined($sub_zkh));

    is($sub_zkh->{'data_read_len'}, 1023,
       'FETCH(): default data read length using subclassed handle');

    my $path;

    SKIP: {
        my $ret = $sub_zkh->exists($root_path);

        skip 'no connection to ZooKeeper', 1 unless
            (defined($ret) and $ret);

        $path = $sub_zkh->create($node_path, 'foo',
                                 'acl' => ZOO_OPEN_ACL_UNSAFE);
        is($path, $node_path,
           'create(): created node with subclassed handle');
    }

    SKIP: {
        skip 'no connection to ZooKeeper', 1 unless
            (defined($path) and $path eq $node_path);

        my @children = $sub_zkh->get_children_absolute($root_path);
        my $child_exists =!! grep {$_ eq $node_path} @children;
        ok($child_exists,
           'get_children_absolute(): retrieved new child with subclassed handle');
    }

    my $sub_stat = $sub_zkh->stat();
    isa_ok($sub_stat, 'Net::ZooKeeper::Test::Stat',
           'stat(): created subclassed stat handle');

    SKIP: {
        skip 'no valid subclassed stat handle', 6 unless
            (defined($sub_stat));

        is($sub_stat->{'ctime'}, 0,
           'stat FETCH(): default ctime using subclassed stat handle');

        SKIP: {
            my $ret = $sub_zkh->exists($node_path, 'stat' => $sub_stat) if
                (defined($path) and $path eq $node_path);

            skip 'no connection to ZooKeeper', 2 unless
                (defined($ret) and $ret);

            my $ctime = $sub_stat->get_ctime();
            ok($ctime > 0,
               'get_ctime(): retrieved ctime with subclassed stat handle');

            is($sub_stat->{'ctime'}, $ctime,
               'stat FETCH(): ctime using subclassed stat handle');
        }

        my $ret = $sub_stat->DESTROY();
        ok($ret,
           'stat DESTROY(): destroyed subclassed stat handle');

        $ret = $sub_stat->DESTROY();
        ok(!$ret,
           'stat DESTROY(): no action on destroyed subclassed stat handle');

        undef $sub_stat;
        ok(!defined($sub_stat),
           'undef: released subclassed stat handle');
    }

    my $sub_watch = $sub_zkh->watch();
    isa_ok($sub_watch, 'Net::ZooKeeper::Test::Watch',
           'watch(): created subclassed watch handle');

    SKIP: {
        skip 'no valid subclassed watch handle', 6 unless
            (defined($sub_watch));

        SKIP: {
            my $ret = $sub_zkh->exists($root_path, 'watch' => $sub_watch);

            skip 'no connection to ZooKeeper', 3 unless
                (defined($ret) and $ret);

            $sub_watch->{'timeout'} = 50;

            is($sub_watch->get_timeout(), 50,
               'get_timeout(): retrieved timeout with subclassed ' .
               'watch handle');

            is($sub_watch->{'timeout'}, 50,
               'watch FETCH(): timeout using subclassed stat handle');

            $ret = $sub_watch->wait();
            ok(!$ret,
               'wait(): watch after checking node existence timed out with ' .
               'subclassed watch handle');
        }

        my $ret = $sub_watch->DESTROY();
        ok($ret,
           'watch DESTROY(): destroyed subclassed watch handle');

        $ret = $sub_watch->DESTROY();
        ok(!$ret,
           'watch DESTROY(): no action on destroyed subclassed watch handle');

        undef $sub_watch;
        ok(!defined($sub_watch),
           'undef: released subclassed watch handle');
    }

    SKIP: {
        skip 'no connection to ZooKeeper', 1 unless
            (defined($path) and $path eq $node_path);

        my $ret = $sub_zkh->delete($node_path);
        ok($ret,
           'delete(): deleted node with subclassed handle');
    }

    my $ret = $sub_zkh->DESTROY();
    ok($ret,
       'DESTROY(): destroyed subclassed handle');

    $ret = $sub_zkh->DESTROY();
    ok(!$ret,
       'DESTROY(): no action on destroyed subclassed handle');

    undef $sub_zkh;
    ok(!defined($sub_zkh),
       'undef: released subclassed handle');
}

