/*
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * See the COPYING file for license information.
 *
 * Guillaume Chazarain <guichaz@yahoo.fr>
 */

/************************************************
 * Files decompression (images and collections) *
 ************************************************/

#include <unistd.h>             /* read(), pipe(), fork(), execlp() ... */
#include <sys/types.h>          /* pid_t */
#include <signal.h>             /* kill(), SIGTERM */
#include <stdio.h>              /* perror() */
#include <sys/wait.h>           /* waitpid() */
#include <errno.h>              /* errno */

#include "gliv.h"
#include "decompression.h"
#include "collection.h"
#include "loading.h"

typedef struct {
    gchar *ext;
    gchar *cmd;
} decompressor;

/* Read from the child process. */
static GdkPixbuf *read_image(gint fd, GError ** error)
{
    guchar buf[4096];
    GdkPixbuf *pixbuf;
    GdkPixbufLoader *loader;
    gsize size;

    loader = gdk_pixbuf_loader_new();

    while ((size = read(fd, buf, sizeof(buf))) > 0 || (errno == EINTR)) {
        if (size > 0 &&
            gdk_pixbuf_loader_write(loader, buf, size, error) == FALSE)
            return NULL;
    }

    if (gdk_pixbuf_loader_close(loader, error) == FALSE)
        return NULL;

    pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
    if (pixbuf != NULL)
        gdk_pixbuf_ref(pixbuf);

    g_object_unref(loader);
    return pixbuf;
}

/* Exec: "%cmd -c %filename" to send the output to stdout, the parent. */
static void decomp_file(const gchar * cmd, const gchar * filename, gint fd)
{
    if (dup2(fd, STDOUT_FILENO) < 0) {
        perror("dup2");
        return;
    }

    if (execlp(cmd, cmd, "-c", filename, NULL) < 0) {
        perror("execlp");
        return;
    }
}

static gboolean init_decompression(const gchar * filename, gint * fd,
                                   pid_t * pid_ptr)
{
    /* *INDENT-OFF* */
    static decompressor all_decomp[] = {
        { "bz2", "bunzip2"    },
        { "gz",  "gunzip"     },
        { "z",   "uncompress" },
        { NULL,  NULL         }
    };
    /* *INDENT-ON* */

    gint filedes[2];
    pid_t pid;
    decompressor *decomp;
    const gchar *ext;

    /* Find the appropriate decompressor. */
    ext = get_extension(filename);
    if (ext == NULL)
        return FALSE;

    for (decomp = all_decomp; decomp->ext != NULL; decomp++)
        if (!g_ascii_strcasecmp(decomp->ext, ext))
            break;

    if (decomp->ext == NULL)
        /* Decompressor not found. */
        return FALSE;

    if (pipe(filedes) < 0) {
        perror("pipe");
        return FALSE;
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return FALSE;
    }

    if (pid == 0) {
        /* Child */
        close(filedes[0]);
        decomp_file(decomp->cmd, filename, filedes[1]);
        return FALSE;
    }

    /* Parent */
    close(filedes[1]);
    *fd = filedes[0];
    *pid_ptr = pid;
    return TRUE;
}

GdkPixbuf *load_compressed_pixbuf(const gchar * filename, GError ** error)
{
    gint fd;
    pid_t pid;
    GdkPixbuf *pixbuf;

    if (init_decompression(filename, &fd, &pid) == FALSE)
        return NULL;

    pixbuf = read_image(fd, error);
    close(fd);
    waitpid(pid, NULL, 0);

    return pixbuf;
}

gint load_compressed_collection(const gchar * filename)
{
    gint fd;
    pid_t pid;
    gint nb_inserted;
    FILE *file;

    if (init_decompression(filename, &fd, &pid) == FALSE)
        return 0;

    file = fdopen(fd, "r");
    if (file == NULL) {
        perror(filename);
        kill(pid, SIGTERM);
        nb_inserted = 0;
    } else
        nb_inserted = load_dot_gliv_from_file(file);

    fclose(file);
    waitpid(pid, NULL, 0);

    return nb_inserted;
}
