/*
 * Sample Program for TextPorter
 *
 * Copyright (C) 2009, AntennaHouse Inc.  All Rights Reserved
 *
 * 2009.10.28 Takeshi Yoneki
 *
 * Revised to return error count as exit code.
 * 2012/12/13 by show
 */
/*
	-a パラメタファイル
	コマンドラインで記述するパラメタをファイルに書きそれを指定する機能。
	各パラメタは改行で区切る。
	例えば
	app_ww C:\in\sample.zip -p DMC_GETTEXT_OPT1_COMPRESS -p DMC_GETTEXT_OPT1_COMPRESS2 -t C:\out
	と同じ機能は 
	param.dmc
	#-------------
	C:\in\sample.zip
	-p DMC_GETTEXT_OPT1_COMPRESS
	-p DMC_GETTEXT_OPT1_COMPRESS2
	-t C:\out
	#-------------
	だとして
	app_ww -a param.dmc
	で実現できる。
	各パラメタは行で区切る。
	パラメタファイルはANSI、UTF-16、UTF-8を受け付ける。
	UNICODEは必ずBOMを入れること。
	（この機能はANSIでは表現できないファイル名を試験で扱うために用意）
	#で始まる行はコメント。

	-eと-mでyes/no、on/offなども通るようにした。
	-fで関数の名前も通るようにした。

	2009.10.30 Takeshi Yoneki
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <fstream>
#include "text_oem.h"
#include "thr_func.h"

#if defined DMC_WIN
	#include <windows.h>
	#include <process.h>
	#include <tchar.h>
	#include <time.h>
#else
	#include <unistd.h>
#endif

/* For Multithread Application */
#define MAX_THREAD_INTERVAL	3
#define MAX_THREAD_NUM		50

void Usage(void);

typedef RESULT_TYPE (CALLING *FuncPtr)(void*);

struct SOptList {
	const char *m_name;
	DWord m_value;
} theOptList[] = {
	"DMC_GETTEXT_OPT_KEISEN", DMC_GETTEXT_OPT_KEISEN,
	"DMC_GETTEXT_OPT_TAG", DMC_GETTEXT_OPT_TAG,
	"DMC_GETTEXT_OPT_CRLF", DMC_GETTEXT_OPT_CRLF,
	"DMC_GETTEXT_OPT_CR", DMC_GETTEXT_OPT_CR,
	"DMC_GETTEXT_OPT_LF", DMC_GETTEXT_OPT_LF,
	"DMC_GETTEXT_OPT_U2028", DMC_GETTEXT_OPT_U2028,
	"DMC_GETTEXT_OPT_U2029", DMC_GETTEXT_OPT_U2029,
	"DMC_GETTEXT_OPT_SHEET", DMC_GETTEXT_OPT_SHEET,
	"DMC_GETTEXT_OPT_RUBI", DMC_GETTEXT_OPT_RUBI,
	"DMC_GETTEXT_OPT_PWD", DMC_GETTEXT_OPT_PWD,
	"DMC_GETTEXT_OPT_OLE", DMC_GETTEXT_OPT_OLE,
	"DMC_GETTEXT_OPT_OLE1", DMC_GETTEXT_OPT_OLE1,
	"DMC_GETTEXT_OPT_OLE2", DMC_GETTEXT_OPT_OLE2,
	"DMC_GETTEXT_OPT_OLE3", DMC_GETTEXT_OPT_OLE3,
	"DMC_GETTEXT_OPT_OUT", DMC_GETTEXT_OPT_OUT,
	"DMC_GETTEXT_OPT_LOOP", DMC_GETTEXT_OPT_LOOP,
	"DMC_GETTEXT_OPT_SHEET1", DMC_GETTEXT_OPT_SHEET1,
	"DMC_GETTEXT_OPT_CELL", DMC_GETTEXT_OPT_CELL,
	"DMC_GETTEXT_OPT_SIZE", DMC_GETTEXT_OPT_SIZE,
	"DMC_GETTEXT_OPT_CSV1", DMC_GETTEXT_OPT_CSV1,
	"DMC_GETTEXT_OPT_CSV2", DMC_GETTEXT_OPT_CSV2,
	"DMC_GETTEXT_OPT_PDFSYM", DMC_GETTEXT_OPT_PDFSYM,
	"DMC_GETTEXT_OPT_OWNERPWD1", DMC_GETTEXT_OPT_OWNERPWD1,
	"DMC_GETTEXT_OPT_OWNERPWD2", DMC_GETTEXT_OPT_OWNERPWD2,
	"DMC_GETTEXT_OPT_OWNERPWD3", DMC_GETTEXT_OPT_OWNERPWD3,
	"DMC_GETTEXT_OPT_OWNERPWD4", DMC_GETTEXT_OPT_OWNERPWD4,
	"DMC_GETTEXT_OPT_ENDCODE", DMC_GETTEXT_OPT_ENDCODE,
	"DMC_GETTEXT_OPT_NULL", DMC_GETTEXT_OPT_NULL,
	"DMC_GETTEXT_OPT_SHFTAG", DMC_GETTEXT_OPT_SHFTAG,
	"DMC_GETTEXT_OPT_SHFHEAD", DMC_GETTEXT_OPT_SHFHEAD,
	NULL, 0
};

struct SOpt1List {
	const char *m_name;
	DWord m_value;
} theOpt1List[] = {
	"DMC_GETTEXT_OPT1_OWNERPWD5", DMC_GETTEXT_OPT1_OWNERPWD5,
	"DMC_GETTEXT_OPT1_TEMP", DMC_GETTEXT_OPT1_TEMP,
	"DMC_GETTEXT_OPT1_INSERTF", DMC_GETTEXT_OPT1_INSERTF,
	"DMC_GETTEXT_OPT1_INSERTF1", DMC_GETTEXT_OPT1_INSERTF1,
	"DMC_GETTEXT_OPT1_INSERTF2", DMC_GETTEXT_OPT1_INSERTF2,
	"DMC_GETTEXT_OPT1_INSERTF3", DMC_GETTEXT_OPT1_INSERTF3,
	"DMC_GETTEXT_OPT1_COMPRESS", DMC_GETTEXT_OPT1_COMPRESS,
	"DMC_GETTEXT_OPT1_COMPRESS1", DMC_GETTEXT_OPT1_COMPRESS1,
	"DMC_GETTEXT_OPT1_COMPRESS2", DMC_GETTEXT_OPT1_COMPRESS2,
	"DMC_GETTEXT_OPT1_COMPRESS3", DMC_GETTEXT_OPT1_COMPRESS3,
	"DMC_GETTEXT_OPT1_COMPRESS4", DMC_GETTEXT_OPT1_COMPRESS4,
	"DMC_GETTEXT_OPT1_TRACK", DMC_GETTEXT_OPT1_TRACK,
	"DMC_GETTEXT_OPT1_COMPRESS5", DMC_GETTEXT_OPT1_COMPRESS5,
	"DMC_GETTEXT_OPT1_INSERTF4", DMC_GETTEXT_OPT1_INSERTF4,
	"DMC_GETTEXT_OPT1_TXCONV", DMC_GETTEXT_OPT1_TXCONV,
	"DMC_GETTEXT_OPT1_TXCONV2", DMC_GETTEXT_OPT1_TXCONV2,
	"DMC_GETTEXT_OPT1_OUTPUT_RAW_NL", DMC_GETTEXT_OPT1_OUTPUT_RAW_NL,
	"DMC_GETTEXT_OPT1_QUOTE_QQ", DMC_GETTEXT_OPT1_QUOTE_QQ,
	NULL, 0
};

class CCmdInfo {
public:
	std::string m_sDstDir;
	std::string m_oFile;	// option -o
	std::string m_GroupName;
	std::string m_DefLangName;
	BOOL m_bBigEndian;
	DWord m_Option;
	DWord m_Option1;
	int m_pages;
	std::string m_password;
	BOOL m_bMultiThread;
	DWord m_size;
	Word m_Csv_c;
	int m_nThreadInterval;
	int m_nThreadNumber;
	FUNC_TYPE m_funcType;
	std::vector<std::string> m_files;
	PARAM_ENCODE m_paramEncode;
	int m_loop;

public:
	CCmdInfo();
	~CCmdInfo();
	static BOOL CmdArgToArgs(int argc, char** argv, std::vector<std::string> *args);
	PARAM_ENCODE LoadParamFile(std::string path, std::vector<std::string> *args);
	static BOOL StringToBOOL(std::string value);
	static std::string TransLang(std::string value);
	static BOOL StringToOption(DWord *o0, DWord *o1, std::string value);
	static FUNC_TYPE StringToFuncType(std::string value);
	void AddPathBreak(char *path);
	BOOL LoadParamFile(std::string);
	BOOL ParseCmdLine(std::vector<std::string> *args);
	void MakeTextInfoV5(DMC_TEXTINFO_V5* pTextInfo_V5);
	void MakeTextInfoV4(DMC_TEXTINFO_V4* pTextInfo_V4);
	void MakeThreadData(CThreadCall* tc, std::string inFilePath);
	std::string MakeOutputFilePath(std::string inFilePath);
	BOOL CallFunction();
	BOOL CallMTFunction();
	void SplitPath(const char *path, char *drive, char *dir, char *fname, char *ext);
	void MakePath(char *path, const char *drive, const char *dir, const char *fname, const char *ext);
};

CCmdInfo::CCmdInfo() {
	m_sDstDir = "";
	m_oFile = "";
	m_GroupName = "Shift_JIS";
	m_DefLangName = "Japanese";
	m_bBigEndian = DMC_TRUE;
	m_Option = 0;
	m_Option1 = 0;
	m_pages = 0;
	m_password = "";
	m_bMultiThread = DMC_FALSE;
	m_size = 0;
	m_Csv_c = 0;
	m_nThreadInterval = MAX_THREAD_INTERVAL;
	m_nThreadNumber = MAX_THREAD_NUM;
	m_funcType = FT_GET_TEXT_V5;
	m_paramEncode = PE_ASIS;
	m_loop = 1;
}

CCmdInfo::~CCmdInfo() {
}


// static
BOOL CCmdInfo::CmdArgToArgs(int argc, char** argv, std::vector<std::string> *args) {
	if (argc < 2 || argv == NULL || args == NULL)
		return DMC_FALSE;
	int i;
	for (i = 1; i < argc; i++) {
		args->push_back(argv[i]);
	}
	return DMC_TRUE;
}

// static
BOOL CCmdInfo::StringToBOOL(std::string value) {
	return (
		value == "1" ||
		value == "TRUE" || value == "true" || value == "True" ||
		value == "YES" || value == "yes" || value == "Yes" ||
		value == "GOOD" || value == "good" || value == "Good" ||
		value == "OK" || value == "Ok" || value == "ok" ||
		value == "ON" || value == "On" || value == "on");
}

// static
std::string CCmdInfo::TransLang(std::string value) {
	if (value == "jp" || value == "JP")
		return "Japanese";
	if (value == "cn" || value == "CN")
		return "Simplified Chinese";
	if (value == "tw" || value == "TW")
		return "Traditional Chinese";
	if (value == "ko" || value == "KO")
		return "Korean";
	if (value == "en" || value == "EN")
		return "English";
	return value;
}

// static
BOOL CCmdInfo::StringToOption(DWord *o0, DWord *o1, std::string value) {
	int i;
	BOOL done = DMC_FALSE; 
	for (i = 0; theOptList[i].m_name != NULL; i++) {
		if (value == theOptList[i].m_name) {
			*o0 |= theOptList[i].m_value;
			done = DMC_TRUE;
			break;
		}
	}
	if (!done) {
		for (i = 0; theOpt1List[i].m_name != NULL; i++) {
			if (value == theOpt1List[i].m_name) {
				*o1 |= theOpt1List[i].m_value;
				done = DMC_TRUE;
				break;
			}
		}
	}
	return done;
}

// static
FUNC_TYPE CCmdInfo::StringToFuncType(std::string value) {
	if (value.length() > 0) {
		if (isdigit(value[0])) {
			int fn = atoi(value.c_str()); 
			if (strlen(GetFuncNameByFuncType((FUNC_TYPE)fn)) > 0)
				return (FUNC_TYPE)fn;
		}
		else {
			return GetFuncTypeByFuncName(value.c_str());
		}
	}
	return (FUNC_TYPE)0;
}

PARAM_ENCODE CCmdInfo::LoadParamFile(std::string path, std::vector<std::string> *args) {
	struct stat status;
	PARAM_ENCODE encode = PE_ASIS;
	if (stat(path.c_str(), &status) == -1)
		return PE_NONE;
	if (status.st_size == 0) {
		return PE_NONE;
	}
	FILE *fp = fopen(path.c_str(), "rb");
	if (fp == NULL)
		return PE_NONE;
	unsigned char *buffer = new unsigned char[status.st_size + 2];
	size_t rc = fread(buffer, 1, status.st_size, fp);
	buffer[status.st_size] = 0;
	buffer[status.st_size + 1] = 0;
	fclose(fp);
	
	if (status.st_size < 3) {
		encode = PE_ASIS;
	}
	else {
#ifdef DMC_WIN
		if (buffer[0] == 0xFF && buffer[1] == 0xFE) {
			encode = PE_UTF16LE;
		}
		else if (buffer[0] == 0xFE && buffer[1] == 0xFF) {
			encode = PE_UTF16BE;
		}
		else if (buffer[0] == 0xEF && buffer[1] == 0xBB || buffer[2] == 0xBF) {
			encode = PE_UTF8;
		}
		else if (buffer[0] == 0 && buffer[1] != 0) {
			encode = PE_UTF16BE;
		}
		else if (buffer[0] != 0 && buffer[1] == 0) {
			encode = PE_UTF16LE;
		}
#endif
	}
#ifdef DMC_WIN
	if (encode == PE_UTF16BE) {
		int i;
		for (i = 0; i < status.st_size; i += 2) {
			unsigned char t = buffer[i];
			buffer[i] = buffer[i + 1];
			buffer[i + 1] = t;
		}
		encode = PE_UTF16LE;
	}
	if (encode == PE_UTF16LE) {
		int buffer8Size = status.st_size * 3 + 2;
		unsigned char *buffer8 = new unsigned char[buffer8Size];
		memset(buffer8, 0, buffer8Size);
		TransUTF16ToUTF8((char *)buffer8, (wchar_t *)buffer, buffer8Size);
		delete [] buffer;
		buffer = buffer8;
		encode = PE_UTF8;
	}
#endif
	if (encode != PE_ASIS && encode != PE_UTF8) {
		delete [] buffer;
		return PE_NONE;
	}
	char *s = NULL;
	int p = 0;
	if (status.st_size >= 3 && encode == PE_UTF8) {
		if (buffer[0] == 0xEF && buffer[1] == 0xBB || buffer[2] == 0xBF) {
			p = 3;
		}
	}
	for (;; p++) {
		if (buffer[p] == '#' && s == NULL) {
			// コメントスキップ
			while (1) {
				p++;
				if (buffer[p] == 0 || buffer[p] == '\r' || buffer[p] == '\n')
					break;
			}
		}
		if (buffer[p] == 0 || 
			(buffer[p] == '\r' || buffer[p] == '\n') || 
			(buffer[p] == ' ' && s != NULL && s[0] == '-')) {
			BOOL done = buffer[p] == 0;
			buffer[p] = 0;
			if (s != NULL) {
				args->push_back(s);
				s = NULL;
			}
			if (done)
				break;
		}
		else {
			if (s == NULL)
				s = (char *)&buffer[p];
		}
	}
	delete [] buffer;
	if (args->size() == 0)
		return PE_NONE;
	return encode;
}

BOOL CCmdInfo::ParseCmdLine(std::vector<std::string> *args) {
	int i, len;

	if (args == NULL || args->size() == 0)
		return DMC_FALSE;

	for(i = 0; i < (int)args->size(); i++) {
		if (args->at(i)[0] == '-') {
			len = args->at(i).length();
			if(len < 2) {
				return DMC_FALSE;
			}
			else if (len == 2) {
				if (i >= (int)args->size() - 1)
					return DMC_FALSE;
				if (args->at(i + 1)[0] == '-')
					return DMC_FALSE;
			}
			std::string value;
			if (len == 2)
				 value = args->at(i + 1);
			else
				value = &args->at(i).c_str()[2];
			//std::string value = len == 2 ? args->at(i + 1) : &args->at(i).c_str()[2];
			BOOL bInc = len == 2;
			switch(args->at(i)[1]) {
			case 't':
			case 'T':
				m_sDstDir = value;
				break;
			case 'o':
			case 'O':
				m_oFile = value;
				break;
			case 'g':
			case 'G':
				m_GroupName = value;
				break;
			case 'w':
			case 'W':
				m_password = value;
				break;
			case 'e':
			case 'E':
				m_bBigEndian = StringToBOOL(value);
				break;
			case 'd':
			case 'D':
				m_DefLangName = TransLang(value);
				break;
			case 'p':
				StringToOption(&m_Option, &m_Option1, value);
				break;
			case 'P':	/* old method */
				m_Option = atoi(value.c_str());
				break;
			case 'm':
			case 'M':
				m_bMultiThread = StringToBOOL(value);
				break;
			case 'f':
			case 'F':
				m_funcType = StringToFuncType(value);
				break;
			case 'n':
			case 'N':
				m_pages = atoi(value.c_str());
				break;
			case 'l':
			case 'L':
				m_loop = atoi(value.c_str());
				break;
			case 's':
			case 'S':
				m_size = atoi(value.c_str());
				if (m_size > 0x80000000)
					m_size = 0x7fffffff;
				break;
			case 'c':
			case 'C':
				m_Csv_c = atoi(value.c_str());
				break;
			case 'h':
			case 'H':
				if (len >= 3) {
					if (len == 3)
						value = args->at(i + 1);
					else
						value = &args->at(i).c_str()[3];
					// Solaris SP でバスエラーが発生。
					// オプティマイズの関係か？　std::stringと三項演算子の相性が悪いのかもしれない。
					// 2012.07.24 Takeshi Yoneki
					//value = len == 3 ? args->at(i + 1) : &args->at(i).c_str()[3];
					bInc = len == 3;
					switch(args->at(i)[2]){
					case 'i':
					case 'I':
						m_nThreadInterval = atoi(value.c_str());
						break;
					case 'n':
					case 'N':
						m_nThreadNumber = atoi(value.c_str());
						break;
					default:
						return DMC_FALSE;
					}
				}
				break;
			case 'a':
				{
					std::vector<std::string> args;
					PARAM_ENCODE enc = LoadParamFile(value, &args);
					if (enc != PE_NONE) {
						m_paramEncode = enc;
						return ParseCmdLine(&args);
					}
				}
				break;
			default:
				return DMC_FALSE;
			}
			if (bInc)
				i++;
		}
		else {
			m_files.push_back(args->at(i));
		}
	}

	if (m_files.size() == 0)
		return DMC_FALSE;
	if (m_Option == 0)
		m_Option= DMC_GETTEXT_OPT_CRLF;

	if (m_sDstDir.length() > 0) {
		char path[MAX_PATH_LEN];
		strcpy(path, m_sDstDir.c_str());
		AddPathBreak(path);
		m_sDstDir = path;
	}

	return DMC_TRUE;
}

// パスの最後にパスデリミタがなければ、追加する
void CCmdInfo::AddPathBreak(char *path) {
	int n = strlen(path);
#if defined DMC_WIN
	if (m_paramEncode == PE_UTF8) {
		if (n > 0 && path[n-1] != '\\')
			strcat(path, "\\");
	}
	else {
		char *peos = path + n - 1;
		if (n > 0 && _tcsrchr(path, '\\') != peos && _tcsrchr(path, '/') != peos && _tcsrchr(path, ':') != peos) {
			*(path + n++) = '\\';
			*(path + n) = '\0';
		}
	}
#else
	if (n > 0 && path[n-1] != '/')
		strcat(path, "/");
#endif
}

void CCmdInfo::SplitPath(const char *path, char *drive, char *dir, char *fname, char *ext) {
#ifdef DMC_WIN
	if (m_paramEncode == PE_UTF8) {
		wchar_t path16[MAX_PATH_LEN];
		wchar_t drive16[MAX_PATH_LEN];
		wchar_t dir16[MAX_PATH_LEN];
		wchar_t fname16[MAX_PATH_LEN];
		wchar_t ext16[MAX_PATH_LEN];
		path16[0] = 0;
		drive16[0] = 0;
		dir16[0] = 0;
		fname16[0] = 0;
		ext16[0] = 0;
		TransUTF8ToUTF16(path16, path, MAX_PATH_LEN);
		_wsplitpath(path16, drive16, dir16, fname16, ext16);
		TransUTF16ToUTF8(drive, drive16, MAX_PATH_LEN);
		TransUTF16ToUTF8(dir, dir16, MAX_PATH_LEN);
		TransUTF16ToUTF8(fname, fname16, MAX_PATH_LEN);
		TransUTF16ToUTF8(ext, ext16, MAX_PATH_LEN);
	}
	else {
		_splitpath(path, drive, dir, fname, ext);
	}
#else
	char *p = (char *)path, *ep;
	if (drive)
		*drive = '\0';
	if (dir)
		*dir = '\0';
	if (fname)
		*fname = '\0';
	if (ext)
		*ext = '\0';
	if (strlen(path) >= MAX_PATH_LEN)
		return;
	// 最後の'/'以前の文字列をディレクトリとする
	ep = strrchr(p, '/');
	if (ep != NULL) {
		int len = ep - p + 1;
		if (dir) {
			strncpy(dir, p, len);
			dir[len] = 0;
		}
		p = ep + 1;
	}
	if ((ep = strrchr(p, '.')) != NULL) {
		// 拡張子あり
		if (fname) {
			strncpy(fname, p, ep - p);
			*(fname + (ep - p)) = '\0';
		}
		if (ext)
			strcpy(ext, ep);
	} else {
		if (fname)
			strcpy(fname, p);
	}
#endif
}

void CCmdInfo::MakePath(char *path, const char *drive, const char *dir, const char *fname, const char *ext) {
#ifdef DMC_WIN
	if (m_paramEncode == PE_UTF8) {
		wchar_t path16[MAX_PATH_LEN];
		wchar_t drive16[MAX_PATH_LEN];
		wchar_t dir16[MAX_PATH_LEN];
		wchar_t fname16[MAX_PATH_LEN];
		wchar_t ext16[MAX_PATH_LEN];
		path16[0] = 0;
		drive16[0] = 0;
		dir16[0] = 0;
		fname16[0] = 0;
		ext16[0] = 0;
		TransUTF8ToUTF16(drive16, drive, MAX_PATH_LEN);
		TransUTF8ToUTF16(dir16, dir, MAX_PATH_LEN);
		TransUTF8ToUTF16(fname16, fname, MAX_PATH_LEN);
		TransUTF8ToUTF16(ext16, ext, MAX_PATH_LEN);
		_wmakepath(path16, drive16, dir16, fname16, ext16);
		TransUTF16ToUTF8(path, path16, MAX_PATH_LEN);
	}
	else {
		_makepath(path, drive, dir, fname, ext);
	}
#else
	if (dir && *dir) {
		int l;
		strcpy(path, dir);
		l = strlen(path);
		if (*(path + l - 1) != '/') {
			*(path + l) = '/';
			*(path + l + 1) = '\0';
		}
	} else
		*path = '\0';
	if (fname)
		strcat(path, fname);
	if (ext) {
		if (*ext != '.')
			strcat(path, ".");
		strcat(path, ext);
	}
#endif
}

std::string CCmdInfo::MakeOutputFilePath(std::string inFilePath) {
	char path[MAX_PATH_LEN];
	char drive[MAX_PATH_LEN];
	char dir[MAX_PATH_LEN];
	char fname[MAX_PATH_LEN];
	char ext[MAX_PATH_LEN];
	if (m_oFile.length() > 0) {
		if (m_sDstDir.length() > 0) {
			SplitPath(m_oFile.c_str(), NULL, NULL, fname, ext);
			SplitPath(m_sDstDir.c_str(), drive, dir, NULL, NULL);
			MakePath(path, drive, dir, fname, ext);
			return path;
		}
		else {
			return m_oFile;
		}
	}
	else {
		if (m_sDstDir.length() > 0) {
			SplitPath(inFilePath.c_str(), NULL, NULL, fname, NULL);
			SplitPath(m_sDstDir.c_str(), drive, dir, NULL, NULL);
			MakePath(path, drive, dir, fname, ".txt");
			return path;
		}
		else {
			SplitPath(inFilePath.c_str(), drive, dir, fname, ext);
			std::string e = ext;
			if (e == ".txt" || e == ".TXT") {
				std::string f = fname;
				f += "_txt";
				MakePath(path, drive, dir, f.c_str(), ".txt");
			}
			else {
				MakePath(path, drive, dir, fname, ".txt");
			}
			return path;
		}
	}
}

void CCmdInfo::MakeTextInfoV5(DMC_TEXTINFO_V5* pTextInfo_V5) {
	strcpy((char *)pTextInfo_V5->GroupName, (const char *)(m_GroupName.c_str()));
	strcpy((char *)pTextInfo_V5->DefLangName,(const char *)(m_DefLangName.c_str()));
	pTextInfo_V5->bBigEndian = m_bBigEndian;
	pTextInfo_V5->Option = m_Option;
	pTextInfo_V5->Option1 = m_Option1;
	pTextInfo_V5->Size = m_size;
	pTextInfo_V5->Csv_c = m_Csv_c;
}

void CCmdInfo::MakeTextInfoV4(DMC_TEXTINFO_V4* pTextInfo_V4) {
	strcpy((char *)pTextInfo_V4->GroupName, (const char *)(m_GroupName.c_str()));
	strcpy((char *)pTextInfo_V4->DefLangName,(const char *)(m_DefLangName.c_str()));
	pTextInfo_V4->bBigEndian = m_bBigEndian;
	pTextInfo_V4->Option = m_Option;
	pTextInfo_V4->Option1 = m_Option1;
	pTextInfo_V4->Size = m_size;
	pTextInfo_V4->Csv_c = m_Csv_c;
}

void CCmdInfo::MakeThreadData(CThreadCall* tc, std::string inFilePath) {
	MakeTextInfoV5(&tc->m_textInfo_V5);
	MakeTextInfoV4(&tc->m_textInfo_V4);
	tc->m_pages = m_pages;
	tc->m_password = m_password;
	tc->m_appfile = inFilePath;
	tc->m_txtfile = MakeOutputFilePath(inFilePath);
	tc->m_bFinished = DMC_FALSE;
	tc->m_paramEncode = m_paramEncode;
	tc->m_funcType = m_funcType;
	tc->m_option = m_Option;
	tc->m_option1 = m_Option1;
	// m_iRet is added for error checking. // 2012/12/13 by show
	tc->m_iRet = 0;
}

BOOL CCmdInfo::CallFunction() {
	int i;
	CThreadCall tc;
	int errCnt = 0; // error checking. 2012/12/13 by show

	for(i = 0; i < (int)m_files.size(); i++) {
		DMC_FILEINFO fileInfo;
		DMC_TEXTINFO_V5 textInfo_V5;
		memset(&textInfo_V5, 0, sizeof(textInfo_V5));
		memset(&fileInfo, 0, sizeof(fileInfo));
		MakeTextInfoV5(&textInfo_V5);
		CCmdFilePath cfPath(m_files[i], m_paramEncode);
		int iRet;
#ifdef DMC_WIN
		if (m_paramEncode == PE_UTF8)
			iRet = DMC_GetFileInfo_V5W(cfPath, &fileInfo, &textInfo_V5);
		else
			iRet = DMC_GetFileInfo_V5(cfPath, &fileInfo, &textInfo_V5);
#else
		iRet = DMC_GetFileInfo_V5(cfPath, &fileInfo, &textInfo_V5);
#endif
		if(iRet == 0) {
			printf("\nDetect file ");
			cfPath.print();
			printf("\n");
			printf("\tDocFormat: \"%s\"\n", fileInfo.DocFormat);
			printf("\tDocSubFormat: \"%s\"\n", fileInfo.DocSubFormat);
			printf("\tDocCountry: \"%s\"\n", fileInfo.DocCountry);
			printf("\tProtectCode: %d\n", fileInfo.ProtectCode);
			printf("\tFileType: %d\n", fileInfo.FileType);
		}
		else {
			printf("\nDetect file ");
			cfPath.print();
			printf("\n");
			printf("DMC_GetFileInfo() return error code %d.\n", iRet);
			errCnt++;
			continue;
		}
		// call function
		MakeThreadData(&tc, m_files[i]);
		ThreadMain(&tc);
		// error checking. 2012/12/13 by show
		if (tc.m_iRet != 0) {
			errCnt++;
		}
	}
	return errCnt == 0;
}

class CThreadHandle {
public:
#if defined DMC_WIN
	HANDLE   m_h;
	unsigned m_thread_id;
#else
	pthread_t m_h;
#endif

	CThreadCall* m_tc; // error checking. 2012/12/13 by show

public:
	CThreadHandle() {
		m_h = 0;
		m_tc = 0;
	}
	BOOL Begin(CThreadCall *data) {
		m_tc = data;
#if defined DMC_WIN
		m_h = (HANDLE)_beginthreadex(NULL, 0, ThreadMain, data, 0, &m_thread_id);
		return m_h != 0;
#else
		int iRet;
		iRet = pthread_create(&m_h, NULL, ThreadMain, data);
		return iRet == 0;
#endif
	}
	unsigned int GetID() {
#if defined DMC_WIN
		return m_thread_id;
#else
		return (unsigned int)m_h;
#endif
	}
	void Sleep(int interval) {
#if defined DMC_WIN
		::Sleep(interval * 1000); //default: sleep for 3 seconds
#else
		sleep(interval); //default: sleep for 3 seconds
#endif
	}
	BOOL Join() {
#if defined DMC_WIN
		// error checking. 2012/12/13 by show
		DWORD dwRet = WaitForSingleObject(m_h, INFINITE);
		BOOL bRet = CloseHandle(m_h);
		return dwRet == WAIT_OBJECT_0 && bRet && m_tc->m_iRet == 0;
#else
		// error checking. 2012/12/13 by show
		int iRet = pthread_join(m_h, NULL);
		return iRet == 0 && m_tc->m_iRet == 0;
#endif
	}
};

BOOL CCmdInfo::CallMTFunction() {
	int i, m;
	CThreadCall *tc = NULL;
	int thrunnum, total_thread_count;
	int nThInterval = MAX_THREAD_INTERVAL;
	int nThNumber = MAX_THREAD_NUM;
	CThreadHandle *th;

	nThInterval = m_nThreadInterval <= 0 ? 0 : m_nThreadInterval;
	if (m_nThreadNumber > 0) 
		nThNumber = m_nThreadNumber;

	printf("\nSetting %d threads. Max number of running thread = %d\n", m_files.size(), nThNumber);
	tc = new CThreadCall[m_files.size()];
	if (tc == NULL) {
		printf("Not enough memory.\n");
		return DMC_FALSE;
	}
	th = new CThreadHandle[m_files.size()];
	if (!th) {
		printf("Not enough memory.\n");
		return DMC_FALSE;
	}
	total_thread_count = 0;
	thrunnum = 0;
	int errCnt = 0; // error checking. 2012/12/13 by show

	/* Create every thread */
	printf("Start creating and executing threads.\n");
	for(i = 0; i < (int)m_files.size(); i++) {
		CCmdFilePath cfPath(m_files[i], m_paramEncode);
		MakeThreadData(&tc[i], m_files[i]);
		/* create and suspend */
		if (!th[i].Begin(&tc[i])) {
			printf("Failed to create thread!\n");
			return DMC_FALSE;
		}
		printf("Created thread: ID = %x, number = %d, file = ", th[i].GetID(), i);
		cfPath.print();
		printf("\n");
		
		total_thread_count++;
		if (nThInterval > 0) {
			printf("Main thread sleep %d seconds.\n", nThInterval);
			th[i].Sleep(nThInterval);
		}
		if(i > 0 && ((i + 1) % nThNumber) == 0){
			printf("Main thread is waiting for termination of %d threads.\n", nThNumber);
			for( m=thrunnum; m <= i; m++ ) {
				if (th[m].m_h != 0 && !tc[m].m_bFinished == DMC_TRUE){
					if (!th[m].Join()) {
						errCnt++;
					}
					tc[m].m_bFinished = DMC_TRUE;
					printf("Thread number %d finished.\n", m);
				}
			}
			thrunnum = i;
		}
	}
	printf("Total count of created threads = %i\n", total_thread_count);
	/* Wait for all thread terminate then main() can free resource and return */
	printf("Main thread is waiting for termination of all threads.\n");
	for(m = 0; m < (int)m_files.size(); m++) {
		if (th[m].m_h != 0 && !tc[m].m_bFinished == DMC_TRUE){
			if (!th[m].Join()) {
				errCnt++;
			}
			tc[m].m_bFinished = DMC_TRUE;
			printf("Thread number %d finished.\n", m);
		}
	}
	delete [] tc;
	delete [] th;
	return errCnt == 0;
}

class CElapsedTime {
public:
#if defined DMC_WIN
	time_t m_time_s, m_time_e;
#else
	int m_time_s, m_time_e;
#endif
public:
	CElapsedTime() {
#if defined DMC_WIN
		time(&m_time_s);
#else
		m_time_s = time(0);
#endif
	}
	void print() {
#if defined DMC_WIN
		time_t time_e;
		time(&time_e);
		printf("Elapsed time = %.0f seconds\n", difftime(time_e, m_time_s));
#else
		int time_e;
		time_e = time(0);
		printf("Elapsed time = %d second\n", time_e - m_time_s);
#endif
	}
};

int main(int argc, char* argv[]) {
	CCmdInfo cmdinfo;
	std::vector<std::string> args;
	int errCnt = 0;

	CCmdInfo::CmdArgToArgs(argc, argv, &args);
	if(!cmdinfo.ParseCmdLine(&args)) {
		errCnt++;
		Usage();
		return errCnt;
	}

#ifdef DMC_WIN
	if (cmdinfo.m_paramEncode == PE_UTF8) {
		setlocale(LC_ALL, cmdinfo.m_DefLangName.c_str());
	}
#endif

	printf("\nGroupName: %s\n", cmdinfo.m_GroupName.c_str());
	printf("DefLangName: %s\n", cmdinfo.m_DefLangName.c_str());
	printf("bBigEndian: %d\n", cmdinfo.m_bBigEndian);
	printf("Option: 0x%08X 0x%08X\n", cmdinfo.m_Option, cmdinfo.m_Option1);
	if (cmdinfo.m_funcType == FT_GET_PAGE_TEXT_V4 || cmdinfo.m_funcType == FT_GET_PAGE_TEXT_V5) {
		printf("Pages: %d\n", cmdinfo.m_pages);
	}

	// Working here ...
	CElapsedTime et;

	if (cmdinfo.m_bMultiThread) {
		// Multi-thread
		for (int lc = 0; lc < cmdinfo.m_loop; lc++) {
			if (!cmdinfo.CallMTFunction()) {
				errCnt++;
			}
		}
	}
	else {
		// Single thread
		for (int lc = 0; lc < cmdinfo.m_loop; lc++) {
			if (!cmdinfo.CallFunction()) {
				errCnt++;
			}
		}
	}

	et.print();

	printf("Number of errors = %d\n", errCnt);
	return errCnt;
}

void Usage(void) {
	std::string app_name;

#if defined DMC_WIN
	app_name = "app_ww";
#elif defined DMC_LINUX
	app_name = "app_ll";
#elif defined DMC_SOLARIS
	app_name = "app_ss";
#elif defined DMC_AIX
	app_name = "app_xx";
#elif defined DMC_HPUX
	app_name = "app_hh";
#else
	#error "Unknown platform!"
#endif

	printf("Usage: %s input-app-file [-o output-txt-file] [-t target-dir] [-g group-name] [-e big-endian] [-d default-langugage-name] [-p option] [-m do-multi-thread] [-n pages] [-f function-number] [-w password] [-s size] [-c csv_c] [-hi thread-interval] [-hn thread-number] [-a param-file] [-l loop-count]\n", app_name.c_str());
	printf("\n");
	printf("default-language-name: en\n");
	printf("                 jp                    \t(default)\n");
	printf("                 cn\n");
	printf("                 tw\n");
	printf("                 ko\n");
	printf("group-name:      EUC-JP\n");
	printf("                 EUC-JP-FIX\n");
	printf("                 ISO-10646-UCS-2\n");
	printf("                 ISO-10646-UCS-4\n");
	printf("                 ISO-2022-JP\n");
	printf("                 ISO-8859-1\n");
	printf("                 Shift_JIS             \t(default)\n");
	printf("                 UTF-16\n");
	printf("                 UTF-8\n");
	printf("                 WINDOWS31J\n");
	printf("                 ChineseGBK(V4 must use GBK)\n");//ChineseGBK->GBK
	printf("                 ChineseBIG5(V4 must use Big5)\n");//ChineseBIG5->Big5
	printf("                 GB18030\n");
	printf("                 KoreanKSC(V4 must use KS_C_5601-1987)\n");//KoreanKSC->KS_C_5601-1987
	printf("                 Shift_JIS-2004\n");//2006/08/02
	printf("                 ISO-2022-JP-2004\n");//2006/08/02
	printf("                 EUC-JIS-2004\n");//2006/08/02
	printf("big-endian:      0: little endian\n");
	printf("                 1: big endian         \t(default)\n");
	printf("option:          DMC_GETTEXT_OPT_KEISEN\n");
	printf("                 DMC_GETTEXT_OPT_TAG\n");
	printf("                 DMC_GETTEXT_OPT_CRLF  \t(default)\n");
	printf("                 DMC_GETTEXT_OPT_CR\n");
	printf("                 DMC_GETTEXT_OPT_LF\n");
	printf("                 DMC_GETTEXT_OPT_U2028\n");
	printf("                 DMC_GETTEXT_OPT_U2029\n");
	printf("                 DMC_GETTEXT_OPT_SHEET\n");
	printf("                 DMC_GETTEXT_OPT_RUBI\n");
	printf("                 DMC_GETTEXT_OPT_PWD\n");
	printf("                 DMC_GETTEXT_OPT_OLE   \t\n");
	printf("                 DMC_GETTEXT_OPT_OLE1\n");
	printf("                 DMC_GETTEXT_OPT_OLE2  \t\n");
	printf("                 DMC_GETTEXT_OPT_OLE3  \t\n");
	printf("                 DMC_GETTEXT_OPT_OUT\n");
	printf("                 DMC_GETTEXT_OPT_LOOP\n");
	printf("                 DMC_GETTEXT_OPT_SHEET1\n");
	printf("                 DMC_GETTEXT_OPT_CELL\n");
	printf("                 DMC_GETTEXT_OPT_SIZE\n");
	printf("                 DMC_GETTEXT_OPT_CSV1\n");
	printf("                 DMC_GETTEXT_OPT_CSV2\n");
	printf("                 DMC_GETTEXT_OPT_PDFSYM\n");
	printf("                 DMC_GETTEXT_OPT_OWNERPWD1\n");
	printf("                 DMC_GETTEXT_OPT_OWNERPWD2\n");
	printf("                 DMC_GETTEXT_OPT_OWNERPWD3\n");
	printf("                 DMC_GETTEXT_OPT_OWNERPWD4\n");
	printf("                 DMC_GETTEXT_OPT1_OWNERPWD5\n");
	printf("                 DMC_GETTEXT_OPT_ENDCODE\n");
	printf("                 DMC_GETTEXT_OPT_NULL\n");
	printf("                 DMC_GETTEXT_OPT_SHFTAG\n");
	printf("                 DMC_GETTEXT_OPT_SHFHEAD\n");
	printf("                 DMC_GETTEXT_OPT1_TEMP\n");
	printf("                 DMC_GETTEXT_OPT1_INSERTF\n");
	printf("                 DMC_GETTEXT_OPT1_INSERTF1\n");
	printf("                 DMC_GETTEXT_OPT1_INSERTF2\n");
	printf("                 DMC_GETTEXT_OPT1_INSERTF3\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS1\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS2\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS3\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS4\n");
	printf("                 DMC_GETTEXT_OPT1_TRACK\n");
	printf("                 DMC_GETTEXT_OPT1_COMPRESS5\n");
	printf("                 DMC_GETTEXT_OPT1_INSERTF4\n");
	printf("                 DMC_GETTEXT_OPT1_TXCONV\n");
	printf("                 DMC_GETTEXT_OPT1_TXCONV2\n");
	printf("                 DMC_GETTEXT_OPT1_OUTPUT_RAW_NL\n");
	printf("                 DMC_GETTEXT_OPT1_QUOTE_QQ\n");
	printf("do-multi-thread: 0: single thread test \t(default)\n");
	printf("                 1: multi-thread test\n");
	printf("pages:           0: get page count     \t(default)\n");
	printf("                 1,2,3...: page number\n");
	printf("function-number: 0: test DMC_GetText_V5\t(default)\n");
	printf("                 1: test DMC_GetPageText_V5\n");
	printf("                 2: test DMC_GetProperty_V5\n");
	printf("                 3: test DMC_GetPwdText_V5\n");
	printf("                 4: test DMC_GetPwdPageText_V5\n");
	printf("                 5: test DMC_GetPwdProperty_V5\n");
	printf("                 6: test DMC_GetTextStream_V5\n");
	printf("                 7: test DMC_GetPageTextStream_V5\n");
	printf("                 8: test DMC_GetText_V4\n");
	printf("                 9: test DMC_GetPageText_V4\n");
	printf("                10: test DMC_GetProperty_V4\n");
	printf("                11: test DMC_GetPwdText_V4\n");
	printf("                12: test DMC_GetPwdPageText_V4\n");
	printf("                13: test DMC_GetPwdProperty_V4\n");
	printf("                14: test DMC_GetTextStream_V4\n");
	printf("                15: test DMC_GetPageTextStream_V4\n");
	printf("thread-interval: the interval of main thread sleeping, the default is 3 seconds. 0 or negative number means no sleeping.\n");
	printf("thread-number:   the number of thread to create.\n");
	printf("                 the default is 50 threads\n");
	printf("loop-count:      loop count.\n");
	//printf("-g from V3->V4 have changed.Please see the usage!\n");
}
