#include "config.h"

#include "flatpak-zstd-decompressor-private.h"

#include <errno.h>
#include <string.h>
#ifdef HAVE_ZSTD
#include <zstd.h>
#endif

static void flatpak_zstd_decompressor_iface_init  (GConverterIface *iface);

struct _FlatpakZstdDecompressor
{
  GObject parent_instance;

#ifdef HAVE_ZSTD
  ZSTD_DStream *dstream;
#endif
};

G_DEFINE_TYPE_WITH_CODE (FlatpakZstdDecompressor, flatpak_zstd_decompressor, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
                                                flatpak_zstd_decompressor_iface_init))

static void
flatpak_zstd_decompressor_finalize (GObject *object)
{
#ifdef HAVE_ZSTD
  FlatpakZstdDecompressor *decompressor;

  decompressor = FLATPAK_ZSTD_DECOMPRESSOR (object);

  ZSTD_freeDStream (decompressor->dstream);
#endif

  G_OBJECT_CLASS (flatpak_zstd_decompressor_parent_class)->finalize (object);
}

static void
flatpak_zstd_decompressor_init (FlatpakZstdDecompressor *decompressor)
{
#ifdef HAVE_ZSTD
  decompressor->dstream = ZSTD_createDStream ();
#endif
}

static void
flatpak_zstd_decompressor_class_init (FlatpakZstdDecompressorClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = flatpak_zstd_decompressor_finalize;
}

FlatpakZstdDecompressor *
flatpak_zstd_decompressor_new (void)
{
  FlatpakZstdDecompressor *decompressor;

  decompressor = g_object_new (FLATPAK_TYPE_ZSTD_DECOMPRESSOR, NULL);

  return decompressor;
}

static void
flatpak_zstd_decompressor_reset (GConverter *converter)
{
#ifdef HAVE_ZSTD
  FlatpakZstdDecompressor *decompressor = FLATPAK_ZSTD_DECOMPRESSOR (converter);

  ZSTD_initDStream (decompressor->dstream);
#endif
}

static GConverterResult
flatpak_zstd_decompressor_convert (GConverter *converter,
                                   const void *inbuf,
                                   gsize       inbuf_size,
                                   void       *outbuf,
                                   gsize       outbuf_size,
                                   GConverterFlags flags,
                                   gsize      *bytes_read,
                                   gsize      *bytes_written,
                                   GError    **error)
{
#ifdef HAVE_ZSTD
  FlatpakZstdDecompressor *decompressor;
  ZSTD_inBuffer input = { inbuf, inbuf_size, 0 };
  ZSTD_outBuffer output = {outbuf, outbuf_size, 0 };
  size_t res;

  decompressor = FLATPAK_ZSTD_DECOMPRESSOR (converter);

  if (decompressor->dstream == NULL)
    {
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                           "Failed to initialize libzstd");
      return G_CONVERTER_ERROR;
    }

  res = ZSTD_decompressStream(decompressor->dstream, &output , &input);
  if (ZSTD_isError (res))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                   "Zstd decompression error: %s", ZSTD_getErrorName (res));
      return G_CONVERTER_ERROR;
    }

  *bytes_read = input.pos;
  *bytes_written = output.pos;

  if (res == 0)
    return G_CONVERTER_FINISHED;

  if (input.pos == 0 && output.pos == 0)
    {
      /* Did nothing, need more input? */
      if (flags & G_CONVERTER_INPUT_AT_END)
        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Zstd failed");
      else
        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "Need more zstd data");

      return G_CONVERTER_ERROR;
    }

  return G_CONVERTER_CONVERTED;

#else
  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                       "libzstd not available");
  return G_CONVERTER_ERROR;
#endif
}

static void
flatpak_zstd_decompressor_iface_init (GConverterIface *iface)
{
  iface->convert = flatpak_zstd_decompressor_convert;
  iface->reset = flatpak_zstd_decompressor_reset;
}
