/* This file is part of the TDE libraries
   Copyright (C) 2026 Sebastião Guerra <sebastiao.luiz.guerra@gmail.com>

   Based on KXzFilter:
   Copyright (C) 2007-2008 Per Øyvind Karlsen <peroyvind@mandriva.org>

   Based on KBzip2Filter:
   Copyright (C) 2000-2005 David Faure <faure@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "klzipfilter.h"
#include <klibloader.h>

#include <stdint.h>
extern "C" {
#include <lzlib.h>
}

#include <kdebug.h>
#include <tqiodevice.h>

class KLzipFilterFactory : public KLibFactory
{
public:
    KLzipFilterFactory() : KLibFactory() {}
    virtual ~KLzipFilterFactory() {}
    TQObject *createObject( TQObject *, const char *, const char*, const TQStringList & )
    {
        return new KLzipFilter;
    }
};

K_EXPORT_COMPONENT_FACTORY( klzipfilter, KLzipFilterFactory )


class KLzipFilter::Private
{
public:
    Private()
        : encoder( 0 ), decoder( 0 ), nextIn( 0 ), availIn( 0 ),
          nextOut( 0 ), availOut( 0 ), mode( 0 ), initialized( false ),
          inputFinished( false ), finishCalled( false )
    {
    }

    struct LZ_Encoder *encoder;
    struct LZ_Decoder *decoder;
    const uint8_t *nextIn;
    uint availIn;
    uint8_t *nextOut;
    uint availOut;
    int mode;
    bool initialized;
    bool inputFinished;
    bool finishCalled;
};


KLzipFilter::KLzipFilter()
    : d( new Private )
{
}


KLzipFilter::~KLzipFilter()
{
    terminate();
    delete d;
}


void KLzipFilter::init( int mode )
{
    if ( d->initialized || d->encoder || d->decoder )
        terminate();

    d->mode = mode;
    d->nextIn = 0;
    d->availIn = 0;
    d->inputFinished = false;
    d->finishCalled = false;

    if ( mode == IO_ReadOnly )
    {
        d->decoder = LZ_decompress_open();
        d->initialized = d->decoder && LZ_decompress_errno( d->decoder ) == LZ_ok;
        if ( !d->initialized )
            kdWarning(7005) << "Unable to initialize Lzip decoder" << endl;
    }
    else if ( mode == IO_WriteOnly )
    {
        // Match lzip's default level -6: 8 MiB dictionary and match length 36.
        // The 2 PiB member limit is also lzip's default.
        d->encoder = LZ_compress_open( 1 << 23, 36, 1ULL << 51 );
        d->initialized = d->encoder && LZ_compress_errno( d->encoder ) == LZ_ok;
        if ( !d->initialized )
            kdWarning(7005) << "Unable to initialize Lzip encoder" << endl;
    }
    else
    {
        kdWarning(7005) << "KLzipFilter: Unsupported mode " << mode
                        << ". Only IO_ReadOnly and IO_WriteOnly supported" << endl;
    }
}


int KLzipFilter::mode() const
{
    return d->mode;
}


void KLzipFilter::terminate()
{
    if ( d->encoder )
    {
        LZ_compress_close( d->encoder );
        d->encoder = 0;
    }
    if ( d->decoder )
    {
        LZ_decompress_close( d->decoder );
        d->decoder = 0;
    }

    d->nextIn = 0;
    d->availIn = 0;
    d->initialized = false;
    d->inputFinished = false;
    d->finishCalled = false;
}


void KLzipFilter::reset()
{
    const int oldMode = d->mode;
    terminate();
    init( oldMode );
}


void KLzipFilter::setOutBuffer( char * data, uint maxlen )
{
    d->nextOut = reinterpret_cast<uint8_t *>( data );
    d->availOut = maxlen;
}


void KLzipFilter::setInBuffer( const char * data, uint size )
{
    d->nextIn = reinterpret_cast<const uint8_t *>( data );
    d->availIn = size;
}


int KLzipFilter::inBufferAvailable() const
{
    return d->availIn;
}


int KLzipFilter::outBufferAvailable() const
{
    return d->availOut;
}


KLzipFilter::Result KLzipFilter::uncompress()
{
    if ( !d->initialized || !d->decoder )
        return ERROR;

    if ( d->availIn )
    {
        const int written = LZ_decompress_write( d->decoder, d->nextIn,
                                                static_cast<int>( d->availIn ) );
        if ( written < 0 )
        {
            kdWarning(7005) << "LZ_decompress_write failed: "
                            << LZ_strerror( LZ_decompress_errno( d->decoder ) ) << endl;
            return ERROR;
        }
        d->nextIn += written;
        d->availIn -= written;
    }

    // lzlib needs to be told explicitly when no more compressed input
    // will arrive so it can validate the final member and detect
    // truncated input.
    if ( !d->inputFinished &&
         d->availIn == 0 &&
         device() &&
         device()->atEnd() )
    {
        LZ_decompress_finish( d->decoder );
        d->inputFinished = true;
    }

    while ( d->availOut )
    {
        const int received = LZ_decompress_read( d->decoder, d->nextOut,
                                                static_cast<int>( d->availOut ) );
        if ( received < 0 || LZ_decompress_errno( d->decoder ) != LZ_ok )
        {
            kdWarning(7005) << "LZ_decompress_read failed: "
                            << LZ_strerror( LZ_decompress_errno( d->decoder ) ) << endl;
            return ERROR;
        }

        d->nextOut += received;
        d->availOut -= received;

        if ( LZ_decompress_finished( d->decoder ) == 1 )
            return END;

        const bool memberFinished =
            LZ_decompress_member_finished( d->decoder ) == 1;

        // Lzlib deliberately makes LZ_decompress_read() return at least once
        // for each member.  In particular it may return 0 at a member
        // boundary even when another concatenated member is already buffered.
        // Keep reading across that boundary instead of exposing a false EOF to
        // KFilterDev.
        if ( memberFinished )
            continue;

        if ( received == 0 || !d->inputFinished )
            break;
    }

    return OK;
}


KLzipFilter::Result KLzipFilter::compress( bool finish )
{
    if ( !d->initialized || !d->encoder )
        return ERROR;

    if ( d->availIn )
    {
        const int written = LZ_compress_write( d->encoder, d->nextIn,
                                              static_cast<int>( d->availIn ) );
        if ( written < 0 )
        {
            kdWarning(7005) << "LZ_compress_write failed: "
                            << LZ_strerror( LZ_compress_errno( d->encoder ) ) << endl;
            return ERROR;
        }
        d->nextIn += written;
        d->availIn -= written;
    }

    if ( finish && !d->finishCalled && d->availIn == 0 )
    {
        LZ_compress_finish( d->encoder );
        if ( LZ_compress_errno( d->encoder ) != LZ_ok )
        {
            kdWarning(7005) << "LZ_compress_finish failed: "
                            << LZ_strerror( LZ_compress_errno( d->encoder ) ) << endl;
            return ERROR;
        }
        d->finishCalled = true;
    }

    if ( d->availOut )
    {
        const int produced = LZ_compress_read( d->encoder, d->nextOut,
                                              static_cast<int>( d->availOut ) );
        if ( produced < 0 || LZ_compress_errno( d->encoder ) != LZ_ok )
        {
            kdWarning(7005) << "LZ_compress_read failed: "
                            << LZ_strerror( LZ_compress_errno( d->encoder ) ) << endl;
            return ERROR;
        }
        d->nextOut += produced;
        d->availOut -= produced;
    }

    if ( d->finishCalled && LZ_compress_finished( d->encoder ) == 1 )
        return END;

    return OK;
}
