/*	
 *	clipmenu.c
 *
 *	汎用メニュー
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2006 Naoyuki Sawa
 *
 *	* Sat Jul 01 20:20:32 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Jul 02 15:36:40 JST 2006 Naoyuki Sawa
 *	- スクロール位置(MENU.top)の設定を、描画直前(menu_disp())に行うよう変更。
 *	* Sun Jul 02 21:27:56 JST 2006 Naoyuki Sawa
 *	- menu_disp()に、スクロールの行き過ぎ補正を追加しました。
 *	  たとえば、メニュー項目数が5で、メニュー領域に完全に表示できる行数が3の場合、
 *	  最上段項目インデクスが3や4になると、下部に無駄な空行が表示されてしまいます。
 *	  スクロールの行き過ぎ補正処理により、最上段項目インデクスを0〜2に制限します。
 */
#include "clip.h"

/*****************************************************************************
 *	汎用メニュー
 *****************************************************************************/

const MENUPALETTE menu_palette_std = {	/*  4階調標準メニューパレット */
	item   :  2,	/* メニュー項目文字 */
	back   :  0,	/* メニュー項目背景 */
	title  :  0,	/* タイトル文字 */
	border :  3,	/* タイトル背景、および、枠 */
};
const MENUPALETTE menu_palette_ldir = {	/* 16階調標準メニューパレット */
	item   : 10,	/* メニュー項目文字 */
	back   :  0,	/* メニュー項目背景 */
	title  :  0,	/* タイトル文字 */
	border : 15,	/* タイトル背景、および、枠 */
};

/* MENU.font → フォントの高さ[ピクセル] */
static const unsigned char font_h_tbl[3] = {
	10,	/* 5×10ドット日本語フォント */
	16,	/* 8×10ドット英語フォント */
	 6,	/* 4× 6ドット英語フォント */
};

/* フォントサイズから、メニュー領域に完全に表示できる行数を算出します。
 */
static int
calc_disp_rows(MENU* menu)
{
	int font_h;
	int disp_rows;

	font_h = font_h_tbl[menu->font];
	disp_rows = (menu->h - 1/*上枠*/
			     - (menu->title ? (font_h/*タイトル*/ + 1/*1ライン空き*/) : 0)
			     - 1/*下枠*/) / font_h;
	if(disp_rows < 1) {
		disp_rows = 1;
	}

	return disp_rows;
}

MENU*
menu_create()
{
	static const LIST_INFO str_list_info = {
		str_copy,	/* 複製 */
		str_delete,	/* 削除 */
	};
	//
	MENU* menu;

	/* メニュー構造体のメモリを確保します。 */
	menu = calloc(1, sizeof(MENU));
	if(!menu) {
		DIE();
	}

	/* メニュー構造体の初期値を設定します。 */
	menu->sel = -1;				/* 選択無し */
	//menu->top = 0;
	//
	//menu->x = 0;				/* 横画面全体表示 */
	//menu->y = 0;
	menu->w = DISP_X;
	menu->h = DISP_Y;
	//menu->font = 0;			/* 10ドット日本語フォント */
	menu->palette = menu_palette_std;	/* 4階調標準メニューパレット */
	//
	//menu->title = NULL;			/* タイトル無し */
	menu->items = list_create(&str_list_info);

	return menu;
}

void
menu_delete(MENU* menu)
{
	/* タイトルが設定されていたら、文字列を開放します。 */
	free(menu->title);

	/* メニュー項目のリストを削除します。 */
	list_delete(menu->items);

	/* メニュー構造体のメモリを開放します。 */
	free(menu);
}

void
menu_set_sel(MENU* menu, int sel)
{
	/* 選択無し、または、有効なインデクスであることを確認します。 */
	if(sel < -1 ||
	   sel > menu_num_items(menu) - 1) {
		DIE();
	}
	menu->sel = sel;
}

int
menu_get_sel(MENU* menu)
{
	return menu->sel;
}

void
menu_set_pos(MENU* menu, int x, int y, int w, int h)
{
	menu->x = x;
	menu->y = y;
	menu->w = w;
	menu->h = h;
}

void
menu_set_font(MENU* menu, int font)
{
	/* 改行フラグをクリア(必須!!)して格納します。 */
	font &= ~0x80;
	if(font < 0 || font > 2) {
		DIE();
	}
	menu->font = font;
}

void
menu_set_palette(MENU* menu, const MENUPALETTE* palette)
{
	/* パレットを複製して格納します。 */
	menu->palette = *palette;
}

void
menu_set_title(MENU* menu, const char* title)
{
	/* タイトルが設定されていたら、文字列を開放します。 */
	free(menu->title);

	/* タイトルが指定されていたら、文字列を複製して格納します。
	 * さもなくば、NULL(タイトル無し)とします。
	 */
	if(title) {
		title = strdup(title);
		if(!title) {
			DIE();
		}
	}
	menu->title = (char*)title; /* 警告抑制のためキャスト */
}

void
menu_add_item(MENU* menu, const char* item)
{
	/* 末尾に、メニュー項目を追加します。 */
	menu_insert_item(menu, menu_num_items(menu), item);
}

void
menu_insert_item(MENU* menu, int i_item, const char* item)
{
	/* 指定された位置に、メニュー項目を追加します。 */
	list_insert(menu->items, i_item, (char*)item); /* 警告抑制のためキャスト */

	/* 一つめのメニュー項目追加だったならば、自動的にこの項目を選択します。 */
	if(menu_num_items(menu) == 1) {
		if(menu->sel != -1) {
			DIE(); /* 有り得ない */
		}
		menu->sel = 0;
	}
}

void
menu_set_item(MENU* menu, int i_item, const char* item)
{
	/* 指定された位置の、メニュー項目を変更します。 */
	list_set(menu->items, i_item, (char*)item); /* 警告抑制のためキャスト */
}

void
menu_remove_item(MENU* menu, int i_item)
{
	/* 指定された位置の、メニュー項目を削除します。 */
	list_remove(menu->items, i_item);

	///* * 選択項目よりも前の項目を削除したら、選択項目インデクスを一つ減らします。
	// *   結果的に、削除前と同じ項目を選択維持することになります。
	// * * 削除前が選択無しだった場合は、削除後においても選択無しを維持します。
	// *   以下のif文において、この条件(menu->sel!=-1)を明示する必要は有りません。
	// *   なぜなら、必ず(0<=i_item<=N-1)であり、(-1<i_item)は成立しないからです。
	// */
	//if(menu->sel > i_item) {
	//	menu->sel--;
	///* * 末尾の項目を選択した状態で、末尾の項目を削除した場合、選択項目インデクスを一つ減らします。
	// *   最後の一つの項目を選択した状態で、それを削除した場合、選択無しとなります。
	// *   以上二つのケースは、以下の一つのif文で処理できます。
	// * * 前述の、「選択項目よりも前の項目を削除したら、選択項目インデクスを一つ減らす」のケースと、
	// *   本ケースが同時に発生することは有りません。
	// *   従って、以下のif文は、前のif文のelseブロックとします。(elseブロックにしなくても結果は同じ)
	// */
	//} else if(menu->sel == menu_num_items(menu)/*既に1減っています*/) {
	//	menu->sel--;
	//}
	//↓上の二つのif文の成立ブロックは同じ処理なので、以下のようにまとめられます。
	if(menu->sel > i_item ||
	   menu->sel == menu_num_items(menu)/*既に1減っています*/) {
		menu->sel--;
	}
}

void
menu_clear_items(MENU* menu)
{
	/* メニュー項目をすべて破棄します。 */
	list_clear(menu->items);

	/* 選択無しに戻します。 */
	menu->sel = -1;
}

int
menu_get_item(MENU* menu, int i_item, char* buf, int buflen)
{
	/* メニュー項目文字列の複製を取得し、文字数を数えます。 */
	char* str = list_get(menu->items, i_item); /* 複製!! */
	int len = strlen(str);

	/* バッファが有れば、メニュー項目文字列を格納します。 */
	if(buf && buflen) {
		strncpy(buf, str, buflen);
		if(len >= buflen) {
			buf[buflen - 1] = '\0';
		}
	}

	/* メニュー項目文字列の複製を解放し、文字数を返します。 */
	free(str); /* 開放!! */
	return len;
}

int
menu_num_items(MENU* menu)
{
	return list_size(menu->items);
}

int
menu_action(MENU* menu, int joy)
{
	int result = 0; /* 継続 */
	//
	int n_items;
	int disp_rows;

	/* メニュー項目数を取得します。 */
	n_items = menu_num_items(menu);

	/* メニュー項目が無ければ、選択変更処理を行いません。 */
	if(!n_items) {
		if(menu->sel != -1) {
			DIE(); /* 有り得ない */
		}

	/* メニュー項目が有れば... */
	} else {
		/* 選択有りならば、通常の選択変更処理を行います。 */
		if(menu->sel >= 0) {
			disp_rows = calc_disp_rows(menu);
			if(joy & TRG_UP) menu->sel--;
			if(joy & TRG_DN) menu->sel++;
			if(joy & TRG_LF) menu->sel -= disp_rows;
			if(joy & TRG_RI) menu->sel += disp_rows;
			if(menu->sel < 0          ) menu->sel = 0;
			if(menu->sel > n_items - 1) menu->sel = n_items - 1;

		/* メニュー項目有り、かつ、選択無しで、選択操作が行われた場合は、
		 * - 選択無し→先頭項目選択
		 * または、
		 * - 選択無し→末尾項目選択
		 * の状態変化となります。選択操作が無ければ、選択無しのままです。
		 */
		} else {
			if(joy & (TRG_DN | TRG_RI)) menu->sel = 0;
			if(joy & (TRG_UP | TRG_LF)) menu->sel = n_items - 1;
		}
	}

	/* 選択決定を判定します。 */
	if((joy & TRG_A) && (menu->sel != -1)) {
		result = 1; /* 完了 */

	/* キャンセルを判定します。 */
	} else if(joy & TRG_B) {
		menu->sel = -1;
		result = 1; /* 完了 */
	}

	return result;
}

void
menu_disp(MENU* menu, RENDER* render)
{
	int x;
	int y;
	int w;
	int h;
	int font_h;
	int disp_rows;
	int n_items;
	int i_item;
	char* str;

	/* クリッピング領域を退避します。 */
	const int save_left   = render->left  ;
	const int save_top    = render->top   ;
	const int save_right  = render->right ;
	const int save_bottom = render->bottom;

	/* 枠と背景を描きます。
	 * これらの描画においては、現在のクリッピング領域が適用され、適切にクリッピングされます。
	 */
	x = menu->x;
	y = menu->y;
	w = menu->w;
	h = menu->h;
	if(w <= 0 || h <= 0) {
		goto L_EXIT;
	}
	render_rectangle(render, x, y, w, h, menu->palette.border); /* 枠 */
	x += 1;
	y += 1;
	w -= 2;
	h -= 2;
	if(w <= 0 || h <= 0) {
		goto L_EXIT;
	}
	render_rectangle_fill(render, x, y, w, h, menu->palette.back); /* 背景 */

	/* 現在のクリッピング領域と、枠を除くメニュー領域の交差領域を求め、新たなクリッピング領域とします。
	 * タイトルやメニュー項目の描画が、メニュー枠やメニューの外にはみ出さないようにするための設定です。
	 */
	if(x     > render->left  ) render->left   = x;
	if(y     > render->top   ) render->top    = y;
	if(x + w < render->right ) render->right  = x + w;
	if(y + h < render->bottom) render->bottom = y + h;
	if(render->left >= render->right || render->top >= render->bottom) {
		goto L_EXIT;
	}

	/* フォントサイズを取得します。 */
	font_h = font_h_tbl[menu->font];

	/* タイトルが有れば、タイトルを描きます。 */
	str = menu->title;
	if(str) {
		render_rectangle_fill(render, x, y, w, font_h, menu->palette.border);
		render_string(render, x + 1, y, str, menu->font, menu->palette.title);
		y += font_h;
	}

	/* メニュー項目数を取得します。 */
	n_items = menu_num_items(menu);

	/* スクロール位置を調整します。 */
	if(menu->sel >= 0) { /* 選択有り */
		disp_rows = calc_disp_rows(menu);
		if(menu->sel < menu->top                  ) menu->top = menu->sel;
		if(menu->sel > menu->top + (disp_rows - 1)) menu->top = menu->sel - (disp_rows - 1);
		/* スクロールの行き過ぎ補正。 */
		if(menu->top > (n_items - disp_rows)) menu->top = (n_items - disp_rows); /* 負になる可能性がありますが */
		if(menu->top < (                  0)) menu->top = (                  0); /* すぐにゼロに補正されます。 */
	} else {             /* 選択無し */
		menu->top = 0;
	}

	/* メニュー項目を描きます。 */
	i_item = menu->top;
	for(;;) {
		if(i_item > n_items - 1 ||
		   y >= render->bottom - 1/*下枠*/) {
			break;
		}
		str = list_get(menu->items, i_item); /* 複製!! */
		if(i_item == menu->sel) {
			render_rectangle_fill(render, x, y, w, font_h + 1, menu->palette.item);
			render_string(render, x + 1, y + 1, str, menu->font, menu->palette.back);
		} else {
			//render_rectangle_fill(render, x, y, w, font_h + 1, menu->palette.back);
			render_string(render, x + 1, y + 1, str, menu->font, menu->palette.item);
		}
		free(str); /* 開放!! */
		y += font_h;
		i_item++;
	}

L_EXIT:
	/* クリッピング領域を復元します。 */
	render->left   = save_left  ;
	render->top    = save_top   ;
	render->right  = save_right ;
	render->bottom = save_bottom;
}
