package Data::MessagePack::PP;

use 5.008000;
use strict;
use Carp ();

our $VERSION = '0.15';

# See also
# http://redmine.msgpack.org/projects/msgpack/wiki/FormatSpec
# http://cpansearch.perl.org/src/YAPPO/Data-Model-0.00006/lib/Data/Model/Driver/Memcached.pm
# http://frox25.no-ip.org/~mtve/wiki/MessagePack.html : reference to using CORE::pack, CORE::unpack


package
    Data::MessagePack;

use Scalar::Util qw( blessed );
use strict;
use B ();

BEGIN {
    # for pack and unpack compatibility
    if ( $] < 5.010 ) {
        # require $Config{byteorder}; my $bo_is_le = ( $Config{byteorder} =~ /^1234/ );
        # which better?
        my $bo_is_le = unpack ( 'd', "\x00\x00\x00\x00\x00\x00\xf0\x3f") == 1; # 1.0LE
        # In really, since 5.9.2 '>' is introduced. but 'n!' and 'N!'?
        *pack_uint64 = $bo_is_le ? sub {
            my @v = unpack( 'V2', pack( 'Q', $_[0] ) );
            return pack 'CN2', 0xcf, @v[1,0];
        } : sub { pack 'CQ', 0xcf, $_[0]; };
        *pack_int64 = $bo_is_le ? sub {
            my @v = unpack( 'V2', pack( 'q', $_[0] ) );
            return pack 'CN2', 0xd3, @v[1,0];
        } : sub { pack 'Cq', 0xd3, $_[0]; };
        *pack_double = $bo_is_le ? sub {
            my @v = unpack( 'V2', pack( 'd', $_[0] ) );
            return pack 'CN2', 0xcb, @v[1,0];
        } : sub { pack 'Cd', 0xcb, $_[0]; };
        *unpack_float = $bo_is_le ? sub {
            my @v = unpack( 'v2', substr( $_[0], $_[1], 4 ) );
            return unpack( 'f', pack( 'n2', @v[1,0] ) );
        } : sub { return unpack( 'f', substr( $_[0], $_[1], 4 ) ); };
        *unpack_double = $bo_is_le ? sub {
            my @v = unpack( 'V2', substr( $_[0], $_[1], 8 ) );
            return unpack( 'd', pack( 'N2', @v[1,0] ) );
        } : sub { return unpack( 'd', substr( $_[0], $_[1], 8 ) ); };
        *unpack_int16  = sub {
            my $v = unpack 'n', substr( $_[0], $_[1], 2 );
            return $v ? $v - 0x10000 : 0;
        };
        *unpack_int32  = sub {
            no warnings; # avoid for warning about Hexadecimal number
            my $v = unpack 'N', substr( $_[0], $_[1], 4 );
            return $v ? $v - 0x100000000 : 0;
        };
        *unpack_int64 = $bo_is_le ? sub {
            my @v = unpack( 'V*', substr( $_[0], $_[1], 8 ) );
            return unpack( 'q', pack( 'N2', @v[1,0] ) );
        } : sub { pack 'q', substr( $_[0], $_[1], 8 ); };
        *unpack_uint64 = $bo_is_le ? sub {
            my @v = unpack( 'V*', substr( $_[0], $_[1], 8 ) );
            return unpack( 'Q', pack( 'N2', @v[1,0] ) );
        } : sub { pack 'Q', substr( $_[0], $_[1], 8 ); };
    }
    else {
        *pack_uint64   = sub { return pack 'CQ>', 0xcf, $_[0]; };
        *pack_int64    = sub { return pack 'Cq>', 0xd3, $_[0]; };
        *pack_double   = sub { return pack 'Cd>', 0xcb, $_[0]; };
        *unpack_float  = sub { return unpack( 'f>', substr( $_[0], $_[1], 4 ) ); };
        *unpack_double = sub { return unpack( 'd>', substr( $_[0], $_[1], 8 ) ); };
        *unpack_int16  = sub { return unpack( 'n!', substr( $_[0], $_[1], 2 ) ); };
        *unpack_int32  = sub { return unpack( 'N!', substr( $_[0], $_[1], 4 ) ); };
        *unpack_int64  = sub { return unpack( 'q>', substr( $_[0], $_[1], 8 ) ); };
        *unpack_uint64 = sub { return unpack( 'Q>', substr( $_[0], $_[1], 8 ) ); };
    }
    # for 5.8 etc.
    unless ( defined &utf8::is_utf8 ) {
       require Encode;
       *utf8::is_utf8 = *Encode::is_utf8;
    }
}


#
# PACK
#

{
    no warnings 'recursion';

    my $max_depth;

sub pack {
    Carp::croak('Usage: Data::MessagePack->pack($dat [,$max_depth])') if @_ < 2;
    $max_depth = defined $_[2] ? $_[2] : 512; # init
    return _pack( $_[1] );
}


sub _pack {
    my ( $value ) = @_;

    return CORE::pack( 'C', 0xc0 ) if ( not defined $value );

    my $b_obj = B::svref_2object( ref $value ? $value : \$value );

    if ( $b_obj->isa('B::AV') ) {
        my $num = @$value;
        my $header = 
              $num < 16          ? CORE::pack( 'C',  0x90 + $num )
            : $num < 2 ** 16 - 1 ? CORE::pack( 'Cn', 0xdc,  $num )
            : $num < 2 ** 32 - 1 ? CORE::pack( 'CN', 0xdd,  $num )
            : die "" # don't arrivie here
        ;
        if ( --$max_depth <= 0 ) {
            Carp::croak("perl structure exceeds maximum nesting level (max_depth set too low?)");
        }
        return join( '', $header, map { _pack( $_ ) } @$value );
    }

    elsif ( $b_obj->isa('B::HV') ) {
        my $num = keys %$value;
        my $header = 
              $num < 16          ? CORE::pack( 'C',  0x80 + $num )
            : $num < 2 ** 16 - 1 ? CORE::pack( 'Cn', 0xde,  $num )
            : $num < 2 ** 32 - 1 ? CORE::pack( 'CN', 0xdf,  $num )
            : die "" # don't arrivie here
        ;
        if ( --$max_depth <= 0 ) {
            Carp::croak("perl structure exceeds maximum nesting level (max_depth set too low?)");
        }
        return join( '', $header, map { _pack( $_ ) } %$value );
    }

    elsif ( blessed( $value ) and blessed( $value ) eq 'Data::MessagePack::Boolean' ) {
        return  CORE::pack( 'C', $$value ? 0xc3 : 0xc2 );
    }

    my $flags = $b_obj->FLAGS;

    if ( $flags & ( B::SVf_IOK | B::SVp_IOK ) ) {

        if ($value >= 0) {
            return    $value <= 127 ?    CORE::pack 'C',        $value
                    : $value < 2 ** 8 ?  CORE::pack 'CC', 0xcc, $value
                    : $value < 2 ** 16 ? CORE::pack 'Cn', 0xcd, $value
                    : $value < 2 ** 32 ? CORE::pack 'CN', 0xce, $value
                    : pack_uint64( $value );
        }
        else {
            return    -$value <= 32 ?      CORE::pack 'C', ($value & 255)
                    : -$value <= 2 **  7 ? CORE::pack 'Cc', 0xd0, $value
                    : -$value <= 2 ** 15 ? CORE::pack 'Cn', 0xd1, $value
                    : -$value <= 2 ** 31 ? CORE::pack 'CN', 0xd2, $value
                    : pack_int64( $value );
        }

    }

    elsif ( $flags & B::SVf_POK ) { # raw / check needs before dboule

        if ( $Data::MessagePack::PreferInteger ) {
            if ( $value =~ /^-?[0-9]+$/ ) { # ok?
                my $value2 = 0 + $value;
                if ( $value > 0xFFFFFFFF or $value < '-'.0x80000000 or # <- needless but for XS compat
                    0 + $value != B::svref_2object( \$value2 )->int_value
                ) {
                    local $Data::MessagePack::PreferInteger; # avoid for PV => NV
                    return _pack( "$value" );
                }
                return _pack( $value + 0 );
            }
        }

        utf8::encode( $value ) if utf8::is_utf8( $value );

        my $num = length $value;
        my $header = 
              $num < 32          ? CORE::pack( 'C',  0xa0 + $num )
            : $num < 2 ** 16 - 1 ? CORE::pack( 'Cn', 0xda, $num )
            : $num < 2 ** 32 - 1 ? CORE::pack( 'CN', 0xdb, $num )
            : die "" # don't arrivie here
        ;

        return $header . $value;

    }

    elsif ( $flags & ( B::SVf_NOK | B::SVp_NOK ) ) { # double only
        return pack_double( $value );
    }

    else {
        die "???";
    }

}

} # PACK


#
# UNPACK
#

{

    my $p; # position variables for speed.

sub unpack {
    $p = 0; # init
    _unpack( $_[1] );
}


sub _unpack {
    my ( $value ) = @_;
    my $byte = CORE::unpack( 'C', substr( $value, $p++, 1 ) ); # get header

    die "invalid data" unless defined $byte;

    if ( ( $byte >= 0x90 and $byte <= 0x9f ) or $byte == 0xdc or $byte == 0xdd ) {
        my $num;
        if ( $byte == 0xdc ) { # array 16
            $num = CORE::unpack 'n', substr( $value, $p, 2 );
            $p += 2;
        }
        elsif ( $byte == 0xdd ) { # array 32
            $num = CORE::unpack 'N', substr( $value, $p, 4 );
            $p += 4;
        }
        else { # fix array
            $num = $byte & ~0x90;
        }
        my @array;
        push @array, _unpack( $value ) while $num-- > 0;
        return \@array;
    }

    elsif ( ( $byte >= 0x80 and $byte <= 0x8f ) or $byte == 0xde or $byte == 0xdf ) {
        my $num;
        if ( $byte == 0xde ) { # map 16
            $num = CORE::unpack 'n', substr( $value, $p, 2 );
            $p += 2;
        }
        elsif ( $byte == 0xdf ) { # map 32
            $num = CORE::unpack 'N', substr( $value, $p, 4 );
            $p += 4;
        }
        else { # fix map
            $num = $byte & ~0x80;
        }
        my %map;
        for ( 0 .. $num - 1 ) {
            no warnings; # for undef key case
            my $key = _unpack( $value );
            my $val = _unpack( $value );
            $map{ $key } = $val;
        }
        return \%map;
    }

    elsif ( $byte >= 0x00 and $byte <= 0x7f ) { # positive fixnum
        return $byte;
    }
    elsif ( $byte == 0xcc ) { # uint8
        CORE::unpack( 'C', substr( $value, $p++, 1 ) );
    }
    elsif ( $byte == 0xcd ) { # uint16
        $p += 2;
        return CORE::unpack 'n', substr( $value, $p - 2, 2 );
    }
    elsif ( $byte == 0xce ) { # unit32
        $p += 4;
        return CORE::unpack 'N', substr( $value, $p - 4, 4 );
    }
    elsif ( $byte == 0xcf ) { # unit64
        $p += 8;
        return unpack_uint64( $value, $p - 8 );
    }
    elsif ( $byte == 0xd3 ) { # int64
        $p += 8;
        return unpack_int64( $value, $p - 8 );
    }
    elsif ( $byte == 0xd2 ) { # int32
        $p += 4;
        return unpack_int32( $value, $p - 4 );
    }
    elsif ( $byte == 0xd1 ) { # int16
        $p += 2;
        return unpack_int16( $value, $p - 2 );
    }
    elsif ( $byte == 0xd0 ) { # int8
        return CORE::unpack 'c',  substr( $value, $p++, 1 ); # c / C
    }
    elsif ( $byte >= 0xe0 and $byte <= 0xff ) { # negative fixnum
        return $byte - 256;
    }

    elsif ( ( $byte >= 0xa0 and $byte <= 0xbf ) or $byte == 0xda or $byte == 0xdb ) { # raw
        my $num;
        if ( $byte == 0xda ) {
            $num = CORE::unpack 'n', substr( $value, $p, 2 );
            $p += 2 + $num;
        }
        elsif ( $byte == 0xdb ) {
            $num = CORE::unpack 'N', substr( $value, $p, 4 );
            $p += 4 + $num;
        }
        else { # fix raw
            $num = $byte & ~0xa0;
            $p += $num;
        }
        return substr( $value, $p - $num, $num );
    }

    elsif ( $byte == 0xc0 ) { # nil
        return undef;
    }
    elsif ( $byte == 0xc2 ) { # boolean
        return false;
    }
    elsif ( $byte == 0xc3 ) { # boolean
        return true;
    }

    elsif ( $byte == 0xcb ) { # double
        $p += 8;
        return unpack_double( $value, $p - 8 );
    }

    elsif ( $byte == 0xca ) { # float
        $p += 4;
        return unpack_float( $value, $p - 4 );
    }

    else {
        die "???";
    }

}


} # UNPACK


#
# Data::MessagePack::Unpacker
#

package
    Data::MessagePack::Unpacker;

use strict;

sub new {
    bless { stack => [] }, shift;
}


sub execute_limit {
    execute( @_ );
}


{
    my $p;

sub execute {
    my ( $self, $data, $offset, $limit ) = @_;
    my $value = substr( $data, $offset, $limit ? $limit : length $data );
    my $len   = length $value;

    $p = 0;

    while ( $len > $p ) {
        _count( $self, $value ) or last;

        if ( @{ $self->{stack} } > 0 ) {
            pop @{ $self->{stack} } if --$self->{stack}->[-1] == 0;
        }
    }

    if ( $len == $p ) {
        $self->{ data } .= substr( $value, 0, $p );
        $self->{ remain } = undef;
    }

    return $p;
}


sub _count {
    my ( $self, $value ) = @_;
    my $byte = unpack( 'C', substr( $value, $p++, 1 ) ); # get header

    if ( ( $byte >= 0x90 and $byte <= 0x9f ) or $byte == 0xdc or $byte == 0xdd ) {
        my $num;
        if ( $byte == 0xdc ) { # array 16
            $num = unpack 'n', substr( $value, $p, 2 );
            $p += 2;
        }
        elsif ( $byte == 0xdd ) { # array 32
            $num = unpack 'N', substr( $value, $p, 4 );
            $p += 4;
        }
        else { # fix array
            $num = $byte & ~0x90;
        }

        push @{ $self->{stack} }, $num + 1;

        return 1;
    }

    elsif ( ( $byte >= 0x80 and $byte <= 0x8f ) or $byte == 0xde or $byte == 0xdf ) {
        my $num;
        if ( $byte == 0xde ) { # map 16
            $num = unpack 'n', substr( $value, $p, 2 );
            $p += 2;
        }
        elsif ( $byte == 0xdf ) { # map 32
            $num = unpack 'N', substr( $value, $p, 4 );
            $p += 4;
        }
        else { # fix map
            $num = $byte & ~0x80;
        }

        push @{ $self->{stack} }, $num * 2 + 1; # a pair

        return 1;
    }

    elsif ( $byte == 0xc0 or $byte == 0xc2 or $byte == 0xc3 ) { # nil, false, true
        return 1;
    }

    elsif ( $byte >= 0x00 and $byte <= 0x7f ) { # positive fixnum
        return 1;
    }

    elsif ( $byte >= 0xcc and $byte <= 0xcf ) { # uint
        $p += $byte == 0xcc ? 1
            : $byte == 0xcd ? 2
            : $byte == 0xce ? 4
            : $byte == 0xcf ? 8
            : die;
        return 1;
    }

    elsif ( $byte >= 0xd0 and $byte <= 0xd3 ) { # int
        $p += $byte == 0xd0 ? 1
            : $byte == 0xd1 ? 2
            : $byte == 0xd2 ? 4
            : $byte == 0xd3 ? 8
            : die;
        return 1;
    }

    elsif ( $byte >= 0xe0 and $byte <= 0xff ) { # negative fixnum
        return 1;
    }

    elsif ( $byte >= 0xca and $byte <= 0xcb ) { # float, double
        $p += $byte == 0xca ? 4 : 8;
        return 1;
    }

    elsif ( ( $byte >= 0xa0 and $byte <= 0xbf ) or $byte == 0xda or $byte == 0xdb ) {
        my $num;
        if ( $byte == 0xda ) {
            $num = unpack 'n', substr( $value, $p, 2 );
            $p += 2;
        }
        elsif ( $byte == 0xdb ) {
            $num = unpack 'N', substr( $value, $p, 4 );
            $p += 4;
        }
        else { # fix raw
            $num = $byte & ~0xa0;
        }
        $p += $num;
        return 1;
    }

    else {
        die "???";
    }

    return 0;
}

} # execute


sub data {
    my $data = Data::MessagePack->unpack( $_[0]->{ data } );
    $_[0]->reset;
    return $data;
}


sub is_finished {
    my ( $self ) = @_;
    ( scalar( @{ $self->{stack} } ) or defined $self->{ remain } ) ? 0 : 1;
}


sub reset {
    $_[0]->{ stack }  = [];
    $_[0]->{ data }   = undef;
    $_[0]->{ remain } = undef;
}

1;
__END__

=pod

=head1 NAME

Data::MessagePack::PP - Pure Perl implementation of Data::MessagePack

=head1 DESCRIPTION

This module is used by L<Data::MessagePack> internally.

=head1 SEE ALSO

L<http://msgpack.sourceforge.jp/>,
L<Data::MessagePack>,
L<http://frox25.no-ip.org/~mtve/wiki/MessagePack.html>,

=head1 AUTHOR

makamaka

=head1 COPYRIGHT AND LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. 

=cut
