/*	
 *	clipemu.c
 *
 *	エミュレータ用サブルーチン集。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2003 Naoyuki Sawa
 *
 *	* Thu Dec 25 06:00:00 JST 2003 Naoyuki Sawa
 *	- 作成開始。
 *	* Mon Mar 15 12:30:00 JST 2004 Naoyuki Sawa
 *	- Zip展開を、オリジナルZlibライブラリ→自作Zlib/Zip互換ライブラリに変更しました。
 *	  Zip展開時に一時的に確保される作業用メモリサイズが、35KB→5KB程度に減りました。
 *	* Sun Dec 19 20:21:00 JST 2004 Naoyuki Sawa
 *	- clippzlモジュールの名称変更(=>モジュール名clipzip。関数、構造体名も変更)に伴い、
 *	  Zip展開関数呼び出しと構造体名を修正しました。
 *	  このclipemuモジュール自身の本質的な変更はありません。
 */
#include "clip.h"

/* ※まだあまりテストしていません。全く高速化していません。 */

/****************************************************************************
 *	キャラクタ描画
 ****************************************************************************/

static void
render_gfx_8x8(RENDER* render, const GFX* gfx, int dx, int dy, int code, int palette, int param)
{
	SURFACE* surface = render->surface;
	int planes = gfx->planes;
	const unsigned char** plane_address = (const unsigned char**)gfx->plane_address;
	//
	unsigned char* dst;
	const unsigned char* src[GFX_MAX_PLANES];
	//
	int i, x, y, offset;
	unsigned char pixel[8];
	unsigned char plane[GFX_MAX_PLANES];

	/* サーフェスの始点アドレスを求めます。 */
	offset = surface->w * dy + dx;
	dst = surface->vbuff + offset;

	/* キャラクタのプレーンアドレスを求めます。
	 * プレーン#0をビット0、プレーン#(planes-1)をビット(planes-1)に合成するために、
	 * あらかじめプレーンアドレスの配列並びを逆にしておきます。
	 * プレーン合成時、先に処理するプレーンほど上位ビット側にシフトされるからです。
	 */
	if(!(param & GFX_REVY)) {
		offset = code * 8;	/* ライン0〜7の順に描画 */
	} else {
		offset = code * 8 + 7;	/* ライン7〜0の順に描画 */
	}
	for(i = 0; i < planes; i++) {
		src[i] = plane_address[(planes - 1) - i] + offset;
	}

	/* プレーン合成、描画処理。 */
	palette <<= planes;
	for(y = 0; y < 8; y++) {
		/* ラインデータ取得。 */
		for(i = 0; i < planes; i++) {
			if(!(param & GFX_REVY)) {
				plane[i] = *src[i]++;	/* ライン0〜7の順に描画 */
			} else {
				plane[i] = *src[i]--;	/* ライン7〜0の順に描画 */
			}
		}

		/* 垂直クリッピング。 */
		if(param & GFX_CLIPY) {
			if(dy + y < render->top || dy + y >= render->bottom) {
				dst += surface->w;
				continue;	/* 次のラインへ。 */
			}
		}

		/* ピクセル描画。 */
		for(x = 0; x < 8; x++, dst++) {
			/* プレーン合成。 */
			pixel[x] = 0;
			for(i = 0; i < planes; i++) {
				if(!(param & GFX_REVX)) {
					pixel[x] <<= 1;
					pixel[x] |= plane[i] >> 7 & 1;	/* ビット7〜0の順に描画 */
					plane[i] <<= 1;
				} else {
					pixel[x] <<= 1;
					pixel[x] |= plane[i] & 1;	/* ビット0〜7の順に描画 */
					plane[i] >>= 1;
				}
			}

			/* 水平クリッピング。 */
			if(param & GFX_CLIPX) {
				if(dx + x < render->left || dx + x >= render->right) {
					continue;
				}
			}

			/* 透過色？ */
			if(param & GFX_TRANSPARENT) {
				if(!pixel[x]) {
					continue;
				}
			}

			/* 描画。 */
			*dst = palette | pixel[x];
		}
		dst += surface->w - 8;	/* 次のラインへ。 */
	}
}

void
render_gfx(RENDER* render, const GFX* gfx, int dx, int dy, int code, int palette, int param)
{
	int w = gfx->width;
	int h = gfx->height;
	//
	int x1 = dx;
	int x2 = dx + w;
	int y1 = dy;
	int y2 = dy + h;

	/* 1プレーン1ピクセル1ビット以外は未対応です。 */
	if(gfx->bits != 1) DIE();

	/* おおまかなクリッピング判定を行います。 */
	if(x2 <= render->left  ) return; /* 完全に画面外(左) */
	if(x1 >= render->right ) return; /* 完全に画面外(右) */
	if(y2 <= render->top   ) return; /* 完全に画面外(上) */
	if(y1 >= render->bottom) return; /* 完全に画面外(下) */

	/* 少なくとも一部が画面内です。クリッピングの要or不要を判断します。 */
	if(x1 < render->left || x2 > render->right ) param |= GFX_CLIPX; /* 要水平クリッピング */
	if(y1 < render->top  || y2 > render->bottom) param |= GFX_CLIPY; /* 要垂直クリッピング */

	/* キャラクタサイズに対応するルーチンを呼び出します。 */
	switch(w | h << 8) {
	case 0x0808:
		render_gfx_8x8(render, gfx, dx, dy, code, palette, param);
		break;
	default:
		DIE();
	}
}

/****************************************************************************
 *	カラールックアップ
 ****************************************************************************/

void
clut_init(unsigned clut[/*count*/], int count)
{
	/* グレイスケールに初期化し、“無効(=要更新)”をマークします。 */
	int i, v;
	for(i = 0; i < count; i++) {
		v = 0xff * i / (count - 1); /* count>=2必須 */
		clut[i] = v | v << 8 | v << 16 | 0xff << 24;
	}
}

void
clut_set_b(unsigned clut[], int i, unsigned char v)
{
	unsigned char* p = (unsigned char*)(clut + i);
	if(p[0] != v) {
		p[0] = v;
		p[3] = 0xff;
	}
}

void
clut_set_g(unsigned clut[], int i, unsigned char v)
{
	unsigned char* p = (unsigned char*)(clut + i);
	if(p[1] != v) {
		p[1] = v;
		p[3] = 0xff;
	}
}

void
clut_set_r(unsigned clut[], int i, unsigned char v)
{
	unsigned char* p = (unsigned char*)(clut + i);
	if(p[2] != v) {
		p[2] = v;
		p[3] = 0xff;
	}
}

void
clut_set_rgb(unsigned clut[], int i, int v)
{
	if((clut[i] & 0x00ffffff) != v) {
		clut[i] = 0xff000000 | v;
	}
}

int
clut_update(unsigned clut[/*count*/], int count)
{
	int result = 0;
	unsigned char* p = (unsigned char*)clut;
	while(count--) {
		if(p[3] == 0xff) {
			p[3] = (p[2] * 5 + p[1] * 8 + p[0] * 3) >> 10 ^ 3;
			//     -------------------------------+ ----+ --+
			//                                    |     |   +- 輝度反転
			//                                    |     +----- 0〜3に変換
			//                                    +----------- R:G:B＝5:8:3
			result++; /* 更新エントリ数カウントアップ */
		}
		p += 4;
	}
	return result;
}

void
render_clut(RENDER* render, const unsigned clut[], int x, int y, int w, int h)
{
	SURFACE* surface;
	unsigned char* p;
	int n;

	/*{{render_rectangle_fill()からコピーして持ってきました*/

	/* サーフェイスを取り出しておきます。 */
	surface = render->surface;

	/* 正規化します。(左上〜右下の形) */
	if(w < 0) { x += w; w = -w; }
	if(h < 0) { y += h; h = -h; }

	/* クリッピングします。 */
	if(x < render->left) {
		n = render->left - x; /* 左にはみ出たピクセル数 */
		x = render->left;
		w -= n;
	}
	if(x + w > render->right) {
		w = render->right - x;
	}
	if(w <= 0) return; /* 画面外 */
	if(y < render->top) {
		n = render->top - y; /* 上にはみ出たピクセル数 */
		y = render->top;
		h -= n;
	}
	if(y + h > render->bottom) {
		h = render->bottom - y;
	}
	if(h <= 0) return; /* 画面外 */

	/*}}render_rectangle_fill()からコピーして持ってきました*/

	/* 指定領域内のカラールックアップ実行。 */
	p = &surface->vbuff[surface->w * y + x];
	for(y = 0; y < h; y++, p += surface->w - w) {
		for(x = 0; x < w; x++, p++) {
			*p = ((unsigned char*)&clut[*p])[3];
		}
	}
}

/****************************************************************************
 *	ROMセットファイル
 ****************************************************************************/

int
rom_load(const char* fname, const ROM* rom, int count, unsigned char* mem)
{
	int retval, size, i;
	const void* romp;
	ZIP zip;
	ZIPDIR dir;

	/* Zipファイルを開きます。(Close不要です) */
	romp = mmap(fname, &size, 1/*Defrag*/);
	if(!romp) return -1; /* Zipファイルが見つからない */
	retval = zip_init(&zip, romp, size);
	if(retval < 0) return retval; /* 不正なZipファイル */

	/* 各ファイルを展開します。 */
	for(i = 0; i < count; i++, rom++) {
		/* ファイル情報を取得します。 */
		retval = zip_find(&zip, rom->name, &dir);
		if(retval < 0) return retval; /* ファイル情報が見つからない */
		if(dir.uncompressed_size != rom->size) return -1; /* サイズ不一致 */
		if(dir.crc32 != rom->crc) return -1; /* CRC不一致 */

		/* ファイルを展開します。 */
		retval = zip_uncompress(&zip, rom->name, &mem[rom->base], rom->size);
		if(retval < 0) return retval; /* 展開失敗 */
	}

	return 0;
}

