/**
 * @file compress.h
 * Heatshrink (LZSS) / Gzip based compression HAL API
 */
#pragma once

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/** Compress Icon control structure */
typedef struct CompressIcon CompressIcon;

/** Initialize icon compressor
 *
 * @param[in]  decode_buf_size  The icon buffer size for decoding. Ensure that
 *                              it's big enough for any icons that you are
 *                              planning to decode with it.
 *
 * @return     Compress Icon instance
 */
CompressIcon* compress_icon_alloc(size_t decode_buf_size);

/** Free icon compressor
 *
 * @param      instance  The Compress Icon instance
 */
void compress_icon_free(CompressIcon* instance);

/** Decompress icon
 *
 * @warning    output pointer set by this function is valid till next
 *             `compress_icon_decode` or `compress_icon_free` call
 *
 * @param      instance   The Compress Icon instance
 * @param      icon_data  pointer to icon data.
 * @param[in]  output     pointer to decoded buffer pointer. Data in buffer is
 *                        valid till next call. If icon data was not compressed,
 *                        pointer within icon_data is returned
 */
void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output);

//////////////////////////////////////////////////////////////////////////

/** Compress control structure */
typedef struct Compress Compress;

/** Supported compression types */
typedef enum {
    CompressTypeHeatshrink = 0,
    CompressTypeGzip = 1,
    CompressTypeMAX,
} CompressType;

/** Configuration for heatshrink compression */
typedef struct {
    uint16_t window_sz2;
    uint16_t lookahead_sz2;
    uint16_t input_buffer_sz;
} CompressConfigHeatshrink;

/** Default configuration for heatshrink compression. Used for image assets. */
extern const CompressConfigHeatshrink compress_config_heatshrink_default;

/** Configuration for gzip compression */
typedef struct {
    uint32_t dict_sz;
    uint16_t input_buffer_sz;
} CompressConfigGzip;

/** Allocate encoder and decoder
 *
 * @param      type     Compression type
 * @param[in]  config   Configuration for compression, specific to type
 *
 * @return     Compress instance
 */
Compress* compress_alloc(CompressType type, const void* config);

/** Free encoder and decoder
 *
 * @param      compress  Compress instance
 */
void compress_free(Compress* compress);

/** Encode data
 *
 * @param      compress       Compress instance
 * @param      data_in        pointer to input data
 * @param      data_in_size   size of input data
 * @param      data_out       maximum size of output data
 * @param[in]  data_out_size  The data out size
 * @param      data_res_size  pointer to result output data size
 *
 * @note       Prepends compressed stream with a header. If data is not compressible,
 *             it will be stored as is after the header.
 * @return     true on success
 */
bool compress_encode(
    Compress* compress,
    uint8_t* data_in,
    size_t data_in_size,
    uint8_t* data_out,
    size_t data_out_size,
    size_t* data_res_size);

/** Decode data
 *
 * @param      compress       Compress instance
 * @param      data_in        pointer to input data
 * @param      data_in_size   size of input data
 * @param      data_out       maximum size of output data
 * @param[in]  data_out_size  The data out size
 * @param      data_res_size  pointer to result output data size
 *
 * @note       Expects compressed stream with a header, as produced by `compress_encode`.
 * @return     true on success
 */
bool compress_decode(
    Compress* compress,
    uint8_t* data_in,
    size_t data_in_size,
    uint8_t* data_out,
    size_t data_out_size,
    size_t* data_res_size);

/** I/O callback for streamed compression/decompression
 * 
 * @param context user context
 * @param buffer buffer to read/write
 * @param size size of buffer
 * 
 * @return number of bytes read/written, 0 on end of stream, negative on error
 */
typedef int32_t (*CompressIoCallback)(void* context, uint8_t* buffer, size_t size);

/** Decompress streamed data
 *
 * @param      compress       Compress instance
 * @param      read_cb        read callback
 * @param      read_context   read callback context
 * @param      write_cb       write callback
 * @param      write_context  write callback context
 *
 * @note       Does not expect a header, just compressed data stream.
 * @return     true on success
 */
bool compress_decode_streamed(
    Compress* compress,
    CompressIoCallback read_cb,
    void* read_context,
    CompressIoCallback write_cb,
    void* write_context);

//////////////////////////////////////////////////////////////////////////

/** CompressStreamDecoder control structure */
typedef struct CompressStreamDecoder CompressStreamDecoder;

/** Allocate stream decoder
 *
 * @param      type          Compression type
 * @param[in]  config        Configuration for compression, specific to type
 * @param      read_cb       The read callback for input (compressed) data
 * @param      read_context  The read context
 *
 * @return     CompressStreamDecoder instance
 */
CompressStreamDecoder* compress_stream_decoder_alloc(
    CompressType type,
    const void* config,
    CompressIoCallback read_cb,
    void* read_context);

/** Free stream decoder
 *
 * @param      instance  The CompressStreamDecoder instance
 */
void compress_stream_decoder_free(CompressStreamDecoder* instance);

/** Read uncompressed data chunk from stream decoder
 *
 * @param      instance       The CompressStreamDecoder instance
 * @param      data_out       The data out
 * @param[in]  data_out_size  The data out size
 *
 * @return     true on success
 */
bool compress_stream_decoder_read(
    CompressStreamDecoder* instance,
    uint8_t* data_out,
    size_t data_out_size);

/** Seek to position in uncompressed data stream
 *
 * @param      instance   The CompressStreamDecoder instance
 * @param[in]  position   The position
 * 
 * @return     true on success
 * @warning    Backward seeking is not supported
 */
bool compress_stream_decoder_seek(CompressStreamDecoder* instance, size_t position);

/** Get current position in uncompressed data stream
 *
 * @param      instance  The CompressStreamDecoder instance
 *
 * @return     current position
 */
size_t compress_stream_decoder_tell(CompressStreamDecoder* instance);

/** Reset stream decoder to the beginning
 * @warning    Read callback must be repositioned by caller separately
 *
 * @param      instance  The CompressStreamDecoder instance
 *
 * @return     true on success
 */
bool compress_stream_decoder_rewind(CompressStreamDecoder* instance);

#ifdef __cplusplus
}
#endif
