/*
 *	app.c
 *
 *	dpavidot - AVI ドット抽出ツール
 *	Copyright (C) 2013-2014 Naoyuki Sawa
 *
 *	* Sun Aug 25 23:30:33 JST 2013 Naoyuki Sawa
 *	- 1st リリース。
 *	* Mon Aug 26 23:11:10 JST 2013 Naoyuki Sawa
 *	- 「-e」オプションを追加しました。
 *	* Tue Aug 27 10:58:50 JST 2013 Naoyuki Sawa
 *	- Lua関数内のエラー処理を、die()⇒luaL_error()に変更しました。
 *	* Sat Sep 07 14:31:20 JST 2013 Naoyuki Sawa
 *	- 「biWanted.biCompression=BI_RGB」は明示不要だったので削除しました。
 *	* Mon Nov 24 17:31:02 JST 2014 Naoyuki Sawa
 *	- 圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。
 *	- CompressDrh,UncompressDrhを追加しました。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Win32 */
#define STRICT
#include <windows.h>
#include <vfw.h>
#pragma comment(lib, "vfw32.lib")
/* Lua */
#include "../../liblua/lua.h"
#include "../../liblua/lauxlib.h"
#include "../../liblua/lualib.h"

/* ■重要■
 * Lua C APIの実行時チェックを有効にするために、以下の設定を行いました。
 * ［プロジェクト］
 *   ⇒［設定］
 *   ⇒「設定の対象」 = Win32 Debug, liblua
 *   ⇒「C/C++」
 *   ⇒「カテゴリ」 = 一般
 *   ⇒「プリプロセッサの定義」 = LUA_USE_APICHECK を追加
 * ［プロジェクト］
 *   ⇒［設定］
 *   ⇒「設定の対象」 = Win32 Release, liblua
 *   ⇒「C/C++」
 *   ⇒「カテゴリ」 = 一般
 *   ⇒「プリプロセッサの定義」 = LUA_USE_APICHECK を追加, NDEBUG を削除
 * (設定の意味は、"../../liblua/Makefile"のコメントを参照してください。)
 */

/****************************************************************************
 *	
 ****************************************************************************/

#define VERSION "20141124"

/* オプション */
int opt_Silent;				/* 1ならば、進捗表示を行わない */

/* Lua */
lua_State* g_pL;			/* Luaステート */
int g_lBegin;				/* Lua関数BEGIN()のスタック上の位置 */
int g_lFrame;				/* Lua関数FRAME()のスタック上の位置 */
int g_lEnd;				/* Lua関数END()  のスタック上の位置 */

/* VFW */
const char* g_pAviFileName;		/* 読み込み中のAVIファイル名 */
PAVIFILE g_pAviFile;			/* VFW用 */
PAVISTREAM g_pAviStream;		/* VFW用 */
AVISTREAMINFO g_AviStreamInfo;		/* VFW用 */
PGETFRAME g_pGetFrame;			/* VFW用 */
BITMAPINFOHEADER* g_pBitmapInfoHeader;	/* VFW用 */

int g_iFrameNumber;			/* デコード中のフレーム番号(0〜) */
int g_nLineBytes;			/* デコードしたビットマップの、1ラインのバイト数 */
const unsigned char* g_pPixelData;	/* デコードしたビットマップの、ピクセルデータ。ボトムアップ形式で、(x,y)=(0,0)を指す */
int g_fFrameValid;			/* 1ならば、LUA関数FRAME()を実行中であることを示す */

/*--------------------------------------------------------------------------*/

__declspec(noreturn) void usage();
__declspec(noreturn) void die(const char* fmt, ...);
#define DIE() die(__FILE__, __LINE__)
__declspec(noreturn) int atpanic(lua_State* L);

/* C関数。Luaのグローバル環境に登録する */
int cGetPixel(lua_State* L);
//{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
//int cCompress(lua_State* L);
//int cUncompress(lua_State* L);
//↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
int cCompressTlz(lua_State* L);
int cUncompressTlz(lua_State* L);
int cCompressDrh(lua_State* L);
int cUncompressDrh(lua_State* L);
//}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
int cGetMD5(lua_State* L);
int cGetSHA1(lua_State* L);
const luaL_Reg TBL_Reg[] = {
	{ "GetPixel", cGetPixel },
    //{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
    //	{ "Compress", cCompress },
    //	{ "Uncompress", cUncompress },
    //↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
	{ "CompressTlz", cCompressTlz },
	{ "UncompressTlz", cUncompressTlz },
	{ "CompressDrh", cCompressDrh },
	{ "UncompressDrh", cUncompressDrh },
    //}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
	{ "GetMD5", cGetMD5 },
	{ "GetSHA1", cGetSHA1 },
	{ NULL, NULL },
};

#define __CLIP_H__
/* getopt */
#include "../../include/getopt.h"
#include "../../getopt.c"
/* 圧縮展開 */
#include "../../cliptlz.h"
#include "../../cliptlz.c"
//{{2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
#include "../../clipsort.c"	//qsort_r()
#include "../../clipbary.h"	//bitarray_msb1st_get(),bitarray_msb1st_set()
#include "../../clipbary.c"
#include "../../frambary.c"
#include "../../cliphfm.h"
#include "../../cliphfm.c"
#include "../../framhfm.c"
#include "../../clipdrh.h"
#include "../../clipdrh.c"
#include "../../framdrh.c"
//}}2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* メッセージダイジェスト */
#include "../../clipcode.h"
#include "../../clipcode.c"
#undef  __CLIP_H__

/****************************************************************************
 *	
 ****************************************************************************/

int main(int argc, char* argv[]) {
	HRESULT hr;
	int top, opt;
	/* VFWを初期化します。 */
	AVIFileInit();
/*{{Lua*/
	/* Luaを初期化します。 */
	g_pL = luaL_newstate();
	if(g_pL == NULL) { die("luaL_newstate が失敗しました。"); }
	lua_atpanic(g_pL, atpanic);
	luaL_openlibs(g_pL);
	/* C関数を登録します。 */
	top = lua_gettop(g_pL);
	  lua_pushglobaltable(g_pL);
	  luaL_setfuncs(g_pL, TBL_Reg, 0);
	lua_settop(g_pL, top);
/*}}Lua*/
	/* コマンドラインを解析します。 */
	if(argc <= 1) { usage(); }
	while((opt = getopt(argc, argv, "f:e:hs")) != -1) {
		switch(opt) {
		case 'f':
			/* Luaファイルを読み込みます。
			 * - 「-f Luaファイル名」オプションは、複数回指定可能です。
			 *   「-f Luaファイル名」オプションが複数回指定された場合、それら全てのLuaファイルを読み込みます。
			 */
		//{{
		//	top = lua_gettop(g_pL);
		//	  if(luaL_dofile(g_pL, optarg) != 0) { die("%s: luaL_dofile が失敗しました。", optarg); }
		//	lua_settop(g_pL, top); /* luaL_dofile()の戻り値の数は'LUA_MULTRET'。明示的に捨てる処理が必要である。 */
		//↑これではロードエラー時のメッセージが判り辛いので、↓このように変更しました。
			top = lua_gettop(g_pL);
			  if(luaL_loadfile(g_pL, optarg) != 0) { die("luaL_loadfile: %s", luaL_checkstring(g_pL, -1)); }
			  lua_call(g_pL, 0, 0);
			lua_settop(g_pL, top);
		//}}
			break;
		case 'e':
			/* Luaスクリプトを実行します。
			 * - 「-e Luaスクリプト」オプションは、複数回指定可能です。
			 *   「-f Luaスクリプト」オプションが複数回指定された場合、それら全てのLuaスクリプトを実行します。
			 */
			top = lua_gettop(g_pL);
			  if(luaL_loadstring(g_pL, optarg) != 0) { die("luaL_loadstring: %s", luaL_checkstring(g_pL, -1)); }
			  lua_call(g_pL, 0, 0);
			lua_settop(g_pL, top);
			break;
		case 'h':
			usage();
			break;
		case 's':
			opt_Silent = 1;
			break;
		default:
			die("%c: オプション文字の使い方が不正です。", optopt);
			break;
		}
	}
/*{{Lua*/
	/* Lua関数を取得します。 */
	lua_getglobal(g_pL, "BEGIN");
	if(!lua_isfunction(g_pL, -1)) { die("%s が定義されていません。", "BEGIN"); }
	g_lBegin = lua_gettop(g_pL);
	lua_getglobal(g_pL, "FRAME");
	if(!lua_isfunction(g_pL, -1)) { die("%s が定義されていません。", "FRAME"); }
	g_lFrame = lua_gettop(g_pL);
	lua_getglobal(g_pL, "END");
	if(!lua_isfunction(g_pL, -1)) { die("%s が定義されていません。", "END"); }
	g_lEnd = lua_gettop(g_pL);
	/* BEGIN()を実行します。 */
	top = lua_gettop(g_pL);
	  lua_pushvalue(g_pL, g_lBegin);
	  lua_call(g_pL, 0, 0);
	lua_settop(g_pL, top);
/*}}Lua*/
	/* オプション以降、残りのコマンドライン引数は、AVIファイル名です。
	 * 各、AVIファイル名について…
	 */
	while((g_pAviFileName = argv[optind++]) != NULL) {
		/*static const*/ BITMAPINFOHEADER biWanted = { /* AVIStreamGetFrameOpen()が非constポインタを要求するので仕方無く… */
			sizeof(BITMAPINFOHEADER),	/* biSize */
			0,	/* biWidth  ⇒ 指定しない */
			0,	/* biHeight ⇒ 指定しない */
			1,	/* biPlanes */
			24,	/* biBitCount */
		//	BI_RGB,	/* biCompression */	//{{2013/09/07削除:BI_RGB=0なので明示不要}}
		};
		int per1, per2;	/* 進捗表示用 */
		/* AVIファイルを開きます。 */
		hr = AVIFileOpen(&g_pAviFile, g_pAviFileName, OF_READ|OF_SHARE_DENY_WRITE, NULL);
		if(hr != 0) { die("%s: AVIFileOpen が失敗しました。", g_pAviFileName); }
		hr = AVIFileGetStream(g_pAviFile, &g_pAviStream, streamtypeVIDEO, 0);
		if(hr != 0) { die("%s: AVIFileGetStream が失敗しました。", g_pAviFileName); }
		hr = AVIStreamInfo(g_pAviStream, &g_AviStreamInfo, sizeof g_AviStreamInfo);
		if(hr != 0) { die("%s: AVIStreamInfo が失敗しました。", g_pAviFileName); }
		g_pGetFrame = AVIStreamGetFrameOpen(g_pAviStream, &biWanted); /* &biWantedの代りにNULLを指定して、無圧縮RGB以外にデコードされた場合は自力で解釈する方法も有るが、簡単のために、無圧縮RGBにデコード出来ない場合はエラーとすることにした */
		if(g_pGetFrame == NULL) { die("%s: AVIStreamGetFrameOpen が失敗しました。", g_pAviFileName); }
		/* 各フレームについて… */
		if(!opt_Silent) { per1 = -1; }	/* 進捗表示用 */
		for(g_iFrameNumber = 0; g_iFrameNumber < (int)g_AviStreamInfo.dwLength; g_iFrameNumber++) {
			if(!opt_Silent) {										/* 進捗表示用 */
				per2 = (g_iFrameNumber + 1) * 100 / g_AviStreamInfo.dwLength;				/* 進捗表示用 */
				if(per1 != per2) { per1 = per2; fprintf(stderr, "%s: %3d%%\r", g_pAviFileName, per1); }	/* 進捗表示用 */
			}												/* 進捗表示用 */
			/* 1フレーム分をデコードします。 */
			g_pBitmapInfoHeader = AVIStreamGetFrame(g_pGetFrame, g_iFrameNumber);
			if(g_pBitmapInfoHeader == NULL) { die("%s: AVIStreamGetFrame が失敗しました。", g_pAviFileName); }
			/* 1ラインのバイト数を求めます。 */
			g_nLineBytes = (g_pBitmapInfoHeader->biWidth * (g_pBitmapInfoHeader->biBitCount / 8) + 3) & ~3;
			/* ピクセルデータへのポインタを求めます。 */
			g_pPixelData = (unsigned char*)g_pBitmapInfoHeader + g_pBitmapInfoHeader->biSize;
			g_pPixelData += g_nLineBytes * (g_pBitmapInfoHeader->biHeight - 1); /* ボトムアップ形式で、(x,y)=(0,0)を指す */
/*{{Lua*/
			/* FRAME()を実行します。 */
			g_fFrameValid = 1; /* ここからGetPixel()使用可能 */
			top = lua_gettop(g_pL);
			  lua_pushvalue(g_pL, g_lFrame);
			  lua_call(g_pL, 0, 0);
			lua_settop(g_pL, top);
			g_fFrameValid = 0; /* ここまでGetPixel()使用可能 */
/*}}Lua*/
		}
		if(!opt_Silent) { fprintf(stderr, "\n"); }	/* 進捗表示用 */
		/* AVIファイルを閉じます。 */
		hr = AVIStreamGetFrameClose(g_pGetFrame);
		if(hr != 0) { die("%s: AVIStreamGetFrameClose が失敗しました。", g_pAviFileName); }
		hr = AVIStreamRelease(g_pAviStream);
		if(hr != 0) { die("%s: AVIStreamRelease が失敗しました。", g_pAviFileName); }
		hr = AVIFileRelease(g_pAviFile);
		if(hr != 0) { die("%s: AVIFileRelease が失敗しました。", g_pAviFileName); }
	}
/*{{Lua*/
	/* END()を実行します。 */
	top = lua_gettop(g_pL);
	  lua_pushvalue(g_pL, g_lEnd);
	  lua_call(g_pL, 0, 0);
	lua_settop(g_pL, top);
	/* Luaを終了します。 */
	lua_close(g_pL);
/*}}Lua*/
	/* VFWを終了します。 */
	AVIFileExit();
	return EXIT_SUCCESS;
}

/*--------------------------------------------------------------------------*/

/* 使い方を表示し、エラー終了する。 */
void usage() {
	fprintf(stderr, "dpavidot - AVI ドット抽出ツール (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2013-2014 Naoyuki Sawa All Rights Reserved.\n");
	fprintf(stderr, "Usage:\n");
	fprintf(stderr, "    dpavidot [-s] -f script.lua [-f script.lua ...] movie.avi [movie.avi ...]\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "    -h            使い方を表示します。\n");
	fprintf(stderr, "    -s            進捗を表示しません。\n");
	fprintf(stderr, "    -f script.lua Luaスクリプトファイルを読み込みます。\n");
	fprintf(stderr, "    -e lua-script Luaスクリプト文字列を直接実行します。\n");
	fprintf(stderr, "Summary:\n");
	fprintf(stderr, "  - AVIファイルの各フレームを、Luaスクリプトで処理します。\n");
	fprintf(stderr, "    Luaスクリプトを複数指定した場合、全て連結したものとして扱います。\n");
	fprintf(stderr, "    AVIファイルを複数指定した場合も、全て連結したものとして扱います。\n");
	fprintf(stderr, "  - Luaスクリプトには、以下の関数が定義されていなければなりません。\n");
	fprintf(stderr, "    BEGIN         最初に一度だけ呼び出されます。\n");
	fprintf(stderr, "    FRAME         各フレームに対して、一度づつ呼び出されます。\n");
	fprintf(stderr, "    END           最後に一度だけ呼び出されます。\n");
	fprintf(stderr, "  - Luaスクリプトは、下記のC関数を利用することができます。\n");
	fprintf(stderr, "    GetPixel      現在のフレームの、指定した座標の輝度を取得します。\n");
    //{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
    //	fprintf(stderr, "    Compress      8bit符号無し値を要素とする、テーブルを圧縮します。\n");
    //	fprintf(stderr, "    Uncompress    8bit符号無し値を要素とする、テーブルを展開します。\n");
    //↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
	fprintf(stderr, "    CompressTlz   8bit符号無し値を要素とする、テーブルを圧縮します。(tlz)\n");
	fprintf(stderr, "    UncompressTlz 8bit符号無し値を要素とする、テーブルを展開します。(tlz)\n");
	fprintf(stderr, "    CompressDrh   8bit符号無し値を要素とする、テーブルを圧縮します。(drh)\n");
	fprintf(stderr, "    UncompressDrh 8bit符号無し値を要素とする、テーブルを展開します。(drh)\n");
    //}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
	fprintf(stderr, "    GetMD5        8bit符号無し値を要素とするテーブルの、MD5  を取得します。\n");
	fprintf(stderr, "    GetSHA1       8bit符号無し値を要素とするテーブルの、SHA-1を取得します。\n");
	fprintf(stderr, "  - 出力処理及び出力するデータの内容は、Luaスクリプト次第です。\n");
	fprintf(stderr, "    Luaスクリプトを変更することによって、様々な応用が可能です。\n");
	fprintf(stderr, "Example:\n");
	fprintf(stderr, "    dpavidot.exe -e \"ERR_LOG=[[err.log]]\" -f Script1.lua -f Script2.lua Movie1.avi Movie2.avi\n");
	fprintf(stderr, "    出力処理及び出力するデータの内容は、LedDefine.lua,LedConfig.lua,LedScript.lua で定義する。\n");
	fprintf(stderr, "    dpavidot.exeが行う処理は、AVIの各フレームについて、規定されたLua関数を呼び出すだけである。\n");
	exit(EXIT_FAILURE);
}

/*--------------------------------------------------------------------------*/

/* メッセージを表示し、エラー終了する。 */
void die(const char* fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	 fprintf(stderr, "\n### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);
#ifdef  _DEBUG
	__asm int 3; /* デバッグし易いように、デバッグ時はデバッガで止める */
#endif/*_DEBUG*/
	exit(EXIT_FAILURE);
}

/*--------------------------------------------------------------------------*/

/* Luaのパニック関数 */
int atpanic(lua_State* L) {
	die("atpanic: %s", luaL_checkstring(L, -1));
}

/****************************************************************************
 *	
 ****************************************************************************/

/* r,g,b = GetPixel(x,y)
 * [in]
 *	x	X座標。範囲は[0,w)。範囲外の座標を指定すると、輝度0を返す。
 *	y	Y座標。範囲は[0,h)。範囲外の座標を指定すると、輝度0を返す。
 * [out]
 *	r	R輝度。範囲は[0,1]。
 *	g	G輝度。範囲は[0,1]。
 *	b	B輝度。範囲は[0,1]。
 */
int cGetPixel(lua_State* L) {
	int x, y;
	if(!g_fFrameValid) { luaL_error(L, "cGetPixel: フレームが無効です。"); }
	/* 座標を取得します。 */
	x = luaL_checkinteger(L, 1);
	y = luaL_checkinteger(L, 2);
	/* 座標がフレーム内ならば、色を取得します。 */
	if(((unsigned)x < (unsigned)g_pBitmapInfoHeader->biWidth ) &&
	   ((unsigned)y < (unsigned)g_pBitmapInfoHeader->biHeight)) {
		const unsigned char* p = g_pPixelData; /* ボトムアップ形式で、(x,y)=(0,0)を指す */
		p -= g_nLineBytes * y;
		p += 3 * x;
		lua_pushnumber(L, (double)p[2] / (double)UCHAR_MAX); /* R */
		lua_pushnumber(L, (double)p[1] / (double)UCHAR_MAX); /* G */
		lua_pushnumber(L, (double)p[0] / (double)UCHAR_MAX); /* B */
	/* 座標がフレーム外ならば、黒と見なします。 */
	} else {
		lua_pushnumber(L, 0.0); /* R */
		lua_pushnumber(L, 0.0); /* G */
		lua_pushnumber(L, 0.0); /* B */
	}
	return 3;
}

/*--------------------------------------------------------------------------*/

//{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
///* cCompress()とcUncompress()の共通処理をまとめた。 */
//static int cCompress_cUncompress(lua_State* L, int (*pTinyLZ_func)(void*, int, const void*, int), const char* pFuncName) {
//↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* cCompressTlz()とcUncompressTlz()の共通処理をまとめた。 */
static int cCompressTlz_cUncompressTlz(lua_State* L, int (*pTinyLZ_func)(void*, int, const void*, int), const char* pFuncName) {
//}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
	int i, v, nSrcLen, nDstLen;
	unsigned char *pSrcBuf, *pDstBuf;
	/* 引数が{圧縮|展開}前データのLuaテーブルであることを確認します。 */
	luaL_checktype(L, 1, LUA_TTABLE);
	/* {圧縮|展開}前データのC配列を作成します。 */
	nSrcLen = lua_rawlen(L, 1);
	pSrcBuf = malloc(nSrcLen);
	if(!pSrcBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
	/* {圧縮|展開}前データをLuaテーブル⇒C配列に変換します。 */
	for(i = 0; i < nSrcLen; i++) {
		lua_rawgeti(L, 1, i + 1); /* Luaは1ベース */
		v = luaL_checkinteger(L, -1);
		if((unsigned)v > UCHAR_MAX) { luaL_error(L, "%s: データが不正です。", pFuncName); }
		pSrcBuf[i] = v;
		lua_pop(L, 1); /* lua_rawgeti()の分 */
	}
	/* {圧縮|展開}前データのLuaテーブルを削除します。 */
	lua_settop(L, 0);
	/* {圧縮|展開}後データのLuaテーブルを作成します。 */
	lua_newtable(L);
	/* {圧縮|展開}後データのC配列を作成します。 */
	nDstLen = (*pTinyLZ_func)(NULL, 0, pSrcBuf, nSrcLen); /* {圧縮|展開}を実行する */
	pDstBuf = malloc(nDstLen);
	if(!pDstBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
	(*pTinyLZ_func)(pDstBuf, nDstLen, pSrcBuf, nSrcLen);  /* {圧縮|展開}を実行する */
	/* {圧縮|展開}後データをC配列⇒Luaテーブルに変換します。 */
	for(i = 0; i < nDstLen; i++) {
		lua_pushinteger(L, pDstBuf[i]);
		lua_rawseti(L, 1, i + 1); /* Luaは1ベース */
	}
	/* C配列を削除します。 */
	free(pSrcBuf);
	free(pDstBuf);
	/* {圧縮|展開}後データのLuaテーブルを返します。 */
	return 1;
}

/*--------------------------------------------------------------------------*/

//{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
///* DstTbl = Compress(SrcTbl)
// * [in]
// *	SrcTbl	圧縮前データのテーブル。[1]から開始するシーケンスであること。
// * [out]
// *	DstTbl	圧縮後データのテーブル。[1]から開始するシーケンスです。
// */
//int cCompress(lua_State* L) {
//	return cCompress_cUncompress(L, TinyLZ_compress, "cCompress");
//}
//↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* DstTbl = CompressTlz(SrcTbl)
 * [in]
 *	SrcTbl	圧縮前データのテーブル。[1]から開始するシーケンスであること。
 * [out]
 *	DstTbl	圧縮後データのテーブル。[1]から開始するシーケンスです。
 */
int cCompressTlz(lua_State* L) {
	return cCompressTlz_cUncompressTlz(L, TinyLZ_compress, "cCompressTlz");
}
//}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。

/*--------------------------------------------------------------------------*/

//{{2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
///* DstTbl = Uncompress(SrcTbl)
// * [in]
// *	SrcTbl	展開前データのテーブル。[1]から開始するシーケンスであること。
// * [out]
// *	DstTbl	展開後データのテーブル。[1]から開始するシーケンスです。
// */
//int cUncompress(lua_State* L) {
//	return cCompress_cUncompress(L, TinyLZ_uncompress, "cUncompress");
//}
//↓2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* DstTbl = UncompressTlz(SrcTbl)
 * [in]
 *	SrcTbl	展開前データのテーブル。[1]から開始するシーケンスであること。
 * [out]
 *	DstTbl	展開後データのテーブル。[1]から開始するシーケンスです。
 */
int cUncompressTlz(lua_State* L) {
	return cCompressTlz_cUncompressTlz(L, TinyLZ_uncompress, "cUncompressTlz");
}
//}}2014/11/24変更:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。

/*--------------------------------------------------------------------------*/

//{{2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* cCompressDrh()とcUncompressDrh()の共通処理をまとめた。 */
static int cCompressDrh_cUncompressDrh(lua_State* L, int bCompressUncompress, const char* pFuncName) {
	int i, v, nSrcLen, nDstLen;
	unsigned char *pSrcBuf, *pDstBuf;
	/* 引数が{圧縮|展開}前データのLuaテーブルであることを確認します。 */
	luaL_checktype(L, 1, LUA_TTABLE);
	/* {圧縮|展開}前データのC配列を作成します。 */
	nSrcLen = lua_rawlen(L, 1);
	pSrcBuf = malloc(nSrcLen);
	if(!pSrcBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
	/* {圧縮|展開}前データをLuaテーブル⇒C配列に変換します。 */
	for(i = 0; i < nSrcLen; i++) {
		lua_rawgeti(L, 1, i + 1); /* Luaは1ベース */
		v = luaL_checkinteger(L, -1);
		if((unsigned)v > UCHAR_MAX) { luaL_error(L, "%s: データが不正です。", pFuncName); }
		pSrcBuf[i] = v;
		lua_pop(L, 1); /* lua_rawgeti()の分 */
	}
	/* {圧縮|展開}前データのLuaテーブルを削除します。 */
	lua_settop(L, 0);
	/* {圧縮|展開}後データのLuaテーブルを作成します。 */
	lua_newtable(L);
	/* {圧縮|展開}後データのC配列を作成します。 */
	if(bCompressUncompress == 0) {
		nDstLen = DeltaRunHuffmanEncoder_Encode(pSrcBuf, nSrcLen, NULL, 0);	/* 圧縮を実行する */
		pDstBuf = malloc(nDstLen);
		if(!pDstBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
		DeltaRunHuffmanEncoder_Encode(pSrcBuf, nSrcLen, pDstBuf, nDstLen);	/* 圧縮を実行する */
	} else {
		nDstLen = DeltaRunHuffmanDecoder_Decode(pSrcBuf, NULL, 0);		/* 展開を実行する */
		pDstBuf = malloc(nDstLen);
		if(!pDstBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
		DeltaRunHuffmanDecoder_Decode(pSrcBuf, pDstBuf, nDstLen);		/* 展開を実行する */
	}
	/* {圧縮|展開}後データをC配列⇒Luaテーブルに変換します。 */
	for(i = 0; i < nDstLen; i++) {
		lua_pushinteger(L, pDstBuf[i]);
		lua_rawseti(L, 1, i + 1); /* Luaは1ベース */
	}
	/* C配列を削除します。 */
	free(pSrcBuf);
	free(pDstBuf);
	/* {圧縮|展開}後データのLuaテーブルを返します。 */
	return 1;
}
//}}2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。

/*--------------------------------------------------------------------------*/

//{{2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* DstTbl = CompressDrh(SrcTbl)
 * [in]
 *	SrcTbl	圧縮前データのテーブル。[1]から開始するシーケンスであること。
 * [out]
 *	DstTbl	圧縮後データのテーブル。[1]から開始するシーケンスです。
 */
int cCompressDrh(lua_State* L) {
	return cCompressDrh_cUncompressDrh(L, 0, "cCompressDrh");
}
//}}2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。

/*--------------------------------------------------------------------------*/

//{{2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。
/* DstTbl = UncompressDrh(SrcTbl)
 * [in]
 *	SrcTbl	展開前データのテーブル。[1]から開始するシーケンスであること。
 * [out]
 *	DstTbl	展開後データのテーブル。[1]から開始するシーケンスです。
 */
int cUncompressDrh(lua_State* L) {
	return cCompressDrh_cUncompressDrh(L, 1, "cUncompressDrh");
}
//}}2014/11/24追加:圧縮方式の追加に伴い、関数名をCompress⇒CompressTlz,Uncompress⇒UncompressTlzに変更しました。CompressDrh,UncompressDrhを追加しました。

/*--------------------------------------------------------------------------*/

static int cGetMD5_cGetSHA1(lua_State* L, void (*pDigestFunc)(unsigned char*, const void*, int), int nDigestLen, const char* pFuncName) {
	luaL_Buffer b;
	int i, v, nSrcLen;
	unsigned char* pSrcBuf;
	unsigned char Digest[20/*nDigestLen*/];
	/* 引数がデータのLuaテーブルであることを確認します。 */
	luaL_checktype(L, 1, LUA_TTABLE);
	/* データのC配列を作成します。 */
	nSrcLen = lua_rawlen(L, 1);
	pSrcBuf = malloc(nSrcLen);
	if(!pSrcBuf) { luaL_error(L, "%s: malloc が失敗しました。", pFuncName); }
	/* データをLuaテーブル⇒C配列に変換します。 */
	for(i = 0; i < nSrcLen; i++) {
		lua_rawgeti(L, 1, i + 1); /* Luaは1ベース */
		v = luaL_checkinteger(L, -1);
		if((unsigned)v > UCHAR_MAX) { luaL_error(L, "%s: データが不正です。", pFuncName); }
		pSrcBuf[i] = v;
		lua_pop(L, 1); /* lua_rawgeti()の分 */
	}
	/* メッセージダイジェストを求めます。 */
	(*pDigestFunc)(Digest, pSrcBuf, nSrcLen);
	/* C配列を削除します。 */
	free(pSrcBuf);
	/* メッセージダイジェストを文字列に変換します。 */
	luaL_buffinit(L, &b);
	for(i = 0; i < nDigestLen; i++) {
	//{{
	//	lua_pushfstring(L, "%02x", Digest[i]);
	//	luaL_addvalue(&b);
	//↓lua_pushfstring()は変換指定子に制限があり使えない。
		char s[2 + 1/*nul*/];
		sprintf(s, "%02x", Digest[i]);
		luaL_addstring(&b, s);
	//}}
	}
	luaL_pushresult(&b);
	/* メッセージダイジェスト文字列を返します。 */
	return 1;
}

/*--------------------------------------------------------------------------*/

int cGetMD5(lua_State* L) {
	return cGetMD5_cGetSHA1(L, md5_digest, 16, "cGetMD5");
}

/*--------------------------------------------------------------------------*/

int cGetSHA1(lua_State* L) {
	return cGetMD5_cGetSHA1(L, sha1_digest, 20, "cGetSHA1");
}

