/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Engrampa
 *
 *  Copyright (C) 2001 The Free Software Foundation, Inc.
 *
 *  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.
 */

#include <config.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <glib.h>
#include "file-data.h"
#include "file-utils.h"
#include "fr-command.h"
#include "fr-enum-types.h"
#include "fr-marshal.h"
#include "fr-proc-error.h"
#include "fr-process.h"
#include "glib-utils.h"

#define INITIAL_SIZE 256


/* Signals */
enum {
	START,
	DONE,
	PROGRESS,
	MESSAGE,
	WORKING_ARCHIVE,
	LAST_SIGNAL
};

/* Properties */
enum {
        PROP_0,
        PROP_FILE,
        PROP_MIME_TYPE,
        PROP_PROCESS,
        PROP_PASSWORD,
        PROP_ENCRYPT_HEADER,
        PROP_COMPRESSION,
        PROP_VOLUME_SIZE
};

static GObjectClass *parent_class = NULL;
static guint fr_command_signals[LAST_SIGNAL] = { 0 };

static void fr_command_class_init  (FrCommandClass *class);
static void fr_command_init        (FrCommand *afile);
static void fr_command_finalize    (GObject *object);

const char *action_names[] = { "NONE",
                               "CREATING_NEW_ARCHIVE",
                               "LOADING_ARCHIVE",
                               "LISTING_CONTENT",
                               "DELETING_FILES",
                               "TESTING_ARCHIVE",
                               "GETTING_FILE_LIST",
                               "COPYING_FILES_FROM_REMOTE",
                               "ADDING_FILES",
                               "EXTRACTING_FILES",
                               "COPYING_FILES_TO_REMOTE",
                               "CREATING_ARCHIVE",
                               "SAVING_REMOTE_ARCHIVE" };

#if MATE_ENABLE_DEBUG
const char * get_action_name (FrAction action)
{
	return action_names[action];
}
#endif

GType
fr_command_get_type ()
{
	static GType type = 0;

	if (! type) {
		GTypeInfo type_info = {
			sizeof (FrCommandClass),
			NULL,
			NULL,
			(GClassInitFunc) fr_command_class_init,
			NULL,
			NULL,
			sizeof (FrCommand),
			0,
			(GInstanceInitFunc) fr_command_init,
			NULL
		};

		type = g_type_register_static (G_TYPE_OBJECT,
					       "FRCommand",
					       &type_info,
					       0);
	}

	return type;
}


static void
base_fr_command_list (FrCommand  *comm)
{
}


static void
base_fr_command_add (FrCommand     *comm,
		     const char    *from_file,
		     GList         *file_list,
		     const char    *base_dir,
		     gboolean       update,
		     gboolean       recursive)
{
}


static void
base_fr_command_delete (FrCommand  *comm,
		        const char *from_file,
			GList       *file_list)
{
}


static void
base_fr_command_extract (FrCommand  *comm,
		         const char *from_file,
			 GList      *file_list,
			 const char *dest_dir,
			 gboolean    overwrite,
			 gboolean    skip_older,
			 gboolean    junk_paths)
{
}


static void
base_fr_command_test (FrCommand *comm)
{
}


static void
base_fr_command_uncompress (FrCommand *comm)
{
}


static void
base_fr_command_recompress (FrCommand *comm)
{
}


static void
base_fr_command_handle_error (FrCommand   *comm,
			      FrProcError *error)
{
}


const char **void_mime_types = { NULL };


static const char **
base_fr_command_get_mime_types (FrCommand *comm)
{
	return void_mime_types;
}


static FrCommandCap
base_fr_command_get_capabilities (FrCommand  *comm,
			          const char *mime_type,
			          gboolean    check_command)
{
	return FR_COMMAND_CAN_DO_NOTHING;
}


static void
base_fr_command_set_mime_type (FrCommand  *comm,
			       const char *mime_type)
{
	comm->mime_type = get_static_string (mime_type);
	fr_command_update_capabilities (comm);
}


static const char *
base_fr_command_get_packages (FrCommand  *comm,
			      const char *mime_type)
{
	return NULL;
}


static void
fr_command_start (FrProcess *process,
		  gpointer   data)
{
	FrCommand *comm = FR_COMMAND (data);

	g_signal_emit (G_OBJECT (comm),
		       fr_command_signals[START],
		       0,
		       comm->action);
}


static void
fr_command_done (FrProcess   *process,
		 FrProcError *error,
		 gpointer     data)
{
	FrCommand *comm = FR_COMMAND (data);

	comm->process->restart = FALSE;
	if (error->type != FR_PROC_ERROR_STOPPED)
		fr_command_handle_error (comm, error);

	if (comm->process->restart) {
		fr_process_start (comm->process);
		return;
	}

	if (comm->action == FR_ACTION_LISTING_CONTENT) {
		/* order the list by name to speed up search */
		g_ptr_array_sort (comm->files, file_data_compare_by_path);
	}

	g_signal_emit (G_OBJECT (comm),
		       fr_command_signals[DONE],
		       0,
		       comm->action,
		       error);
}


static void
fr_command_set_process (FrCommand *comm,
		        FrProcess *process)
{
	if (comm->process != NULL) {
		g_signal_handlers_disconnect_matched (G_OBJECT (comm->process),
					      G_SIGNAL_MATCH_DATA,
					      0,
					      0, NULL,
					      0,
					      comm);
		g_object_unref (G_OBJECT (comm->process));
		comm->process = NULL;
	}

	if (process == NULL)
		return;

	g_object_ref (G_OBJECT (process));
	comm->process = process;
	g_signal_connect (G_OBJECT (comm->process),
			  "start",
			  G_CALLBACK (fr_command_start),
			  comm);
	g_signal_connect (G_OBJECT (comm->process),
			  "done",
			  G_CALLBACK (fr_command_done),
			  comm);
}


static void
fr_command_set_property (GObject      *object,
			 guint         prop_id,
			 const GValue *value,
			 GParamSpec   *pspec)
{
	FrCommand *comm;

	comm = FR_COMMAND (object);

	switch (prop_id) {
	case PROP_PROCESS:
		fr_command_set_process (comm, g_value_get_object (value));
		break;
	case PROP_FILE:
		fr_command_set_file (comm, g_value_get_object (value));
		break;
	case PROP_MIME_TYPE:
		fr_command_set_mime_type (comm, g_value_get_string (value));
		break;
	case PROP_PASSWORD:
		g_free (comm->password);
		comm->password = g_strdup (g_value_get_string (value));
		break;
	case PROP_ENCRYPT_HEADER:
		comm->encrypt_header = g_value_get_boolean (value);
		break;
	case PROP_COMPRESSION:
		comm->compression = g_value_get_enum (value);
		break;
	case PROP_VOLUME_SIZE:
		comm->volume_size = g_value_get_uint (value);
		break;
	default:
		break;
	}
}


static void
fr_command_get_property (GObject    *object,
			 guint       prop_id,
			 GValue     *value,
			 GParamSpec *pspec)
{
	FrCommand *comm;

	comm = FR_COMMAND (object);

	switch (prop_id) {
	case PROP_PROCESS:
		g_value_set_object (value, comm->process);
		break;
	case PROP_FILE:
		g_value_take_object (value, g_file_new_for_path (comm->filename));
		break;
	case PROP_MIME_TYPE:
		g_value_set_static_string (value, comm->mime_type);
		break;
	case PROP_PASSWORD:
		g_value_set_string (value, comm->password);
		break;
	case PROP_ENCRYPT_HEADER:
		g_value_set_boolean (value, comm->encrypt_header);
		break;
	case PROP_COMPRESSION:
		g_value_set_enum (value, comm->compression);
		break;
	case PROP_VOLUME_SIZE:
		g_value_set_uint (value, comm->volume_size);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
fr_command_class_init (FrCommandClass *class)
{
	GObjectClass *gobject_class;

	parent_class = g_type_class_peek_parent (class);

	gobject_class = G_OBJECT_CLASS (class);

	/* virtual functions */

	gobject_class->finalize = fr_command_finalize;
	gobject_class->set_property = fr_command_set_property;
	gobject_class->get_property = fr_command_get_property;

	class->list             = base_fr_command_list;
	class->add              = base_fr_command_add;
	class->delete           = base_fr_command_delete;
	class->extract          = base_fr_command_extract;
	class->test             = base_fr_command_test;
	class->uncompress       = base_fr_command_uncompress;
	class->recompress       = base_fr_command_recompress;
	class->handle_error     = base_fr_command_handle_error;
	class->get_mime_types   = base_fr_command_get_mime_types;
	class->get_capabilities = base_fr_command_get_capabilities;
	class->set_mime_type    = base_fr_command_set_mime_type;
	class->get_packages     = base_fr_command_get_packages;
	class->start            = NULL;
	class->done             = NULL;
	class->progress         = NULL;
	class->message          = NULL;

	/* signals */

	fr_command_signals[START] =
		g_signal_new ("start",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (FrCommandClass, start),
			      NULL, NULL,
			      fr_marshal_VOID__INT,
			      G_TYPE_NONE,
			      1, G_TYPE_INT);
	fr_command_signals[DONE] =
		g_signal_new ("done",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (FrCommandClass, done),
			      NULL, NULL,
			      fr_marshal_VOID__INT_BOXED,
			      G_TYPE_NONE, 2,
			      G_TYPE_INT,
			      FR_TYPE_PROC_ERROR);
	fr_command_signals[PROGRESS] =
		g_signal_new ("progress",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (FrCommandClass, progress),
			      NULL, NULL,
			      fr_marshal_VOID__DOUBLE,
			      G_TYPE_NONE, 1,
			      G_TYPE_DOUBLE);
	fr_command_signals[MESSAGE] =
		g_signal_new ("message",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (FrCommandClass, message),
			      NULL, NULL,
			      fr_marshal_VOID__STRING,
			      G_TYPE_NONE, 1,
			      G_TYPE_STRING);
	fr_command_signals[WORKING_ARCHIVE] =
			g_signal_new ("working_archive",
				      G_TYPE_FROM_CLASS (class),
				      G_SIGNAL_RUN_LAST,
				      G_STRUCT_OFFSET (FrCommandClass, working_archive),
				      NULL, NULL,
				      fr_marshal_VOID__STRING,
				      G_TYPE_NONE, 1,
				      G_TYPE_STRING);

	/* properties */

	g_object_class_install_property (gobject_class,
					 PROP_PROCESS,
					 g_param_spec_object ("process",
							      "Process",
							      "The process object used by the command",
							      FR_TYPE_PROCESS,
							      G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_FILE,
					 g_param_spec_object ("file",
							      "File",
							      "The archive local file",
							      G_TYPE_FILE,
							      G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_MIME_TYPE,
					 g_param_spec_string ("mime-type",
							      "Mime type",
							      "The file mime-type",
							      NULL,
							      G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_PASSWORD,
					 g_param_spec_string ("password",
							      "Password",
							      "The archive password",
							      NULL,
							      G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_ENCRYPT_HEADER,
					 g_param_spec_boolean ("encrypt-header",
							       "Encrypt header",
							       "Whether to encrypt the archive header when creating the archive",
							       FALSE,
							       G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_COMPRESSION,
					 g_param_spec_enum ("compression",
							    "Compression type",
							    "The compression type to use when creating the archive",
							    FR_TYPE_COMPRESSION,
							    FR_COMPRESSION_NORMAL,
							    G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_VOLUME_SIZE,
					 g_param_spec_uint ("volume-size",
							    "Volume size",
							    "The size of each volume or 0 to not use volumes",
							    0L,
							    G_MAXUINT,
							    0,
							    G_PARAM_READWRITE));
}


static void
fr_command_init (FrCommand *comm)
{
	comm->files = g_ptr_array_sized_new (INITIAL_SIZE);

	comm->password = NULL;
	comm->encrypt_header = FALSE;
	comm->compression = FR_COMPRESSION_NORMAL;
	comm->volume_size = 0;
	comm->filename = NULL;
	comm->e_filename = NULL;
	comm->fake_load = FALSE;

	comm->propAddCanUpdate = FALSE;
	comm->propAddCanReplace = FALSE;
	comm->propAddCanStoreFolders = FALSE;
	comm->propExtractCanAvoidOverwrite = FALSE;
	comm->propExtractCanSkipOlder = FALSE;
	comm->propExtractCanJunkPaths = FALSE;
	comm->propPassword = FALSE;
	comm->propTest = FALSE;
	comm->propCanExtractAll = TRUE;
	comm->propCanDeleteNonEmptyFolders = TRUE;
	comm->propCanExtractNonEmptyFolders = TRUE;
	comm->propListFromFile = FALSE;
}


static void
fr_command_finalize (GObject *object)
{
	FrCommand* comm;

	g_return_if_fail (object != NULL);
	g_return_if_fail (FR_IS_COMMAND (object));

	comm = FR_COMMAND (object);

	g_free (comm->filename);
	g_free (comm->e_filename);
	g_free (comm->password);
	if (comm->files != NULL)
		g_ptr_array_free_full (comm->files, (GFunc) file_data_free, NULL);
	fr_command_set_process (comm, NULL);

	/* Chain up */
	if (G_OBJECT_CLASS (parent_class)->finalize)
		G_OBJECT_CLASS (parent_class)->finalize (object);
}


static void
fr_command_set_filename (FrCommand  *comm,
			 const char *filename)
{
	g_return_if_fail (FR_IS_COMMAND (comm));

	if (comm->filename != NULL) {
		g_free (comm->filename);
		comm->filename = NULL;
	}

	if (comm->e_filename != NULL) {
		g_free (comm->e_filename);
		comm->e_filename = NULL;
	}

	if (filename != NULL) {
		filename = get_7z_first_volume_name(filename);
		if (! g_path_is_absolute (filename)) {
			char *current_dir;

			current_dir = g_get_current_dir ();
			comm->filename = g_strconcat (current_dir,
						      "/",
						      filename,
						      NULL);
			g_free (current_dir);
		}
		else
			comm->filename = g_strdup (filename);

		comm->e_filename = g_shell_quote (comm->filename);

		debug (DEBUG_INFO, "filename : %s\n", comm->filename);
		debug (DEBUG_INFO, "e_filename : %s\n", comm->e_filename);
	}

	fr_command_working_archive (comm, comm->filename);
}


char *
get_7z_first_volume_name(const char *name)
{
	if (name == NULL)
		return name;

	const char *pattern = "^(.*\\.7z.)([0-9]{3}[0-9]*)$";
	const char *zipPattern = "^(.*\\.zip.)([0-9]{3}[0-9]*)$";

	GRegex *re = g_regex_new (pattern, G_REGEX_CASELESS, 0, NULL);
	GRegex *zipRegex = g_regex_new (zipPattern, G_REGEX_CASELESS, 0, NULL);
	if (g_regex_match (re, name, 0, NULL)) {
		char **parts;
		parts = g_regex_split(re, name, 0);
		strcpy(parts[2], "001");
		name = g_strjoinv ("", parts);
	} else if (g_regex_match (zipRegex, name, 0, NULL)) {
		char **parts;
		parts = g_regex_split(zipRegex, name, 0);
		strcpy(parts[2], "001");
		name = g_strjoinv ("", parts);
	} else if  (check_is_tar_volumn(name)) {
		char *split = g_strrstr(name, ".");
		int i = 0;
		while (++split != NULL && *split != '\0')  {
			if (i >= 3) {
				*split = '\0';
				break;
			}
			*split = '0';
			++i;
		}
	}

	g_regex_unref (re);
	g_regex_unref (zipRegex);

	return name;
}

void
fr_command_set_file (FrCommand *comm,
		     GFile     *file)
{
	char *filename;

	filename = g_file_get_path (file);
	fr_command_set_filename (comm, filename);

	g_free (filename);
}


void
fr_command_set_multi_volume (FrCommand *comm,
			     GFile     *file)
{
	comm->multi_volume = TRUE;
	fr_command_set_file (comm, file);
}


void
fr_command_list (FrCommand *comm)
{
	g_return_if_fail (FR_IS_COMMAND (comm));

	fr_command_progress (comm, -1.0);

	if (comm->files != NULL) {
		g_ptr_array_free_full (comm->files, (GFunc) file_data_free, NULL);
		comm->files = g_ptr_array_sized_new (INITIAL_SIZE);
	}

	comm->action = FR_ACTION_LISTING_CONTENT;
	fr_process_set_out_line_func (comm->process, NULL, NULL);
	fr_process_set_err_line_func (comm->process, NULL, NULL);
	fr_process_use_standard_locale (comm->process, TRUE);
	comm->multi_volume = FALSE;

	if (! comm->fake_load) {
		FR_COMMAND_GET_CLASS (G_OBJECT (comm))->list (comm);
	} else {
		/* fix: 150916 【归档管理器】【单元测试】所有格式的加密文档复制剪切加密文件和文件夹后无法成功粘贴到指定目录 */
		/* 清空错误信息，防止之前命令的错误结果影响本次结果的判断 */
		comm->process->error.type = FR_PROC_ERROR_NONE;
		comm->process->error.status = 0;
		g_clear_error (&comm->process->error.gerror);

		g_signal_emit (G_OBJECT (comm),
			       fr_command_signals[DONE],
			       0,
			       comm->action,
			       &comm->process->error);
	}
}


void
fr_command_add (FrCommand     *comm,
		const char    *from_file,
		GList         *file_list,
		const char    *base_dir,
		gboolean       update,
		gboolean       recursive)
{
	fr_command_progress (comm, -1.0);

	comm->action = FR_ACTION_ADDING_FILES;
	fr_process_set_out_line_func (FR_COMMAND (comm)->process, NULL, NULL);
	fr_process_set_err_line_func (FR_COMMAND (comm)->process, NULL, NULL);

	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->add (comm,
						     from_file,
						     file_list,
						     base_dir,
						     update,
						     recursive);
}


void
fr_command_delete (FrCommand   *comm,
		   const char  *from_file,
		   GList       *file_list)
{
	fr_command_progress (comm, -1.0);

	comm->action = FR_ACTION_DELETING_FILES;
	fr_process_set_out_line_func (FR_COMMAND (comm)->process, NULL, NULL);
	fr_process_set_err_line_func (FR_COMMAND (comm)->process, NULL, NULL);

	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->delete (comm, from_file, file_list);
}


void
fr_command_extract (FrCommand  *comm,
		    const char *from_file,
		    GList      *file_list,
		    const char *dest_dir,
		    gboolean    overwrite,
		    gboolean    skip_older,
		    gboolean    junk_paths)
{
	fr_command_progress (comm, -1.0);

	comm->action = FR_ACTION_EXTRACTING_FILES;
	fr_process_set_out_line_func (FR_COMMAND (comm)->process, NULL, NULL);
	fr_process_set_err_line_func (FR_COMMAND (comm)->process, NULL, NULL);

	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->extract (comm,
							 from_file,
							 file_list,
							 dest_dir,
							 overwrite,
							 skip_older,
							 junk_paths);
}


void
fr_command_test (FrCommand *comm)
{
	fr_command_progress (comm, -1.0);

	comm->action = FR_ACTION_TESTING_ARCHIVE;
	fr_process_set_out_line_func (FR_COMMAND (comm)->process, NULL, NULL);
	fr_process_set_err_line_func (FR_COMMAND (comm)->process, NULL, NULL);

	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->test (comm);
}


void
fr_command_uncompress (FrCommand *comm)
{
	fr_command_progress (comm, -1.0);
	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->uncompress (comm);
}


void
fr_command_recompress (FrCommand *comm)
{
	fr_command_progress (comm, -1.0);
	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->recompress (comm);
}


const char **
fr_command_get_mime_types (FrCommand *comm)
{
	return FR_COMMAND_GET_CLASS (G_OBJECT (comm))->get_mime_types (comm);
}


void
fr_command_update_capabilities (FrCommand *comm)
{
	comm->capabilities = fr_command_get_capabilities (comm, comm->mime_type, TRUE);
}


FrCommandCap
fr_command_get_capabilities (FrCommand  *comm,
			     const char *mime_type,
			     gboolean    check_command)
{
	return FR_COMMAND_GET_CLASS (G_OBJECT (comm))->get_capabilities (comm, mime_type, check_command);
}


gboolean
fr_command_is_capable_of (FrCommand     *comm,
			  FrCommandCaps  requested_capabilities)
{
	return (((comm->capabilities ^ requested_capabilities) & requested_capabilities) == 0);
}


const char *
fr_command_get_packages (FrCommand  *comm,
			 const char *mime_type)
{
	return FR_COMMAND_GET_CLASS (G_OBJECT (comm))->get_packages (comm, mime_type);
}


/* fraction == -1 means : I don't known how much time the current operation
 *                        will take, the dialog will display this info pulsing
 *                        the progress bar.
 * fraction in [0.0, 1.0] means the amount of work, in percentage,
 *                        accomplished.
 */
void
fr_command_progress (FrCommand *comm,
		     double     fraction)
{
	g_signal_emit (G_OBJECT (comm),
		       fr_command_signals[PROGRESS],
		       0,
		       fraction);
}


void
fr_command_message (FrCommand  *comm,
		    const char *msg)
{
	g_signal_emit (G_OBJECT (comm),
		       fr_command_signals[MESSAGE],
		       0,
		       msg);
}


void
fr_command_working_archive (FrCommand  *comm,
		            const char *archive_name)
{
	g_signal_emit (G_OBJECT (comm),
		       fr_command_signals[WORKING_ARCHIVE],
		       0,
		       archive_name);
}


void
fr_command_set_n_files (FrCommand *comm,
			int        n_files)
{
	comm->n_files = n_files;
	comm->n_file = 0;
}


void
fr_command_add_file (FrCommand *comm,
		     FileData  *fdata)
{
	file_data_update_content_type (fdata);
	g_ptr_array_add (comm->files, fdata);
	if (! fdata->dir)
		comm->n_regular_files++;
}


void
fr_command_set_mime_type (FrCommand  *comm,
			  const char *mime_type)
{
	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->set_mime_type (comm, mime_type);
}


void
fr_command_handle_error (FrCommand   *comm,
			 FrProcError *error)
{
	FR_COMMAND_GET_CLASS (G_OBJECT (comm))->handle_error (comm, error);
}
