/*
 * Copyright (C) Jan 2006 Mellanox Technologies Ltd. All rights reserved.
 * Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "txz.h"

#include <libtar.h>

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/param.h>
#include <string.h>

#include <lzma.h>

tbuf_t* xzopen_frontend(char* pathname, int oflags, int mode);
int xzread(tbuf_t* tbuf, void* buf, size_t len);
int xzwrite(tbuf_t* tbuf, const void* buf, size_t len);

void xz_init() {}

uint32_t xz_io_crc32(const uint8_t* buf, size_t size, uint32_t crc)
{
    return lzma_crc32(buf, size, crc);
}

void* xz_decoder_init(int* status_code)
{
    lzma_stream* strm;
    lzma_stream tmp = LZMA_STREAM_INIT;
    *status_code = 0;

    strm = (lzma_stream*)malloc(sizeof(lzma_stream));
    if (strm == NULL)
    {
        *status_code = _ERR(TXZ_ERR_MEM_ALLOC);
        return NULL;
    }

    *strm = tmp;

    if (init_decoder(strm))
    {
        // Decoder initialization failed. There's no point
        // to retry it so we need to exit.
        *status_code = _ERR(TXZ_ERR_DECODER_INIT);
        goto err_clean_up;
    }
    strm->next_in = NULL;
    strm->avail_in = 0;
    return strm;

err_clean_up:
    free(strm);
    return NULL;
}

void xz_close(void* strm)
{
    lzma_end((lzma_stream*)strm);
}

int init_decoder(lzma_stream* strm)
{
    lzma_ret ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED);

    if (ret == LZMA_OK)
    {
        return 0;
    }

    const char* msg;
    switch (ret)
    {
        case LZMA_MEM_ERROR:
            msg = "Memory allocation failed";
            break;

        case LZMA_OPTIONS_ERROR:
            msg = "Unsupported decompressor flags";
            break;

        default:
            msg = "Unknown error, possibly a bug";
            break;
    }

    printf("Error initializing the decoder: %s (error code %u)\n", msg, ret);
    return -1;
}

int lzma_code_ret(lzma_ret ret);

int xzread(tbuf_t* tbuf, void* buf, size_t len)
{
    lzma_stream* strm;
    lzma_action action = LZMA_RUN;

    strm = (lzma_stream*)tbuf->envptr;
    strm->next_out = (uint8_t*)buf;
    strm->avail_out = len;

    while (1)
    {
        if (strm->avail_in == 0 && action != LZMA_FINISH)
        {
            int chunksz = tbuf->size - tbuf->pos;
            if (chunksz > BUFSIZ)
            {
                chunksz = BUFSIZ;
            }

            strm->next_in = &tbuf->buf[tbuf->pos];
            tbuf->pos += chunksz;

            strm->avail_in = chunksz;

            // Once the end of the input file has been reached,
            // we need to tell lzma_code() that no more input
            // will be coming. As said before, this isn't required
            // if the LZMA_CONATENATED flag isn't used when
            // initializing the decoder.
            if (!strm->avail_in)
            {
                action = LZMA_FINISH;
            }
        }

        lzma_ret ret = lzma_code(strm, action);
        if (lzma_code_ret(ret))
        {
            return -1;
        }

        if (strm->avail_out == 0 || ret == LZMA_STREAM_END)
        {
            size_t write_size = len - strm->avail_out;
            return write_size;
        }
    }
    return 0;
}

tbuf_t* xzopen_frontend(char* pathname, int oflags, int mode)
{
    if (pathname || oflags || mode)
    { // omits compiler warnings
        return NULL;
    }
    return NULL;
}

int xzclose(tbuf_t* tbuf)
{
    if (tbuf)
    { // omits compiler warnings
        return 0;
    }
    return 0;
}

int xzwrite(tbuf_t* tbuf, const void* buf, size_t len)
{
    if (tbuf || buf || len)
    { // omits compiler warnings
        return 0;
    }
    return 0;
}

int lzma_code_ret(lzma_ret ret)
{
    if (ret != LZMA_OK)
    {
        // Once everything has been decoded successfully, the
        // return value of lzma_code() will be LZMA_STREAM_END.
        //
        // It is important to check for LZMA_STREAM_END. Do not
        // assume that getting ret != LZMA_OK would mean that
        // everything has gone well or that when you aren't
        // getting more output it must have successfully
        // decoded everything.
        if (ret == LZMA_STREAM_END)
        {
            return 0;
        }

        // It's not LZMA_OK nor LZMA_STREAM_END,
        // so it must be an error code. See lzma/base.h
        // (src/liblzma/api/lzma/base.h in the source package
        // or e.g. /usr/include/lzma/base.h depending on the
        // install prefix) for the list and documentation of
        // possible values. Many values listen in lzma_ret
        // enumeration aren't possible in this example, but
        // can be made possible by enabling memory usage limit
        // or adding flags to the decoder initialization.
        const char* msg;
        switch (ret)
        {
            case LZMA_MEM_ERROR:
                msg = "Memory allocation failed";
                break;

            case LZMA_FORMAT_ERROR:
                // .xz magic bytes weren't found.
                msg = "The input is not in the .xz format";
                break;

            case LZMA_OPTIONS_ERROR:
                // For example, the headers specify a filter
                // that isn't supported by this liblzma
                // version (or it hasn't been enabled when
                // building liblzma, but no-one sane does
                // that unless building liblzma for an
                // embedded system). Upgrading to a newer
                // liblzma might help.
                //
                // Note that it is unlikely that the file has
                // accidentally became corrupt if you get this
                // error. The integrity of the .xz headers is
                // always verified with a CRC32, so
                // unintentionally corrupt files can be
                // distinguished from unsupported files.
                msg = "Unsupported compression options";
                break;

            case LZMA_DATA_ERROR:
                msg = "Compressed file is corrupt";
                break;

            case LZMA_BUF_ERROR:
                // Typically this error means that a valid
                // file has got truncated, but it might also
                // be a damaged part in the file that makes
                // the decoder think the file is truncated.
                // If you prefer, you can use the same error
                // message for this as for LZMA_DATA_ERROR.
                msg = "Compressed file is truncated or "
                      "otherwise corrupt";
                break;

            default:
                // This is most likely LZMA_PROG_ERROR.
                msg = "Unknown error, possibly a bug";
                break;
        }

        printf("Decoder error: "
               "%s (error code %u)\n",
               msg, ret);
        return -1;
    }
    return 0;
}
