#define INITGUID
#include "7zip/CPP/Common/MyInitGuid.h"
#include "7zip/CPP/7zip/IStream.h"
#include "7zip/CPP/7zip/Compress/ZlibEncoder.h"
#include "7zip/CPP/7zip/Common/FileStreams.h"
#include "7zip/CPP/7zip/Common/StreamObjects.h"

extern "C"
{
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
}

void setCoderProps(NCompress::NDeflate::NEncoder::CCOMCoder* c, unsigned int algo, unsigned int pass, unsigned int fast, unsigned int cycles) {
  HRESULT hr;
  PROPVARIANT v;
  PROPID algoProp = NCoderPropID::kAlgorithm;
  PROPID passProp = NCoderPropID::kNumPasses;
  PROPID fastProp = NCoderPropID::kNumFastBytes;
  PROPID cyclProp = NCoderPropID::kMatchFinderCycles;

  v.vt = VT_UI4;

  // TODO: This probably leaks memory on croaking.

  v.ulVal = algo;
  hr = c->SetCoderProperties(&algoProp, &v, 1);
  if (hr != S_OK)
    croak("Bad algo value");

  v.ulVal = pass;
  hr = c->SetCoderProperties(&passProp, &v, 1);
  if (hr != S_OK)
    croak("Bad pass value");

  v.ulVal = fast;
  hr = c->SetCoderProperties(&fastProp, &v, 1);
  if (hr != S_OK)
    croak("Bad fast value");

  v.ulVal = cycles;
  hr = c->SetCoderProperties(&cyclProp, &v, 1);
  if (hr != S_OK)
    croak("Bad cycles value");
}

SV* internalZlib7(const char* data, size_t len, unsigned int algo, unsigned int pass, unsigned int fast, unsigned int cycles) {

  NCompress::NZlib::CEncoder c;
  CBufInStream* inStream = new CBufInStream;
  CDynBufSeqOutStream* outStream = new CDynBufSeqOutStream;

  inStream->Init((const Byte*)data, len);

  CMyComPtr<ISequentialInStream> in(inStream);
  CMyComPtr<ISequentialOutStream> out(outStream);

  c.Create();

  setCoderProps(c.DeflateEncoderSpec, algo, pass, fast, cycles);

  c.Code(in, out, NULL, NULL, NULL);

  const char* deflated = (const char*)outStream->GetBuffer();
  return newSVpvn(deflated, outStream->GetSize());
}

SV* internalDeflate7(const char* data, size_t len, unsigned int algo, unsigned int pass, unsigned int fast, unsigned int cycles) {

  // TODO: Factor the common code

  NCompress::NDeflate::NEncoder::CCOMCoder c;
  CBufInStream* inStream = new CBufInStream;
  CDynBufSeqOutStream* outStream = new CDynBufSeqOutStream;

  inStream->Init((const Byte*)data, len);

  CMyComPtr<ISequentialInStream> in(inStream);
  CMyComPtr<ISequentialOutStream> out(outStream);

  setCoderProps(&c, algo, pass, fast, cycles);

  c.Code(in, out, NULL, NULL, NULL);

  const char* deflated = (const char*)outStream->GetBuffer();
  return newSVpvn(deflated, outStream->GetSize());
}


MODULE = Compress::Deflate7		PACKAGE = Compress::Deflate7		

void
_zlib7(sv, algo, pass, fb, cycles)
    SV* sv
    unsigned int algo
    unsigned int pass
    unsigned int fb
    unsigned int cycles
  PREINIT:
    STRLEN len;
    char* data;
  PPCODE:
    data = SvPVbyte(sv, len);
    mXPUSHs(internalZlib7(data, len, algo, pass, fb, cycles));

void
_deflate7(sv, algo, pass, fb, cycles)
    SV* sv
    unsigned int algo
    unsigned int pass
    unsigned int fb
    unsigned int cycles
  PREINIT:
    STRLEN len;
    char* data;
  PPCODE:
    data = SvPVbyte(sv, len);
    mXPUSHs(internalDeflate7(data, len, algo, pass, fb, cycles));