#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_LIBZIP
# include <zip.h>
#endif
#include <glib.h>
#include <unistd.h>

#include "../include/string.h"
#include "../include/prochandle.h"

#include "cfg.h"
#include "endeavour2.h"
#include "edv_archive_cmt.h"
#include "edv_utils.h"
#include "edv_cfg_list.h"
#include "config.h"


/*
 *	Return values legend:
 *
 *	0	Success.
 *	-1	General error.
 *	-2	Invalid value.
 *	-3	Systems error; out of memory or out of disk space.
 *	-4	User responded with "Cancel".
 *	-5	User responded with "No" or response was not available.
 *	-6	Call would cause reentry.
 */


static gchar *G_STRCAT(gchar *s, const gchar *s2);

/* Get Archive Comment */
static gchar *EDVArchCommentGetARJ(
	edv_core_struct *core, const gchar *arch_obj
);
static gchar *EDVArchCommentGetRAR(
	edv_core_struct *core, const gchar *arch_obj
);
static gchar *EDVArchCommentGetZip(
	edv_core_struct *core, const gchar *arch_obj
);
gchar *EDVArchCommentGet(
	edv_core_struct *core, const gchar *arch_obj
);

/* Set Archive Comment */
static void EDVArchCommentSetWriteCommentSpool(
	const gchar *path,
	const gchar *prefix, const gchar *postfix,
	const gchar *comment
);
static gint EDVArchCommentSetARJ(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
);
static gint EDVArchCommentSetRAR(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
);
#ifdef HAVE_LIBZIP   
static struct zip *EDVOpenZipWriting(
	const gchar *arch_obj, gint *zip_error
);
#endif
static gint EDVArchCommentSetZip(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
);
gint EDVArchCommentSet(
	edv_core_struct *core, const gchar *arch_obj,
	gchar **strv, gint strc
);


#define ISCR(c)		(((c) == '\n') || ((c) == '\r'))

#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)

#define UNLINK(p)	(((p) != NULL) ? (gint)unlink((const char *)(p)) : -1)


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}


/*
 *	Gets the archive comment from the ARJ archive.
 */
static gchar *EDVArchCommentGetARJ(
	edv_core_struct *core, const gchar *arch_obj
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path, *comment = NULL;

	/* Format the archive comment output command */
	cmd = g_strdup_printf(
	    "\"%s\" l -y -i \"%s\" nosuchfile",
	    prog_arj, arch_obj
	);
	if(cmd == NULL)
	    return(comment);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the view archive comment command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	if(p <= 0)
	{
	    g_free(cmd);
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(comment);
	}

	/* Open the archive comment file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gboolean got_header = FALSE;
	    gint i;
	    const gchar	*delim_header = "Archive created:",
			*delim_footer = "     0 file(s)";
	    gchar buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) == NULL)
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		{
		    got_header = TRUE;
		    break;
		}
	    }

	    /* Read past header? */
	    if(got_header)
	    {
		/* Begin reading subsequent lines and save each line as
		 * comment lines
		 */
		while(fgets(buf, sizeof(buf), fp) != NULL)
		{
		    buf[sizeof(buf) - 1] = '\0';

		    /* Footer deliminator encountered? */
		    if(strpfx((const char *)buf, (const char *)delim_footer))
			break;

		    /* Add this line to the comment */
		    comment = G_STRCAT(comment, buf);
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(cmd);
	g_free(stdout_path);
	g_free(stderr_path);

	return(comment);
}

/*
 *	Gets the archive comment from the RAR archive.
 */
static gchar *EDVArchCommentGetRAR(
	edv_core_struct *core, const gchar *arch_obj
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	FILE *fp;
	gint p;
	gchar *cmd, *comment_path, *comment = NULL;

	/* Generate a tempory file path for the comment output file */
	comment_path = EDVTmpName(NULL);

	/* Format the view archive comment command */
	cmd = g_strdup_printf(
	    "\"%s\" cw -y -inul \"%s\" \"%s\"",
	    prog_rar, arch_obj, comment_path
	);
	if(cmd == NULL)
	{
	    g_free(comment_path);
	    return(comment);
	}

	/* Execute the view archive comment command */
	p = (gint)ExecB((const char *)cmd);
	if(p <= 0)
	{
	    g_free(cmd);
	    UNLINK(comment_path);
	    g_free(comment_path);
	    return(comment);
	}

	g_free(cmd);

	/* Open the archive comment file for reading */
	fp = fopen((const char *)comment_path, "rb");
	if(fp != NULL)
	{
	    gchar buf[10000];

	    /* Read each line */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		comment = G_STRCAT(comment, buf);
	    }

	    fclose(fp);
	}

	/* Delete the comment output file */
	UNLINK(comment_path);
	g_free(comment_path);

	return(comment);
}

/*
 *	Gets the archive comment from the PKZip archive.
 */
static gchar *EDVArchCommentGetZip(
	edv_core_struct *core, const gchar *arch_obj
)
{
#ifdef HAVE_LIBZIP
	struct zip *archive;
	gint zip_error, comment_len = 0;
	const gchar *comment;
	gchar *dcomment;

	/* Open the PKZip archive for reading */
	archive = zip_open(arch_obj, 0, &zip_error);
	if(archive == NULL)
	    return(NULL);

	/* Get the PKZip archive comment */
	comment = zip_get_archive_comment(
	    archive, &comment_len, 0
	);
	if((comment == NULL) || (comment_len <= 0))
	{
	    zip_close(archive);
	    return(NULL);
	}

	/* Make a copy of the comment and null terminate it */
	dcomment = (gchar *)g_malloc((comment_len + 1) * sizeof(gchar));
	if(dcomment == NULL)
	{
	    zip_close(archive);
	    return(NULL);
	}
	memcpy(dcomment, comment, comment_len * sizeof(gchar));
	dcomment[comment_len] = '\0';

	/* Close the PKZip archive */
	zip_close(archive);

	return(dcomment);
#else
	const gchar *prog_unzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNZIP
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path, *comment = NULL;

	/* Format the view archive comment command */
	cmd = g_strdup_printf(
	    "\"%s\" -z \"%s\"",
	    prog_unzip, arch_obj
	);
	if(cmd == NULL)
	    return(comment);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the view archive comment command */
	p = (gint)ExecBOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    g_free(cmd);
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(comment);
	}

	/* Open the archive comment file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *s2, buf[10000];

	    /* Read past header, skip 1 line */
	    for(i = 0; i < 1; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) == NULL)
		    break;
	    }

	    /* Able to read past the header? */
	    if(i >= 1)
	    {
		/* Begin reading subsequent lines and save each line as
		 * comment lines
		 */
		while(fgets(buf, sizeof(buf), fp) != NULL)
		{
		    buf[sizeof(buf) - 1] = '\0';

		    comment = G_STRCAT(comment, buf);
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(cmd);
	g_free(stdout_path);
	g_free(stderr_path);

	return(comment);
#endif
}

/*
 *	Gets the archive comment from the archive specified by arch_obj.
 *
 *	The calling function must delete the returned string.
 */
gchar *EDVArchCommentGet(
	edv_core_struct *core, const gchar *arch_obj
)
{
	const gchar *arch_name;
	struct stat stat_buf;
	gchar *comment = NULL;

	if((core == NULL) || STRISEMPTY(arch_obj))
	    return(NULL);

	arch_name = g_basename(arch_obj);

	/* Check if the archive exists and is a file */
	if(stat(arch_obj, &stat_buf))
	    return(comment);
#ifdef S_ISREG
	if(!S_ISREG(stat_buf.st_mode))
	    return(comment);
#endif

	/* Get the archive comment */

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    comment = EDVArchCommentGetARJ(core, arch_obj);
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    comment = EDVArchCommentGetRAR(core, arch_obj);
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    comment = EDVArchCommentGetZip(core, arch_obj);
	}

	return(comment);
}


/*
 *	Used by EDVArchCommentSet*() functions to write the comments
 *	to file and adding a prefix and postfix (if not NULL) to it.
 *
 *	This file is then used be sent to the archive program for
 *	adding of comment.
 *
 *	Inputs assumed valid.
 */
static void EDVArchCommentSetWriteCommentSpool(
	const gchar *path,
	const gchar *prefix, const gchar *postfix,
	const gchar *comment
)
{
	FILE *fp = fopen(path, "wb");
	if(fp == NULL)
	    return;

	if(!STRISEMPTY(prefix))
	    fwrite(prefix, sizeof(gchar), STRLEN(prefix), fp);

	if(!STRISEMPTY(comment))
	    fwrite(comment, sizeof(gchar), STRLEN(comment), fp);

	if(!STRISEMPTY(postfix))
	    fwrite(postfix, sizeof(gchar), STRLEN(postfix), fp);

	fclose(fp);
}

/*
 *	Set comment for the ARJ Archive.
 */
static gint EDVArchCommentSetARJ(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint p;

	/* Get comment spool file path */
	gchar *spool_file = EDVTmpName(NULL);

	/* Format set archive comment command */
	gchar *cmd = g_strdup_printf(
	    "\"%s\" c -y -i \"%s\" \"-z%s\"",
	    prog_arj,
	    arch_obj,
	    spool_file
	);

	/* Generate the output file paths */
	gchar   *stdout_path = EDVTmpName(NULL),
		*stderr_path = EDVTmpName(NULL);

	/* Write comment to spool file for archive program to use */
	EDVArchCommentSetWriteCommentSpool(
	    spool_file,
	    NULL,		/* No prefix */
	    NULL,		/* No postfix */
	    comment		/* Comment */
	);

	/* Execute set archive comment command */
	p = (gint)ExecBOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);

	/* Delete command */
	g_free(cmd);

	/* Delete the output files */
	UNLINK(stdout_path);
	g_free(stdout_path);
	UNLINK(stderr_path);
	g_free(stderr_path);

	/* Remove comment spool file */
	UNLINK(spool_file);
	g_free(spool_file);

	return((p == 0) ? -1 : 0);
}

/*
 *      Set comment for the RAR Archive.
 */
static gint EDVArchCommentSetRAR(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint p;

	/* Get comment spool file path */
	gchar *spool_file = EDVTmpName(NULL);

	/* Format set archive comment command */
	gchar *cmd = g_strdup_printf(
	    "\"%s\" c -y -inul \"-z%s\" \"%s\"",
	    prog_rar,
	    spool_file,
	    arch_obj
	);

	/* Write comment to spool file for archive program to use */
	EDVArchCommentSetWriteCommentSpool(
	    spool_file,
	    NULL,		/* No prefix */
	    NULL,		/* No postfix */
	    comment		/* Comment */
	);

	/* Execute set archive comment command */
	p = (gint)ExecB((const char *)cmd);

	/* Delete command */
	g_free(cmd);

	/* Remove comment spool file */
	UNLINK(spool_file);
	g_free(spool_file);

	return((p == 0) ? -1 : 0);
}

#ifdef HAVE_LIBZIP
/*
 *	Opens the PKZip archive for writing.
 *
 *	Returns the handle to the PKZip archive.
 */
static struct zip *EDVOpenZipWriting(
	const gchar *arch_obj, gint *zip_error
)
{
	struct stat stat_buf;

	/* If the PKZip archive exists but has a size of 0 bytes then
	 * it must be deleted first or else libzip will return an
	 * error stating that it is not a valid PKZip archive
	 */
	if(!stat(arch_obj, &stat_buf))
	{
	    if(stat_buf.st_size == 0l)
		unlink(arch_obj);
	}

	return(zip_open(arch_obj, ZIP_CREATE, zip_error));
}
#endif	/* HAVE_LIBZIP */

/*
 *	Set comment for the PKZip archive.
 */
static gint EDVArchCommentSetZip(
	edv_core_struct *core, const gchar *arch_obj,
	const gchar *comment
)
{
#ifdef HAVE_LIBZIP
	struct zip *archive;
	gint zip_error;

	/* Open the PKZip archive for writing */
	archive = EDVOpenZipWriting(arch_obj, &zip_error);
	if(archive == NULL)
	    return(-1);

	/* Set the PKZip archive comment */
	if(zip_set_archive_comment(archive, comment, STRLEN(comment)))
	{
	    zip_close(archive);
	    return(-1);
	}

	/* Write/flush changes to the PKZip archive and close it */
	if(zip_close(archive))
	    return(-1);

	return(0);
#else
	const gchar *prog_zip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ZIP
	);
	gint pid, status;

	/* Get comment spool file path */
	gchar *spool_file = EDVTmpName(NULL);

	/* Format set archive comment command */
	gchar *cmd = g_strdup_printf(
	    "\"%s\" %s | %s -z %s > /dev/null 2> /dev/null",
	    "cat",
	    spool_file,
	    prog_zip,
	    arch_obj
	);

	/* Write comment to spool file for archive program to use */
	EDVArchCommentSetWriteCommentSpool(
	    spool_file,
	    NULL,		/* No prefix */
	    "\n.\n",		/* Postfix */
	    comment		/* Comment */
	);

	/* Execute set archive comment command */
	pid = EDVSystemBlock(cmd, &status);

	/* Delete command */
	g_free(cmd);

	/* Remove comment spool file */
	UNLINK(spool_file);
	g_free(spool_file);

	return((pid > 0) ? 0 : -1);
#endif
}

/*
 *	Sets comment for the archive specified by arch_obj.
 *
 *	Returns values:
 *
 *	0	Success
 *	-1	Error setting comment
 *	-2	Comments are not supported by this archive format
 *	-3	Systems error
 */
gint EDVArchCommentSet(
	edv_core_struct *core, const gchar *arch_obj,
	gchar **strv, gint strc
)
{
	gint i, status;
	const gchar *arch_name;
	gchar *comment;
	struct stat stat_buf;

	if((core == NULL) || STRISEMPTY(arch_obj))
	    return(-1);

	arch_name = g_basename(arch_obj);

	/* Get archive stats and make sure it is a file */
	if(stat(arch_obj, &stat_buf))
	    return(-1);
#ifdef S_ISREG
	if(!S_ISREG(stat_buf.st_mode))
	    return(-1);
#endif

	/* Generate comment line */
	comment = NULL;
	for(i = 0; i < strc; i++)
	{
	    if(strv[i] == NULL)
		continue;

	    comment = G_STRCAT(comment, strv[i]);
	    if(i < (strc - 1))
		comment = G_STRCAT(comment, "\n");
	}

	/* Set comment by the archive's extension */
	status = -2;

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchCommentSetARJ(
		core, arch_obj, comment
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchCommentSetRAR(
		core, arch_obj, comment
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchCommentSetZip(
		core, arch_obj, comment
	    );
	}

	g_free(comment);

	return(status);
}
