##
##  OSSP uuid - Universally Unique Identifier
##  Copyright (c) 2004-2007 Ralf S. Engelschall <rse@engelschall.com>
##  Copyright (c) 2004-2007 The OSSP Project <http://www.ossp.org/>
##
##  This file is part of OSSP uuid, a library for the generation
##  of UUIDs which can found at http://www.ossp.org/pkg/lib/uuid/
##
##  Permission to use, copy, modify, and distribute this software for
##  any purpose with or without fee is hereby granted, provided that
##  the above copyright notice and this permission notice appear in all
##  copies.
##
##  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
##  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
##  SUCH DAMAGE.
##
##  uuid.pm: Perl Binding (Perl part)
##

##
##  High-Level Perl Module TIE-style API
##  (just a functionality-reduced TIE wrapper around the OO-style API)
##

package OSSP::uuid::tie;

use 5.008;
use strict;
use warnings;
use Carp;

#   inhert from Tie::Scalar
require Tie::Scalar;
our @ISA = qw(Tie::Scalar);

#   helper function
sub mode_sanity {
    my ($mode) = @_;
    if (not (    defined($mode)
             and ref($mode) eq 'ARRAY'
             and (   (@{$mode} == 1 and $mode->[0] =~ m|^v[14]$|)
                  or (@{$mode} == 3 and $mode->[0] =~ m|^v[35]$|)))) {
        return (undef, "invalid UUID generation mode specification");
    }
    if ($mode->[0] =~ m|^v[35]$|) {
        my $uuid_ns = new OSSP::uuid;
        $uuid_ns->load($mode->[1])
            or return (undef, "failed to load UUID $mode->[0] namespace");
        $mode->[1] = $uuid_ns;
    }
    return ($mode, undef);
}

#   constructor
sub TIESCALAR {
    my ($class, @args) = @_;
    my $self = {};
    bless ($self, $class);
    $self->{-uuid} = new OSSP::uuid
       or croak "failed to create OSSP::uuid object";
    my ($mode, $error) = mode_sanity(defined($args[0]) ? [ @args ] : [ "v1" ]);
    croak $error if defined($error);
    $self->{-mode} = $mode;
    return $self;
}

#   destructor
sub DESTROY {
    my ($self) = @_;
    delete $self->{-uuid};
    delete $self->{-mode};
    return;
}

#   fetch value from scalar
#   (applied semantic: export UUID in string format)
sub FETCH {
    my ($self) = @_;
    $self->{-uuid}->make(@{$self->{-mode}})
       or croak "failed to generate new UUID";
    my $value = $self->{-uuid}->export("str")
       or croak "failed to export new UUID";
    return $value;
}

#   store value into scalar
#   (applied semantic: configure new UUID generation mode)
sub STORE {
    my ($self, $value) = @_;
    my ($mode, $error) = mode_sanity($value);
    croak $error if defined($error);
    $self->{-mode} = $mode;
    return;
}

##
##  High-Level Perl Module OO-style API
##  (just an OO wrapper around the C-style API)
##

package OSSP::uuid;

use 5.008;
use strict;
use warnings;
use Carp;
use XSLoader;
use Exporter;

#   API version
our $VERSION = do { my @v = ('1.6.2' =~ m/\d+/g); sprintf("%d.".("%02d"x$#v), @v); };

#   API inheritance
our @ISA = qw(Exporter);

#   API symbols
my $symbols = {
    'const' => [qw(
        UUID_VERSION
        UUID_LEN_BIN
        UUID_LEN_STR
        UUID_LEN_SIV
        UUID_RC_OK
        UUID_RC_ARG
        UUID_RC_MEM
        UUID_RC_SYS
        UUID_RC_INT
        UUID_RC_IMP
        UUID_MAKE_V1
        UUID_MAKE_V3
        UUID_MAKE_V4
        UUID_MAKE_V5
        UUID_MAKE_MC
        UUID_FMT_BIN
        UUID_FMT_STR
        UUID_FMT_SIV
        UUID_FMT_TXT
    )],
    'func' => [qw(
        uuid_create
        uuid_destroy
        uuid_load
        uuid_make
        uuid_isnil
        uuid_compare
        uuid_import
        uuid_export
        uuid_error
        uuid_version
    )]
};

#   API symbol exportation
our %EXPORT_TAGS = (
    'all'   => [ @{$symbols->{'const'}}, @{$symbols->{'func'}} ],
    'const' => [ @{$symbols->{'const'}} ],
    'func'  => [ @{$symbols->{'func'}}  ]
);
our @EXPORT_OK = @{$EXPORT_TAGS{'all'}};
our @EXPORT    = ();

#   constructor
sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-uuid} = undef;
    $self->{-rc}   = $self->UUID_RC_OK;
    my $rc = uuid_create($self->{-uuid});
    if ($rc != $self->UUID_RC_OK) {
        croak(sprintf("OSSP::uuid::new: uuid_create: %s (%d)", uuid_error($rc), $rc));
        return undef;
    }
    return $self;
}

#   destructor
sub DESTROY ($) {
    my ($self) = @_;
    $self->{-rc} = uuid_destroy($self->{-uuid}) if (defined($self->{-uuid}));
    if ($self->{-rc} != $self->UUID_RC_OK) {
        carp(sprintf("OSSP::uuid::DESTROY: uuid_destroy: %s (%d)", uuid_error($self->{-rc}), $self->{-rc}));
        return;
    }
    $self->{-uuid} = undef;
    $self->{-rc}   = undef;
    return;
}

sub load ($$) {
    my ($self, $name) = @_;
    $self->{-rc} = uuid_load($self->{-uuid}, $name);
    return ($self->{-rc} == $self->UUID_RC_OK);
}

sub make ($$;@) {
    my ($self, $mode, @valist) = @_;
    my $mode_code = 0;
    foreach my $spec (split(/,/, $mode)) {
        if    ($spec eq 'v1') { $mode_code |= $self->UUID_MAKE_V1; }
        elsif ($spec eq 'v3') { $mode_code |= $self->UUID_MAKE_V3; }
        elsif ($spec eq 'v4') { $mode_code |= $self->UUID_MAKE_V4; }
        elsif ($spec eq 'v5') { $mode_code |= $self->UUID_MAKE_V5; }
        elsif ($spec eq 'mc') { $mode_code |= $self->UUID_MAKE_MC; }
        else  { croak("invalid mode specification \"$spec\""); }
    }
    if (($mode_code & $self->UUID_MAKE_V3) or ($mode_code & $self->UUID_MAKE_V5)) {
        if (not (ref($valist[0]) and $valist[0]->isa("OSSP::uuid"))) {
            croak("UUID_MAKE_V3/UUID_MAKE_V5 requires namespace argument to be OSSP::uuid object");
        }
        my $ns   = $valist[0]->{-uuid};
        my $name = $valist[1];
        $self->{-rc} = uuid_make($self->{-uuid}, $mode_code, $ns, $name);
    }
    else {
        $self->{-rc} = uuid_make($self->{-uuid}, $mode_code);
    }
    return ($self->{-rc} == $self->UUID_RC_OK);
}

sub isnil ($) {
    my ($self) = @_;
    my $result;
    $self->{-rc} = uuid_isnil($self->{-uuid}, $result);
    return ($self->{-rc} == $self->UUID_RC_OK ? $result : undef);
}

sub compare ($$) {
    my ($self, $other) = @_;
    my $result = 0;
    if (not (ref($other) and $other->isa("OSSP::uuid"))) {
        croak("argument has to an OSSP::uuid object");
    }
    $self->{-rc} = uuid_compare($self->{-uuid}, $other->{-uuid}, $result);
    return ($self->{-rc} == $self->UUID_RC_OK ? $result : undef);
}

sub import {
    #   ATTENTION: The OSSP uuid API function "import" conflicts with
    #   the standardized "import" method the Perl world expects from
    #   their modules. In order to keep the Perl binding consist
    #   with the C API, we solve the conflict under run-time by
    #   distinguishing between the two types of "import" calls.
    if (defined($_[0]) and ref($_[0]) =~ m/^OSSP::uuid/) {
        #   the regular OSSP::uuid "import" method
        croak("import method expects 3 or 4 arguments") if (@_ < 3 or @_ > 4); # emulate prototype
        my ($self, $fmt, $data_ptr, $data_len) = @_;
        if    ($fmt eq 'bin') { $fmt = $self->UUID_FMT_BIN; }
        elsif ($fmt eq 'str') { $fmt = $self->UUID_FMT_STR; }
        elsif ($fmt eq 'siv') { $fmt = $self->UUID_FMT_SIV; }
        elsif ($fmt eq 'txt') { $fmt = $self->UUID_FMT_TXT; }
        else  { croak("invalid format \"$fmt\""); }
        $data_len ||= length($data_ptr); # functional redudant, but Perl dislikes undef value here
        $self->{-rc} = uuid_import($self->{-uuid}, $fmt, $data_ptr, $data_len);
        return ($self->{-rc} == $self->UUID_RC_OK);
    }
    else {
        #   the special Perl "import" method
        #   (usually inherited from the Exporter)
        no strict "refs";
        return OSSP::uuid->export_to_level(1, @_);
    }
}

sub export {
    #   ATTENTION: The OSSP uuid API function "export" conflicts with
    #   the standardized "export" method the Perl world expects from
    #   their modules. In order to keep the Perl binding consist
    #   with the C API, we solve the conflict under run-time by
    #   distinguishing between the two types of "export" calls.
    if (defined($_[0]) and ref($_[0]) =~ m/^OSSP::uuid/) {
        #   the regular OSSP::uuid "export" method
        croak("export method expects 2 arguments") if (@_ != 2); # emulate prototype
        my ($self, $fmt) = @_;
        my $data_ptr;
        if    ($fmt eq 'bin') { $fmt = $self->UUID_FMT_BIN; }
        elsif ($fmt eq 'str') { $fmt = $self->UUID_FMT_STR; }
        elsif ($fmt eq 'siv') { $fmt = $self->UUID_FMT_SIV; }
        elsif ($fmt eq 'txt') { $fmt = $self->UUID_FMT_TXT; }
        else  { croak("invalid format \"$fmt\""); }
        $self->{-rc} = uuid_export($self->{-uuid}, $fmt, $data_ptr, undef);
        return ($self->{-rc} == $self->UUID_RC_OK ? $data_ptr : undef);
    }
    else {
        #   the special Perl "export" method
        #   (usually inherited from the Exporter)
        return Exporter::export(@_);
    }
}

sub error ($;$) {
    my ($self, $rc) = @_;
    $rc = $self->{-rc} if (not defined($rc));
    return wantarray ? (uuid_error($rc), $rc) : uuid_error($rc);
}

sub version (;$) {
    my ($self) = @_;
    return uuid_version();
}

##
##  Low-Level Perl XS C-style API
##  (actually just the activation of the XS part)
##

#   auto-loading constants
sub AUTOLOAD {
    my $constname;
    our $AUTOLOAD;
    ($constname = $AUTOLOAD) =~ s/.*:://;
    croak "&OSSP::uuid::constant not defined" if ($constname eq 'constant');
    my ($error, $val) = constant($constname);
    croak $error if ($error);
    { no strict 'refs'; *$AUTOLOAD = sub { $val }; }
    goto &$AUTOLOAD;
}

#   static-loading functions
XSLoader::load('OSSP::uuid', $VERSION);

1;