#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#define LZSS_DEBUG	/* このシンボルを定義すると、慎重に検査しながら実行します。プログラムのバグ検出用です。プログラムが安定したら、コメントアウトしてください。 */
//{{調整可
typedef const unsigned short LZSS_CHAR;		/* フレーズを構成する文字単位 */
#define LZSS_MATCH_LEN_MAX	(15 + 2)	/* フレーズにマッチする最大長。最小長は0だが、一致長が2未満の一致の場合、通常は無圧縮として出力する */
#define LZSS_MATCH_DIST_MAX	(4095 + 1)	/* ウインドウサイズ。現在の入力位置から、マッチ可能な過去のフレーズまでの最大距離。最小距離は1 */
//}}調整可
#include "lzss.h"

/****************************************************************************
 *	LZSS応用ルーチン
 ****************************************************************************/

#pragma warning(disable: 4100)	/* 引数は関数の本体部で1度も参照されません。 */
#pragma warning(disable: 4127)	/* 条件式が定数です。 */
#pragma warning(disable: 4244)	/* '?'から'?'に変換しました。データが失われているかもしれません。 */

/* ハーフワード単位で、LZSS圧縮を行ないます。
 * [in]
 *	dst_data	出力バッファ
 *	dst_lim		出力バッファのサイズ(ハーフワード単位)
 *	src_data	入力データ
 *	src_len		入力データの長さ(ハーフワード単位)
 * [out]
 *	戻り値		圧縮データの長さ(ハーフワード単位)
 * [note]
 *	* 出力バッファのサイズが、圧縮データの長さよりも小さい場合、超過した分は格納しません。
 *	  その場合も、圧縮データの長さは、本来の圧縮データの長さを返します。
 */
int
LZSS_compress2(unsigned short* dst_data/*[dst_lim]*/, int dst_lim, const unsigned short* src_data/*[src_len]*/, int src_len)
{
	int flag_mask = 0;
	int flag_pos = 0;
	int dst_len = 0;
	int src_pos;
	int match_len;
	int match_dist;
	int hdr;
	int i;

	/* ツリーを初期化する */
	src_pos = LZSS_init(src_data, src_len);
	while(src_pos < src_len) {
		/* フラグ領域を確保する */
		if(!flag_mask) {
			flag_mask = 1 << 15;
			flag_pos = dst_len++;
			if(flag_pos < dst_lim) {
				dst_data[flag_pos] = 0;
			}
		}
		/* 最長一致フレーズを検索する */
		match_len = LZSS_search(&match_dist);
		/* 一致長が2文字以上ならば、圧縮コードとして出力する */
		if(match_len >= 2) {
			/* 圧縮コードとして {(match_dist-1),(match_len-2)} を出力する */
			hdr = (match_len - 2) | ((match_dist - 1) << 4);
			if(dst_len < dst_lim) {
				dst_data[dst_len] = hdr;
			}
			dst_len++;
			/* ①の分、入力位置を進める */
			src_pos = LZSS_advance();
			/* 1文字目以降の、一致した範囲の入力位置をスキップする */
			for(i = 1; i < match_len; i++) {
				/* スキップする範囲のデータも、ツリーに反映させることを忘れずに */
				LZSS_search(NULL); /* ② */
				/* ②の分、入力位置を進める */
				src_pos = LZSS_advance();
			}
		/* 一致長が2文字未満ならば、無圧縮コードとして出力する */
		} else {
			/* 無圧縮コードであることを示すため、フラグをセットする */
			if(flag_pos < dst_lim) {
				dst_data[flag_pos] |= flag_mask;
			}
			/* 無圧縮コードを出力する */
			if(dst_len < dst_lim) {
				dst_data[dst_len] = src_data[src_pos];
			}
			dst_len++;
			/* ①の分、入力位置を進める */
			src_pos = LZSS_advance();
		}
		/* フラグビット位置を進める */
		flag_mask >>= 1;
	}

	return dst_len;
}

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

/* ハーフワード単位でLZSS圧縮されたデータを、展開します。
 * [in]
 *	dst_data	出力バッファ
 *	dst_lim		出力バッファのサイズ(ハーフワード単位)
 *	src_data	入力データ
 *	src_len		入力データの長さ(ハーフワード単位)
 * [out]
 *	戻り値		展開データの長さ(ハーフワード単位)
 * [note]
 *	* 出力バッファのサイズが、展開データの長さよりも小さい場合、超過した分は格納しません。
 *	  その場合も、展開データの長さは、本来の展開データの長さを返します。
 */
int
LZSS_uncompress2(unsigned short* dst_data/*[dst_lim]*/, int dst_lim, const unsigned short* src_data/*[src_len]*/, int src_len)
{
	int flag_mask = 0;
	int flag = 0;
	int dst_len = 0;
	int src_pos = 0;
	int hdr;
	int len;
	int dist;
	int pos;

	while(src_pos < src_len) {
		/* フラグを取得する */
		if(!flag_mask) {
			flag_mask = 1 << 15;
			flag = src_data[src_pos++];
		}
		/* 圧縮コードの場合 */
		if(!(flag & flag_mask)) {
			/* 圧縮コード {(match_dist-1),(match_len-2)} を取得する */
			hdr = src_data[src_pos++];
			len = (hdr & 15) + 2;
			dist = (hdr >> 4) + 1;
			/* 圧縮コードを展開する */
			pos = dst_len - dist;
			do {
				if(dst_len < dst_lim) {
					dst_data[dst_len] = dst_data[pos];
				}
				dst_len++;
				pos++;
			} while(--len);
		/* 無圧縮コードの場合 */
		} else {
			/* 無圧縮コードを出力する */
			if(dst_len < dst_lim) {
				dst_data[dst_len] = src_data[src_pos];
			}
			dst_len++;
			src_pos++;
		}
		/* フラグビット位置を進める */
		flag_mask >>= 1;
	}

	return dst_len;
}

