/*
 *  $Id: gwyzip-zzlib.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_ZZLIB_H__
#define __GWY_FILE_ZZLIB_H__

#include <zzip/lib.h>

#undef GWYZIP_HAS_MEMORY_FILES

typedef struct {
    gchar *filename;
    zzip_off_t off;
} GwyZipFilePos;

struct _GwyZipFile {
    ZZIP_DIR *archive;
    ZZIP_DIRENT *current;
    GArray *filepos;
    gchar *tmpfilename;
};

G_GNUC_UNUSED
static GwyZipFile
gwyzip_open(const char *path, GError **error)
{
    struct _GwyZipFile *zipfile;
    ZZIP_DIR *archive;

    if (!(archive = zzip_dir_open(path, NULL))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Zzip");
        return NULL;
    }

    zipfile = g_new0(struct _GwyZipFile, 1);
    zipfile->archive = archive;
    return zipfile;
}

G_GNUC_UNUSED
static void
gwyzip_close(GwyZipFile zipfile)
{
    if (zipfile->filepos) {
        GArray *filepos = zipfile->filepos;
        gsize i;

        for (i = 0; i < filepos->len; i++)
            g_free(g_array_index(filepos, GwyZipFilePos, i).filename);
        g_array_free(filepos, TRUE);
    }
    zzip_dir_close(zipfile->archive);
    if (zipfile->tmpfilename) {
        g_unlink(zipfile->tmpfilename);
        g_free(zipfile->tmpfilename);
    }
    g_free(zipfile);
}

G_GNUC_UNUSED
static void
err_ZIP_NOFILE(GError **error)
{
    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                _("%s error while reading the zip file: %s."), "Zzip", _("End of list of files"));
}

G_GNUC_UNUSED
static void
err_ZIP(GwyZipFile zipfile, GError **error)
{
    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                _("%s error while reading the zip file: %s."), "Zzip", zzip_strerror_of(zipfile->archive));
}

G_GNUC_UNUSED
static gboolean
gwyzip_next_file(GwyZipFile zipfile, GError **error)
{
    if ((zipfile->current = zzip_readdir(zipfile->archive)))
        return TRUE;
    err_ZIP_NOFILE(error);
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_first_file(GwyZipFile zipfile, GError **error)
{
    /* This rewinds ‘before’ the first file. */
    zzip_rewinddir(zipfile->archive);
    /* This actually goes to the first file. */
    return gwyzip_next_file(zipfile, error);
}

G_GNUC_UNUSED
static gboolean
gwyzip_get_current_filename(GwyZipFile zipfile, gchar **filename, GError **error)
{
    if (!zipfile->current) {
        err_ZIP_NOFILE(error);
        *filename = NULL;
        return FALSE;
    }

    /* No idea what is the encoding. */
    *filename = g_strdup(zipfile->current->d_name);
    return TRUE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_locate_file(GwyZipFile zipfile, const gchar *filename, gint casesens, GError **error)
{
    GArray *filepos = zipfile->filepos;
    const GwyZipFilePos *pos;
    zzip_off_t off;
    gsize i;

    /* There is no look-for-file-but-do-not-open-it function and we do not want to open the file for reading here yet.
     * The caller may be just checking if certain files exist. Make our own index. Also the index building is silly
     * because we always need the previous offset, not the current one… */
    if (!filepos) {
        zzip_rewinddir(zipfile->archive);
        off = zzip_telldir(zipfile->archive);
        if (!(zipfile->current = zzip_readdir(zipfile->archive))) {
            err_ZIP_NOFILE(error);
            return FALSE;
        }

        filepos = zipfile->filepos = g_array_new(FALSE, FALSE, sizeof(GwyZipFilePos));
        do {
            GwyZipFilePos newpos;
            newpos.filename = g_strdup(zipfile->current->d_name);
            newpos.off = off;
            off = zzip_telldir(zipfile->archive);
            g_array_append_val(filepos, newpos);
        } while ((zipfile->current = zzip_readdir(zipfile->archive)));
    }

    if (!filepos->len) {
        err_ZIP_NOFILE(error);
        return FALSE;
    }

    for (i = 0; i < filepos->len; i++) {
        pos = &g_array_index(filepos, GwyZipFilePos, i);
        if (casesens) {
            if (gwy_strequal(filename, pos->filename))
                break;
        }
        else {
            /* FIXME: Encoding? Anyone? */
            if (!g_ascii_strcasecmp(filename, pos->filename))
                break;
        }
    }

    if (i == filepos->len) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("File %s is missing in the zip file."), filename);
        return FALSE;
    }

    /* This does not actually do much for us. See the rant below in gwyzip_get_file_content(). */
    zzip_seekdir(zipfile->archive, pos->off);
    if ((zipfile->current = zzip_readdir(zipfile->archive)))
        return TRUE;
    return FALSE;
}

G_GNUC_UNUSED
static guchar*
gwyzip_get_file_content(GwyZipFile zipfile, gsize *contentsize, GError **error)
{
    ZZIP_FILE* fp;
    guchar *buffer;
    gsize decomp_size;

    if (!zipfile->current) {
        err_ZIP_NOFILE(error);
        return NULL;
    }

    decomp_size = zipfile->current->st_size;
    /* XXX: This is rather silly because it scans the archive even though we know exactly which file it is. There is
     * no open-the-bloody-current-file function. So modules going through the archive sequentially – which is normally
     * very efficient – will, unfortunately, pay the quadratic price with Zzip. */
    fp = zzip_file_open(zipfile->archive, zipfile->current->d_name, 0);
    if (!fp) {
        err_ZIP(zipfile, error);
        return NULL;
    }

    gwy_debug("uncompressed_size: %lu", (gulong)decomp_size);
    buffer = g_new(guchar, decomp_size + 1);
    if (zzip_file_read(fp, buffer, decomp_size) != decomp_size) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO, _("Cannot read file contents."));
        zzip_file_close(fp);
        g_free(buffer);
        return NULL;
    }
    zzip_file_close(fp);

    buffer[decomp_size] = '\0';
    if (contentsize)
        *contentsize = decomp_size;
    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 : */
