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

/*
 *  Engrampa
 *
 *  Copyright (C) 2004 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#include <regex.h>
#include <gtk-utils.h>
#include <unistd.h>

#include <glib.h>

#include "file-data.h"
#include "fr-process.h"
#include "file-utils.h"
#include "glib-utils.h"
#include "fr-command.h"
#include "fr-command-iso.h"

#define MAX_TOKENS 9
#define TOKEN_LENGTH 10000

static void fr_command_iso_class_init  (FrCommandIsoClass *class);
static void fr_command_iso_init        (FrCommand         *afile);
static void fr_command_iso_finalize    (GObject           *object);

/* Parent Class */

static FrCommandClass *parent_class = NULL;

static char*
get_file_modifi_time(const char* filePath) {
    // 构建7z命令
    char command[256];
    snprintf(command, sizeof(command), "7z l -slt \"%s\"", filePath);

    // 执行7z命令并捕获输出
    FILE* pipe = popen(command, "r");
    if (!pipe) {
        perror("Failed to execute 7z command");
        return NULL;
    }

    // 读取命令输出
    char buffer[128];
    char* result = NULL;
    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
        if (strstr(buffer, "Modified") != NULL) {
            // 提取时间字符串
            result = strdup(buffer + strlen("Modified = "));
            // 去除后缀
            result[strcspn(result, "\n")] = 0;
            break;
        }
    }

    // 关闭管道
    pclose(pipe);

    return result;
}

static char *
get_year_from_modifi_time(const char* timeString) {
    // 解析时间字符串
    struct tm tm;
    memset(&tm, 0, sizeof(struct tm));
    strptime(timeString, "%Y-%m-%d %H:%M:%S", &tm);

    // 获取年份
    int year = tm.tm_year + 1900;

    // 将年份转换为字符串
    char *yearStr = malloc(5); // 年份字符串最多4位加上一个终止符
    if (yearStr == NULL) {
        return NULL; // 内存分配失败
    }

    sprintf(yearStr, "%d", year);

    return yearStr;
}

static time_t
mktime_from_string (char *month,
		    char *mday,
		    char *year)
{
	static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	struct tm tm = {0, };

	tm.tm_isdst = -1;

	if (month != NULL) {
		int i;
		for (i = 0; i < 12; i++)
			if (strcmp (months[i], month) == 0) {
				tm.tm_mon = i;
				break;
			}
	}
	tm.tm_mday = atoi (mday);
	tm.tm_year = atoi (year) - 1900;

	return mktime (&tm);
}

/**
    1 通过7z 命令获取镜像文件最后的修改时间
    2 通过xorriso -indev filename -find / -exec -lsdl 获取文件列表
    3 处理 step2中的输出更新filedata
 */

static char *
get_modify_time_yy(const char* filename)
{
	char* modificationTime = get_file_modifi_time(filename);
	if (modificationTime == NULL) {
		perror("Failed to get modification time");
		return 0;
	}

	// 获取年份
	char* year = get_year_from_modifi_time(modificationTime);
	// printf("Modification Year: %s\n", year);

	return year;
}

static char **
split_string(const char *input, const char *delimiter, int *token_count)
{
    // 复制输入字符串，因为 strtok 会修改原始字符串
    char *input_copy = strdup(input);
    if (input_copy == NULL) {
        return NULL; // 内存分配失败
    }

    // 初始化子字符串数组
    char **tokens = (char **)malloc(MAX_TOKENS * sizeof(char *));
    if (tokens == NULL) {
        free(input_copy);
        return NULL; // 内存分配失败
    }

    // 初始化上下文指针
    char *context;
    int count = 0;

    // 使用 strtok 分割字符串
    context = strtok(input_copy, delimiter);
    while (context != NULL && count < MAX_TOKENS) {
        tokens[count] = (char *)malloc(TOKEN_LENGTH * sizeof(char));
        if (tokens[count] == NULL) {
            // 清理已分配的内存
            for (int i = 0; i < count; i++) {
                free(tokens[i]);
            }
            free(tokens);
            free(input_copy);
            return NULL; // 内存分配失败
        }
        size_t context_length = strlen(context);
        if (context_length >= TOKEN_LENGTH) {
            // 如果 context 长度超过 TOKEN_LENGTH，截断并记录错误
            fprintf(stderr, "Error: Token length exceeds buffer size.\n");
            tokens[count][TOKEN_LENGTH - 1] = '\0';
        } else {
            strncpy(tokens[count], context, context_length + 1);
        }
        count++;
        context = strtok(NULL, delimiter);
    }

    // 设置 token_count
    *token_count = count;

    // 释放输入字符串副本
    free(input_copy);

    // 返回子字符串数组
    return tokens;
}

static void
free_tokens(char **tokens, int token_count)
{
    // 释放子字符串数组中的每个子字符串
    for (int i = 0; i < token_count; i++) {
        free(tokens[i]);
    }
    // 释放子字符串数组本身
    free(tokens);
}

static char *
get_file_path(const char * fields_path)
{
    if (fields_path == NULL) {
        return NULL;
    }

    int lp = 0, rp = strlen(fields_path) - 1;

    while (lp < rp && fields_path[lp] != '\'') {
        lp++;
    }

    while (lp < rp && fields_path[rp] != '\'') {
        rp--;
    }

    if (fields_path[lp] == '\'' && fields_path[rp] == '\'') {
        lp++;
        rp--;
    }

    // 分配足够的内存给 file_path
    char *file_path = g_malloc(rp - lp + 2);
    strncpy(file_path, fields_path + lp, rp - lp + 1);
    file_path[rp - lp + 1] = '\0';  // 确保字符串以 \0 结束

    // 去除字符串两端的空白字符
    return g_strstrip(file_path);
}

static void
list__process_line (char     *line,
		    gpointer  data)
{
	FileData      *fdata = NULL;
	FrCommand     *comm = FR_COMMAND (data);
	FrCommandIso  *comm_iso = FR_COMMAND_ISO (comm);
	char         **fields;
	const char     delimiter[] = " ";

	// 命令输出存在空行 需要忽略
	if(line == NULL) {
		return;
	}
	// debug(DEBUG_INFO, "line is : %s\n", line);

	// 第一行的输出为 "/" 需要忽略
	if (strcmp(line, "\'\\\'") == 0) {
		return;
	}

	/**
	* xorriso 的文件信息输出为单行格式 具体形如下面：
	* dr-xr-xr-x    1 0        0               0 Jun  5 14:26 '/'
	* dr-xr-xr-x    1 0        0               0 Jun  5 14:26 '/kylin-ide_1.4.6_amd64'
	* dr-xr-xr-x    1 0        0               0 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/DEBIAN'
	* -r--r--r--    1 0        0            1510 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/DEBIAN/control'
	* -r-xr-xr-x    1 0        0            3194 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/DEBIAN/postinst'
	* -r-xr-xr-x    1 0        0             648 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/DEBIAN/postrm'
	* -r-xr-xr-x    1 0        0            1781 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/DEBIAN/prerm'
	* dr-xr-xr-x    1 0        0               0 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/opt'
	* dr-xr-xr-x    1 0        0               0 Jun  5 14:26 '/kylin-ide_1.4.6_amd64/opt/apps'
	*/

	// 文件数据初始化
	if (fdata == NULL) {
		fdata = file_data_new();
	}
	int fields_count; // 字段数目
	fields = split_string(line, delimiter, &fields_count);

	// file size 对应上面的第5个字段
	fdata->size = g_ascii_strtoull (fields[4], NULL, 10);
	// file time 字段对应上面的第6、7、8的字段 注意时间格式
	fdata->modified = mktime_from_string (fields[5], fields[6],
						strchr(fields[7], ':') != NULL ? comm->modifi_year : fields[7]);

	// file path 处理 对应上面的第9及以后字段

	fdata->full_path = get_file_path(line);
	fdata->original_path = fdata->full_path;
	fdata->name = g_strdup (file_name_from_path (fdata->full_path));
	fdata->path = remove_level_from_path (fdata->full_path);

	// 检查是否为目录信息 目录前面带 'd'
	if (line[0] == 'd') {
		fdata->dir = TRUE;

	} else if (line[0] == '-') { /* Is file */
		fdata->dir = FALSE;
	}
	// debug_fdata_info(fdata); // 输出文件数据信息
	fr_command_add_file (comm, fdata);
	free_tokens(fields, fields_count);
}


static void
list__begin (gpointer data)
{
	FrCommandIso *comm = data;
}


static void
fr_command_iso_list (FrCommand *comm)
{
	// test 获取iso文件修改时间 年份
	// test_get_file_modifi_time();
	fr_command_set_modifi_year(comm, get_modify_time_yy(comm->filename));

	fr_process_set_out_line_func (comm->process, list__process_line, comm);

	fr_process_begin_command (comm->process, "xorriso");
	fr_process_set_begin_func (comm->process, list__begin, comm);
	fr_process_add_arg (comm->process, "-indev");
	fr_process_add_arg (comm->process, comm->filename);
	fr_process_add_arg (comm->process, "-find");
	fr_process_add_arg (comm->process, "/");
	fr_process_add_arg (comm->process, "-exec");
	fr_process_add_arg (comm->process, "-lsdl");
	fr_process_end_command (comm->process);

	fr_process_start (comm->process);
}


static gboolean
file_path_is_dir(FrCommand *comm, const char *path)
{
    // 检查路径是否为空
    if (path == NULL || *path == '\0') {
        return FALSE;
    }

    // 查找路径在文件数据数组中的索引
    int index = find_path_in_file_data_array(comm->files, path);

    // 如果索引有效，则获取对应的文件数据
    if (index >= 0) {
        FileData *fdata = g_ptr_array_index(comm->files, index);
        return file_data_is_dir(fdata);
    }

    // 如果路径不在文件数据数组中，返回 FALSE
    return FALSE;
}

static GList *
get_extract_file_list(FrCommand  *comm, GList *file_list)
{
    GList *extract_file_list = NULL;
    char *base_dir_name = NULL;

    for (GList *scan = file_list; scan; scan = scan->next) {
        char *path = scan->data;
        char *filename = g_strdup(path);

        if (file_path_is_dir(comm, filename)) {
		if (!base_dir_name
		    || (base_dir_name && !g_str_has_prefix(filename, base_dir_name)) ) {
			base_dir_name = g_strdup(filename);
			extract_file_list = g_list_prepend(extract_file_list, filename);
		}
	}
	else if (!base_dir_name
			|| (base_dir_name && !g_str_has_prefix(filename, base_dir_name))) {
		extract_file_list = g_list_prepend(extract_file_list, filename);
	}
    }

    // Free the allocated base_dir_name if it was set
    if (base_dir_name) {
        g_free(base_dir_name);
    }

    return extract_file_list;
}


static void
fr_command_iso_extract (FrCommand  *comm,
			const char *from_file,
			GList      *file_list,
			const char *dest_dir,
			gboolean    overwrite,
			gboolean    skip_older,
			gboolean    junk_paths)
{
	{
	#if 0
	// 显示要提取的文件列表
	display_file_list(file_list);

	// 打印调试信息
	debug(DEBUG_INFO, "filename: %s\n", comm->filename);
	debug(DEBUG_INFO, "destination directory: %s\n", dest_dir);
	#endif
    }
	// 定义常量
	const char *XORRISO_CMD = "xorriso";
	const char *OSIRROX_ARG = "-osirrox";
	const char *ON_ARG = "on";
	const char *INDEV_ARG = "-indev";
	const char *EXTRACT_ARG = "-extract";

	// 解压所有文件
	if (file_list == NULL) {
	fr_process_begin_command(comm->process, XORRISO_CMD);
	fr_process_add_arg(comm->process, OSIRROX_ARG);
	fr_process_add_arg(comm->process, ON_ARG);
	fr_process_add_arg(comm->process, INDEV_ARG);
	fr_process_add_arg(comm->process, comm->filename);
	fr_process_add_arg(comm->process, EXTRACT_ARG);
	fr_process_add_arg(comm->process, "/");
	fr_process_add_arg(comm->process, dest_dir);
	fr_process_end_command(comm->process);
	}
	// 解压特定文件
	else {
		GList *extract_file_list = get_extract_file_list(comm, file_list);

		GList *scan;
		for (scan = extract_file_list; scan; scan = scan->next) {
			const char *filename = scan->data;

			fr_process_begin_command(comm->process, XORRISO_CMD);
			fr_process_add_arg(comm->process, OSIRROX_ARG);
			fr_process_add_arg(comm->process, ON_ARG);
			fr_process_add_arg(comm->process, INDEV_ARG);
			fr_process_add_arg(comm->process, comm->filename);
			fr_process_add_arg(comm->process, EXTRACT_ARG);
			fr_process_add_arg(comm->process, filename);
			fr_process_add_arg_concat(comm->process, dest_dir, filename, NULL);
			fr_process_end_command(comm->process);
		}
		// 释放文件列表
		g_list_free(extract_file_list);
	}

	// 设置目标目录权限
	fr_process_begin_command(comm->process, "chmod");
	fr_process_add_arg(comm->process, "-R");
	fr_process_add_arg(comm->process, "755");
	fr_process_add_arg(comm->process, dest_dir);
	fr_process_end_command(comm->process);
}


const char *iso_mime_type[] = { "application/x-cd-image", NULL };


static const char **
fr_command_iso_get_mime_types (FrCommand *comm)
{
	return iso_mime_type;
}


static FrCommandCap
fr_command_iso_get_capabilities (FrCommand  *comm,
			         const char *mime_type,
				 gboolean    check_command)
{
	FrCommandCap capabilities;

	capabilities = FR_COMMAND_CAN_ARCHIVE_MANY_FILES;
	if (is_program_available ("isoinfo", check_command))
		capabilities |= FR_COMMAND_CAN_READ;

	return capabilities;
}


static const char *
fr_command_iso_get_packages (FrCommand  *comm,
			     const char *mime_type)
{
	return PACKAGES ("xorriso");
}


static void
fr_command_iso_class_init (FrCommandIsoClass *class)
{
	GObjectClass   *gobject_class = G_OBJECT_CLASS (class);
	FrCommandClass *afc;

	parent_class = g_type_class_peek_parent (class);
	afc = (FrCommandClass*) class;

	gobject_class->finalize = fr_command_iso_finalize;

	afc->list             = fr_command_iso_list;
	afc->extract          = fr_command_iso_extract;
	afc->get_mime_types   = fr_command_iso_get_mime_types;
	afc->get_capabilities = fr_command_iso_get_capabilities;
	afc->get_packages     = fr_command_iso_get_packages;
}


static void
fr_command_iso_init (FrCommand *comm)
{
	FrCommandIso *comm_iso = FR_COMMAND_ISO (comm);

	comm_iso->joliet = TRUE;

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


static void
fr_command_iso_finalize (GObject *object)
{
	FrCommandIso *comm_iso;

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

	comm_iso = FR_COMMAND_ISO (object);

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


GType
fr_command_iso_get_type ()
{
	static GType type = 0;

	if (! type) {
		GTypeInfo type_info = {
			sizeof (FrCommandIsoClass),
			NULL,
			NULL,
			(GClassInitFunc) fr_command_iso_class_init,
			NULL,
			NULL,
			sizeof (FrCommandIso),
			0,
			(GInstanceInitFunc) fr_command_iso_init,
			NULL
		};

		type = g_type_register_static (FR_TYPE_COMMAND,
					       "FRCommandIso",
					       &type_info,
					       0);
	}

	return type;
}
