/*
 *  $Id: gwyzip-minizip2.h 29403 2026-01-29 17:37:40Z yeti-dn $
 *  Copyright (C) 2015-2024 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Do not include this file directly, let gwyzip.h include it as needed! */

#ifndef __GWY_FILE_MINIZIP2_H__
#define __GWY_FILE_MINIZIP2_H__

/* This either includes stdint.h or replicates its typedefs. */
#if defined(HAVE_MINIZIP_MZ_H)
#include <minizip/mz.h>
#include <minizip/mz_zip.h>
#include <minizip/mz_strm.h>
#include <minizip/mz_strm_mem.h>
#elif defined(HAVE_MZ_H)
#include <mz.h>
#include <mz_zip.h>
#include <mz_strm.h>
#include <mz_strm_mem.h>
#endif
/* If we have neither things break, but then I looked everywhere and I still have no fucking idea where minizip put
 * its headers this time. */

#define GWYZIP_HAS_MEMORY_FILES 1

struct _GwyZipFile {
    GMappedFile *mfile;
    void *mem_stream;
    void *zip_handle;
};

G_GNUC_UNUSED
static void
gwyzip_minizip_free(GwyZipFile zipfile)
{
    if (zipfile->zip_handle) {
        mz_zip_close(zipfile->zip_handle);
        mz_zip_delete(&zipfile->zip_handle);
    }
    if (zipfile->mem_stream)
        mz_stream_mem_delete(&zipfile->mem_stream);
    if (zipfile->mfile)
        g_mapped_file_free(zipfile->mfile);
    g_free(zipfile);
}

G_GNUC_UNUSED
static void
err_MINIZIP(int32_t status, GError **error)
{
    gchar buf[12];

    g_snprintf(buf, sizeof(buf), "%d", -status);
    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                _("%s error while reading the zip file: %s."), "Minizip", buf);
}

G_GNUC_UNUSED
static GwyZipFile
gwyzip_open_memory(const guchar *data, gsize size, GError **error)
{
    struct _GwyZipFile *zipfile;
    GError *err = NULL;
    int32_t status;

    zipfile = g_new0(struct _GwyZipFile, 1);

    mz_zip_create(&zipfile->zip_handle);
    mz_stream_mem_create(&zipfile->mem_stream);
    mz_stream_mem_set_buffer(zipfile->mem_stream, data, size);
    mz_stream_open(zipfile->mem_stream, NULL, MZ_OPEN_MODE_READ);
    status = mz_zip_open(zipfile->zip_handle, zipfile->mem_stream, MZ_OPEN_MODE_READ);
    if (status != MZ_OK) {
        err_MINIZIP(status, error);
        gwyzip_minizip_free(zipfile);
        return NULL;
    }

    return zipfile;
}

/* We do not want minizip doing I/O itself, in particular in MS Windows.  Just mmap the input file and give the
 * library a memory stream... */
G_GNUC_UNUSED
static GwyZipFile
gwyzip_open(const gchar *path, GError **error)
{
    struct _GwyZipFile *zipfile;
    GMappedFile *mfile;
    GError *err = NULL;

    if (!(mfile = g_mapped_file_new(path, FALSE, &err))) {
        /* err_GET_FILE_CONTENTS() */
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("Cannot read file contents: %s"), err->message);
        g_clear_error(&err);
        g_mapped_file_free(mfile);
        return NULL;
    }

    if (!(zipfile = gwyzip_open_memory(g_mapped_file_get_contents(mfile), g_mapped_file_get_length(mfile), error))) {
        g_mapped_file_free(mfile);
        return NULL;
    }

    zipfile->mfile = mfile;
    return zipfile;
}

G_GNUC_UNUSED
static void
gwyzip_close(GwyZipFile zipfile)
{
    gwyzip_minizip_free(zipfile);
}

G_GNUC_UNUSED
static gboolean
gwyzip_next_file(GwyZipFile zipfile, GError **error)
{
    int32_t status;
    if ((status = mz_zip_goto_next_entry(zipfile->zip_handle)) == MZ_OK)
        return TRUE;
    err_MINIZIP(status, error);
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_first_file(GwyZipFile zipfile, GError **error)
{
    int32_t status;
    if ((status = mz_zip_goto_first_entry(zipfile->zip_handle)) == MZ_OK)
        return TRUE;
    err_MINIZIP(status, error);
    return FALSE;
}

/* Pass non-NULL for the attributes you are interested in...
 * XXX: I am not sure if file_info is guaranteed to survive closing the entry, so I am not going to try using it
 * afterwards.  */
G_GNUC_UNUSED
static gboolean
gwyminizip_get_common_file_info(GwyZipFile zipfile, gchar **filename, gsize *uncomp_size, GError **error)
{
    mz_zip_file *file_info = NULL;
    int32_t status;

    status = mz_zip_entry_read_open(zipfile->zip_handle, 0, NULL);
    if (status != MZ_OK) {
        err_MINIZIP(status, error);
        return FALSE;
    }

    status = mz_zip_entry_get_info(zipfile->zip_handle, &file_info);
    if (status != MZ_OK) {
        err_MINIZIP(status, error);
        mz_zip_entry_close(zipfile->zip_handle);
        return FALSE;
    }

    if (filename)
        *filename = g_strdup(file_info->filename);
    if (uncomp_size)
        *uncomp_size = file_info->uncompressed_size;

    /* No minizip code example seems to free file_info in any manner.  So hopefully it points to static storage... */

    return TRUE;
}

/* This function returns the file name as UTF-8.  The others probably don't. */
G_GNUC_UNUSED
static gboolean
gwyzip_get_current_filename(GwyZipFile zipfile, gchar **filename, GError **error)
{
    gchar *fnm;

    if (gwyminizip_get_common_file_info(zipfile, &fnm, NULL, error)) {
        *filename = fnm;
        return TRUE;
    }
    *filename = NULL;
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_locate_file(GwyZipFile zipfile, const gchar *filename, gint casesens, GError **error)
{
    int32_t status;

    /* Negate the last argument; it is called ignore_case. */
    status = mz_zip_locate_entry(zipfile->zip_handle, filename, !casesens);
    if (status != MZ_OK) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("File %s is missing in the zip file."), filename);
        return FALSE;
    }
    return TRUE;
}

G_GNUC_UNUSED
static guchar*
gwyzip_get_file_content(GwyZipFile zipfile, gsize *contentsize, GError **error)
{
    gsize uncsize, remsize;
    guchar *buffer;
    int32_t status;

    if (!gwyminizip_get_common_file_info(zipfile, NULL, &uncsize, error))
        return NULL;

    status = mz_zip_entry_read_open(zipfile->zip_handle, 0, NULL);
    if (status != MZ_OK) {
        err_MINIZIP(status, error);
        return NULL;
    }

    buffer = g_new(guchar, uncsize + 1);
    remsize = uncsize;
    while (remsize) {
        int32_t bytes_to_read = (remsize > (guint)G_MAXINT32 ? G_MAXINT32 : remsize);
        status = mz_zip_entry_read(zipfile->zip_handle, buffer + (uncsize - remsize), bytes_to_read);
        /* When positive, the return value it is the number of bytes read. Otherwise an error code?  Anyway, do not
         * accept zero. */
        if (status <= 0) {
            err_MINIZIP(status, error);
            g_free(buffer);
            return NULL;
        }
        if (status > bytes_to_read) {
            /* XXX: What to do when the library does this? */
            err_MINIZIP(MZ_INTERNAL_ERROR, error);
            g_free(buffer);
            return NULL;
        }
        /* We read at least one byte.  So proceed. */
        remsize -= status;
    }
    buffer[uncsize] = '\0';
    if (contentsize)
        *contentsize = uncsize;

    mz_zip_entry_close(zipfile->zip_handle);

    return buffer;
}

#endif

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
