/*
 * Python Bindings for LZMA
 *
 * Copyright (c) 2004-2006 by Joachim Bauch, mail@joachim-bauch.de
 * 7-Zip Copyright (C) 1999-2005 Igor Pavlov
 * LZMA SDK Copyright (C) 1999-2005 Igor Pavlov
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * $Id: pylzma_compressfile.cpp 104 2006-01-08 18:17:14Z jojo $
 *
 */

#include <Python.h>
#include <cStringIO.h>
#include <7zip/Common/MyWindows.h>
#include <7zip/7zip/IStream.h>
#include <7zip/7zip/Compress/LZMA/LZMAEncoder.h>

#include "pylzma.h"
#include "pylzma_streams.h"
#include "pylzma_compress.h"
#include "pylzma_compressfile.h"

int set_encoder_properties(NCompress::NLZMA::CEncoder *encoder, int dictionary, int posBits,
    int literalContextBits, int literalPosBits, int algorithm, int fastBytes, int eos,
    int multithreading, const char *matchfinder);

typedef struct {
    PyObject_HEAD
    NCompress::NLZMA::CEncoder *encoder;
    CInStream *inStream;
    COutStream *outStream;
    PyObject *inFile;
} CCompressionFileObject;

static char *doc_compfile_read = \
    "docstring is todo\n";

static PyObject *pylzma_compfile_read(CCompressionFileObject *self, PyObject *args)
{
    PyObject *result = NULL;
    int length, bufsize=0;
    
    if (!PyArg_ParseTuple(args, "|l", &bufsize))
        return NULL;
    
    while (!bufsize || self->outStream->getMaxRead() < bufsize)
    {
        UInt64 processedInSize;
        UInt64 processedOutSize;
        INT32 finished;
        
        Py_BEGIN_ALLOW_THREADS
        self->encoder->CodeOneBlock(&processedInSize, &processedOutSize, &finished);
        Py_END_ALLOW_THREADS
        if (finished)
            break;
    }
    
    if (bufsize)
        length = min(bufsize, self->outStream->getMaxRead());
    else
        length = self->outStream->getMaxRead();
    result = PyString_FromStringAndSize((const char *)self->outStream->getReadPtr(), length);
    if (result == NULL) {
        PyErr_NoMemory();
        goto exit;
    }
    self->outStream->increaseReadPos(length);
    
exit:
    return result;
}

static PyMethodDef pylzma_compfile_methods[] = {
    {"read",   (PyCFunction)pylzma_compfile_read, METH_VARARGS, doc_compfile_read},
    {NULL, NULL},
};

static void pylzma_compfile_dealloc(CCompressionFileObject *self)
{
    DEC_AND_NULL(self->inFile);
    DELETE_AND_NULL(self->encoder);
    DELETE_AND_NULL(self->inStream);
    self->ob_type->tp_free((PyObject*)self);
}

#ifdef __cplusplus
extern "C" {
#endif

static PyObject* pylzma_compfile_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CCompressionFileObject *self;
    
    self = (CCompressionFileObject*)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->encoder = NULL;
        self->inStream = NULL;
        self->outStream = NULL;
        self->inFile = NULL;
    }
    
    return (PyObject*)self;
}

int pylzma_compfile_init(CCompressionFileObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *inFile;
    NCompress::NLZMA::CEncoder *encoder;
    int result = -1;
    
    // possible keywords for this function
    static char *kwlist[] = {"infile", "dictionary", "fastBytes", "literalContextBits",
                             "literalPosBits", "posBits", "algorithm", "eos", "multithreading", "matchfinder", NULL};
    int dictionary = 23;         // [0,28], default 23 (8MB)
    int fastBytes = 128;         // [5,255], default 128
    int literalContextBits = 3;  // [0,8], default 3
    int literalPosBits = 0;      // [0,4], default 0
    int posBits = 2;             // [0,4], default 2
    int eos = 1;                 // write "end of stream" marker?
    int multithreading = 1;      // use multithreading if available?
    char *matchfinder = "bt4";   // matchfinder algorithm
    int algorithm = 2;
    int res;
    
    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|iiiiiiiis", kwlist, &inFile, &dictionary, &fastBytes,
                                                                 &literalContextBits, &literalPosBits, &posBits, &algorithm, &eos, &multithreading, &matchfinder))
        return -1;
    
    CHECK_RANGE(dictionary,         0,  28, "dictionary must be between 0 and 28");
    CHECK_RANGE(fastBytes,          5, 255, "fastBytes must be between 5 and 255");
    CHECK_RANGE(literalContextBits, 0,   8, "literalContextBits must be between 0 and 8");
    CHECK_RANGE(literalPosBits,     0,   4, "literalPosBits must be between 0 and 4");
    CHECK_RANGE(posBits,            0,   4, "posBits must be between 0 and 4");
    CHECK_RANGE(algorithm,          0,   2, "algorithm must be between 0 and 2");
    
    if (PyString_Check(inFile)) {
        // create new cStringIO object from string
        inFile = PycStringIO->NewInput(inFile);
        if (inFile == NULL)
        {
            PyErr_NoMemory();
            return -1;
        }
    } else if (!PyObject_HasAttrString(inFile, "read")) {
        PyErr_SetString(PyExc_ValueError, "first parameter must be a file-like object");
        return -1;
    } else
        // protect object from being refcounted out...
        Py_XINCREF(inFile);
    
    encoder = new NCompress::NLZMA::CEncoder();
    CHECK_NULL(encoder);
    
    if ((res = set_encoder_properties(encoder, dictionary, posBits, literalContextBits, literalPosBits, algorithm, fastBytes, eos, multithreading, matchfinder) != 0))
    {
        Py_XDECREF(inFile);
        DELETE_AND_NULL(encoder);
        PyErr_SetString(PyExc_TypeError, "can't set coder properties");
        goto exit;
    }
    
    self->inFile = inFile;
    self->encoder = encoder;
    self->inStream = new CInStream(inFile);
    if (self->inStream == NULL)
    {
        DEC_AND_NULL(self->inFile);
        DELETE_AND_NULL(encoder);
        DEC_AND_NULL(self);
        PyErr_NoMemory();
        goto exit;
    }
    self->outStream = new COutStream();
    if (self->outStream == NULL)
    {
        DEC_AND_NULL(self->inFile);
        DELETE_AND_NULL(encoder);
        DELETE_AND_NULL(self->inStream);
        DEC_AND_NULL(self);
        PyErr_NoMemory();
        goto exit;
    }

    Py_BEGIN_ALLOW_THREADS
    encoder->SetStreams(self->inStream, self->outStream, 0, 0);
    encoder->WriteCoderProperties(self->outStream);
    Py_END_ALLOW_THREADS
    result = 0;
    
exit:
    return result;
}

#ifdef __cplusplus
}
#endif

PyTypeObject CCompressionFileObject_Type = {
    //PyObject_HEAD_INIT(&PyType_Type)
    PyObject_HEAD_INIT(NULL)
    0,
    "LZMACompressFile",                  /* char *tp_name; */
    sizeof(CCompressionFileObject),      /* int tp_basicsize; */
    0,                                   /* int tp_itemsize;       // not used much */
    (destructor)pylzma_compfile_dealloc, /* destructor tp_dealloc; */
    NULL,                                /* printfunc  tp_print;   */
    NULL,                                /* getattrfunc  tp_getattr; // __getattr__ */
    NULL,                                /* setattrfunc  tp_setattr;  // __setattr__ */
    NULL,                                /* cmpfunc  tp_compare;  // __cmp__ */
    NULL,                                /* reprfunc  tp_repr;    // __repr__ */
    NULL,                                /* PyNumberMethods *tp_as_number; */
    NULL,                                /* PySequenceMethods *tp_as_sequence; */
    NULL,                                /* PyMappingMethods *tp_as_mapping; */
    NULL,                                /* hashfunc tp_hash;     // __hash__ */
    NULL,                                /* ternaryfunc tp_call;  // __call__ */
    NULL,                                /* reprfunc tp_str;      // __str__ */
    0,                                   /* tp_getattro*/
    0,                                   /* tp_setattro*/
    0,                                   /* tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,  /*tp_flags*/
    "File compression class",           /* tp_doc */
    0,                                   /* tp_traverse */
    0,                                   /* tp_clear */
    0,                                   /* tp_richcompare */
    0,                                   /* tp_weaklistoffset */
    0,                                   /* tp_iter */
    0,                                   /* tp_iternext */
    pylzma_compfile_methods,             /* tp_methods */
    0,                                   /* tp_members */
    0,                                   /* tp_getset */
    0,                                   /* tp_base */
    0,                                   /* tp_dict */
    0,                                   /* tp_descr_get */
    0,                                   /* tp_descr_set */
    0,                                   /* tp_dictoffset */
    (initproc)pylzma_compfile_init,      /* tp_init */
    0,                                   /* tp_alloc */
    pylzma_compfile_new,                 /* tp_new */
};
