/** \file archive.c
 *   ARCHIVE PROCESSING
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2018 Adam Richards and Martin Fischer
 *
 *  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 <fcntl.h>

#include <zip.h>

#include "archive.h"
#include "directory.h"
#include "dynstring.h"
#include "misc.h"
#include "paths.h"
#include "include/utf8convert.h"
#include "common-ui.h"
#include "fileio.h"

int log_zip = 0;

//char *
//NativeToUtf8(const char *nativeString)
//{
//
//#ifdef XINDOWS
//
//	int cnt = 2 * (strlen(nativeString) + 1);
//	char *tempBuffer = MyMalloc( cnt );
//	char *destBuffer = MyMalloc( cnt );
//
//	//// find the
//	//cnt = MultiByteToWideChar(CP_ACP,
//	//	0,
//	//	nativeString,
//	//	-1,
//	//	tempBuffer,
//	//	0);
//
//	//tempBuffer = realloc(tempBuffer, cnt * 2 + 4);
//
//	// convert to wide character (UTF16)
//	MultiByteToWideChar(CP_ACP,
//		0,
//		nativeString,
//		-1,
//		(LPWSTR)tempBuffer,
//		cnt);
//
//	// convert from wide char to UTF-8
//	WideCharToMultiByte(CP_UTF8,
//		0,
//		(LPCWCH)tempBuffer,
//		-1,
//		(LPSTR)destBuffer,
//		cnt,
//		NULL,
//		NULL);
//
//	MyFree(tempBuffer);
//#else
//	char * destBuffer = MyStrdup(nativeString);
//#endif
//
//	return(destBuffer);
//}

/**
 * Create the full path for temporary directories used in zip archive operations
 *
 * \param archive operation
 * \return pointer to full path, must be free'd by caller
 */

char *
GetZipDirectoryName(enum ArchiveOps op)
{
	char *opDesc;
	char *directory;
	DynString zipDirectory;

	DynStringMalloc(&zipDirectory, 0);

	switch (op) {
	case ARCHIVE_READ:
		opDesc = "in";
		break;
	case ARCHIVE_WRITE:
		opDesc = "out";
		break;
	default:
		opDesc = "err";
		break;
	}

	DynStringPrintf(&zipDirectory,
	                "%s" FILE_SEP_CHAR "zip_%s.%d",
	                workingDir,
	                opDesc,
	                getpid());

	directory = strdup(DynStringToCStr(&zipDirectory));
	DynStringFree(&zipDirectory);
	return (directory);
}

/*****************************************************************************
 * Add directory to archive
 *
 * \param IN zip 		The open zip archive handle
 * \param IN dir_path 	The path to add
 * \param IN prefix 	The prefix in the archive
 *
 * \returns 	TRUE if OK
 */

BOOL_T AddDirectoryToArchive(
        struct zip * za,
        const char * dir_path,
        const char * prefix)
{

	char *full_path;
	char *arch_path;
	DIR *dir;
	const char * buf;
	struct stat stat_path, stat_entry;
	struct dirent *entry;

	zip_source_t * zt;

	// stat for the path
	stat(dir_path, &stat_path);

	// if path does not exists or is not dir - exit with status -1
	if (S_ISDIR(stat_path.st_mode) == 0) {
		NoticeMessage(MSG_NOT_DIR_FAIL,
		              _("Continue"), NULL, dir_path);
		return FALSE;
	}

	// if not possible to read the directory for this user
	if ((dir = opendir(dir_path)) == NULL) {
		NoticeMessage(MSG_OPEN_DIR_FAIL,
		              _("Continue"), NULL, dir_path);
		return FALSE;
	}

	// iteration through entries in the directory
	while ((entry = readdir(dir)) != NULL) {
		// skip entries "." and ".."
		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
			continue;
		}

		// determinate a full path of an entry
		MakeFullpath(&full_path, dir_path, entry->d_name, NULL);

		// stat for the entry
		stat(full_path, &stat_entry);

		if (prefix && prefix[0]) {
			MakeFullpath(&arch_path, prefix, entry->d_name, NULL);
		} else {
			MakeFullpath(&arch_path, entry->d_name, NULL);
		}

		// recursively add a nested directory
		if (S_ISDIR(stat_entry.st_mode) != 0) {
			if (zip_dir_add(za, arch_path, 0) < 0) {
				zip_error_t  *ziperr = zip_get_error(za);
				buf = zip_error_strerror(ziperr);
				NoticeMessage(MSG_ZIP_DIR_ADD_FAIL,
				              _("Continue"), NULL, arch_path, buf);
#if DEBUG
				printf("Added Directory %s \n", arch_path);
#endif
			}

			if (AddDirectoryToArchive(za, full_path, arch_path) != TRUE) {
				free(full_path);
				free(arch_path);
				return FALSE;
			}
			free(arch_path);
			continue;
		} else {
			char *archPathUtf8 = MyStrdup(arch_path);
			char *fullPathUtf8 = MyStrdup(full_path);
#ifdef UTFCONVERT
			archPathUtf8 = Convert2UTF8(archPathUtf8);
			fullPathUtf8 = Convert2UTF8(fullPathUtf8);
			ConvertPathForward(archPathUtf8);
#endif // UTFCONVERT
			zt = zip_source_file(za, fullPathUtf8, 0, -1);
			if (zip_file_add(za, archPathUtf8, zt, ZIP_FL_ENC_UTF_8) == -1) {
				zip_error_t  *ziperr = zip_get_error(za);
				buf = zip_error_strerror(ziperr);
				NoticeMessage(MSG_ZIP_FILE_ADD_FAIL, _("Continue"), NULL, full_path, arch_path,
				              buf);
				free(full_path);
				free(arch_path);
				MyFree(fullPathUtf8);
				MyFree(archPathUtf8);
				return FALSE;
			}
			MyFree(fullPathUtf8);
			MyFree(archPathUtf8);
#if DEBUG
			printf("Added File %s", full_path);
#endif
		}
		free(arch_path);
		free(full_path);
	}

	closedir(dir);
	return TRUE;
}

/***********************************************************************
 * Create Archive
 *
 * \param IN dir_path The place to create the archive
 * \param IN fileName The name of the archive
 *
 * \return TRUE if ok
 */

BOOL_T CreateArchive(
        const char * dir_path,
        const char * fileName)
{
	struct zip *za;
	int err;
	zip_error_t error;

	char * archive = MyStrdup(fileName);  	// Because of const char
	char * archive_name = FindFilename(archive);
	char * archive_path;
	char * archiveUtf8;

	MakeFullpath(&archive_path, workingDir, archive_name, NULL);

	archiveUtf8 = MyStrdup(archive_path);
#ifdef UTFCONVERT
	archiveUtf8 = Convert2UTF8(archiveUtf8);
#endif // UTFCONVERT

	MyFree(archive);

	if ((za = zip_open(archiveUtf8, ZIP_CREATE, &err)) == NULL) {
		zip_error_init_with_code(&error, err);
		NoticeMessage(MSG_ZIP_CREATE_FAIL, _("Continue"), NULL, archiveUtf8,
		              zip_error_strerror(&error));
		zip_error_fini(&error);
		MyFree(archiveUtf8);
		return FALSE;
	}
#if DEBUG
	printf("====================== \n");
	printf("Started Archive %s", archive_path);
#endif

	AddDirectoryToArchive(za, dir_path, "");

	if (zip_close(za) == -1) {
		zip_error_init_with_code(&error, err);
		NoticeMessage(MSG_ZIP_CLOSE_FAIL, _("Continue"), NULL, archiveUtf8,
		              zip_error_strerror(&error));
		zip_error_fini(&error);
		free(archive_path);
		MyFree(archiveUtf8);
		return FALSE;
	}

	unlink(fileName); 							//Delete Old
	if (rename(archive_path, fileName) == -1) {	//Move zip into place
		if ( Copyfile( archive_path, fileName ) == -1 ) {
			NoticeMessage(MSG_ZIP_RENAME_FAIL, _("Continue"), NULL, archiveUtf8,
			              fileName, strerror(errno));
			free(archive_path);
			MyFree(archiveUtf8);
			return FALSE;
		} else {
			unlink( archive_path );
		}
	}
	free(archive_path);
	MyFree(archiveUtf8);

#if DEBUG
	printf("Moved Archive to %s", fileName);
	printf("====================== \n");
#endif
	return TRUE;
}

/**************************************************************************
 * Unpack_Archive_for
 *
 * \param IN pathName the name of the archive
 * \param IN fileName just the filename and extension of the layout
 * \param IN tempDir The directory to use to unpack into
 *
 * \returns TRUE if all worked
 */
BOOL_T UnpackArchiveFor(
        const char * pathName,  /*Full name of archive*/
        const char * fileName,  /*Layout name and extension */
        const char * tempDir,   /*Directory to unpack into */
        BOOL_T file_only)
{
	char *dirName;
	struct zip *za;
	struct zip_file *zf;
	struct zip_stat sb;
	char buf[100];
	zip_error_t error;
	int err;
	int i;
	int64_t len;
	FILE  *fd;
	long long sum;

	char *destBuffer = MyStrdup(pathName);
#ifdef UTFCONVERT
	destBuffer = Convert2UTF8(destBuffer);
#endif // UTFCONVERT


	if ((za = zip_open(destBuffer, 0, &err)) == NULL) {
		zip_error_init_with_code(&error, err);
		NoticeMessage(MSG_ZIP_OPEN_FAIL, _("Continue"), NULL, pathName,
		              zip_error_strerror(&error));
		fprintf(stderr, "xtrkcad: can't open xtrkcad zip archive `%s': %s \n",
		        pathName, zip_error_strerror(&error));
		zip_error_fini(&error);

		MyFree(destBuffer);
		return FALSE;
	}

	for (i = 0; i < zip_get_num_entries(za, 0); i++) {
		if (zip_stat_index(za, i, 0, &sb) == 0) {
			len = strlen(sb.name);

#if DEBUG
			printf("==================\n");
			printf("Name: [%s], ", sb.name);
			printf("Size: [%llu], ", sb.size);
			printf("mtime: [%u]\n", (unsigned int)sb.mtime);
			printf("mtime: [%u]\n", (unsigned int)sb.mtime);
#endif

			LOG(log_zip, 1, ("================= \n"))
			LOG(log_zip, 1, ("Zip-Name [%s] \n", sb.name))
			LOG(log_zip, 1, ("Zip-Size [%llu] \n", sb.size))
			LOG(log_zip, 1, ("Zip-mtime [%u] \n", (unsigned int)sb.mtime))

			if (sb.name[len - 1] == '/' && !file_only) {
				MakeFullpath(&dirName, tempDir, &sb.name[0], NULL);
				if (SafeCreateDir(dirName) != TRUE) {
					free(dirName);
					return FALSE;
				}
				free(dirName);
			} else {
				zf = zip_fopen_index(za, i, 0);
				if (!zf) {
					NoticeMessage(MSG_ZIP_INDEX_FAIL, _("Continue"), NULL);
					fprintf(stderr, "xtrkcad zip archive open index error \n");
					return FALSE;
				}

				if (file_only) {
					if (strncmp(sb.name, fileName, strlen(fileName)) != 0) {
						continue; /* Ignore any other files than the one we asked for */
					}
				}
				MakeFullpath(&dirName, tempDir, &sb.name[0], NULL);
#ifdef UTFCONVERT
				ConvertUTF8ToSystem(dirName);
#endif // UTFCONVERT
				fd = fopen(dirName, "wb");
				if (!fd) {
					NoticeMessage(MSG_ZIP_FILE_OPEN_FAIL, _("Continue"), NULL, dirName,
					              strerror(errno));
					free(dirName);
					return FALSE;
				}

				sum = 0;
				while (sum != sb.size) {
					len = zip_fread(zf, buf, 100);
					if (len < 0) {
						NoticeMessage(MSG_ZIP_READ_FAIL, _("Continue"), NULL, dirName, &sb.name[0]);
						free(dirName);
						fclose(fd);
						return FALSE;
					}
					fwrite(buf, 1, (unsigned int)len, fd);
					sum += len;
				}
				fclose(fd);
				free(dirName);
				zip_fclose(zf);
			}
		} else {
			LOG(log_zip, 1, ("Zip-Unknown File[%s] Line[%d] \n", __FILE__, __LINE__))
#if DEBUG
			printf("File[%s] Line[%d]\n", __FILE__, __LINE__);
#endif
		}
	}

	MyFree(destBuffer);

	if (zip_close(za) == -1) {
		NoticeMessage(MSG_ZIP_CLOSE_FAIL, _("Continue"), NULL, dirName, &sb.name[0]);
		return FALSE;
	}
	return TRUE;
}

