/*	
 *	clipnvlg.c
 *
 *	不揮発性ログバッファ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Fri Oct 30 21:25:15 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 不揮発性メモリに随時書き込めむ事を目的とした、ログバッファです。
 *	  ログ機能としては既にcliplogb.cモジュールが有りますが、それは画面表示を目的としていて保存は意識していませんでした。
 *	- 当モジュールの機能の特徴は、ログバッファが壊れても正常に復帰出来る事です。
 *	  nvlog_puts()等は、プログラムの異常を検出した時にも呼び出される事を想定しています。
 *	  その時点で、ログバッファのメモリも壊れている可能性が有ります。
 *	  nvlog_puts()はログバッファが壊れていても正常な部分のみを使用して処理を継続します。
 *	  ログバッファが壊れても、nvlog_puts()を何度か呼び出すと、正常な部分だけが残って、ログバッファ全体が正常復帰します。
 *	- 当モジュール自身には、不揮発性メモリに書き込む機能は含めていません。
 *	  アプリケーション側で、nvlog_puts()を呼び出し直後に、その都度、ログバッファ全体を不揮発性メモリに書き込んで下さい。
 *	- ログバッファの内容を表示するには、プログラムからnvlog_dump()を呼び出すか、又は、
 *	  不揮発性メモリのデータをPCに読み出す事が可能ならば、そのデータに対してnvlogd.exeを使用して下さい。
 *	  P/ECEでの使用例は、nvlogd.bat,test.pce/n.bat,test.pce/v.batを参照して下さい。
 *	- ログバッファの初期化は不要です。
 *	  上述のとおり、ログバッファが不定値でも(=壊れていても)、nvlog_puts()の呼び出しによって正常復帰出来るからです。
 *	  又は、明示的に初期化するには、ログバッファの先頭1バイトに0を書き込めば、確実に空の状態となります。
 *	  ログバッファをBSSに置けば自然に空の状態から開始しますし、或いは、不定値で開始したとしても有りません。
 *	  要するに、ログバッファの初期状態を気にする必要は有りません。
 *	- ログバッファをダンプする方法について説明します。
 *	  一般的な標準入出力を利用出来る環境(Win32,P/ECE,等)ならば、nvlog_dump()を使って下さい。
 *	  nvlog_first(),nvlog_next()を使う必要は有りません。(使っても構いません)
 *	  一般的な標準入出力を利用出来ない組み込み環境等では、nvlog_first(),nvlog_next()を使って下さい。
 *	  nvlog_first(),nvlog_next()の、使用方法はnvlog_dump()の実装を参照して下さい。
 *	  特に注意すべき点は、nvlog_first(),nvlog_next()が返す値はヌル終端文字列では無い点に注意して下さい。
 */
#include "clip.h"
/****************************************************************************
 *	
 ****************************************************************************/
void nvlog_printf(void* nvlog/*[NVLOG_SIZE]*/, const char* fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	nvlog_vprintf(nvlog, fmt, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
void nvlog_vprintf(void* nvlog/*[NVLOG_SIZE]*/, const char* fmt, va_list ap) {
	char new_str[256];
	vsnprintf(new_str, sizeof new_str, fmt, ap);
	nvlog_puts(nvlog, new_str);
}
/*--------------------------------------------------------------------------*/
void nvlog_puts(void* nvlog/*[NVLOG_SIZE]*/, const char* new_str) {
	unsigned char* const buf = nvlog;
	unsigned char* const end = buf + NVLOG_SIZE;
	unsigned char* pos;
	int len, new_len, size, new_size, max_size;
	//今回追加する文字列の文字数を求める。
	new_len = strlen(new_str);
	//空文字列だった場合は何もせずに帰る。
	if(!new_len) { return; }
	//今回追加する文字列の文字数を、255文字以下に切り詰める。
	if(new_len > 255) { new_len = 255; }
	//今回追加するチャンクのバイト数を求める。
	new_size = new_len + 2/*ヘッダ,トレイラ*/;
	//今回のチャンクを追加するためには、バッファ内に有るチャンクの合計バイト数を、max_size以下にしなければならない。
	max_size = NVLOG_SIZE - new_size;
	//現在バッファ内に有るチャンクの終端のトレイラ位置を求める。
	pos = buf;
	for(;;) {
		//このチャンクのヘッダを取得する。
		len = *pos;
		//ヘッダが0ならば、終端を意味する。
		//要するに、このヘッダは、最後に文字列を追加した時のトレイラである。
		if(!len) { break; }
		//ヘッダが0でなければ、文字数である。
		//ヘッダの位置に、ヘッダのサイズ(1)と文字数を加算し、次のヘッダ位置を求める。
		len += 1/*ヘッダ*/;	//ヘッダのサイズ＋文字数
		pos += len;		//次のチャンクのヘッダ位置
		//正常ならば(<end)になるはずだが、もし(>=end)になったらバッファが壊れている。			//┐
		//バッファが壊れている場合はこのチャンクを破棄し、このヘッダを終端のトレイラと見なす事にする。	//├異常系処理
		if(pos >= end) { pos -= len; break; }	//バッファが壊れている場合				//┘
	}
	//現在バッファ内に有るチャンクの合計バイト数(終端のトレイラ含まず)を求める。
	size = pos - buf;
	//現在バッファ内に有るチャンクの合計バイト数が、max_size以下になるまで…
	pos = buf;
	while(size > max_size) {
		//このチャンクを削除する。
		len = *pos + 1/*ヘッダ*/;	//このチャンクのバイト数を求める。
		pos += len;	//次のチャンクのヘッダ位置へ進める。
		size -= len;	//このチャンクのバイト数を減算する。
	}
	//残ったチャンクをバッファの先頭へ移動する。
	memmove(buf, pos, size);
	//チャンクを追加する。
	pos = buf + size;
	*pos++ = new_len;			//ヘッダ
	pos = mempcpy(pos, new_str, new_len);	//文字列
	*pos = 0;				//トレイラ
}
/*--------------------------------------------------------------------------*/
const unsigned char* nvlog_first(const void* nvlog/*[NVLOG_SIZE]*/, struct nvlog_iter* iter) {
	iter->pos = nvlog;
	iter->end = iter->pos + NVLOG_SIZE;
	return nvlog_next(iter);
}
/*--------------------------------------------------------------------------*/
const unsigned char* nvlog_next(struct nvlog_iter* iter) {
	//ヘッダの位置を記憶しておく。
	const unsigned char* const pos = iter->pos;
	//このチャンクのヘッダを取得し、文字列の位置へ進める。
	int len = *iter->pos++;
	//ヘッダが0だったならば、終端を意味する。
	//要するに、このヘッダは、最後に文字列を追加した時のトレイラである。
	if(!len) { return NULL; }
	//文字列の位置に、文字数を加算し、次のヘッダ位置を求める。
	iter->pos += len;	//次のチャンクのヘッダ位置
	//正常ならば(<end)になるはずだが、もし(>=end)になったらバッファが壊れている。	//┐
	//バッファが壊れている場合は、これ以降のチャンクを表示しない。			//├異常系処理
	if(iter->pos >= iter->end) { return NULL; }	//バッファが壊れている場合	//┘
	//記憶しておいたヘッダの位置を返す。
	return pos;
}
/*--------------------------------------------------------------------------*/
#ifdef  USE_NVLOG_DUMP
void nvlog_dump(FILE* fp, void* nvlog/*[NVLOG_SIZE]*/) {
	struct nvlog_iter iter;
	const unsigned char* pos;
	for(pos = nvlog_first(nvlog, &iter);
	    pos;
	    pos = nvlog_next(&iter)) {
		int len = *pos++;			//文字数を取得する。
		fprintf(fp, "%.*s\n", len, pos);	//文字列を表示する。ヌル終端文字列ではない事に注意せよ。
	}
}
#endif//USE_NVLOG_DUMP
