/*
 * This file is part of the Aaru Data Preservation Suite.
 * Copyright (c) 2019-2025 Natalia Portillo.
 * Copyright © 2018-2019 David Ryskalczyk
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "../library.h"

#define SPEOF   256  // Special end-of-file token.
#define NUMVALS 257  // Number of values in the Huffman tree (256 chars + SPEOF).

// Node structure for the Huffman decoding tree.
struct nd
{
    int child[2];  // Children of the node.
};

// Static variables for the decompression state.
static struct nd nodes[NUMVALS];  // The Huffman tree.
static int       numnodes;        // Number of nodes in the tree.

static int           bpos;   // Bit position in the current byte.
static unsigned char curin;  // Current byte being read.

// Pointers for buffer management.
static const unsigned char *in_buf_ptr;
static size_t               in_len_rem;
static unsigned char       *out_buf_ptr;
static size_t               out_len_rem;

// Reads a byte from the input buffer.
static int get_byte()
{
    if(in_len_rem == 0) { return EOF; }
    in_len_rem--;
    return *in_buf_ptr++;
}

static int arc_decompress_huffman(const unsigned char *in_buf, size_t in_len, unsigned char *out_buf, size_t *out_len)
{
    // Basic validation of pointers.
    if(!in_buf || !out_buf || !out_len) { return -1; }

    // Initialize buffer pointers and lengths.
    in_buf_ptr  = in_buf;
    in_len_rem  = in_len;
    out_buf_ptr = out_buf;
    out_len_rem = *out_len;

    bpos = 99;  // Force initial read.

    // Read the number of nodes in the Huffman tree.
    if(in_len_rem < 2) return -1;
    numnodes = get_byte();
    numnodes |= get_byte() << 8;

    if(numnodes < 0 || numnodes >= NUMVALS)
    {
        return -1;  // Invalid tree.
    }

    // ARC: initialize for possible empty tree (SPEOF only)
    nodes[0].child[0] = -(SPEOF + 1);
    nodes[0].child[1] = -(SPEOF + 1);

    // Read the Huffman tree from the input buffer, sign-extend 16-bit values
    for(int i = 0; i < numnodes; ++i)
    {
        if(in_len_rem < 4) return -1;
        uint8_t b0        = get_byte();
        uint8_t b1        = get_byte();
        uint8_t b2        = get_byte();
        uint8_t b3        = get_byte();
        nodes[i].child[0] = (int16_t)((b0) | (b1 << 8));
        nodes[i].child[1] = (int16_t)((b2) | (b3 << 8));
    }

    size_t written = 0;
    // bpos is already 99 from init

    while(written < *out_len)
    {
        int i = 0;
        // follow bit stream in tree to a leaf
        while(i >= 0)
        {
            if(++bpos > 7)
            {
                int c = get_byte();
                if(c == EOF)
                {
                    *out_len = written;
                    return 0;  // End of input
                }
                curin = c;
                bpos  = 0;
                // move a level deeper in tree
                i     = nodes[i].child[curin & 1];
            }
            else { i = nodes[i].child[1 & (curin >>= 1)]; }
        }

        // decode fake node index to original data value
        int value = -(i + 1);

        if(value == SPEOF)
        {
            break;  // End of data
        }

        *out_buf_ptr++ = value;
        written++;
    }

    *out_len = written;
    return 0;
}

// Decompresses data using Huffman squeezing.
AARU_EXPORT int AARU_CALL arc_decompress_squeeze(const unsigned char *in_buf, size_t in_len, unsigned char *out_buf,
                                                 size_t *out_len)
{
    size_t         temp_len = *out_len * 2;
    unsigned char *temp_buf = malloc(temp_len);
    if(!temp_buf) return -1;

    int result = arc_decompress_huffman(in_buf, in_len, temp_buf, &temp_len);
    if(result == 0) { result = arc_decompress_pack(temp_buf, temp_len, out_buf, out_len); }

    free(temp_buf);
    return result;
}
