/*
 * Copyright (C) 1994-1998 T. Teranishi
 * (C) 2021- TeraTerm Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <stdio.h>
#include <windows.h>
#include <assert.h>
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <wchar.h>
#if (defined(_MSC_VER) && (_MSC_VER >= 1600)) || !defined(_MSC_VER)
#include <stdint.h>
#endif

#include "teraterm.h"
#include "tttypes.h"
#include "ttlib.h"
#include "tt_res.h"
#include "codeconv.h"
#include "compat_win.h"
#include "asprintf.h"
#include "ttcommon.h"

#include "ttcmn_dup.h"

#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef unsigned char uint8_t;
#endif

/**
 *	データシリアライズ情報構造体
 */
typedef struct {
	size_t size;		// データのサイズ(0で終了)
	size_t offset;		// 先頭からのオフセット
	enum {
		COPY = 0,				// データをコピーする
		MALLOCED_WSTRING = 1,	// mallocした領域の wchar_t 文字列
	} type;
} TSerializeInfo;

/**
 *	データをシリアライズする
 *
 *	@param		data	シリアライズするデータ
 *	@param		info	シリアライズ情報
 *	@param[out]	size_	シリアライズしたblobサイズ(byte)
 *	@return				シリアライズしたblobデータへのポインタ
 *						不要になったらfree()すること
 */
static uint8_t *SerializeData(const void *data, const TSerializeInfo *info, size_t *size_)
{
	const unsigned char *src = (unsigned char *)data;
	uint8_t *blob = NULL;
	size_t size = 0;
	while (info->size != 0) {
		switch (info->type) {
		default:
			assert(FALSE);
			// fall torught
		case TSerializeInfo::COPY: {
			const size_t l = info->size;
			unsigned char *d;
			const unsigned char *s = (unsigned char *)src  + info->offset;
			uint8_t *p = (uint8_t*)realloc(blob, size + l);
			if (p == NULL) {
				free(blob);
				return NULL;
			}
			blob = p;
			d = blob + size;
			memcpy(d, s, l);
			size += l;
			break;
		}
		case TSerializeInfo::MALLOCED_WSTRING: {
			wchar_t **s = (wchar_t **)((unsigned char *)src  + info->offset);
			const wchar_t *src_str = *s;
			size_t str_len = 0;
			if (src_str != NULL) {
				str_len = wcslen(src_str) + 1;
			}
			const size_t str_len_byte = str_len * sizeof(wchar_t);
			const size_t alloc_len = sizeof(size_t) + str_len_byte;
			uint8_t *p = (uint8_t*)realloc(blob, size + alloc_len);
			if (p == NULL) {
				free(blob);
				return NULL;
			}
			blob = p;
			uint8_t *d = blob + size;
			*(size_t *)d = str_len;
			d += sizeof(size_t);
			wmemcpy((wchar_t *)d, src_str, str_len);
			size += alloc_len;
			break;
		}
		}
		info++;
	}
	*size_ = size;
	return blob;
}

/**
 *	アンシリアライズする
 *
 *	@param		blob	シリアライズされたデータへのポインタ
 *	@param		size	シリアライズされたデータのサイズ
 *	@param		info	シリアライズ情報
 *	@param[out]	data	書き戻すデータ
 */
static void UnserializeData(const void *blob, size_t size, const TSerializeInfo *info,
							void *data)
{
	const unsigned char *src = (const unsigned char *)blob;
	const unsigned char *end = src + size;
	while (info->size != 0) {
		switch (info->type) {
		default:
			assert(FALSE);
			break;
		case TSerializeInfo::COPY: {
			unsigned char *d = (unsigned char *)data  + info->offset;
			const size_t l = info->size;
			memcpy(d, src, l);
			src += l;
			break;
		}
		case TSerializeInfo::MALLOCED_WSTRING: {
			wchar_t **d = (wchar_t **)((unsigned char *)data  + info->offset);
			assert(info->size == sizeof(wchar_t *));
			const size_t l = *(size_t *)src;
			src += sizeof(size_t);
			if (l == 0) {
				*d = NULL;
			}
			else {
				const size_t byte_len = l * sizeof(wchar_t);
				wchar_t *str = (wchar_t *)malloc(byte_len);
				assert(str != NULL);
				if (str != NULL) {
					wmemcpy(str, (wchar_t*)src, l);
				}
				*d = str;
				src += byte_len;
			}
			break;
		}
		};
		if (src > end) {
			// データがおかしい?
			assert(FALSE);
			break;
		}
		info++;
	}
}

static const uint8_t signature[] = {
	'T', 'E', 'R', 'A', 'T' , 'E', 'R', 'M',
	TT_VERSION_MAJOR,
	TT_VERSION_MINOR,
	sizeof(void *),		// 4 or 8 = 32bit or 64bit
	0,	// reserver
	(uint8_t)((sizeof(TTTSet) >> (8 * 0)) & 0xff),
	(uint8_t)((sizeof(TTTSet) >> (8 * 1)) & 0xff),
	(uint8_t)((sizeof(TTTSet) >> (8 * 2)) & 0xff),
	(uint8_t)((sizeof(TTTSet) >> (8 * 3)) & 0xff),
};

/**
 *	@return		0以外	スキップするサイズ
 *				0		error(異なった)
 */
static uint8_t *MakeSignature(size_t *size)
{
	uint8_t *p = (uint8_t *)malloc(sizeof(signature));
	assert(p != NULL);
	memcpy(p, signature, sizeof(signature));
	*size = sizeof(signature);
	return p;
}

/**
 *	@return		0以外	スキップするサイズ
 *				0		error(異なった)
 */
static size_t CheckSignature(const uint8_t *ptr, size_t size)
{
	if (size < sizeof(signature)) {
		return 0;
	}
	if (memcmp(ptr, signature, sizeof(signature)) != 0) {
		return 0;
	}
	return sizeof(signature);
}

#if !defined(offsetof)
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
#endif

#define MALLOCED_WSTRING_INFO(st, member) \
	sizeof(((st *)0)->member),			  \
	offsetof(st, member),			  \
	TSerializeInfo::MALLOCED_WSTRING

/**
 *	TTTsetを1つのバイナリにするための情報
 *	  方針
 *		- 全体をコピー
 *		- 動的確保された文字列を追加
 */
static const TSerializeInfo serialize_info[] = {
	{ sizeof(TTTSet), 0, TSerializeInfo::COPY},	// 全体をコピー
	{ MALLOCED_WSTRING_INFO(TTTSet, HomeDirW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, SetupFNameW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, KeyCnfFNW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, MacroFNW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, UILanguageFileW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, UILanguageFileW_ini) },
	{ MALLOCED_WSTRING_INFO(TTTSet, ExeDirW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, LogDirW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, LogDefaultPathW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, FileDirW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, EtermLookfeel.BGThemeFileW) },
	{ MALLOCED_WSTRING_INFO(TTTSet, EtermLookfeel.BGSPIPathW) },
	{ 0, 0, TSerializeInfo::COPY },
};

/**
 *	TTTSet 構造体をバイナリデータに変換
 *
 *	@param[in]		ts	TTTSet構造体へのポインタ
 *	@param[out]		バイナリデータのサイズ
 *	@return			バイナリデータへポインタ
 */
void *TTCMNSerialize(const TTTSet *ts, size_t *size)
{
	size_t signature_size;
	uint8_t *signature_data = MakeSignature(&signature_size);
	size_t data_size;
	uint8_t *data = SerializeData(ts, serialize_info, &data_size);

	uint8_t *dest = (uint8_t *)malloc(signature_size + data_size);
	assert(dest != NULL);
	memcpy(dest, signature_data, signature_size);
	memcpy(dest + signature_size, data, data_size);
	free(data);
	free(signature_data);
	*size = signature_size + data_size;
	return dest;
}

/**
 *	バイナリデータをTTTSet 構造体に変換、
 *
 *	@param[in]	ptr		バイナリデータへのポインタ
 *	@param[in]	size	バイナリデータのサイズ
 *	@param[out]	ts		TTTSet構造体へのポインタ
 */
void TTCMNUnserialize(const void *ptr, size_t size, TTTSet *ts)
{
	const uint8_t *data = (uint8_t *)ptr;
	size_t signature_size = CheckSignature(data, size);
	if (signature_size != 0) {
		data += signature_size;
		size -= signature_size;
		UnserializeData(data, size, serialize_info, ts);
	}

	// ログファイル名は複製元と同一にはできないので引き継いでいない
	// ポインタとパス(ANSI)をクリアする
	ts->LogFNW = NULL;
	ts->LogFN[0] = 0;
}
