/*
	Free Cell & Speed for WonderSwan
	(メインと外部変数と共通関数)
		H.Iwasaki
*/

#define	_MAIN_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/bios.h>
#include <sys/text.h>
#include "hima.h"

static void init_disp( void );
static void init_sound( void );
static int title( void );
BOOL get_entry(fent_t far *fentp, int index);
	/* contrib\tools\c.mos\player\filelist.c 内の関数です */
static void makeForm( char *buf, int m, int n );
static void dispOption( int pos, int exit );
static void option( void );

u_short waku[] = {
	0xffff, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0,
	0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0xffff, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303,
	0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303,
	0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xc0c0, 0xffff,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff,
	0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0x0303, 0xffff
};

/* キャラクタ関連変数 */
u_short	card_char[4][13][9];
u_short	cell_char[9];
u_short	waku_spr[9];

/* スクリーン制御関連変数 */
int 	scroll_y = 0;

/* 成績保存用変数 */
int 	win[4], lose[4], draw[4];
/* ただし，draw[3]は読み込みカードデータのファイル名を
　 格納した配列のインデックスとして使う
 */

/* SPEEDのCOMレベル */
int	level;

/* FreeCell用カードデータのファイル名 */
char	fnam[MAX_DATAFILE + 1][6] = { " OFF " };

main()
{
	int 	mode = 0;

	init_disp();
	init_sound();
	score_read();

	srand( sys_get_tick_count() );

	while( mode != 3 ) {
		switch( mode = title() ) {
			case 0:
				fcell_main();
				break;
			case 1:
				speed_main();
				break;
			case 2:
				option();
				break;
			case 3:
				/* exit */
				break;
		}
	}
	score_write();
	bios_exit();
}

static void init_disp( void )
{
	lcd_off();	/* ディスプレイを消す */

	palette_set_color(0, 0x6420);		/* カード用パレット */
	palette_set_color(1, 0x2460);		/* 選択カード用パレット */
	palette_set_color(2, 0x3330);		/* カードを置く場所用パレット */
	palette_set_color(12, 0x6420);		/* スプライト(枠)用パレット */
	lcd_set_color(0x7420, 0xfca8);		/* LCD階調データ初期化 */

	display_control(DCM_SCR1);	/* ディスプレイ設定 */

	/* clear screen */
	screen_fill_char(SCREEN1, 0, 0, 32, 32, CHAR_SPACE);

	lcd_on();	/* ディスプレイを点ける */
}

/**********************************************
 * 渡されたフォント(color)を90度回転
 *　戻り値:回転後のフォントデータ
 **********************************************/
u_short far *rotFont( u_short font[] )
{
	static u_short buf[9];
	int 	i, j;

	for( i = 0; i < 8; i++ ) {
		buf[i] = 0;
		for( j = 7; j >= 0; j-- )
			buf[i] |= (((font[j] >> 15 - i) & 1) << 8 + j)
				| (((font[j] >> 7 - i) & 1) << j);
	}
	return buf;
}

/**********************************************
 * 渡されたフォント(mono)を90度回転
 *　戻り値:回転後のフォントデータ
 **********************************************/
u_char far *rotFontMono( u_char font[] )
{
	static u_char buf[9];
	int 	i, j;

	for( i = 0; i < 8; i++ ) {
		buf[i] = 0;
		for( j = 7; j >= 0; j-- )
			buf[i] |= (((font[j] >> 7 - i) & 1) << j);
	}
	return buf;
}

/**********************************************
 *　3 * 3のスプライトを表示
 *
 **********************************************/
void putSprite33( int n, int x, int y )
{
	int 	i;

	for( i = 0; i < 9; i++ )
		sprite_set_location(i + n, x + (i % 3) * 8, y + i / 3 * 8 - scroll_y);
}

/**********************************************
 *　ウィンドウの枠を表示(rot:回転フラグ)
 *
 **********************************************/
void dispWaku( int x, int y, int w, int h, int rot )
{
	int 		i, n, m;

	screen_fill_char(SCREEN1, x, y, w, h, CHAR_SPACE);

	n = CHAR_WAKU + (rot ? 3 : 1);
	m = CHAR_WAKU + (rot ? 5 : 7);
	for( i = 1; i < w - 1; i++ ) {
		screen_set_char(SCREEN1, x + i, y,         1, 1, &n);
		screen_set_char(SCREEN1, x + i, y + h - 1, 1, 1, &m);
	}

	n = CHAR_WAKU + (rot ? 7 : 3);
	m = CHAR_WAKU + (rot ? 1 : 5);
	for( i = 1; i < h - 1; i++ ) {
		screen_set_char(SCREEN1, x,         y + i, 1, 1, &n);
		screen_set_char(SCREEN1, x + w - 1, y + i, 1, 1, &m);
	}

	n = CHAR_WAKU + (rot ? 6 : 0);
	screen_set_char(SCREEN1, x,         y,         1, 1, &n);

	n = CHAR_WAKU + (rot ? 0 : 2);
	screen_set_char(SCREEN1, x + w - 1, y,         1, 1, &n);

	n = CHAR_WAKU + (rot ? 8 : 6);
	screen_set_char(SCREEN1, x,         y + h - 1, 1, 1, &n);

	n = CHAR_WAKU + (rot ? 2 : 8);
	screen_set_char(SCREEN1, x + w - 1, y + h - 1, 1, 1, &n);
}

/**********************************************
 *　文字列表示(rev:反転フラグ,rot:回転フラグ
 *             font_pos:使用するフォント番号)
 **********************************************/
int msg( int x, int y, char *str, int rev, int rot, int font_pos )
{
	static u_char font[9];
	int 		code, i;

	while( (code = *str++) != 0 ) {
		text_get_fontdata(code, font);
		if( rev )
			for( i = 0; i < 8; i++ )
				font[i] = ~font[i];
		if( rot ) {
			font_set_monodata(font_pos, 1, rotFontMono(font));
			screen_set_char(SCREEN1, x, y++, 1, 1, &font_pos);
		}
		else {
			font_set_monodata(font_pos, 1, font);
			screen_set_char(SCREEN1, x++, y, 1, 1, &font_pos);
		}
		font_pos++;
	}
	return font_pos;
}

/**********************************************
 *　中断メニューの表示
 *
 **********************************************/
int exitMenu( int x, int y, int w, int h, char *list[], int lines, int rot )
{
	int 	i, n, key = 0, pos = 0;

	dispWaku(x, y, w, h, rot);
	putSprite33( 0, 224, 0 );
	while( 1 ) {
		n = CHAR_STR;
		if( rot ) {
			for( i = 0; i < lines; i++ )
				n = msg(x + w - 2 - i, y + 1, list[i], pos == i, 1, n);
			key = key_wait();
			play_key_push();
			if( key & KEY_X3 )	/* 決定ボタン */
				break;
			if( key & KEY_X4 ) {	/* キャンセルボタン */
				pos = lines;
				break;
			}
			pos += (key == KEY_Y4) + (key == KEY_Y2) * (lines - 1);
			pos %= lines;
		}
		else {
			for( i = 0; i < lines; i++ )
				n = msg(x + 1, y + 1 + i, list[i], pos == i, 0, n);
			key = key_wait();
			play_key_push();
			if( key & KEY_A )	/* 決定ボタン */
				break;
			if( key & KEY_B ) {	/* キャンセルボタン */
				pos = lines;
				break;
			}
			pos += (key == KEY_DOWN1) + (key == KEY_UP1) * (lines - 1);
			pos %= lines;
		}
	}
	screen_fill_char(SCREEN1, x, y, w, h, CHAR_SPACE);
	return pos;
}

void score_read( void )
{
	int 	fd, i, n = 0;

	for( i = 0; i < 4; i++ )
		win[i] = lose[i] = draw[i] = 0;
	level = 1;
	screen_fill_char(SCREEN1, 0, 0, 28, 30, CHAR_SPACE);
	if( (fd = open(FILE_PATH, O_RDONLY, 0)) < 0 ) {
		if( creat(FILE_PATH, FMODE_R | FMODE_W, 1) != E_FS_SUCCESS ) {
			i = msg(0,0,FILE_PATH,0,0,CHAR_STR);
			msg(1,1,"file create error.",0,0,i);
			key_wait();
			return;
		}
		fs_setinfo(FILE_PATH, FILE_INFO);
		score_write();
		return;
	}
	else {
		n = read(fd, (char *)win, sizeof(win));
		n += read(fd, (char *)lose, sizeof(lose));
		n += read(fd, (char *)draw, sizeof(draw));
		n += read(fd, (char *)&level, sizeof(level));
		if( n != sizeof(win) + sizeof(lose) + sizeof(draw) + sizeof(level)) {
			i = msg(0,0,FILE_PATH,0,0,CHAR_STR);
			msg(1,1,"file read error.",0,0,i);
			key_wait();
			for( i = 0; i < 4; i++ )
				win[i] = lose[i] = draw[i] = 0;
			level = 1;
		}
		close(fd);
	}
	if( draw[3] >= fnam_read(fnam) )
		draw[3] = 0;
}

void score_write( void )
{
	int 	fd, m, n = 0;

	if( (fd = open(FILE_PATH, O_WRONLY, 0)) < 0 ) {
		m = msg(0,0,FILE_PATH,0,0,CHAR_STR);
		msg(1,1,"file write open error.",0,0,m);
		key_wait();
	}
	else {
		lseek(fd,0,0);
		n = write(fd, (char *)win, sizeof(win));
		n += write(fd, (char *)lose, sizeof(lose));
		n += write(fd, (char *)draw, sizeof(draw));
		n += write(fd, (char *)&level, sizeof(level));
		if( n != sizeof(win) + sizeof(lose) + sizeof(draw) + sizeof(level)) {
			m = msg(0,0,FILE_PATH,0,0,CHAR_STR);
			msg(1,1,"file write error.",0,0,m);
			key_wait();
		}
		close(fd);
	}
}

void screenFadeOut( void )
{
	palette_set_color(0, 0x5420);
	sys_wait(10);
	palette_set_color(0, 0x4420);
	sys_wait(10);
	palette_set_color(0, 0x3320);
	sys_wait(10);
	palette_set_color(0, 0x2220);
	palette_set_color(2, 0x2220);
	sys_wait(10);
	palette_set_color(0, 0x1110);
	palette_set_color(2, 0x1110);
	sys_wait(10);
	palette_set_color(0, 0x0000);
	palette_set_color(2, 0x0000);
}

void screenFadeIn( void )
{
	palette_set_color(0, 0x1110);
	palette_set_color(2, 0x1110);
	sys_wait(10);
	palette_set_color(0, 0x2220);
	palette_set_color(2, 0x2220);
	sys_wait(10);
	palette_set_color(0, 0x3320);
	palette_set_color(2, 0x3330);
	sys_wait(10);
	palette_set_color(0, 0x4420);
	sys_wait(10);
	palette_set_color(0, 0x5420);
	sys_wait(10);
	palette_set_color(0, 0x6420);
}

static void init_sound( void )
{
	static u_char wave[16] = {
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	sound_init();			/* サウンド初期化 */
	sound_set_output(0x0f);		/* 出力設定 */
	sound_set_channel(0xcd);	/* チャンネル1,3,4を出力,チャンネル4はノイズモード */
					/* チャンネル3はスウィープON */
	sound_set_noise(0x18 | 0);	/* ノイズイネーブル,種類は0 */
	sound_set_wave(0,wave);		/* チャンネル1の波形のセット */
	sound_set_wave(2,wave);		/* チャンネル3の波形のセット */
	sound_set_sweep( 10, 10 );	/* スウィープ値,ステップ値の設定 */
}

void play_card_put( void )
{
	sound_set_pitch(3,freq2n(440));
	sound_set_volume(3,0xaa);
	sys_wait(2);
	sound_set_volume(3,0);
	sound_set_pitch(3,0);
}

void play_key_push( void )
{
	sound_set_pitch(0,freq2n(4000));
	sound_set_volume(0,0x77);
	sys_wait(2);
	sound_set_volume(0,0);
	sound_set_pitch(0,0);
}

void play_disp_msg( void )
{
	int 	i;

	for( i = 0; i < 3; i++ ) {
		sound_set_pitch(0,freq2n(200));
		sound_set_volume(0,0xcc);
		sys_wait(2);
		sound_set_volume(0,0);
		sound_set_pitch(0,0);
		sys_wait(4);
	}
}

void play_game_clear( void )
{
	sound_set_pitch(2,freq2n(440));
	sound_set_volume(2,0xaa);
	sys_wait(45);
	sound_set_volume(2,0);
	sound_set_pitch(2,0);
}

static int title( void )
{
	static int	pos = 0;
	int 		key, i;

	palette_set_color(0, 0x0000);
	display_control(DCM_SCR1);	/* ディスプレイ設定 */
	screen_set_scroll(SCREEN1, 0, 0);	/* スクロール値初期化 */
	screen_fill_char(SCREEN1, 0, 0, 28, 30, CHAR_SPACE);
	/* FONT LOAD */
	font_set_colordata(CHAR_CARDS, title_width * title_height, bmp_title);
	for( i = 0; i < title_width * title_height; i++ )
		*((u_short *)card_char + i) = i;
	screen_set_char(SCREEN1, 4, 1, title_width, title_height, (u_short *)card_char);
	i = msg(9,12,"FREE CELL", pos == 0, 0, CHAR_STR);
	i = msg(9,14,"  SPEED  ", pos == 1, 0, i);
	i = msg(9,16," OPTION  ", pos == 2, 0, i);

	screenFadeIn();

	while( 1 ) {
		i = msg(9,12,"FREE CELL", pos == 0, 0, CHAR_STR);
		i = msg(9,14,"  SPEED  ", pos == 1, 0, i);
		i = msg(9,16," OPTION  ", pos == 2, 0, i);
		key = key_wait();
		pos += (key == KEY_DOWN1) + (key == KEY_UP1) * 2;
		pos %= 3;
		if( key == KEY_A )
			break;
		else if( key == KEY_START ) {
			pos = 3;
			break;
		}
	}
	return pos;
}

/*
  \WWitch\contrib\tools\c.mos\player\filelist.c
  内の関数です。非常に助かりました。
*/
BOOL get_entry(fent_t far *fentp, int index)
{
	int i;
	int count = 0;
	int entries = fs_getil(cwfs)->_n_entries(cwfs);
	for(i=0; i < entries; i++) {
		if(fs_getil(cwfs)->_getent(cwfs, i, fentp) == E_FS_SUCCESS
			&& fentp->count != -1  && (fentp->mode & ~(FMODE_R|FMODE_W)) == 0) {
			if(count++ == index) return TRUE;
		}
	}
	return FALSE;
}

int fnam_read( char fnam[][6] )
{
	int 		i = 0, n = 1;
	struct stat	statbuf;

	chdir(DATA_DIR);
	while( get_entry(&statbuf, i++) ) {
		if( strncmp(statbuf.info, DATA_INFO, strlen(DATA_INFO)) == 0 )
			strncpy(fnam[n++], statbuf.name, 5);
	}
	return n;
}

static void makeForm( char *buf, int m, int n )
{
	int 	i, j;

	if( n ) {
		i = (u_long)m * 100 / n;
		j = (u_long)m * 1000 / n % 10;
	}
	else
		i = j = 0;
	sprintf(buf,"%3d.%1d%%",i,j);
}

#define	X	1
#define	Y	2
void dispOption( int pos, int exit )
{
	int 		n;
	static char	str[10];
	static char	*list[] = { " EASY ", "NORMAL", " HARD " };

	n = msg(11,0,"OPTION",1,0,0);

	dispWaku(X + 0, Y + 0, 11, 6, 0);
	n = msg(X + 1, Y + 1,"FREE CELL", 0, 0, n);
	n = msg(X + 1, Y + 2,"CARD LOAD", 0, 0, n);
	n = msg(X + 3, Y + 4, fnam[draw[3]], pos == 0, 0, n);

	dispWaku(X + 0, Y + 6, 11, 6, 0);
	n = msg(X + 1, Y + 7, "SPEED", 0, 0, n);
	n = msg(X + 5, Y + 8, "LEVEL", 0, 0, n);
	n = msg(X + 2, Y + 10, list[level], pos == 1, 0, n);

	dispWaku(X + 0, Y + 12, 11, 3, 0);
	n = msg(X + 1, Y + 13, "EXIT", pos == 2 && exit, 0, n);
	n = msg(X + 6, Y + 13, "EDIT", pos == 2 && !exit, 0, n);

	dispWaku(X + 11, Y + 0, 15, 15, 0);
	n = msg(X + 12, Y + 1, "SCORE", 0, 0, n);
	n = msg(X + 13, Y + 3, "FREE CELL", 0, 0, n);
	sprintf(str,"WIN :%4d",win[3]);
	n = msg(X + 14, Y + 4, str, 0, 0, n);
	sprintf(str,"LOSE:%4d",lose[3]);
	n = msg(X + 14, Y + 5, str, 0, 0, n);
	makeForm(str, win[3], win[3] + lose[3]);
	n = msg(X + 18, Y + 6, str, 0, 0, n);

	n = msg(X + 13, Y + 8, "SPEED", 0, 0, n);
	sprintf(str,"WIN :%4d",win[level]);
	n = msg(X + 14, Y + 9, str, 0, 0, n);
	sprintf(str,"LOSE:%4d",lose[level]);
	n = msg(X + 14, Y + 10, str, 0, 0, n);
	sprintf(str,"DRAW:%4d",draw[level]);
	n = msg(X + 14, Y + 11, str, 0, 0, n);
	makeForm(str, win[level], win[level] + lose[level] + draw[level]);
	n = msg(X + 18, Y + 12, str, 0, 0, n);
}

static void option( void )
{
	int 	key, loop = 1;
	int	fnam_max, pos = 0, exit = 1;

	screenFadeOut();
	font_set_colordata(CHAR_WAKU, 9, waku);

	fnam_max = fnam_read(fnam);

	screen_fill_char(SCREEN1, 0, 0, 28, 18, CHAR_SPACE);
	dispOption(pos, exit);

	sys_wait(10);
	screenFadeIn();

	while( loop ) {
		key = key_wait();
		pos += (key == KEY_DOWN1) + (key == KEY_UP1) * 2;
		pos %= 3;
		switch( pos ) {
			case 0:
				draw[3] += (key == KEY_RIGHT1) + (key == KEY_LEFT1)
					 * (fnam_max - 1);
				draw[3] %= fnam_max;
				break;
			case 1:
				level += (key == KEY_RIGHT1) + (key == KEY_LEFT1) * 2;
				level %= 3;
				break;
			case 2:
				if( key == KEY_LEFT1 )
					exit = 1;
				if( key == KEY_RIGHT1 )
					exit = 0;
				if( key == KEY_A )
					if( exit )
						loop = 0;
					else {
						edit();
						fnam_max = fnam_read(fnam);
						screen_fill_char(SCREEN1, 0, 0,
							28, 18, CHAR_SPACE);
					}
		}
		dispOption(pos, exit);
	}
	score_write();
	screenFadeOut();
}
