The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define _CRT_SECURE_NO_DEPRECATE /* Win32 compilers close eyes... */
#define PERL_NO_GET_CONTEXT
#undef  PERL_IMPLICIT_SYS /* Sigsetjmp will not work under this */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"


#ifndef PERL_UNUSED_VAR
#  define PERL_UNUSED_VAR(var) if (0) var = var
#endif

#ifndef STATIC_INLINE /* a public perl API from 5.13.4 */
#   if defined(__GNUC__) || defined(__cplusplus__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))
#       define STATIC_INLINE static inline
#   else
#       define STATIC_INLINE static
#   endif
#endif /* STATIC_INLINE */

#ifndef inline /* don't like borgs definitions */ /* inline is keyword for STDC compiler  */
#   if defined(__GNUC__) || defined(__cplusplus__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))
#   	if defined(__APPLE__)
#	    define inline 
#	endif
#   else
#	if defined(WIN32) && defined(_MSV) /* Microsoft Compiler */
#	    define inline _inline
#	else 
#	    define inline 
#	endif
#   endif
#endif /* inline  */
/* Stupid FreeBSD compiler failed with plain inline */
#define FREE_INLINE STATIC_INLINE

#define MARKER3_UNDEF	  '\x00'
#define MARKER3_NULL	  '\x01'
#define MARKER3_FALSE	  '\x02'
#define MARKER3_TRUE	  '\x03'
#define MARKER3_INTEGER	  '\x04'
#define MARKER3_DOUBLE    '\x05'
#define MARKER3_STRING    '\x06'
#define MARKER3_XML_DOC   '\x07'
#define MARKER3_DATE      '\x08'
#define MARKER3_ARRAY	  '\x09'
#define MARKER3_OBJECT	  '\x0a'
#define MARKER3_XML	  '\x0b'
#define MARKER3_BYTEARRAY '\x0c'
#define MARKER3_AMF_PLUS	  '\x11' 

#define MARKER0_NUMBER		  '\x00'
#define MARKER0_BOOLEAN		  '\x01'
#define MARKER0_STRING  	  '\x02'
#define MARKER0_OBJECT		  '\x03'
#define MARKER0_CLIP		  '\x04'
#define MARKER0_UNDEFINED  	  '\x05'
#define MARKER0_NULL		  '\x06'
#define MARKER0_REFERENCE 	  '\x07'
#define MARKER0_ECMA_ARRAY 	  '\x08'
#define MARKER0_OBJECT_END	  '\x09'
#define MARKER0_STRICT_ARRAY	  '\x0a'
#define MARKER0_DATE	  	  '\x0b'
#define MARKER0_LONG_STRING       '\x0c'
#define MARKER0_UNSUPPORTED	  '\x0d'
#define MARKER0_RECORDSET	  '\x0e'
#define MARKER0_XML_DOCUMENT      '\x0f'
#define MARKER0_TYPED_OBJECT	  '\x10'
#define MARKER0_AMF_PLUS	  '\x11' 

#define ERR_EOF                 1
#define ERR_AMF0_REF            2
#define ERR_MARKER              3
#define ERR_BAD_OBJECT          4
#define ERR_OVERFLOW            5
#define ERR_UNIMPLEMENTED       6
#define ERR_BAD_STRING_REF      7
#define ERR_BAD_DATE_REF        8
#define ERR_BAD_OBJECT_REF      9
#define ERR_BAD_ARRAY_REF       10
#define ERR_BAD_STRING_REF_UNUSED 11
#define ERR_BAD_TRAIT_REF       12
#define ERR_BAD_XML_REF         13
#define ERR_BAD_BYTEARRAY_REF   14
#define ERR_EXTRA_BYTE          15
#define ERR_INT_OVERFLOW        16
#define ERR_RECURRENT_OBJECT    17
#define ERR_BAD_REFVAL          18
#define ERR_INTERNAL            19
#define ERR_ARRAY_TOO_BIG       20
#define ERR_BAD_OPTION          21

#define OPT_STRICT        1
#define OPT_DECODE_UTF8   2
#define OPT_ENCODE_UTF8   4
#define OPT_RAISE_ERROR   8
#define OPT_MILLSEC_DATE  16
#define OPT_PREFER_NUMBER 32
#define OPT_JSON_BOOLEAN  64
#define OPT_MAPPER        128
#define OPT_TARG          256
#define OPT_SKIP_BAD      512

#define EXPERIMENT1

#define AMF0_VERSION 0
#define AMF3_VERSION 3


#define STR_EMPTY    '\x01'
#define TRACE(ELEM) PerlIO_printf( PerlIO_stderr(), ELEM);
#undef TRACE
#define TRACE(ELEM) ;

#if BYTEORDER == 0x1234
    #define GAX "LIT"
    #define GET_NBYTE(ALL, IPOS, TYPE) (ALL - 1 - IPOS)
#else
#if BYTEORDER == 0x12345678
    #define GAX "LIT"
    #define GET_NBYTE(ALL, IPOS, TYPE) (ALL - 1 - IPOS)
#else
#if BYTEORDER == 0x87654321
    #define GAX "BIG"
    #define GET_NBYTE(ALL, IPOS, TYPE) (sizeof(TYPE) -ALL + IPOS)
#else
#if  BYTEORDER == 0x4321
    #define GAX "BIG"
    #define GET_NBYTE(ALL, IPOS, TYPE) (sizeof(TYPE) -ALL + IPOS)
#else
    #error Unknown byteorder. Please append your byteorder to Storable/AMF.xs
#endif
#endif
#endif
#endif

#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))

#define SIGN_BOOL_APPLY( obj, sign, mask ) ( sign > 0 ? obj|=mask : sign <0 ? obj&=~mask : 0 ) 
#define DEFAULT_MASK (OPT_PREFER_NUMBER|OPT_TARG)

char *error_messages[] = {
    "ERR_EOF", 
    "ERR_BAD_AMF0_REF", 
    "ERR_MARKER", 
    "ERR_BAD_OBJECT", 
    "ERR_OVERFLOW", 
    "ERR_UNIMPLEMENTED", 
    "ERR_BAD_STRING_REF", 
    "ERR_BAD_DATE_REF", 
    "ERR_BAD_OBJECT_REF", 
    "ERR_BAD_ARRAY_REF", 
    "ERR_BAD_STRING_REF_UNUSED",
    "ERR_BAD_TRAIT_REF",
    "ERR_BAD_XML_REF", 
    "ERR_BAD_BYTEARRAY_REF", 
    "ERR_EXTRA_BYTE", 
    "ERR_INT_OVERFLOW", 
    "ERR_RECURRENT_OBJECT", 
    "ERR_BAD_REFVAL",  
    "ERR_INTERNAL",
    "ERR_ARRAY_TOO_BIG",
    "ERR_BAD_OPTION",
    0
};
struct io_amf_option;
/*#define TRACE0 */
struct amf3_restore_point{
    int offset_buffer;
    int offset_object;
    int offset_trait;
    int offset_string;
    int arr_max;
};

struct io_struct{
    unsigned char * ptr;
    unsigned char * pos;
    unsigned char * end;
    SV * sv_buffer;
    int buffer_step_inc;
    int arr_max;
    Sigjmp_buf target_error;
    int error_code;
    AV *arr_string;
    AV *arr_object;
    AV *arr_trait;
    HV *hv_string;
    HV *hv_object;
    HV *hv_trait;
    int rc_string;
    int rc_object;
    int rc_trait;
    int version;
    int final_version;
    int options;
    struct io_amf_option* ext_option;
    SV * (*parse_one_object)(pTHX_ struct io_struct * io);
    char *subname;
    char status;
    bool reuse;
};

FREE_INLINE SV*  get_tmp_storage( pTHX_ SV *option );
FREE_INLINE void destroy_tmp_storage( pTHX_ SV *self);

STATIC_INLINE SV * amf0_parse_one(pTHX_ struct io_struct * io);
STATIC_INLINE SV * amf3_parse_one(pTHX_ struct io_struct * io);
FREE_INLINE void io_in_destroy(pTHX_ struct io_struct * io, AV *);
FREE_INLINE void io_out_cleanup(pTHX_ struct io_struct * io);

FREE_INLINE void io_test_eof(pTHX_ struct io_struct *io);
FREE_INLINE void io_register_error(struct io_struct *io, int);
FREE_INLINE void io_register_error_and_free(pTHX_ struct io_struct *io, int, void *);
FREE_INLINE int
io_position(struct io_struct *io){
    return io->pos-io->ptr;
}

FREE_INLINE void
io_set_position(struct io_struct *io, int pos){
    io->pos = io->ptr + pos;
}

FREE_INLINE void 
io_savepoint(pTHX_ struct io_struct *io, struct amf3_restore_point *p){
    p->offset_buffer = io_position(io);
    p->offset_object = av_len(io->arr_object);
    p->offset_trait  = av_len(io->arr_trait);
    p->offset_string = av_len(io->arr_string);
}
FREE_INLINE void
io_restorepoint(pTHX_ struct io_struct *io, struct amf3_restore_point *p){
    io_set_position(io, p->offset_buffer);	
    while(av_len(io->arr_object) > p->offset_object){
        SV * abc = av_pop(io->arr_object);
        sv_2mortal(abc);
    }
    while(av_len(io->arr_trait) > p->offset_trait){
        sv_2mortal(av_pop(io->arr_trait));
    }
    while(av_len(io->arr_string) > p->offset_string){
        sv_2mortal(av_pop(io->arr_string));
    }
}


FREE_INLINE void
io_move_backward(struct io_struct *io, int step){
    io->pos-= step;
}

FREE_INLINE void
io_move_forward(struct io_struct *io, int len){
    io->pos+=len;	
}

FREE_INLINE void
io_require(struct io_struct *io, int len){
    if (io->end - io->pos < len){
        io_register_error(io, ERR_EOF);
    }
}

FREE_INLINE void
io_reserve(pTHX_ struct io_struct *io, int len){
    if (io->end - io->pos< len){
        unsigned int ipos = io->pos - io->ptr;
        unsigned int buf_len;

        SvCUR_set(io->sv_buffer, ipos);
        buf_len = SvLEN(io->sv_buffer);
        while( buf_len < ipos + len + io->buffer_step_inc){
            buf_len *= 4;
        }
        io->ptr = (unsigned char *) SvGROW(io->sv_buffer, buf_len);
        io->pos = io->ptr + ipos;
        io->end = io->ptr + SvLEN(io->sv_buffer);
    }
}
FREE_INLINE void io_register_error(struct io_struct *io, int errtype){
    io->error_code = errtype;
    Siglongjmp(io->target_error, errtype);
}

FREE_INLINE void io_test_eof(pTHX_ struct io_struct *io){
    if (io->pos!=io->end){
	io_register_error( io, ERR_EOF );
    }
}
void io_format_error(pTHX_ struct io_struct *io ){
    int error_code = io->error_code;
    char *message;
    if ( error_code < 1 || error_code >= ARRAY_SIZE( error_messages )){
	error_code = ERR_INTERNAL;
    };
    message = error_messages[ error_code - 1];



    if ( io->status == 'r' ){
	io_in_destroy(aTHX_  io, 0); /* all objects */
	if (io->options & OPT_RAISE_ERROR){
	    croak("Parse AMF%d: %s (ERR-%d)", io->version, message, error_code);
	}
	else {
	    sv_setiv(ERRSV, error_code);
	    sv_setpvf(ERRSV, "Parse AMF%d: %s (ERR-%d)", io->version, message, error_code);
	    SvIOK_on(ERRSV);
	}
    }
    else { /* io->status == 'w' */
        io_out_cleanup(aTHX_ io);
	if (io->options & OPT_RAISE_ERROR){
	    croak("Format AMF%d: %s (ERR-%d)", io->version, message, error_code);
	}
	else {
	    sv_setiv(ERRSV, error_code);
	    sv_setpvf(ERRSV, "Format AMF%d: %s (ERR-%d)", io->version, message, error_code);
	    SvIOK_on(ERRSV);
	}
    }
}

FREE_INLINE void io_register_error_and_free(pTHX_ struct io_struct *io, int errtype, void *pointer){
    if (pointer)
        sv_2mortal((SV*) pointer);
    Siglongjmp(io->target_error, errtype);
}
FREE_INLINE SV*  get_tmp_storage(pTHX_ SV* option){
    struct io_struct * io;
    SV *sv ;
    Newxz( io, 1, struct io_struct );
    sv = sv_newmortal();
    sv_setref_iv( sv, "Storable::AMF0::TemporaryStorage", PTR2IV( io ) );

    io->arr_trait  = newAV();
    io->arr_string = newAV();
    io->arr_object = newAV();

    io->hv_object        = newHV();
    HvSHAREKEYS_off( io->hv_object );
    io->hv_string        = newHV();
    HvSHAREKEYS_off( io->hv_string );
    io->hv_trait         = newHV();
    HvSHAREKEYS_off( io->hv_trait );

    /*
    io->sv_buffer        = newSV(0);
    (void)SvUPGRADE(io->sv_buffer, SVt_PV);
    SvPOK_on( io->sv_buffer );
    SvGROW( io->sv_buffer, 512 ); */

    if ( option ){
        io->options = SvIV(option);
    }
    else {
        io->options = DEFAULT_MASK;
    }
    return sv;
}
FREE_INLINE void destroy_tmp_storage( pTHX_ SV *self ){
    if ( ! SvROK( self )) {
        croak( "Bad Storable::AMF0::TemporaryStorage" );
    }
    else {
        struct io_struct *io;
        io = INT2PTR( struct io_struct*, SvIV( SvRV(  self )) );
        SvREFCNT_dec( (SV *) io->arr_trait );
        SvREFCNT_dec( (SV *) io->arr_string );
        SvREFCNT_dec( (SV *) io->arr_object );
        SvREFCNT_dec( (SV *) io->hv_object );
        SvREFCNT_dec( (SV *) io->hv_string );
        SvREFCNT_dec( (SV *) io->hv_trait );
        /* SvREFCNT_dec( (SV *) io->sv_buffer ); */
        Safefree( io );  
    /*    fprintf( stderr, "Destroy\n"); */
    }
}
FREE_INLINE void io_in_cleanup(pTHX_ struct io_struct *io){
    av_clear( io->arr_object );
    if ( AMF3_VERSION == io->final_version ){
        av_clear( io->arr_string );
        av_clear( io->arr_trait );
    };
}
FREE_INLINE void io_out_cleanup(pTHX_ struct io_struct *io){
    hv_clear( io->hv_object );
    if ( AMF3_VERSION == io->version ){
        hv_clear( io->hv_string );
        hv_clear( io->hv_trait );
    };
}
FREE_INLINE void io_in_init(pTHX_ struct io_struct * io,  SV* data, int amf_version, SV * sv_option){
    struct io_struct *reuse_storage_ptr;
    bool  reuse_storage;
    /*    PerlInterpreter *my_perl = io->interpreter; */
    if ( sv_option ){
        if (! SvIOK(sv_option)){
            if ( ! sv_isobject( sv_option )){
                warn( "options are not integer" );
                io_register_error( io, ERR_BAD_OPTION );
                return;
            }
            else {
                reuse_storage = 1;
                reuse_storage_ptr = INT2PTR( struct io_struct *, SvIV( SvRV( sv_option )));
                io->options       = reuse_storage_ptr->options;
            }
        } 
        else {        
            reuse_storage = 0;
            io->options = SvIV(sv_option);
        }
    }
    else {
        io->options = DEFAULT_MASK;
        reuse_storage     = 0;
    }
    io->reuse = reuse_storage;
    
    if (SvMAGICAL(data))
        mg_get(data);

    if (!SvPOKp(data))
        croak("%s. data must be a string", io->subname);

    if (SvUTF8(data)) 
        croak("%s: data is utf8. Can't process utf8", io->subname);
    
    io->ptr = (unsigned char *) SvPVX(data);
    io->end = io->ptr + SvCUR(data);
    io->pos = io->ptr;
    io->status  = 'r';
    io->version = amf_version;
    if ( amf_version == AMF0_VERSION && (io->ptr[0] == MARKER0_AMF_PLUS) ){
	amf_version = AMF3_VERSION;
	++io->pos;
    };
    io->final_version = amf_version; 
    /* Support when  array extend is too big */
    io->arr_max = SvCUR( data );

    if ( reuse_storage ){
        io->arr_object = reuse_storage_ptr->arr_object;
        if (amf_version == AMF3_VERSION) {
            io->arr_string = reuse_storage_ptr->arr_string;
            io->arr_trait =  reuse_storage_ptr->arr_trait;
            io->parse_one_object = amf3_parse_one;
        }
        else {
            io->parse_one_object = amf0_parse_one;
        }
    }
    else {
        sv_2mortal( (SV *) (io->arr_object = newAV()) );
        av_extend( io->arr_object, 32 ); 
        if (amf_version == AMF3_VERSION) {
            io->arr_string = newAV();
            sv_2mortal((SV*) io->arr_string);
            io->arr_trait = newAV();
            sv_2mortal((SV*) io->arr_trait);
            io->parse_one_object = amf3_parse_one;
        }
        else {
            io->parse_one_object = amf0_parse_one;
        }
    }
}
FREE_INLINE void io_in_destroy(pTHX_ struct io_struct * io, AV *a){
    int i;
    SV **ref_item;
    int alen;
    SV *item;
    if (a) {
        alen = av_len(a);
        for(i = 0; i<= alen; ++i){
            ref_item = av_fetch(a,i,0);
            if (ref_item){
                if (SvROK(*ref_item)){
                    item = SvRV(*ref_item);
                    if (SvTYPE(item) == SVt_PVAV){
                        av_clear((AV*) item);
                    }
                    else if (SvTYPE(item) == SVt_PVHV){
                        HV * h = (HV*) item;
                        hv_clear(h);
                    }
                }
            }
        }
        av_clear(a); /* cleaning array */
    }
    else {
        if (io->final_version == AMF0_VERSION){
            io_in_destroy(aTHX_  io, io->arr_object);
        }
        else if (io->final_version == AMF3_VERSION) {
            io_in_destroy(aTHX_  io, io->arr_object);
            io_in_destroy(aTHX_  io, io->arr_trait); /* May be not needed */
            io_in_destroy(aTHX_  io, io->arr_string);
        }
        else {
            croak("bad version at destroy");
        }
    }
}
STATIC_INLINE void io_out_init(pTHX_ struct io_struct *io, SV*sv_option, int amf_version){
    SV *sbuffer;
    unsigned int ibuf_size ;
    unsigned int ibuf_step ;
    io->version = amf_version;

    if ( sv_option ){
        if ( SvROK(sv_option) && sv_isobject( sv_option )){
                struct io_struct *reuse_storage_ptr;
                reuse_storage_ptr = INT2PTR( struct io_struct *, SvIV( SvRV( sv_option )));
                io->options       = reuse_storage_ptr->options;
                io->reuse = 1;
                io->rc_string = 0;
                io->rc_trait  = 0;
                io->rc_object = 0;
                
                /*io->sv_buffer = reuse_storage_ptr->sv_buffer; */

                io->hv_string = reuse_storage_ptr->hv_string;
                io->hv_object = reuse_storage_ptr->hv_object;
                io->hv_trait  = reuse_storage_ptr->hv_trait;

    ibuf_size = 10240;
    ibuf_step = 20480;


    if ( io->options & OPT_TARG ){
        dXSTARG;

        io->sv_buffer = sbuffer = TARG;
        (void)SvUPGRADE(sbuffer, SVt_PV);
        SvPOK_on(sbuffer);
        SvGROW( sbuffer, 512 );
    }
    else {
        sbuffer = sv_2mortal(newSVpvn("",0));
        SvGROW(sbuffer, ibuf_size);
        io->sv_buffer = sbuffer;
    }

                io->buffer_step_inc = 1024;
                io->ptr = (unsigned char *) SvPV_nolen(io->sv_buffer);
                io->pos = io->ptr;
                io->end = (unsigned char *) SvEND(io->sv_buffer);
                io->status  = 'w';
                return ;
        }
        else if ( !SvIOK(sv_option)){
            io_register_error( io, ERR_BAD_OPTION );
        }
        else {
            io->options = SvIV( sv_option );
        }
    }
    else {
        io->options = DEFAULT_MASK;
    };
    io->reuse = 0;
    /* FIXME */
    ibuf_size = 10240;
    ibuf_step = 20480;


    if ( io->options & OPT_TARG ){
        dXSTARG;

        io->sv_buffer = sbuffer = TARG;
        (void)SvUPGRADE(sbuffer, SVt_PV);
        SvPOK_on(sbuffer);
        SvGROW( sbuffer, 512 );
    }
    else {
        sbuffer = sv_2mortal(newSVpvn("",0));
        SvGROW(sbuffer, ibuf_size);
        io->sv_buffer = sbuffer;
    }

    
    if (amf_version) {

        io->hv_string = newHV();
        io->hv_trait  = newHV();
        io->hv_object = newHV();

        io->rc_string = 0;
        io->rc_trait  = 0;
        io->rc_object = 0;

        HvSHAREKEYS_off( io->hv_object );
        HvSHAREKEYS_off( io->hv_trait );
        HvSHAREKEYS_off( io->hv_string );

        sv_2mortal((SV *)io->hv_string);
        sv_2mortal((SV *)io->hv_object);
        sv_2mortal((SV *)io->hv_trait);
    }
    else {
        io->hv_object   = newHV();
        io->rc_object = 0;
        HvSHAREKEYS_off( io->hv_object ); 
        sv_2mortal((SV*)io->hv_object);
    }
    io->buffer_step_inc = ibuf_step;
    io->ptr = (unsigned char *) SvPV_nolen(io->sv_buffer);
    io->pos = io->ptr;
    io->end = (unsigned char *) SvEND(io->sv_buffer);
    io->status  = 'w';
    
}

FREE_INLINE SV * io_buffer(struct io_struct *io){
    SvCUR_set(io->sv_buffer, io->pos - io->ptr);
    return io->sv_buffer;
}

FREE_INLINE double io_read_double(struct io_struct *io);
FREE_INLINE unsigned char io_read_marker(struct io_struct * io);
FREE_INLINE int io_read_u8(struct io_struct * io);
FREE_INLINE int io_read_u16(struct io_struct * io);
FREE_INLINE int io_read_u32(struct io_struct * io);
FREE_INLINE int io_read_u24(struct io_struct * io);


#define MOVERFLOW(VALUE, MAXVALUE, PROC)\
	if (VALUE > MAXVALUE) { \
		PerlIO_printf( PerlIO_stderr(), "Overflow in %s. expected less %d. got %d\n", PROC, MAXVALUE, VALUE); \
		io_register_error(io, ERR_OVERFLOW); \
	}



FREE_INLINE void io_write_double(pTHX_ struct io_struct *io, double value){
    const int step = 8;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    io_reserve(aTHX_  io, step );
    v.nv = value;
    io->pos[0] = v.c[GET_NBYTE(step, 0, value)];
    io->pos[1] = v.c[GET_NBYTE(step, 1, value)];
    io->pos[2] = v.c[GET_NBYTE(step, 2, value)];
    io->pos[3] = v.c[GET_NBYTE(step, 3, value)];
    io->pos[4] = v.c[GET_NBYTE(step, 4, value)];
    io->pos[5] = v.c[GET_NBYTE(step, 5, value)];
    io->pos[6] = v.c[GET_NBYTE(step, 6, value)];
    io->pos[7] = v.c[GET_NBYTE(step, 7, value)];
    io->pos+= step ;
    return;
}
FREE_INLINE void io_write_marker(pTHX_ struct io_struct * io, char value)	{
    const int step = 1;
    io_reserve(aTHX_  io, 1);
    io->pos[0]= value;
    io->pos+=step;
    return;
}
FREE_INLINE void io_write_uchar (pTHX_ struct io_struct * io, unsigned char value)	{
    const int step = 1;
    io_reserve(aTHX_  io, 1);
    io->pos[0]= value;
    io->pos+=step;
    return;
}

FREE_INLINE void io_write_u8(pTHX_ struct io_struct * io, unsigned int value){
    const int step = 1;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    v.uv = value;
    MOVERFLOW(value, 255, "write_u8");
    io_reserve(aTHX_  io, 1);
    io->pos[0]= v.c[GET_NBYTE(step, 0,value)]; 
    io->pos+=step ;
    return;
}


FREE_INLINE void io_write_s16(pTHX_ struct io_struct * io, signed int value){
    const int step = 2;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    v.iv = value;
    MOVERFLOW(value, 32767, "write_s16");
    io_reserve(aTHX_  io, step);
    io->pos[0]= v.c[GET_NBYTE(step, 0, value)];
    io->pos[1]= v.c[GET_NBYTE(step, 1, value)];
    io->pos+=step;
    return;
}

FREE_INLINE void io_write_u16(pTHX_ struct io_struct * io, unsigned int value){
    const int step = 2;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    io_reserve(aTHX_  io,step);
    MOVERFLOW(value, 65535 , "write_u16");
    v.uv = value;
    io->pos[0] = v.c[GET_NBYTE(step, 0, value)];
    io->pos[1] = v.c[GET_NBYTE(step, 1, value)];
    io->pos+=step;
    return;
}

FREE_INLINE void io_write_u32(pTHX_ struct io_struct * io, unsigned int value){
    const int step = 4;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    io_reserve(aTHX_  io,step);
    v.uv = value;
    io->pos[0] = v.c[GET_NBYTE(step, 0, value)];
    io->pos[1] = v.c[GET_NBYTE(step, 1, value)];
    io->pos[2] = v.c[GET_NBYTE(step, 2, value)];
    io->pos[3] = v.c[GET_NBYTE(step, 3, value)];
    io->pos+=step;
    return;
}

FREE_INLINE void io_write_u24(pTHX_ struct io_struct * io, unsigned int value){
    const int step = 3;
    union {
        signed   int iv;
        unsigned int uv;
        double nv;
        char   c[8];
    } v;
    io_reserve(aTHX_  io,step);
    MOVERFLOW(value,16777215 , "write_u16");
    v.uv = value;
    io->pos[0] = v.c[GET_NBYTE(step, 0, value)];
    io->pos[1] = v.c[GET_NBYTE(step, 1, value)];
    io->pos[2] = v.c[GET_NBYTE(step, 2, value)];
    io->pos+=step;
    return;
}
FREE_INLINE void io_write_bytes(pTHX_ struct io_struct* io, const char * const buffer, int len){
    io_reserve(aTHX_  io, len);
    Copy(buffer, io->pos, len, char);
    io->pos+=len;
}	
/* Date checking */
FREE_INLINE bool   util_is_date(SV *one);
FREE_INLINE double util_date_time(SV *one);

STATIC_INLINE void amf0_format_one(pTHX_ struct io_struct *io, SV * one);
STATIC_INLINE void amf0_format_number(pTHX_ struct io_struct *io, SV * one);
STATIC_INLINE void amf0_format_string(pTHX_ struct io_struct *io, SV * one);
STATIC_INLINE void amf0_format_strict_array(pTHX_ struct io_struct *io, AV * one);
STATIC_INLINE void amf0_format_object(pTHX_ struct io_struct *io, HV * one);
STATIC_INLINE void amf0_format_null(pTHX_ struct io_struct *io);
STATIC_INLINE void amf0_format_typed_object(pTHX_ struct io_struct *io, HV * one);

FREE_INLINE bool util_is_date(SV *one){
    if (SvNOKp(one)){
	HV* stash = SvSTASH(one);
	char *class_name = HvNAME(stash);
	if (*class_name == '*' && class_name[1] == 0){
	    return 1;
	}
	else {
	    return 0;
	}
    }
    else {
	return 0;
    }
}
FREE_INLINE double util_date_time(SV *one){
    return (SvNVX(one)*1000);
}
FREE_INLINE void amf0_format_reference(pTHX_ struct io_struct * io, SV *ref){
    io_write_marker(aTHX_  io, MARKER0_REFERENCE);
    io_write_u16(aTHX_  io, SvIV(ref));
}
FREE_INLINE void amf0_format_scalar_ref(pTHX_ struct io_struct * io, SV *ref){
    const char *const reftype = "REFVAL";
    
    io_write_marker(aTHX_  io, MARKER0_TYPED_OBJECT);
    /* special type */
    io_write_u16(aTHX_  io, 6);
    io_write_bytes(aTHX_  io, reftype, 6);

    /* type */
    io_write_u16(aTHX_  io, 6);
    io_write_bytes(aTHX_  io, reftype, 6);
    amf0_format_one(aTHX_  io, ref);
    /* end marker */
    io_write_u16(aTHX_  io, 0);
    io_write_marker(aTHX_  io, MARKER0_OBJECT_END);
}

FREE_INLINE void amf0_format_one(pTHX_ struct io_struct *io, SV * one){
    SV *rv = 0;
    bool is_perl_bool = 0;
    if (SvROK(one)){
        rv = (SV*) SvRV(one);
	if ( sv_isobject( one )){
            HV* stash = SvSTASH(rv);
            char *class_name = HvNAME(stash);
            if ( class_name[0] == 'J' ){
                if ( sv_isa(one, "JSON::PP::Boolean")){
                    is_perl_bool =  1;
                }
                else if ( sv_isa(one, "JSON::XS::Boolean") ){
                    is_perl_bool =  1;
                }
            }
            else if ( class_name[0] == 'b' ){
                if ( sv_isa(one, "boolean" )){
                    is_perl_bool  = 1;
                }
            }
	    if ( is_perl_bool ){
		io_write_marker(aTHX_ io, MARKER0_BOOLEAN );
		/*  TODO SvTRUE can call die or something like */
		io_write_uchar(aTHX_  io, SvTRUE( SvRV( one  )) ? 1 : 0);
		return ;
	    }
	}
    }

    if (rv){
        /*  test has stored */
        SV **OK = hv_fetch(io->hv_object, (char *)(&rv), sizeof (rv), 1);
        if (SvOK(*OK)) {
            amf0_format_reference(aTHX_  io, *OK);
        }
        else {
            int type = SvTYPE(rv);
            sv_setiv(*OK, io->rc_object);
            ++io->rc_object;

            if (sv_isobject(one)) {
		if ( io->options & OPT_MAPPER ){
		    GV *to_amf = gv_fetchmethod_autoload (SvSTASH (rv), "TO_AMF", 0);
		    if ( to_amf ) {
		    dSP;

		    ENTER; SAVETMPS; PUSHMARK (SP);
		    XPUSHs (sv_bless (sv_2mortal (newRV_inc (rv)), SvSTASH (rv)));

		    /* calling with G_SCALAR ensures that we always get a 1 return value */
		    PUTBACK;
		    call_sv ((SV *)GvCV (to_amf), G_SCALAR);
		    SPAGAIN;

		    /* catch this surprisingly common error */
		    if (SvROK (TOPs) && SvRV (TOPs) == rv)
			croak ("%s::TO_AMF method returned same object as was passed instead of a new one", HvNAME (SvSTASH (rv)));

		    rv = POPs;
		    PUTBACK;

		    amf0_format_one( aTHX_  io, rv);

		    FREETMPS; LEAVE;
		    return ;

		    }
		}
                if (SvTYPE(rv) == SVt_PVHV){
                    amf0_format_typed_object(aTHX_  io, (HV *) rv);
                }
		else if ( util_is_date( rv ) ) {
		    io_write_marker(aTHX_ io, MARKER0_DATE );
		    io_write_double(aTHX_ io, util_date_time( rv ));
		    io_write_s16(aTHX_ io, 0 );
		}
		else {		    
                    /* may be i has to format as undef */
		    if ( io->options & OPT_SKIP_BAD ){
			io_write_marker( aTHX_ io, MARKER0_UNDEFINED );
		    }
		    else 
			io_register_error(io, ERR_BAD_OBJECT);
                }
            }
            else if (SvTYPE(rv) == SVt_PVAV) 
                amf0_format_strict_array(aTHX_  io, (AV*) rv);
            else if (SvTYPE(rv) == SVt_PVHV) {
                io_write_marker(aTHX_  io, MARKER0_OBJECT);
                amf0_format_object(aTHX_  io, (HV*) rv);
            }
            else if ( type != SVt_PVCV && type !=  SVt_PVGV ) {
                amf0_format_scalar_ref(aTHX_  io, (SV*) rv);
            }
            else {
		if ( io->options & OPT_SKIP_BAD ) 
		    io_write_marker( aTHX_ io, MARKER0_UNDEFINED );
		else
		    io_register_error(io, ERR_BAD_OBJECT);
            }
        }
    }
    else {
        if (SvOK(one)){
	    #if defined( EXPERIMENT1 )
	    if ( (io->options & OPT_PREFER_NUMBER )){
		if (SvNIOK(one)){
		    amf0_format_number(aTHX_  io, one);
		}
		else {
		    amf0_format_string(aTHX_  io, one);
		}
	    }
	    else 
	    #endif
		if (SvPOK(one)){
		    amf0_format_string(aTHX_  io, one);
		}
		else if ( SvNIOK(one) ){
		    amf0_format_number(aTHX_  io, one);
		}
		else {
		    if (io->options & OPT_SKIP_BAD ){
			io_write_marker(aTHX_ io, MARKER0_UNDEFINED );
		    }
		    else 
			io_register_error(io, ERR_BAD_OBJECT);
		}
        }
        else {
            amf0_format_null(aTHX_  io);
        }
    }
}

FREE_INLINE void amf0_format_number(pTHX_ struct io_struct *io, SV * one){

    io_write_marker(aTHX_  io, MARKER0_NUMBER);
    io_write_double(aTHX_  io, SvNV(one));	
}
FREE_INLINE void amf0_format_string(pTHX_ struct io_struct *io, SV * one){

    /* TODO: process long string */
    if (SvPOK(one)){
        STRLEN str_len;
        char * pv;
        pv = SvPV(one, str_len);
        if (str_len > 65500){
            io_write_marker(aTHX_  io, MARKER0_LONG_STRING);
            io_write_u32(aTHX_  io, str_len);
            io_write_bytes(aTHX_  io, pv, str_len);
        }
        else {

            io_write_marker(aTHX_  io, MARKER0_STRING);
            io_write_u16(aTHX_  io, SvCUR(one));
            io_write_bytes(aTHX_  io, SvPV_nolen(one), SvCUR(one));
        }
    }else{
        amf0_format_null(aTHX_  io);
    }
}
FREE_INLINE void amf0_format_strict_array(pTHX_ struct io_struct *io, AV * one){
    int i, len;
    AV * one_array;
    one_array =  one;
    len = av_len(one_array);

    io_write_marker(aTHX_  io, '\012');
    io_write_u32(aTHX_  io, len + 1);
    for(i = 0; i <= len; ++i){
        SV ** ref_value = av_fetch(one_array, i, 0);
        if (ref_value) {
            amf0_format_one(aTHX_  io, *ref_value);
        }
        else {
            amf0_format_null(aTHX_  io);
        }
    }
}
FREE_INLINE void amf0_format_object(pTHX_ struct io_struct *io, HV * one){
    I32 key_len;
    SV * value;
    char *key_str;
    hv_iterinit(one);
    while(( value = hv_iternextsv(one, &key_str, &key_len))){
	io_write_u16(aTHX_  io, key_len);
	io_write_bytes(aTHX_  io, key_str, key_len);
	amf0_format_one(aTHX_  io, value);
    }
    io_write_u16(aTHX_  io, 0);
    io_write_marker(aTHX_  io, MARKER0_OBJECT_END);
}
FREE_INLINE void amf0_format_null(pTHX_ struct io_struct *io){

    io_write_marker(aTHX_  io, MARKER0_UNDEFINED);
}
FREE_INLINE void amf0_format_typed_object(pTHX_ struct io_struct *io,  HV * one){
    HV* stash = SvSTASH(one);
    char *class_name = HvNAME(stash);
    io_write_marker(aTHX_  io, MARKER0_TYPED_OBJECT);
    io_write_u16(aTHX_  io, (U16) strlen(class_name));
    io_write_bytes(aTHX_  io, class_name, strlen(class_name));
    amf0_format_object(aTHX_  io, one);
}

STATIC_INLINE SV * amf0_parse_one(pTHX_ struct io_struct * io);
STATIC_INLINE SV* amf0_parse_boolean(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_object(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_movieclip(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_null(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_undefined(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_reference(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_object_end(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_strict_array(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_ecma_array(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_date(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_long_string(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_unsupported(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_recordset(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_xml_document(pTHX_ struct io_struct *io);
STATIC_INLINE SV* amf0_parse_typed_object(pTHX_ struct io_struct *io);

FREE_INLINE  void io_write_double(pTHX_ struct io_struct *io, double value);
FREE_INLINE  void io_write_marker(pTHX_ struct io_struct * io, char value);
FREE_INLINE  void io_write_uchar (pTHX_ struct io_struct * io, unsigned char value);
FREE_INLINE  void io_write_u8(pTHX_ struct io_struct * io, unsigned int value);
FREE_INLINE  void io_write_s16(pTHX_ struct io_struct * io, signed int value);
FREE_INLINE  void io_write_u16(pTHX_ struct io_struct * io, unsigned int value);
FREE_INLINE  void io_write_u32(pTHX_ struct io_struct * io, unsigned int value);
FREE_INLINE  void io_write_u24(pTHX_ struct io_struct * io, unsigned int value);
/*
*/

FREE_INLINE  double io_read_double(struct io_struct *io){
    const int step = sizeof(double);
    double a;
    unsigned char * ptr_in  = io->pos;
    char * ptr_out = (char *) &a; 
    io_require(io, step);
    ptr_out[GET_NBYTE(step, 0, a)] = ptr_in[0] ;
    ptr_out[GET_NBYTE(step, 1, a)] = ptr_in[1] ;
    ptr_out[GET_NBYTE(step, 2, a)] = ptr_in[2] ;
    ptr_out[GET_NBYTE(step, 3, a)] = ptr_in[3] ;
    ptr_out[GET_NBYTE(step, 4, a)] = ptr_in[4] ;
    ptr_out[GET_NBYTE(step, 5, a)] = ptr_in[5] ;
    ptr_out[GET_NBYTE(step, 6, a)] = ptr_in[6] ;
    ptr_out[GET_NBYTE(step, 7, a)] = ptr_in[7] ;
    io->pos += step;
    return a;
}
FREE_INLINE  char *io_read_bytes(struct io_struct *io, int len){
    char * pos = ( char * )io->pos;
    io_require(io, len);
    io->pos+=len;
    return pos;
}
FREE_INLINE  char *io_read_chars(struct io_struct *io, int len){
    char * pos = ( char * )io->pos;
    io_require(io, len);
    io->pos+=len;
    return pos;
}

FREE_INLINE  unsigned char io_read_marker(struct io_struct * io){
    const int step = 1;
    unsigned char marker;
    io_require(io, step);
    marker = *(io->pos);
    io->pos++;
    return marker;
}
FREE_INLINE  int io_read_u8(struct io_struct * io){
    const int step = 1;
    union{
        unsigned int x;
        unsigned char bytes[8];
    } str;
    io_require(io, step);
    str.x = 0;
    str.bytes[GET_NBYTE(step, 0, str.x)] = io->pos[0];
    io->pos+= step;
    return (int) str.x;
}
FREE_INLINE  int io_read_s16(struct io_struct * io){
    const int step = 2;
    union{
        int x;
        char bytes[8];
    } str;
    io_require(io, step);
    str.x =  io->pos[step - 1] & '\x80' ? -1 : 0;
    str.bytes[GET_NBYTE(step, 0, str.x)] = io->pos[0];
    str.bytes[GET_NBYTE(step, 1, str.x)] = io->pos[1];
    io->pos+= step;
    return (int) str.x;
}
FREE_INLINE  int io_read_u16(struct io_struct * io){
    const int step = 2;
    union{
        unsigned int x;
        char bytes[8];
    } str;
    io_require(io, step);
    str.x = 0;
    str.bytes[GET_NBYTE(step, 0, str.x)] = io->pos[0];
    str.bytes[GET_NBYTE(step, 1, str.x)] = io->pos[1];
    io->pos+= step;
    return (int) str.x;
}
FREE_INLINE  int io_read_u24(struct io_struct * io){
    const int step = 3;
    union{
        unsigned int x;
        char bytes[8];
    } str;
    io_require(io, step);
    str.x = 0;
    str.bytes[GET_NBYTE(step, 0, str.x)] = io->pos[0];
    str.bytes[GET_NBYTE(step, 1, str.x)] = io->pos[1];
    str.bytes[GET_NBYTE(step, 2, str.x)] = io->pos[2];
    io->pos+= step;
    return (int) str.x;
}
FREE_INLINE  int io_read_u32(struct io_struct * io){
    const int step = 4;
    union{
        unsigned int x;
        char bytes[8];
    } str;
    io_require(io, step);
    str.x = 0;
    str.bytes[GET_NBYTE(step, 0, str.x)] = io->pos[0];
    str.bytes[GET_NBYTE(step, 1, str.x)] = io->pos[1];
    str.bytes[GET_NBYTE(step, 2, str.x)] = io->pos[2];
    str.bytes[GET_NBYTE(step, 3, str.x)] = io->pos[3];
    io->pos+= step;
    return (int) str.x;
}
FREE_INLINE  void amf3_write_integer(pTHX_ struct io_struct *io, IV ivalue){
    UV value;
    if (ivalue<0){
	if ( ivalue < -( 1 << 28) ){
	    io_register_error( io, ERR_INT_OVERFLOW );
	};
        value = 0x1fffffff & (UV) ivalue;	
    }
    else {
        value = ivalue;
    }
    if (value<128){
        io_reserve(aTHX_  io, 1);
        io->pos[0]= (U8) value;
        io->pos+=1;
    }
    else if (value<= 0x3fff ) {
        io_reserve(aTHX_  io, 2);
        io->pos[0] = (U8) (value>>7) | 128;
        io->pos[1] = (U8) (value & 0x7f);
        io->pos+=2;
    }
    else if (value <= 0x1fffff) {
        io_reserve(aTHX_  io, 3);

        io->pos[0] = (U8) (value>>14) | 128;
        io->pos[1] = (U8) (value>>7 & 0x7f) |128;
        io->pos[2] = (U8) (value & 0x7f);
        io->pos+=3;
    }
    else if ((value <= 0x1fffffff)){
        io_reserve(aTHX_  io, 4);

        io->pos[0] = (U8) (value>>22 & 0xff) |128;
        io->pos[1] = (U8) (value>>15 & 0x7f) |128;
        io->pos[2] = (U8) (value>>8  & 0x7f) |128;
        io->pos[3] = (U8) (value     & 0xff);
        io->pos+=4;
    }
    else {
        io_register_error( io, ERR_INT_OVERFLOW);
        return;
    }
    return;
}

FREE_INLINE int amf3_read_integer(struct io_struct *io){
    I32 value;
    io_require(io, 1);
    if ((U8) io->pos[0] > 0x7f) {
        io_require(io, 2);
        if ((U8) io->pos[1] >0x7f) {

            io_require(io, 3);
            if ((U8) io->pos[2] >0x7f) {
                io_require(io, 4);

                value =  ((io->pos[0] & 0x7f) <<22)| ((io->pos[1] & 0x7f) <<15) | ((io->pos[2] & 0x7f) <<8) | io->pos[3];

                if ((U8) io->pos[0] >= 0xc0) {
                    value = value | ~(0x0fffffff);
                }
                else {
                    /* no return value; */ ;
                }
                io_move_forward(io, 4);
            }
            else {
                value = ((io->pos[0] & 0x7f) <<14) + ((io->pos[1] & 0x7f) <<7) + io->pos[2];
                io_move_forward(io, 3);
            }
        }
        else {
            value = ((io->pos[0] & 0x7f) << 7) + io->pos[1];
            io_move_forward(io, 2);
        }
    }
    else {
        value = (U8) io->pos[0];
        io_move_forward(io, 1);
    }
    return value;
}
STATIC_INLINE SV * amf0_parse_utf8(pTHX_ struct io_struct * io){
    int string_len = io_read_u16(io);
    SV * RETVALUE;
    char *x = io_read_chars(io, string_len);
    RETVALUE = newSVpvn(x, string_len);
    if (io->options & OPT_DECODE_UTF8)
	SvUTF8_on(RETVALUE);

    return RETVALUE;
}

STATIC_INLINE SV * amf0_parse_object(pTHX_ struct io_struct * io){
    HV * obj;
    int len_next;
    char * key;
    SV * value;
    SV *RETVALUE;

    obj =  newHV();
    RETVALUE = newRV_noinc( (SV *) obj );
    av_push(io->arr_object, RETVALUE);
    while(1){
        len_next = io_read_u16(io);
        if (len_next == 0) {
            char object_end;
            object_end= io_read_marker(io);
            if ( MARKER0_OBJECT_END == object_end )
            {
                if (io->options & OPT_STRICT){
                    if (SvREFCNT(RETVALUE) > 1)
                        io_register_error(io, ERR_RECURRENT_OBJECT);
                    ;
                    SvREFCNT_inc_simple_void_NN(RETVALUE);
                    return RETVALUE;
                }
                else {
                    SvREFCNT_inc_simple_void_NN(RETVALUE);
                    return RETVALUE;
                }
            }
            else {
                io->pos--;
                key = "";
                value = amf0_parse_one(aTHX_  io);
            }
        }
        else {
            key = io_read_chars(io, len_next);
            value = amf0_parse_one(aTHX_  io);
        }

        (void) hv_store(obj, key, len_next, value, 0);
    }
}

STATIC_INLINE SV* amf0_parse_movieclip(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    RETVALUE = newSV(0);
    return RETVALUE;
}
STATIC_INLINE SV* amf0_parse_null(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    RETVALUE = newSV(0);
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_undefined(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    RETVALUE = newSV(0);
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_reference(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    int object_offset;
    AV * ar_refs;
    object_offset = io_read_u16(io);
    ar_refs = (AV *) io->arr_object;
    if (object_offset > av_len(ar_refs)){
        io_register_error(io, ERR_AMF0_REF);
    }
    else {
        RETVALUE = *av_fetch(ar_refs, object_offset, 0);
        SvREFCNT_inc_simple_void_NN(RETVALUE);
    }
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_object_end(pTHX_ struct io_struct *io){
    io_read_marker(io);
    return 0;
}

STATIC_INLINE SV* amf0_parse_strict_array(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    int array_len;
    AV* this_array;
    AV * refs = io->arr_object;
    int i;

    refs = (AV*) io->arr_object;
    array_len = io_read_u32(io);
    
    /* report error before av_extent */
    if ( array_len > io->arr_max )
	io_register_error( io, ERR_ARRAY_TOO_BIG );
    else 
	io->arr_max -= array_len;

    this_array = newAV();
    av_extend(this_array, array_len);
    av_push(refs, RETVALUE = newRV_noinc((SV*) this_array));

    for(i=0; i<array_len; ++i){
        av_push(this_array, amf0_parse_one(aTHX_  io));
    }
    if (SvREFCNT(RETVALUE) > 1 && io->options & OPT_STRICT)
    io_register_error(io, ERR_RECURRENT_OBJECT);
    SvREFCNT_inc_simple_void_NN(RETVALUE);

    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_ecma_array(pTHX_ struct io_struct *io){
    SV* RETVALUE;

    U32 array_len;
    AV * this_array;
    AV * refs = io->arr_object;
    int  position; /*remember offset for array convertion to hash */
    int last_len;
    char last_marker;
    int av_refs_len;
    int key_len;
    char *key_ptr;
    array_len = io_read_u32(io);
    position= io_position(io);


    /* report_array early */
    if ( array_len > io->arr_max )
	io_register_error( io, ERR_ARRAY_TOO_BIG);
    else 
	io->arr_max -= array_len;

    this_array = newAV();
    av_extend(this_array, array_len);

    av_refs_len = av_len(refs);
    av_push(refs, RETVALUE = newRV_noinc((SV*) this_array));

    #ifdef TRACEA
    fprintf( stderr, "Start parse array %d\n", array_len);
    fprintf( stderr, "position %d\n", io_position(io));
    #endif
    if (1){
        bool ok;
        UV index;
        key_len = io_read_u16(io);
        key_ptr = io_read_chars(io, key_len);


        ok = ((key_len == 1) && (IS_NUMBER_IN_UV & grok_number(key_ptr, key_len, &index)) &&	 (index < array_len ));
        if (ok){
            av_store(this_array, index, amf0_parse_one(aTHX_  io));
        }
        else {
            if (((key_len) == 6  &&  strnEQ(key_ptr, "length", 6))){
                ok = 1;
                array_len++; /* safe for flash v.9.0 */
                sv_2mortal( amf0_parse_one(aTHX_  io));
            }
            else {
                ok = 0;
            };
        }
        if (ok){ 
	    U32 i;
            for(i=1; i<array_len; ++i){
                UV index;
                int key_len= io_read_u16(io);
                char *s = io_read_chars(io, key_len);

                #ifdef TRACEA
                fprintf( stderr, "index =%d, position %d\n", i, io_position(io));
                #endif
                if ((IS_NUMBER_IN_UV & grok_number(s, key_len, &index)) &&
                    (index < array_len)){
                    av_store(this_array, index, amf0_parse_one(aTHX_  io));
                    #ifdef TRACEA
                    fprintf( stderr, "index =%d, position %d\n", i, io_position(io));
                    #endif
                }
                else {
                    if ((key_len) != 6  || strnEQ(key_ptr, "length", 6)!=0){
                        io_move_backward(io, key_len + 2);
                        break;
                    }
                    else {
                        array_len++;
                        sv_2mortal( amf0_parse_one(aTHX_  io));
                    }
                }
            }
        }
        else {
            io_move_backward(io, key_len + 2);
        }
    }


    #ifdef TRACEA
    fprintf( stderr, "almost at end parse array %d\n", array_len);
    fprintf( stderr, "position %d\n", io_position(io));
    #endif
    last_len = io_read_u16(io);
    last_marker = io_read_marker(io);
    #ifdef TRACEA
    fprintf( stderr, "at end parse array %d\n", array_len);
    fprintf( stderr, "position %d\n", io_position(io));
    #endif
    if ((last_len == 0) && (last_marker == MARKER0_OBJECT_END)) {
        if (io->options & OPT_STRICT && (SvREFCNT(RETVALUE) > 1))
            io_register_error(io, ERR_RECURRENT_OBJECT); ;
        SvREFCNT_inc_simple_void_NN(RETVALUE);
    }
    else{
        /* Need rollback referenses */
        int i;
        for( i = av_len(refs) - av_refs_len; i>0 ;--i){
            SV * ref = av_pop(refs);
            SV * value= SvRV(ref);
            if ( SVt_PVHV == SvTYPE( value ) )
                hv_clear( (HV *) value );
            else if ( SVt_PVAV == SvTYPE( value ))
                av_clear( (AV *) value);
            else {
                /* FIXME I am not sure about simple mortalizing values this need to be reused or cleanups*/
                sv_2mortal(ref);
                io_register_error( io, ERR_INTERNAL );
            }
            sv_2mortal(ref);
        }
        io_set_position(io, position);
        RETVALUE = amf0_parse_object(aTHX_  io);
    }
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_date(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    double time;
    time = io_read_double(io);
    (void)io_read_s16(io);
    if ( io->options & OPT_MILLSEC_DATE )
	RETVALUE = newSVnv(time);
    else 
	RETVALUE = newSVnv(time/1000.0);
    av_push(io->arr_object, RETVALUE);
    SvREFCNT_inc_simple_void_NN(RETVALUE);
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_long_string(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    STRLEN len;
    len = io_read_u32(io);

    RETVALUE = newSVpvn(io_read_chars(io, len), len);
    if (io->options & OPT_DECODE_UTF8)
	SvUTF8_on(RETVALUE);
    return RETVALUE;
}

STATIC_INLINE SV* amf0_parse_unsupported(pTHX_ struct io_struct *io){
    io_register_error(io, ERR_UNIMPLEMENTED);
    return 0;
}
STATIC_INLINE SV* amf0_parse_recordset(pTHX_ struct io_struct *io){
    io_register_error(io, ERR_UNIMPLEMENTED);
    return 0;
}
STATIC_INLINE SV* amf0_parse_xml_document(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    RETVALUE = amf0_parse_long_string(aTHX_  io);
    SvREFCNT_inc_simple_void_NN(RETVALUE);
    av_push(io->arr_object, RETVALUE);
    return RETVALUE;
}
FREE_INLINE SV *parse_scalar_ref(pTHX_ struct io_struct *io){
        SV * obj;
        int obj_pos;
        int len_next;
        char *key;
        SV *value;

        io->pos+=6;
        obj =  newSV(0);
        av_push(io->arr_object,  obj);
        obj_pos = av_len(io->arr_object);
        value = 0;

        while(1){
            len_next = io_read_u16(io);
            if (len_next == 0) {
                char object_end;
                object_end= io_read_marker(io);
                if (MARKER0_OBJECT_END == object_end)
                {
                    SV* RETVALUE = *av_fetch(io->arr_object, obj_pos, 0);
                    if (!value)
                        io_register_error(io, ERR_BAD_REFVAL);
                        sv_setsv(obj, newRV_noinc(value));

                    if (io->options & OPT_STRICT){
                        if (SvREFCNT(RETVALUE) > 1)
                            io_register_error_and_free(aTHX_ io, ERR_RECURRENT_OBJECT, value);
                        ;
                        SvREFCNT_inc_simple_void_NN(RETVALUE);
                        return RETVALUE;
                    }
                    else {
                        SvREFCNT_inc_simple_void_NN(RETVALUE);
                        return RETVALUE;
                    }
                }
                else {
                    io_register_error_and_free(aTHX_ io, ERR_BAD_REFVAL, value);
                }
            }
            else if ( len_next ==  6) {
                key = io_read_chars(io, len_next);
                if (strncmp(key, "REFVAL", 6) || value )
                    io_register_error_and_free(aTHX_ io, ERR_BAD_REFVAL, value);
                
                value = amf0_parse_one(aTHX_  io);
            }
            else {
                io_register_error_and_free(aTHX_ io, ERR_BAD_REFVAL, value);
            }
    }
}
STATIC_INLINE SV* amf0_parse_typed_object(pTHX_ struct io_struct *io){
    SV* RETVALUE;
    HV *stash;
    int len;

    len = io_read_u16(io);
    if (len == 6 && !strncmp( (char *)io->pos, "REFVAL", 6)){
        /* SCALAR */
        RETVALUE = parse_scalar_ref(aTHX_ io);
        if (RETVALUE)
            return RETVALUE;
        
    }
    if (io->options & OPT_STRICT){
        stash = gv_stashpvn((char *)io->pos, len, 0);
    }
    else {
        stash = gv_stashpvn((char *)io->pos, len, GV_ADD );
    }
    io->pos+=len;
    RETVALUE = amf0_parse_object(aTHX_  io);
    if (stash) 
    sv_bless(RETVALUE, stash);
    return RETVALUE;
}
STATIC_INLINE SV* amf0_parse_double(pTHX_ struct io_struct * io){
    return newSVnv(io_read_double(io));
}

FREE_INLINE SV*  util_boolean(pTHX_ struct io_struct *io, bool value){
    if ( ! (io->options & OPT_JSON_BOOLEAN) ){
	SV *sv = boolSV( value );
	/* SvREFCNT_inc_simple_void_NN( sv ); */
	return sv;
    } 
    else {
	SV * sv =  value ? newSViv(1) :newSViv( 0 );
	SV * rv = newRV_noinc( sv  );
	HV * stash;
	stash = gv_stashpvn( "JSON::XS::Boolean", 17, GV_ADD );
	sv_bless( rv, stash );
	
	/*Stupid but it works */
	return rv;
    }
}
STATIC_INLINE SV* amf0_parse_boolean(pTHX_ struct io_struct * io){
    unsigned char marker;
    bool value; 
    marker = io_read_marker(io);
    value = (marker != '\000');
    return util_boolean(aTHX_ io, value);
}

/* 
STATIC_INLINE SV* parse_boolean(pTHX_ struct io_struct * io){
    char marker;
    marker = io_read_marker(io);

    int count;
    SV *value = 0;
    SAVETMPS;

    dSP;

    ENTER; SAVETMPS; PUSHMARK (SP);
    PUTBACK;
    if ( marker == '\000' ) {
        count = call_pv("JSON::XS::false", G_SCALAR);
    }
    else {
        count = call_pv("JSON::XS::true", G_SCALAR);
    }
    SPAGAIN;
    if (count == 1)
        value = newSVsv(POPs);

    if (count != 1 || !SvOK(value)) {
        value = newSViv( marker == '\000' ? 0 : 1 );
    }

    PUTBACK;
    FREETMPS; LEAVE;
    return value;
}
*/ 
FREE_INLINE SV * amf3_parse_one(pTHX_ struct io_struct *io);
STATIC_INLINE SV * amf3_parse_undefined(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE = newSV(0);
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_null(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE = newSV(0);
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_false(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE =  util_boolean( aTHX_ io, 0 );
    return RETVALUE;
}

STATIC_INLINE SV * amf3_parse_true(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE =  util_boolean( aTHX_ io, 1 );
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_integer(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE = newSViv(amf3_read_integer(io));
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_double(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE = newSVnv(io_read_double(io));
    return RETVALUE;
}
FREE_INLINE char * amf3_read_string(pTHX_ struct io_struct *io, int ref_len, STRLEN *str_len){

    AV * arr_string = io->arr_string;
    if (ref_len & 1) {
        *str_len = ref_len >> 1;
        if (*str_len>0){
            char *pstr;
            pstr = io_read_chars(io, *str_len);
            av_push(io->arr_string, newSVpvn(pstr, *str_len));
            return pstr;
        }
        else {
            return "";
        }
    }
    else {
        int ref = ref_len >> 1;	
        SV ** ref_sv  = av_fetch(arr_string, ref, 0);
        if (ref_sv) {
            char* pstr;
            pstr = SvPV(*ref_sv, *str_len);
            return pstr; 
        }
        else {
            /* Exception: May be there throw some */
            io_register_error(io, ERR_BAD_STRING_REF);
	    return 0; /* Never reach this lime */
        }
    }
}
STATIC_INLINE SV * amf3_parse_string(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int ref_len;
    STRLEN plen;
    char* pstr;
    ref_len  = amf3_read_integer(io);
    pstr = amf3_read_string(aTHX_  io, ref_len, &plen);
    RETVALUE = newSVpvn(pstr, plen);
    if (io->options & OPT_DECODE_UTF8) 
	SvUTF8_on(RETVALUE);
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_xml(pTHX_ struct io_struct *io);
STATIC_INLINE SV * amf3_parse_xml_doc(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    RETVALUE = amf3_parse_xml(aTHX_  io);
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_date(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int i = amf3_read_integer(io);
    if (i&1){

        double x = io_read_double(io);
	if ( io->options & OPT_MILLSEC_DATE ){
	    RETVALUE = newSVnv(x);
	}
	else {
	    RETVALUE = newSVnv(x/1000.0);
	};
	SvREFCNT_inc_simple_void_NN(RETVALUE);
        av_push(io->arr_object, RETVALUE);
    }
    else {
        SV ** item = av_fetch(io->arr_object, i>>1, 0);
        if (item) {
            RETVALUE = *item;
            SvREFCNT_inc_simple_void_NN(RETVALUE);
        }
        else{
            io_register_error(io, ERR_BAD_DATE_REF);
	    RETVALUE = 0; /* did not make any harm */
        }
    }
    return RETVALUE;
}


FREE_INLINE void amf3_store_object(pTHX_ struct io_struct *io, SV * item){
    av_push(io->arr_object, newRV_noinc(item));
}
FREE_INLINE void amf3_store_object_rv(pTHX_ struct io_struct *io, SV * item){
    av_push(io->arr_object, item);
}

STATIC_INLINE SV * amf3_parse_array(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int ref_len = amf3_read_integer(io);
    if (ref_len & 1){
        /* Not referense */
        int len = ref_len>>1;
        int str_len;
        SV * item;
        char * pstr;
        bool recover;
        STRLEN plen;		
        struct amf3_restore_point rec_point; 
        int old_vlen;
        SV * item_value;
        UV item_index;

        AV * array;
        str_len = amf3_read_integer(io);
        old_vlen = str_len;

        io_savepoint(aTHX_  io, &rec_point);		

        /* Пытаемся востановить как массив 
         Считаем что это массив если первый индекс от 0 до 9 и все индексы числовые
        */
        array=newAV();
        item = (SV *) array;
        RETVALUE = newRV_noinc(item);

        amf3_store_object_rv(aTHX_  io, RETVALUE);

        recover = FALSE;
        if (str_len !=1){
            pstr = amf3_read_string(aTHX_  io, str_len, &plen);
            if (IS_NUMBER_IN_UV & grok_number(pstr, plen, &item_index) && item_index< 10){

                item_value= amf3_parse_one(aTHX_  io);
                av_store(array, item_index, item_value);

                str_len = amf3_read_integer(io);
                while(str_len != 1){
                    pstr = amf3_read_string(aTHX_  io, str_len, &plen);
                    if (IS_NUMBER_IN_UV & grok_number(pstr, plen, &item_index)){

                        item_value= amf3_parse_one(aTHX_  io);
                        av_store(array, item_index, item_value);

                        str_len = amf3_read_integer(io);
                    }
                    else {
                        /* recover */
                        recover = TRUE;
                        break;
                    }
                };
            }
            else {
                /* recover */
                recover = TRUE;
            }
        }

        if (!recover) {
            int i;
            for(i=0; i< len; ++i){
                SV *item = amf3_parse_one(aTHX_  io);
                av_store(array, i, item);
            };
        }
        else {
            /*востанавливаем как хэш */
            HV * hv;
            char *pstr;
            STRLEN plen;
            char buf[2+2*sizeof(int)];
            int i;

            io_restorepoint(aTHX_  io, &rec_point);	

            str_len = old_vlen;
            hv   = newHV();
            item = (SV *) hv;
            RETVALUE = newRV_noinc(item);
            amf3_store_object_rv(aTHX_  io, RETVALUE);
            while(str_len != 1){
                SV *one;
                pstr = amf3_read_string(aTHX_  io, str_len, &plen);
                one = amf3_parse_one(aTHX_  io);
                (void) hv_store(hv, pstr, plen, one, 0);
                str_len = amf3_read_integer(io);

            };
            for(i=0; i<len;++i){
                (void) snprintf(buf, sizeof(buf), "%d", i);
                (void) hv_store(hv, buf, strlen(buf), amf3_parse_one(aTHX_  io), 0);
            }

            /* (void) snprintf(buf, sizeof(buf), "%d", 2);
            (void) hv_store(hv, buf, strlen(buf), newSVpvn( "abc", 3), 0);
            (void) snprintf(buf, sizeof(buf), "%d", 1);
            (void) hv_store(hv, buf, strlen(buf), newSVpvn( "abd", 3), 0); */

        };
        if (io->options & OPT_STRICT){
            if (SvREFCNT(RETVALUE)>1){
                io_register_error(io, ERR_RECURRENT_OBJECT);
            }
        }
        SvREFCNT_inc_simple_void_NN(RETVALUE);
    }
    else {
        SV ** value = av_fetch(io->arr_object, ref_len>>1, 0);	
        if (value) {
            SvREFCNT_inc_simple_void_NN(*value);
            RETVALUE = *value;
        }
        else {
            io_register_error(io, ERR_BAD_ARRAY_REF);
	    RETVALUE = 0; /* did not make any harm */
        }
    }
    return RETVALUE;
}
struct amf3_trait_struct{
    int sealed;
    bool dynamic;
    bool externalizable;
    SV* class_name;
    HV* stash;
};
STATIC_INLINE SV * amf3_parse_object(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int obj_ref = amf3_read_integer(io);
    #ifdef TRACE0
    fprintf(stderr, "obj_ref = %d\n", obj_ref);
    #endif
    if (obj_ref & 1) {/* not a ref object */
        AV * trait;
        int sealed;
        bool dynamic;
	bool externalizable;
        SV * class_name_sv;
        HV *one;
        int i;

        if (!(obj_ref & 2)){/* not trait ref */
            SV** trait_item	= av_fetch(io->arr_trait, obj_ref>>2, 0);
            if (! trait_item) {
                io_register_error(io, ERR_BAD_TRAIT_REF);
            };
            trait = (AV *) SvRV(*trait_item);

            sealed  = (int)  SvIV(*av_fetch(trait, 0, 0));
            dynamic = (bool) SvIV(*av_fetch(trait, 1, 0));
	    externalizable = (bool) SvIV(*av_fetch(trait, 2, 0));
            class_name_sv = *av_fetch(trait, 3, 0);
        }
        else {	
            int i;
	    trait = newAV();
	    av_push(io->arr_trait, newRV_noinc((SV *) trait));
	    sealed  = obj_ref >>4;
	    dynamic = obj_ref & 8;
	    externalizable = ( obj_ref  & 0x04) != 0;
	    class_name_sv = amf3_parse_string(aTHX_  io);

	    av_push(trait, newSViv(sealed));
	    av_push(trait, newSViv(dynamic));
	    av_push(trait, newSViv( externalizable )); /* external processing */
	    av_push(trait, class_name_sv);

	    for(i =0; i<sealed; ++i){
		SV * prop_name;

		prop_name = amf3_parse_string(aTHX_  io);
		av_push(trait, prop_name);
	    }			
        };
        one = newHV();
        RETVALUE = newRV_noinc((SV*) one);
        amf3_store_object_rv(aTHX_  io, RETVALUE);

	if ( externalizable ){
	    (void) hv_store( one, "externalizedData", 16, amf3_parse_one(aTHX_  io), 0);
	};

        for(i=0; i<sealed; ++i){
            (void) hv_store_ent( one, *av_fetch(trait, 4+i, 0), amf3_parse_one(aTHX_  io), 0);	
        };

        if (dynamic) {
            char *pstr;
            STRLEN plen;
            int varlen;
            varlen = amf3_read_integer(io);
            pstr = amf3_read_string(aTHX_  io, varlen, &plen);

            while(plen != 0) { 
                (void) hv_store(one, pstr, plen, amf3_parse_one(aTHX_  io), 0);				
                varlen = -1;
                plen = -1;
                varlen = amf3_read_integer(io);
                pstr = amf3_read_string(aTHX_  io, varlen, &plen);
            }
        }
        if (SvREFCNT(RETVALUE) > 1 && io->options & OPT_STRICT){
            io_register_error(io, ERR_RECURRENT_OBJECT);
        };
        SvREFCNT_inc_simple_void_NN(RETVALUE);
        if (SvCUR(class_name_sv)) {
            HV *stash;
            if (io->options & OPT_STRICT){
                stash = gv_stashsv(class_name_sv, 0 );
            }
            else {
                stash = gv_stashsv(class_name_sv, GV_ADD );
            }
            if (stash) 
            sv_bless(RETVALUE, stash);
        }
        else {
            /* No bless */
        }
    }
    else {
        SV ** ref = av_fetch(io->arr_object, obj_ref>>1, 0);
        if (ref) {
            RETVALUE = *ref;
            SvREFCNT_inc_simple_void_NN(RETVALUE);
        }
        else {
            io_register_error(io, ERR_BAD_TRAIT_REF);
            RETVALUE = &PL_sv_undef;	
        }
    }
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_xml(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int Bi = amf3_read_integer(io);
    if (Bi & 1) { /* value */
        int len = Bi>>1;
        char *b = io_read_bytes(io, len);
        RETVALUE = newSVpvn(b, len);
        if (io->options & OPT_DECODE_UTF8)
	    SvUTF8_on(RETVALUE);
        SvREFCNT_inc_simple_void_NN(RETVALUE);
        av_push(io->arr_object, RETVALUE);
    }
    else {
        SV ** sv = av_fetch(io->arr_object, Bi>>1, 0);
        if (sv) {
            RETVALUE = newSVsv(*sv);
        }		
        else {
            io_register_error(io, ERR_BAD_XML_REF);
        }
    }
    return RETVALUE;
}
STATIC_INLINE SV * amf3_parse_bytearray(pTHX_ struct io_struct *io){
    SV * RETVALUE;
    int Bi = amf3_read_integer(io);
    if (Bi & 1) { /* value */
        int len = Bi>>1;
        char *b = io_read_bytes(io, len);
        RETVALUE = newSVpvn(b, len);
        SvREFCNT_inc_simple_void_NN(RETVALUE);
        av_push(io->arr_object, RETVALUE);
    }
    else {
        SV ** sv = av_fetch(io->arr_object, Bi>>1, 0);
        if (sv) {
            RETVALUE = newSVsv(*sv);
        }		
        else {
            io_register_error(io, ERR_BAD_BYTEARRAY_REF);
        }
    }
    return RETVALUE;
}
FREE_INLINE void amf3_format_date( pTHX_ struct io_struct *io, SV * one){
    io_write_marker( aTHX_ io, MARKER3_DATE );
    amf3_write_integer( aTHX_ io, 1 );
    io_write_double( aTHX_ io, util_date_time( one ));
}
FREE_INLINE void amf3_format_one(pTHX_ struct io_struct *io, SV * one);
FREE_INLINE void amf3_format_integer(pTHX_ struct io_struct *io, SV *one){

    IV i = SvIV(one);
    if (i <= 0xfffffff && i>= -(0x10000000)){
        io_write_marker(aTHX_  io, MARKER3_INTEGER);
        amf3_write_integer(aTHX_  io, SvIV(one));
    }
    else {
        io_write_marker(aTHX_  io, MARKER3_DOUBLE);
        io_write_double(aTHX_  io, (double) i);
    }
}

FREE_INLINE void amf3_format_double(pTHX_ struct io_struct * io, SV *one){

    io_write_marker(aTHX_  io, MARKER3_DOUBLE);
    io_write_double(aTHX_  io, SvNV(one));
}

FREE_INLINE void amf3_format_undef(pTHX_ struct io_struct *io){
    io_write_marker(aTHX_  io, MARKER3_UNDEF);
}
FREE_INLINE void amf3_format_null(pTHX_ struct io_struct *io){
    io_write_marker(aTHX_  io, MARKER3_NULL);
}

FREE_INLINE void amf3_write_string_pvn(pTHX_ struct io_struct *io, char *pstr, STRLEN plen){
    HV* rhv;
    SV ** hv_item;

    rhv = io->hv_string;
    hv_item = hv_fetch(rhv, pstr, plen, 0);

    if (hv_item && SvOK(*hv_item)){
        int sref = SvIV(*hv_item);
        amf3_write_integer(aTHX_  io, sref <<1);
    }
    else {
        if (plen) {
            amf3_write_integer(aTHX_  io, (plen << 1)	| 1);
            io_write_bytes(aTHX_  io, pstr, plen);
            (void) hv_store(rhv, pstr, plen, newSViv(io->rc_string), 0);
            io->rc_string++;
        }
        else {
            io_write_marker(aTHX_  io, STR_EMPTY);
        }
    }
}

FREE_INLINE void amf3_format_string(pTHX_ struct io_struct *io, SV *one){
    char *pstr;
    STRLEN plen;
    pstr = SvPV(one, plen);
    io_write_marker(aTHX_  io, MARKER3_STRING);
    amf3_write_string_pvn(aTHX_  io, pstr, plen);
}

FREE_INLINE void amf3_format_reference(pTHX_ struct io_struct *io, SV *num){
    amf3_write_integer(aTHX_  io, SvIV(num)<<1);
}

FREE_INLINE void amf3_format_array(pTHX_ struct io_struct *io, AV * one){
    int alen;
    int i;
    SV ** aitem;
    io_write_marker(aTHX_  io, MARKER3_ARRAY);
    alen = av_len(one)+1;
    amf3_write_integer(aTHX_  io, 1 | (alen) <<1 );
    io_write_marker(aTHX_  io, STR_EMPTY); /*  no sparse array; */
    for( i = 0; i<alen ; ++i){
        aitem = av_fetch(one, i, 0);
        if (aitem) {
            amf3_format_one(aTHX_  io, *aitem);
        }
        else {
            io_write_marker(aTHX_  io, MARKER3_NULL);
        }
    }
}
FREE_INLINE void amf3_format_object(pTHX_ struct io_struct *io, SV * rone){
    AV * trait;
    SV ** rv_trait;
    char *class_name;
    int class_name_len;
    HV *one;
    one =(HV *) SvRV(rone);

    io_write_marker(aTHX_  io, MARKER3_OBJECT);
    if (sv_isobject((SV*)rone)){
        HV* stash = SvSTASH(one);
        class_name = HvNAME(stash);
        class_name_len = strlen(class_name);
    }
    else {

        class_name = "";
        class_name_len = 0;
    };

    rv_trait = hv_fetch(io->hv_trait, class_name, class_name_len, 0);
    if (rv_trait){
        int ref_trait;
        trait = (AV *) SvRV(*rv_trait);	
        ref_trait = SvIV( *av_fetch(trait, 1, 0));

        amf3_write_integer(aTHX_  io, (ref_trait<< 2) | 1);		
    }
    else {
        SV * class_name_sv;
        int const sealed_count = 0;
        trait = newAV();
        av_extend(trait, 3);
        class_name_sv = newSVpvn(class_name, class_name_len);
        rv_trait = hv_store( io->hv_trait, class_name, class_name_len, newRV_noinc((SV*)trait), 0);
        av_store(trait, 0, class_name_sv);
        av_store(trait, 1, newSViv(io->rc_trait));
        av_store(trait, 2, newSViv(0));

        amf3_write_integer(aTHX_  io, ( sealed_count << 4) | 0x0b );
        amf3_write_string_pvn(aTHX_  io, class_name, class_name_len);
        io->rc_trait++;

    }

    /* where must enumeration of sealed attributes

    where will dynamic properties
    */

    if (1){
        HV *hv;
        SV * value;
        char * key_str;
        I32 key_len;

        hv = one;

        hv_iterinit(hv);
        while( (value  = hv_iternextsv(hv, &key_str, &key_len)) ){
            if (key_len){
                amf3_write_string_pvn(aTHX_  io, key_str, key_len);
                amf3_format_one(aTHX_  io, value);
            };
        }
    }

    io_write_marker(aTHX_  io, STR_EMPTY); 
}

FREE_INLINE void amf3_format_one(pTHX_ struct io_struct *io, SV * one){
    SV *rv=0;
    bool is_perl_bool = 0;
    if (SvROK(one)){
        rv = (SV*) SvRV(one);
	if ( sv_isobject( one )){
            HV* stash = SvSTASH(rv);
            char *class_name = HvNAME(stash);
            if ( class_name[0] == 'J' ){
                if ( sv_isa(one, "JSON::PP::Boolean")){
                    is_perl_bool =  1;
                }
                else if ( sv_isa(one, "JSON::XS::Boolean") ){
                    is_perl_bool =  1;
                }
            }
            else if ( class_name[0] == 'b' ){
                if ( sv_isa(one, "boolean" )){
                    is_perl_bool  = 1;
                }
            }
	    if ( is_perl_bool ){
		io_write_marker(aTHX_ io, (SvTRUE( SvRV( one )) ? MARKER3_TRUE : MARKER3_FALSE ) );
		return ;
	    }
	}
    }

    if (rv){
        /* test has stored */
        SV **OK = hv_fetch(io->hv_object, (char *)(&rv), sizeof (rv), 1);
        if (SvOK(*OK)) {
            if (SvTYPE(rv) == SVt_PVAV) {
                io_write_marker(aTHX_  io, MARKER3_ARRAY);
                amf3_format_reference(aTHX_  io, *OK);
            }
            else if (SvTYPE(rv) == SVt_PVHV){
                io_write_marker(aTHX_  io, MARKER3_OBJECT);
                amf3_format_reference(aTHX_  io, *OK);
            }
	    else if (sv_isobject(one) && util_is_date(rv)){
		io_write_marker(aTHX_ io, MARKER3_OBJECT ); /*#TODO */
		amf3_format_reference(aTHX_  io, *OK);
	    }
            else {
		if ( io->options & OPT_SKIP_BAD ){
		    io_write_marker( aTHX_ io, MARKER3_UNDEF );
		}
		else {
		    io_register_error(io, ERR_BAD_OBJECT);
		}
            }
        }
        else {
            sv_setiv(*OK, io->rc_object);
            (void) hv_store(io->hv_object, (char *) (&rv), sizeof (rv), newSViv(io->rc_object), 0);
            ++io->rc_object;

	    if ( io->options & OPT_MAPPER ){
		if ( sv_isobject( one ) ){
		    
		    GV *to_amf = gv_fetchmethod_autoload (SvSTASH (rv), "TO_AMF", 0);
		    if ( to_amf ) {
			dSP;

			ENTER; SAVETMPS; PUSHMARK (SP);
			XPUSHs (sv_bless (sv_2mortal (newRV_inc (rv)), SvSTASH (rv)));

			/* calling with G_SCALAR ensures that we always get a 1 return value */
			PUTBACK;
			call_sv ((SV *)GvCV (to_amf), G_SCALAR);
			SPAGAIN;

			/* catch this surprisingly common error */
			if (SvROK (TOPs) && SvRV (TOPs) == rv)
			    croak ("%s::TO_AMF method returned same object as was passed instead of a new one", HvNAME (SvSTASH (rv)));

			rv = POPs;
			PUTBACK;

			amf3_format_object( aTHX_  io, rv);

			FREETMPS; LEAVE;
			return ;
		    }
		}
	    }

            if (SvTYPE(rv) == SVt_PVAV) 
		amf3_format_array(aTHX_  io, (AV*) rv);
            else if (SvTYPE(rv) == SVt_PVHV) {
                amf3_format_object(aTHX_  io, one);
            }
	    else if (sv_isobject( one ) && util_is_date( rv ) ){
		amf3_format_date(aTHX_ io, rv );
	    }
            else {
		if ( io->options & OPT_SKIP_BAD )
		    io_write_marker( aTHX_ io, MARKER3_UNDEF );
		else 
		    io_register_error(io, ERR_BAD_OBJECT);
            }
        }
    }
    else {
        if (SvOK(one)){
	    #if defined( EXPERIMENT1 )
	    if ( (io->options & OPT_PREFER_NUMBER )){
		if (SvNIOK(one)){
		    if ( SvIOK( one ) ){
			amf3_format_integer(aTHX_ io, one );
		    }
		    else {
			amf3_format_double(aTHX_  io, one);
		    }
		}
		else {
		    amf3_format_string(aTHX_  io, one);
		}
	    }
	    else 
	    #endif
            if (SvPOK(one)) {
                amf3_format_string(aTHX_  io, one);
            } else 
            if (SvIOK(one)){
                amf3_format_integer(aTHX_  io, one);
            }
            else if (SvNOK(one)){
                amf3_format_double(aTHX_  io, one);
            }
	    else {
		if ( io->options & OPT_SKIP_BAD )
		    io_write_marker( aTHX_ io, MARKER3_UNDEF );
		else 
		    io_register_error(io, ERR_BAD_OBJECT );
	    }
        }
        else {
            amf3_format_null(aTHX_  io);
        }
    }
}
typedef SV* (*parse_sub)(pTHX_ struct io_struct *io);


parse_sub parse_subs[] = {
    &amf0_parse_double,
    &amf0_parse_boolean,
    &amf0_parse_utf8,
    &amf0_parse_object,
    &amf0_parse_movieclip,
    &amf0_parse_null,
    &amf0_parse_undefined,
    &amf0_parse_reference,
    &amf0_parse_ecma_array,
    &amf0_parse_object_end,
    &amf0_parse_strict_array,
    &amf0_parse_date,
    &amf0_parse_long_string,
    &amf0_parse_unsupported,
    &amf0_parse_recordset,
    &amf0_parse_xml_document,
    &amf0_parse_typed_object
};

parse_sub amf3_parse_subs[] = {
    &amf3_parse_undefined,
    &amf3_parse_null,
    &amf3_parse_false,
    &amf3_parse_true,
    &amf3_parse_integer,
    &amf3_parse_double,
    &amf3_parse_string,
    &amf3_parse_xml_doc,
    &amf3_parse_date,
    &amf3_parse_array,
    &amf3_parse_object,
    &amf3_parse_xml,
    &amf3_parse_bytearray,
};

FREE_INLINE SV * amf3_parse_one(pTHX_ struct io_struct * io){
    unsigned char marker;

    marker = (unsigned char) io_read_marker(io);
    if (marker < ARRAY_SIZE( amf3_parse_subs )){
        return (amf3_parse_subs[marker])(aTHX_ io);
    }
    else {
        io_register_error(io, ERR_MARKER);
	return 0; /* Never reach this statement */
    }
}
FREE_INLINE SV* amf0_parse_one_tmp( pTHX_ struct io_struct *io, SV * reuse ){
    SV * RETVALUE;
    HV * obj;

    int len_next;
    char * key;
    SV * value;
    int obj_pos;

    io_require( io, 1 );
    RETVALUE = reuse;

    if ( MARKER0_OBJECT != MARKER0_OBJECT || ! SvROK( reuse ) ){
        io_register_error( io, ERR_BAD_OBJECT );
    }
    obj = (HV *) SvRV(reuse);
    if ( SvTYPE( obj ) != SVt_PVHV ){
        io_register_error( io, ERR_BAD_OBJECT );
    }
    ++io->pos;
        

    hv_clear( obj );
    SvREFCNT_inc_simple_void_NN( RETVALUE );
    av_push(io->arr_object, RETVALUE);
    obj_pos = av_len(io->arr_object);
    while(1){
        len_next = io_read_u16(io);
        if (len_next == 0) {
            char object_end;
            object_end= io_read_marker(io);
            if (MARKER0_OBJECT_END == object_end)
            {
                if (io->options & OPT_STRICT){
                    SV* RETVALUE = *av_fetch(io->arr_object, obj_pos, 0);
                    if (SvREFCNT(RETVALUE) > 1)
                        io_register_error(io, ERR_RECURRENT_OBJECT);
                    ;
                    SvREFCNT_inc_simple_void_NN(RETVALUE);
                    return RETVALUE;
                }
                else {
                    SvREFCNT_inc_simple_void_NN( RETVALUE );
                    return RETVALUE;
                    /* return (SV*) newRV_inc((SV*)obj); */
                }
            }
            else {
                io->pos--;
                key = "";
                value = amf0_parse_one(aTHX_  io);
            }
        }
        else {
            key = io_read_chars(io, len_next);
            value = amf0_parse_one(aTHX_  io);
        }

        (void) hv_store(obj, key, len_next, value, 0);
    }
}
STATIC_INLINE SV * amf0_parse_one(pTHX_ struct io_struct * io){
    unsigned char marker;
    marker = (unsigned char) io_read_marker(io);
    if ( marker < ARRAY_SIZE( parse_subs )){
        return (parse_subs[marker])(aTHX_ io);
    }
    else {
        return io_register_error(io, ERR_MARKER),(SV *)0;
    }
}
FREE_INLINE SV * deep_clone(pTHX_ SV * value);
FREE_INLINE AV * deep_array(pTHX_ AV* value){
    AV* copy =  (AV*) newAV();
    int c_len;
    int i;
    av_extend(copy, c_len = av_len(value));
    for(i = 0; i <= c_len; ++i){
        av_store(copy, i, deep_clone(aTHX_  *av_fetch(value, i, 0)));
    }
    return copy;
}

FREE_INLINE HV * deep_hash(pTHX_ HV* value){
    HV * copy =  (HV*) newHV();
    SV * key_value;
    char * key_str;
    I32 key_len;
    SV*	copy_val;

    hv_iterinit(value);
    while((key_value  = hv_iternextsv(value, &key_str, &key_len)) ){
        copy_val = deep_clone(aTHX_  key_value);
        (void) hv_store(copy, key_str, key_len, copy_val, 0);
    }
    return copy;
}

FREE_INLINE SV * deep_scalar(pTHX_ SV * value){
    return deep_clone(aTHX_  value);
}

FREE_INLINE SV * deep_clone(pTHX_ SV * value){
    if (SvROK(value)){
        SV * rv = (SV*) SvRV(value);
        SV * copy;
        if (SvTYPE(rv) == SVt_PVHV) {
            copy = newRV_noinc((SV*)deep_hash(aTHX_  (HV*) rv));
        }
        else if (SvTYPE(rv) == SVt_PVAV) {
            copy = newRV_noinc((SV*)deep_array(aTHX_  (AV*) rv));
        }
        else if (SvROK(rv)) {
            copy = newRV_noinc((SV*)deep_clone(aTHX_  (SV*) rv));
        }
        else {
            /* TODO: error checking
            return newSV(0); */
            copy = newRV_noinc(deep_clone(aTHX_  rv));
        }
        if (sv_isobject(value)) {
            HV * stash;
            stash = SvSTASH(rv);
            sv_bless(copy, stash);
        }
        return copy;
    }
    else {
        SV * copy;
        copy = newSV(0);
        if (SvOK(value)){
            sv_setsv(copy, value);
        }
        return copy;
    }
}
FREE_INLINE void ref_clear(pTHX_ HV * go_once, SV *sv){

    SV *ref_addr;
    if (! SvROK(sv))
    return;
    ref_addr = SvRV(sv);
    if (hv_exists(go_once, (char *) &ref_addr, sizeof (ref_addr)))
    return;
    (void) hv_store( go_once, (char *) &ref_addr, sizeof(ref_addr), &PL_sv_undef, 0);

    if (SvTYPE(ref_addr) == SVt_PVAV){
        AV * refarray = (AV*) ref_addr;
        int ref_len = av_len(refarray);
        int ref_index;
        for( ref_index = 0; ref_index <= ref_len; ++ref_index){
            SV ** ref_item = av_fetch( refarray, ref_index, 0);
            if (ref_item)
            ref_clear(aTHX_  go_once, *ref_item);
        }
        av_clear(refarray);
    }
    else if (SvTYPE(ref_addr) == SVt_PVHV){
        HV *ref_hash = (HV *) ref_addr;
        char *   key;
        I32  key_len;
        SV*  item;

        hv_iterinit(ref_hash);
        while( (item = hv_iternextsv(ref_hash, &key, &key_len)) ){
            ref_clear(aTHX_  go_once, item);
        };
        hv_clear(ref_hash);
    }
}    
/* Start XS defines
 *
 *
 *
 *
 *
 */

/* Temporary Intenale Storage */
MODULE = Storable::AMF0 PACKAGE = Storable::AMF0::TemporaryStorage

void
new(SV *class, SV *option=0)
    PPCODE:
    PERL_UNUSED_VAR( class );
    XPUSHs( sv_2mortal( get_tmp_storage( aTHX_ option )));

void
DESTROY(SV *self)
    PPCODE:
    destroy_tmp_storage( aTHX_ self );

MODULE = Storable::AMF0 PACKAGE = Storable::AMF0		

void 
dclone(SV * data)
    ALIAS:
	Storable::AMF::dclone= 1
	Storable::AMF3::dclone= 2
    PROTOTYPE: $
    INIT:
        SV* retvalue;
    PPCODE:
	PERL_UNUSED_VAR(ix);
        retvalue = deep_clone(aTHX_  data);
        sv_2mortal(retvalue);
        XPUSHs(retvalue);

void 
amf_tmp_storage(SV *option = 0)
    INIT:
        SV * retvalue;
    PPCODE:
        retvalue = get_tmp_storage(aTHX_ option);
        XPUSHs(retvalue);

void
thaw(SV *data, SV *sv_option = 0)
    ALIAS:
	Storable::AMF::thaw=1
	Storable::AMF::thaw0=2
    PROTOTYPE: $;$
    INIT:
        SV* retvalue;
        struct io_struct io[1];
    PPCODE:
	PERL_UNUSED_VAR(ix);
        if ( ! Sigsetjmp(io->target_error, 0) ){
            io->subname = "Storable::AMF0::thaw( data, option )";
            io_in_init(aTHX_  io, data, AMF0_VERSION, sv_option);
            retvalue = (SV*) (io->parse_one_object(aTHX_  io));
            /* clean up storable unless need */
            if ( io->reuse )
                io_in_cleanup(aTHX_ io);
            retvalue = sv_2mortal(retvalue);
            io_test_eof( aTHX_ io );
            sv_setsv(ERRSV, &PL_sv_undef);
            XPUSHs(retvalue);
        }
        else {
            io_format_error( aTHX_ io );
        }

void
deparse_amf(SV *data, SV * sv_option = 0)
    PROTOTYPE: $;$
    ALIAS:
	Storable::AMF::deparse_amf=1
	Storable::AMF::deparse_amf0=2
    INIT:
        SV* retvalue;
	struct io_struct io[1];
    PPCODE:
	PERL_UNUSED_VAR(ix);
        if ( ! Sigsetjmp(io->target_error, 0)){
            io->subname = "Storable::AMF0::deparse( data, option )";
            io_in_init(aTHX_  io, data, AMF0_VERSION, sv_option);
            
            retvalue = (SV*) (io->parse_one_object(aTHX_  io));
            /* clean up storable unless need */
            if ( io->reuse )
                io_in_cleanup(aTHX_ io);
            retvalue = sv_2mortal(retvalue);
            sv_setsv(ERRSV, &PL_sv_undef);
            if (GIMME_V == G_ARRAY){
                XPUSHs(retvalue);
                XPUSHs( sv_2mortal(newSViv( io->pos - io->ptr )) );
            }
            else {
                XPUSHs(retvalue);
            }
        }
        else {
            io_format_error( aTHX_ io );
        }


void freeze(SV *data, SV *sv_option = 0 )
    ALIAS:
	Storable::AMF::freeze=1
	Storable::AMF::freeze0=2
    PROTOTYPE: $;$
    INIT:
        SV * retvalue;
        struct io_struct io[1];
    PPCODE:
	PERL_UNUSED_VAR(ix);
        if (! Sigsetjmp(io->target_error, 0)){
            io_out_init(aTHX_  io, sv_option, AMF0_VERSION);
            amf0_format_one(aTHX_  io, data);
            if (io->reuse )
                io_out_cleanup(aTHX_ io);
            retvalue = io_buffer(io);
            XPUSHs(retvalue);
            sv_setsv(ERRSV, &PL_sv_undef);
        }
        else{
	    io_format_error( aTHX_ io );
        }


MODULE = Storable::AMF0		PACKAGE = Storable::AMF3		

void
deparse_amf(SV *data, SV* sv_option = 0)
    ALIAS: 
        Storable::AMF::deparse_amf3 = 1
    PROTOTYPE: $;$
    INIT:
        SV* retvalue;
        struct io_struct io[1];
    PPCODE:
	PERL_UNUSED_VAR(ix);
        if ( ! Sigsetjmp(io->target_error, 0)){
            io->subname = "Storable::AMF3::deparse_amf( data, option )";
            io_in_init(aTHX_  io, data, AMF3_VERSION, sv_option);
            retvalue = (SV*) (amf3_parse_one(aTHX_  io));
            /* clean up storable unless need */
            if ( io->reuse )
                io_in_cleanup(aTHX_ io);
            sv_2mortal(retvalue);
            sv_setsv(ERRSV, &PL_sv_undef);

            XPUSHs(retvalue);
            if (GIMME_V == G_ARRAY){
                XPUSHs( sv_2mortal(newSViv( io->pos - io->ptr )) );
            }
        }
        else {
            io_format_error(aTHX_ io );
        }

void
thaw(SV *data, SV *sv_option = 0)
    PROTOTYPE: $;$
    INIT:
        SV* retvalue;
        struct io_struct io[1];
    ALIAS:
	Storable::AMF::thaw3=1
    PPCODE:
	PERL_UNUSED_VAR(ix);
        if ( ! Sigsetjmp(io->target_error, 0)){
            io->subname = "Storable::AMF3::thaw( data, option )";
            io_in_init(aTHX_  io, data, AMF3_VERSION, sv_option);
            retvalue = (SV*) (amf3_parse_one(aTHX_  io));
            /* clean up storable unless need */
            if ( io->reuse )
                io_in_cleanup(aTHX_ io);
            sv_2mortal(retvalue);
            io_test_eof( aTHX_ io );
            sv_setsv(ERRSV, &PL_sv_undef);
            XPUSHs(retvalue);
        }
        else {
            io_format_error(aTHX_ io);
        }

void
_test_thaw_integer(SV*data)
    INIT:
        SV* retvalue;
        struct io_struct io[1];
    PPCODE:
        if ( ! Sigsetjmp(io->target_error, 0)){
            io->subname = "Storable::AMF3::_test_thaw_integer( data, option )";
            io_in_init(aTHX_  io, data, AMF3_VERSION, 0 );
            retvalue = (SV*) (amf3_parse_integer(aTHX_  io));
            sv_2mortal(retvalue);
            io_test_eof( aTHX_ io );

            sv_setsv(ERRSV, &PL_sv_undef);
            XPUSHs(retvalue);
        }
        else {
            io_format_error(aTHX_ io );
        }

void
_test_freeze_integer(SV*data)
    PREINIT:
        SV * retvalue;
        struct io_struct io[1];
    PPCODE:
        if (! Sigsetjmp(io->target_error, 0)){
            io_out_init(aTHX_  io, 0, AMF3_VERSION);
            amf3_write_integer(aTHX_  io, SvIV(data));
            retvalue = io_buffer(io);
            XPUSHs(retvalue);
            sv_setsv(ERRSV, &PL_sv_undef);
        }
        else {
	    io_format_error( aTHX_ io );
        }


void 
endian()
    PREINIT:
        SV * retvalue;
    PPCODE:
    retvalue = newSVpvf("%s %x\n",GAX, BYTEORDER);
    sv_2mortal(retvalue);
    XPUSHs(retvalue);

void freeze(SV *data, SV *sv_option = 0 )
    PROTOTYPE: $;$
    PREINIT:
        SV * retvalue;
        struct io_struct io[1];
    ALIAS:
	Storable::AMF::freeze3=1
    PPCODE:
	PERL_UNUSED_VAR(ix); 
        if (! Sigsetjmp(io->target_error, 0)){
            io_out_init(aTHX_  io, sv_option, AMF3_VERSION);
            amf3_format_one(aTHX_  io, data);
            if (io->reuse )
                io_out_cleanup(aTHX_ io);
            retvalue = io_buffer(io);
            XPUSHs(retvalue);
            sv_setsv(ERRSV, &PL_sv_undef);
        }
        else {
	    io_format_error( aTHX_ io );
        }

void
new_amfdate(NV timestamp )
    PREINIT:
    SV *mortal;
    PROTOTYPE: $
    ALIAS:
	Storable::AMF::new_amfdate =1
	Storable::AMF0::new_amfdate=2
	Storable::AMF::new_date =3
	Storable::AMF0::new_date=4
	Storable::AMF3::new_date=5
    PPCODE:
	PERL_UNUSED_VAR( ix );
	mortal=sv_newmortal();
	sv_setref_nv( mortal, "*", timestamp ); /*Stupid but it works */
	XPUSHs( mortal );

void 
perl_date(SV *date)
    PREINIT:
    SV *mortal;
    PROTOTYPE: $
    ALIAS: 
	Storable::AMF::perl_date=1
	Storable::AMF0::perl_date=2
    PPCODE:
	PERL_UNUSED_VAR( ix );
	if ( SvROK( date ) && util_is_date( (SV*) SvRV(date))){
	    XPUSHs((SV*) SvRV(date));
	}
	else if ( SvNOK( date )){
	    mortal = sv_newmortal();
	    sv_setnv( mortal, SvNV( date ));
	    XPUSHs(mortal);
	}
	else {
	    croak("Expecting perl/amf date as argument" );
	}

void
parse_option(char * s, int options=0)
    PREINIT: 
    int s_strict;
    int s_utf8_decode;
    int s_utf8_encode;
    int s_milldate;
    int s_raise_error;
    int s_prefer_number;
    int s_ext_boolean; /* I8 -> int*/
    int s_targ;
    int sign;  
    char *word;
    char *current;
    bool error;
    PROTOTYPE: $;$
    ALIAS:
    Storable::AMF::parse_option=1
    Storable::AMF0::parse_option=2
    Storable::AMF::parse_serializator_option=3
    Storable::AMF3::parse_serializator_option=4
    Storable::AMF0::parse_serializator_option=5
    PPCODE:
    PERL_UNUSED_VAR( ix );
    s_strict = 0;
    s_utf8_decode = 0;
    s_utf8_encode = 0;
    s_milldate    = 0;
    s_raise_error = 0;
    s_prefer_number = 0;
    s_ext_boolean   = 0;
    options         = 0;
    s_targ          = 1;

    for( current = s;*current && ( !isALPHA( *current ) && *current!='+' && *current!='-' ) ; ++current ); 

    word = current;
    while( *word ){
	++current;
	error = 0;
	sign  = 1;
	if ( *word == '+' ){
	    ++word;
	}
	else if ( *word =='-' ){
	    sign = -1;
	    ++word;
	}
	for( ; *current && ( isALNUM( *current ) || *current == '_' ); ++current );
	switch( current - word ){
        case 4:
            if ( !strncmp( "targ", word, 4)){
                    s_targ = sign;
            }
            else {
                error = 1;
            };
            break;
	case 6:
	    if (!strncmp("strict", word, 6)){
		s_strict = sign;
	    }
	    else {
		error = 1;
	    };
	    break;
	case 11:
	    if (!strncmp( "utf8_decode", word, 11)){
		s_utf8_decode = sign;
	    }
	    else if (!strncmp( "utf8_encode", word, 11)){
		s_utf8_encode = sign;
	    }
	    else if (!strncmp("raise_error", word, 9)){
		s_raise_error=sign;
	    }
	    else {
		error = 1;
	    }
	    break;
	case 13:
	    if (!strncmp( "prefer_number", word, 13)){
		s_prefer_number = sign;
	    }
	    else {
		error = 1;
	    };
	    break;
	case   12:
	    if (!strncmp("json_boolean", word, 12)){
		s_ext_boolean = sign;
	    }
	    else if (!strncmp("boolean_json", word, 12)){
		s_ext_boolean = sign;
	    }
	    else  
		error = 1;
	    break;
	case   16:
	    if (!strncmp("millisecond_date", word, 16)){
		s_milldate = sign;
	    }
	    else 
		error = 1;
	    break;
	default:
	    error = 1;
	};
	if (error)
	    croak("Storable::AMF0::parse_option: unknown option '%.*s'", (int)(current - word), word);

	for(; *current && !isALPHA(*current) && *current!='+' && *current!='-'; ++current);
	word = current;
    };	
    SIGN_BOOL_APPLY( options, s_strict,        OPT_STRICT );
    SIGN_BOOL_APPLY( options, s_milldate,      OPT_MILLSEC_DATE );
    SIGN_BOOL_APPLY( options, s_utf8_decode,   OPT_DECODE_UTF8 );
    SIGN_BOOL_APPLY( options, s_utf8_encode,   OPT_ENCODE_UTF8 );
    SIGN_BOOL_APPLY( options, s_raise_error,   OPT_RAISE_ERROR );
    SIGN_BOOL_APPLY( options, s_prefer_number, OPT_PREFER_NUMBER );
    SIGN_BOOL_APPLY( options, s_ext_boolean,   OPT_JSON_BOOLEAN );
    SIGN_BOOL_APPLY( options, s_targ,          OPT_TARG );
    mXPUSHi(  options ); 

MODULE = Storable::AMF0 PACKAGE = Storable::AMF::Util

void
total_sv()
    PPCODE:
    I32 visited  = 0;
    SV* sva;
    for( sva = PL_sv_arenaroot; sva; sva = (SV*)SvANY(sva)) {
        SV * svend = &sva[SvREFCNT(sva)];
        SV * svi;
        /* fprintf( stderr, "=%p %d\n", sva, SvREFCNT( sva ) ); */
        for( svi = sva + 1; svi<svend; ++svi ){
            if ( (unsigned int)SvTYPE(svi) != SVTYPEMASK && SvREFCNT(svi) ){
                /** skip pads, they have a PVAV as their first element inside a PVAV **/
                if (SvTYPE(svi) == SVt_PVAV &&  av_len( (AV*) svi) != -1) {
                    SV** first = AvARRAY((AV*)svi);
                    if (first && *first && SvTYPE(*first) == SVt_PVAV) {
                        continue;
                    }
                    if (first && *first && SvTYPE(*first) == SVt_PVCV) {
                        continue;
                    }
                }
                if (SvTYPE(svi) == SVt_PVCV && CvROOT((CV*)svi) == 0) {
                    continue;
                }
                ++visited;
            }
        }
    }
    mXPUSHi( visited );

MODULE=Storable::AMF0 PACKAGE = Storable::AMF 

void 
thaw0_sv( SV * data, SV * element, SV *sv_option = 0)
    INIT: 
        SV * retvalue;
        struct io_struct io[1];
    PPCODE:
	/* PERL_UNUSED_VAR(ix); */
        if ( ! Sigsetjmp(io->target_error, 0) ){
            io->subname = "Storable::AMF0::thaw( data, option )";
            io_in_init(aTHX_  io, data, AMF0_VERSION, sv_option);
            retvalue = (SV*) (amf0_parse_one_tmp( aTHX_  io, element ));
            /* clean up storable unless need */
            if ( io->reuse )
                io_in_cleanup(aTHX_ io);
            retvalue = sv_2mortal(retvalue);
            io_test_eof( aTHX_ io );
            sv_setsv(ERRSV, &PL_sv_undef);

            /* XPUSHs(retvalue); */
        }
        else {
            io_format_error( aTHX_ io );
        }

MODULE=Storable::AMF