#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define LZF_STANDALONE 1
#define LZF_STATE_ARG 1

#include "lzf_c.c"
#include "lzf_d.c"
#include "lzf_c_best.c"

/* we re-use the storable header for our purposes */
#define MAGIC_LO	0
#define MAGIC_U		0 /* uncompressed data follows */
#define MAGIC_C		1 /* compressed data follows */
#define MAGIC_undef	2 /* the special value undef */
#define MAGIC_CR	3 /* storable (reference, freeze), compressed */
#define MAGIC_R		4 /* storable (reference, freeze) */
#define MAGIC_CR_deref	5 /* storable (NO reference, freeze), compressed */
#define MAGIC_R_deref	6 /* storable (NO reference, freeze) */
#define MAGIC_HI	7 /* room for one higher storable major */
/* for historical reasons, MAGIC_undef + MAGIC_R and MAGIC_undef + MAGIC_R_deref are used, too */

#define IN_RANGE(v,l,h) ((unsigned int)((unsigned)(v) - (unsigned)(l)) <= (unsigned)(h) - (unsigned)(l))

static SV *serializer_package, *serializer_mstore, *serializer_mretrieve;
static CV *storable_mstore, *storable_mretrieve;

#if Size_t_size > 4
# define MAX_LENGTH ((Size_t)0x80000000L)
#else
# define MAX_LENGTH ((Size_t) 0x8000000L)
#endif

static SV *
compress_sv (SV *data, char cprepend, int uprepend, int best)
{
  LZF_STATE *state;
  STRLEN usize, csize;
  char *src = (char *)SvPVbyte (data, usize);

  if (usize)
    {
      SV *ret = NEWSV (0, usize + 1);
      unsigned char *dst;
      int skip = 0;

      SvPOK_only (ret);
      dst = (unsigned char *)SvPVX (ret);

      if (cprepend)
        dst[skip++] = cprepend;

      if (usize <= 0x7f)
        {
          dst[skip++] = usize;
        }
      else if (usize <= 0x7ff)
        {
          dst[skip++] = (( usize >>  6)         | 0xc0);
          dst[skip++] = (( usize        & 0x3f) | 0x80);
        }
      else if (usize <= 0xffff) 
        {
          dst[skip++] = (( usize >> 12)         | 0xe0);
          dst[skip++] = (((usize >>  6) & 0x3f) | 0x80);
          dst[skip++] = (( usize        & 0x3f) | 0x80);
        }
      else if (usize <= 0x1fffff) 
        {
          dst[skip++] = (( usize >> 18)         | 0xf0);
          dst[skip++] = (((usize >> 12) & 0x3f) | 0x80);
          dst[skip++] = (((usize >>  6) & 0x3f) | 0x80);
          dst[skip++] = (( usize        & 0x3f) | 0x80);
        }
      else if (usize <= 0x3ffffff) 
        {
          dst[skip++] = (( usize >> 24)         | 0xf8);
          dst[skip++] = (((usize >> 18) & 0x3f) | 0x80);
          dst[skip++] = (((usize >> 12) & 0x3f) | 0x80);
          dst[skip++] = (((usize >>  6) & 0x3f) | 0x80);
          dst[skip++] = (( usize        & 0x3f) | 0x80);
        }
      else if (usize <= 0x7fffffff) 
        {
          dst[skip++] = (( usize >> 30)         | 0xfc);
          dst[skip++] = (((usize >> 24) & 0x3f) | 0x80);
          dst[skip++] = (((usize >> 18) & 0x3f) | 0x80);
          dst[skip++] = (((usize >> 12) & 0x3f) | 0x80);
          dst[skip++] = (((usize >>  6) & 0x3f) | 0x80);
          dst[skip++] = (( usize        & 0x3f) | 0x80);
        }
      else
        croak ("compress can only compress up to %ld bytes", 0x7fffffffL);

      New (0, state, 1, LZF_STATE);
      if (!state)
        croak ("Compress::LZF unable to allocate memory for compression state");

      /* 11 bytes is the smallest compressible string */
      csize = usize < 11 ? 0 :
              (best ? lzf_compress_best (src, usize, dst + skip, usize - skip)
                    : lzf_compress (src, usize, dst + skip, usize - skip, *state));

      Safefree (state);

      if (csize)
        {
          SvCUR_set (ret, csize + skip);
        }
      else if (uprepend < 0)
        {
          SvREFCNT_dec (ret);
          ret = SvREFCNT_inc (data);
        }
      else
        {
          *dst++ = uprepend;

          Move ((void *)src, (void *)dst, usize, unsigned char);

          SvCUR_set (ret, usize + 1);
        }

      return ret;
    }
  else
    return newSVpv ("", 0);
}

static SV *
decompress_sv (SV *data, int skip)
{
  STRLEN usize, csize;
  unsigned char *src = (unsigned char *)SvPVbyte (data, csize) + skip;

  if (csize)
    {
      void *dst;
      SV *ret;

      csize -= skip;

      if (src[0])
        {
          if (!(src[0] & 0x80) && csize >= 1)
            {
              csize -= 1;
              usize =                 *src++ & 0xff;
            }
          else if (!(src[0] & 0x20) && csize >= 2)
            {
              csize -= 2;
              usize =                 *src++ & 0x1f;
              usize = (usize << 6) | (*src++ & 0x3f);
            }
          else if (!(src[0] & 0x10) && csize >= 3)
            {
              csize -= 3;
              usize =                 *src++ & 0x0f;
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
            }
          else if (!(src[0] & 0x08) && csize >= 4)
            {
              csize -= 4;
              usize =                 *src++ & 0x07;
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
            }
          else if (!(src[0] & 0x04) && csize >= 5)
            {
              csize -= 5;
              usize =                 *src++ & 0x03;
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
            }
          else if (!(src[0] & 0x02) && csize >= 6)
            {
              csize -= 6;
              usize =                 *src++ & 0x01;
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
              usize = (usize << 6) | (*src++ & 0x3f);
            }
          else
            croak ("compressed data corrupted (invalid length)");

          if (!usize)
            croak ("compressed data corrupted (invalid length)");
                    
          ret = NEWSV (0, usize);
          SvPOK_only (ret);
          dst = SvPVX (ret);

          if (lzf_decompress (src, csize, dst, usize) != usize)
            {
              SvREFCNT_dec (ret);
              croak ("compressed data corrupted (size mismatch)", csize, skip, usize);
            }
        }
      else
        {
          usize = csize - 1;
          ret = NEWSV (0, usize | 1);
          SvPOK_only (ret);

          Move ((void *)(src + 1), (void *)SvPVX (ret), usize, unsigned char);
      }

      SvCUR_set (ret, usize);

      return ret;
    }
  else
    return newSVpvn ("", 0);
}

static void
need_storable (void)
{
  eval_sv (sv_2mortal (newSVpvf ("require %s", SvPVbyte_nolen (serializer_package))), G_VOID | G_DISCARD);

  storable_mstore    = (CV *)SvREFCNT_inc (GvCV (gv_fetchpv (SvPVbyte_nolen (serializer_mstore   ), TRUE, SVt_PVCV)));
  storable_mretrieve = (CV *)SvREFCNT_inc (GvCV (gv_fetchpv (SvPVbyte_nolen (serializer_mretrieve), TRUE, SVt_PVCV)));
}

MODULE = Compress::LZF   PACKAGE = Compress::LZF

BOOT:
        serializer_package   = newSVpv ("Storable", 0);
        serializer_mstore    = newSVpv ("Storable::net_mstore", 0);
        serializer_mretrieve = newSVpv ("Storable::mretrieve", 0);

void
set_serializer(package, mstore, mretrieve)
	SV *	package
	SV *	mstore
	SV *	mretrieve
        PROTOTYPE: $$$
        PPCODE:
        SvSetSV (serializer_package  , package  );
        SvSetSV (serializer_mstore   , mstore   );
        SvSetSV (serializer_mretrieve, mretrieve);
        SvREFCNT_dec (storable_mstore   ); storable_mstore    = 0;
        SvREFCNT_dec (storable_mretrieve); storable_mretrieve = 0;

void
compress(data)
        SV *	data
        ALIAS:
        compress_best = 1
        PROTOTYPE: $
        PPCODE:
        XPUSHs (sv_2mortal (compress_sv (data, 0, MAGIC_U, ix)));

void
decompress(data)
        SV *	data
        PROTOTYPE: $
        PPCODE:
        XPUSHs (sv_2mortal (decompress_sv (data, 0)));

void
sfreeze(sv)
	SV *	sv
        ALIAS:
        sfreeze         = 0
        sfreeze_cr      = 1
        sfreeze_c       = 2
        sfreeze_best    = 4
        sfreeze_cr_best = 5
        sfreeze_c_best  = 6
        PROTOTYPE: $
        PPCODE:
{
	int best = ix & 4;
        ix &= 3;

        SvGETMAGIC (sv);

        if (!SvOK (sv))
          XPUSHs (sv_2mortal (newSVpvn ("\02", 1))); /* 02 == MAGIC_undef */
        else if (SvROK (sv)
                 || SvUTF8 (sv)
                 || (SvTYPE(sv) != SVt_IV
                     && SvTYPE(sv) != SVt_NV
                     && SvTYPE(sv) != SVt_PV
                     && SvTYPE(sv) != SVt_PVIV
                     && SvTYPE(sv) != SVt_PVNV
                     && SvTYPE(sv) != SVt_PVMG)) /* mstore */
          {
            int deref = !SvROK (sv);
            char *pv;

            if (!storable_mstore)
              {
                PUTBACK;
                need_storable ();
                SPAGAIN;
              }

            if (deref)
              sv = newRV_noinc (sv);

            PUSHMARK (SP);
            XPUSHs (sv);
            PUTBACK;

            if (1 != call_sv ((SV *)storable_mstore, G_SCALAR))
              croak ("%s didn't return a single scalar", SvPVbyte_nolen (serializer_mstore));

            SPAGAIN;

            sv = POPs;
            pv = SvPV_nolen (sv);

            if (*pv == MAGIC_R)
              {
                if (deref)
                  *pv = MAGIC_R_deref;
              }
            else
              {
                char pfx[2];

                pfx[0] = MAGIC_undef;
                pfx[1] = deref ? MAGIC_R_deref : MAGIC_R;

                sv_insert (sv, 0, 0, pfx, 2);
              }

            if (ix) /* compress */
              sv = sv_2mortal (compress_sv (sv, deref ? MAGIC_CR_deref : MAGIC_CR, -1, best));

            XPUSHs (sv);
          }
        else if (SvPOKp (sv) && IN_RANGE (SvPVX (sv)[0], MAGIC_LO, MAGIC_HI))
          XPUSHs (sv_2mortal (compress_sv (sv, MAGIC_C, MAGIC_U, best))); /* need to prefix only */
        else if (ix == 2) /* compress always */
          XPUSHs (sv_2mortal (compress_sv (sv, MAGIC_C, -1, best)));
        else if (SvNIOK (sv)) /* don't compress */
          {
            STRLEN len;
            char *s = SvPV (sv, len);
            XPUSHs (sv_2mortal (newSVpvn (s, len)));
          }
        else /* don't compress */
          XPUSHs (sv_2mortal (newSVsv (sv)));
}

void
sthaw(sv)
	SV *	sv
        PROTOTYPE: $
        PPCODE:
{
        STRLEN svlen;
        int deref = 0;

        SvGETMAGIC (sv);
        if (SvPOK (sv) && IN_RANGE (SvPVbyte (sv, svlen)[0], MAGIC_LO, MAGIC_HI))
          {
            redo:

            switch (SvPVX (sv)[0])
              {
                case MAGIC_undef:
                  if (svlen <= 1)
                    XPUSHs (sv_2mortal (NEWSV (0, 0)));
                  else
                    {
                      if (SvPVX (sv)[1] == MAGIC_R_deref)
                        deref = 1;
                      else if (SvPVX (sv)[1] != MAGIC_R)
                        croak ("Compress::LZF::sthaw(): invalid data, maybe you need a newer version of Compress::LZF?");

                      sv_chop (sv, SvPVX (sv) + 2);

                      if (!storable_mstore)
                        {
                          PUTBACK;
                          need_storable ();
                          SPAGAIN;
                        }

                      PUSHMARK (SP);
                      XPUSHs (sv);
                      PUTBACK;

                      if (1 != call_sv ((SV *)storable_mretrieve, G_SCALAR))
                        croak ("%s didn't return a single scalar", SvPVbyte_nolen (serializer_mretrieve));

                      SPAGAIN;

                      if (deref)
                        SETs (sv_2mortal (SvREFCNT_inc (SvRV (TOPs))));
                      else
                        SETs (sv_2mortal (newSVsv (TOPs)));
                    }
                  break;

                case MAGIC_U:
                  XPUSHs (sv_2mortal (decompress_sv (sv, 0)));
                  break;

                case MAGIC_C:
                  XPUSHs (sv_2mortal (decompress_sv (sv, 1)));
                  break;

                case MAGIC_R_deref:
                  deref = 1;
                  SvPVX (sv)[0] = MAGIC_R;
                  goto handle_MAGIC_R;

                case MAGIC_CR_deref:
                  deref = 1;
                case MAGIC_CR:
                  sv = sv_2mortal (decompress_sv (sv, 1)); /* mortal could be optimized */
                  if (deref)
                    if (SvPVX (sv)[0] == MAGIC_R_deref)
                      SvPVX (sv)[0] = MAGIC_R;

                  goto redo;

                case MAGIC_R:
                handle_MAGIC_R:
                  if (!storable_mstore)
                    {
                      PUTBACK;
                      need_storable ();
                      SPAGAIN;
                    }

                  PUSHMARK (SP);
                  XPUSHs (sv);
                  PUTBACK;

                  if (1 != call_sv ((SV *)storable_mretrieve, G_SCALAR))
                    croak ("%s didn't return a single scalar", SvPVbyte_nolen (serializer_mretrieve));

                  SPAGAIN;

                  if (deref)
                    {
                      SETs (sv_2mortal (SvREFCNT_inc (SvRV (TOPs))));

                      if (SvPVX (sv)[0] == MAGIC_R)
                        SvPVX (sv)[0] = MAGIC_R_deref;
                    }
                  else
                    SETs (sv_2mortal (newSVsv (TOPs)));

                  break; 

                default:
                  croak ("Compress::LZF::sthaw(): invalid data, maybe you need a newer version of Compress::LZF?");
              }
          }
        else
          XPUSHs (sv_2mortal (newSVsv (sv)));
}