// ---------------------------------------------------------------------------
//  M88 - PC-8801 emulator
//	Copyright (C) cisc 1998, 1999.
// ---------------------------------------------------------------------------
//	$Id: status.cpp,v 1.8 2002/04/07 05:40:10 cisc Exp $

#include "headers.h"
#include "status.h"

//#define LOGNAME "status"
#include "diag.h"

StatusDisplay statusdisplay;

// ---------------------------------------------------------------------------
//	Constructor/Destructor
//
StatusDisplay::StatusDisplay()
{
	hwnd = 0;
	hwndparent = 0;
	list = 0;
	timerid = 0;
	height = 0;
	litstat[0] = litstat[1] = litstat[2] = 0;
	litcurrent[0] = litcurrent[1] = litcurrent[2] = 0;
	updatemessage = 0;
	showfdstat = false;
	stopped = false;
	hfonttext = 0;
	currentduration = 0;
	currentpriority = 10000;
}
	
StatusDisplay::~StatusDisplay()
{
	Cleanup();
	while (list)
	{
		List* next = list->next;
		delete list;
		list = next;
	}
}

bool StatusDisplay::Init(HWND hwndp)
{
	hwndparent = hwndp;
	return true;
}

bool StatusDisplay::Enable(bool showfd)
{
	if (!hwnd)
	{
		hwnd = CreateStatusWindow(WS_CHILD | WS_VISIBLE, 0, hwndparent, 1);

		if (!hwnd)
			return false;
	}
	{
		showfdstat = showfd;

		UpdateParts();
		InvalidateRect(hwndparent, 0, false);

		RECT rect;
		GetWindowRect(hwnd, &rect);
		height = rect.bottom - rect.top;

		PostMessage(hwnd,  SB_SETTEXT, SBT_OWNERDRAW | 1, 0);
	}
	return true;
}

// ---------------------------------------------------------------------------
//	パーツ幅の再計算
//	親ウインドウの幅が変わったときに呼ぶ
//
void StatusDisplay::UpdateParts()
{
	if (!hwnd)
		return;

	SendMessage(hwnd, SB_GETBORDERS, 0, (LPARAM) &border);

	RECT rect;
	GetClientRect(hwndparent, &rect);
	int width = rect.right - rect.left - border.vertical;
	int lamp = (showfdstat ? 80 : 64) + border.split;

	int widths[2];
	widths[0] = (width > lamp) ? (width - lamp) : 0;
	widths[1] = -1;
	SendMessage(hwnd, SB_SETPARTS, 2, (LPARAM) widths);
}

bool StatusDisplay::Disable()
{
	if (hfonttext)
	{
		DeleteObject(hfonttext);
		hfonttext = 0;
	}
	if (hwnd)
	{
		DestroyWindow(hwnd);
		hwnd = 0;
		height = 0;
	}
	return true;
}

void StatusDisplay::Cleanup()
{
	Disable();
	if (timerid) {
		KillTimer(hwndparent, timerid);
		timerid = 0;
	}
}

// ---------------------------------------------------------------------------
//	デバッガで停止中かどうかを設定する
//	停止中はステータスバーの背景色を変える
//
void StatusDisplay::SetStopped(bool s)
{
	if (stopped == s)
		return;
	stopped = s;
	if (hwnd)
		InvalidateRect(hwnd, 0, true);
}

// ---------------------------------------------------------------------------
//	メッセージ表示に使うフォントを作る (初回だけ)
//	コントロールの既定フォントを1〜2ポイント大きくしてボールドにする。
//	WM_SETFONT でコントロール自体に設定するとバーの高さが変わって
//	画面の割り付けが動くので、描画のときに DC へ選ぶだけにする
//
HFONT StatusDisplay::GetTextFont()
{
	if (hfonttext)
		return hfonttext;

	HFONT hf = hwnd ? (HFONT) SendMessage(hwnd, WM_GETFONT, 0, 0) : 0;
	if (!hf)
		hf = (HFONT) GetStockObject(DEFAULT_GUI_FONT);

	LOGFONT lf;
	if (!GetObject(hf, sizeof(lf), &lf))
		return 0;

	lf.lfWeight = FW_BOLD;
	// lfHeight は負なら文字の高さ、正ならセルの高さ。どちらでも「大きく」なる向きへ
	lf.lfHeight += (lf.lfHeight < 0) ? -2 : 2;

	hfonttext = CreateFontIndirect(&lf);
	return hfonttext;
}

// ---------------------------------------------------------------------------
//	DrawItem
//
void StatusDisplay::DrawItem(DRAWITEMSTRUCT* dis)
{
	COLORREF bk = stopped ? RGB(0xe6, 0x82, 0xc8) : GetSysColor(COLOR_3DFACE);

	// 塗る範囲。パーツの矩形はステータスバーの縁(SB_GETBORDERS のぶん)より
	// 内側にあり、パーツ同士の間にも隙間がある。そこだけ地の色が残ると、
	// 角が丸い窓の左下・右下に元の色が三日月状に残って見える。
	// 停止中だけ、自分の側の端をコントロールの縁まで広げて塗る
	// (通常時は従来どおり。地のテーマ描画を上書きしないため)
	RECT rc = dis->rcItem;
	if (stopped)
	{
		RECT client;
		if (GetClientRect(dis->hwndItem, &client))
		{
			rc.top = client.top;
			rc.bottom = client.bottom;
			if (dis->itemID == 0)
				rc.left = client.left, rc.right += border.split;	// パーツ間の隙間も埋める
			else
				rc.right = client.right;
		}
	}

	switch (dis->itemID)
	{
	case 0:
		{
			SetBkColor(dis->hDC, bk);
			SetTextColor(dis->hDC, stopped ? RGB(0, 0, 0) : GetSysColor(COLOR_MENUTEXT));
			char* text = reinterpret_cast<char*>(dis->itemData);

			HFONT hfont = GetTextFont();
			HFONT hfontold = hfont ? (HFONT) SelectObject(dis->hDC, hfont) : 0;

			// パーツの高さの中で縦中央に置く (フォントを大きくしたぶん、
			// 上詰めのままだと下にはみ出す)
			TEXTMETRIC tm;
			GetTextMetrics(dis->hDC, &tm);
			int y = dis->rcItem.top
				  + ((dis->rcItem.bottom - dis->rcItem.top) - tm.tmHeight) / 2;
			if (y < dis->rcItem.top)
				y = dis->rcItem.top;

			// 左端に2文字ぶんの余白を空ける (ギリギリだと窮屈に見えるため)
			int x = dis->rcItem.left + tm.tmAveCharWidth * 2;

			// ETO_OPAQUE で矩形ごと塗る (TextOut だと文字の背後しか塗られず、
			// 停止中の色がパーツ全体に乗らない)。塗り潰しの範囲は rc
			ExtTextOut(dis->hDC, x, y, ETO_OPAQUE,
					   &rc, text, (UINT) strlen(text), 0);

			if (hfontold)
				SelectObject(dis->hDC, hfontold);
			break;
		}
	case 1:
		{
			const DWORD color[] =
			{
				RGB(64, 0, 0), RGB(255, 0, 0), RGB(0, 64, 0), RGB(0, 255, 0),
				RGB(0, 32, 64), RGB(0, 128, 255),
			};

			// ランプの下地も塗る (パーツ0 と色が食い違わないように)
			HBRUSH hbk = CreateSolidBrush(bk);
			FillRect(dis->hDC, &rc, hbk);
			DeleteObject(hbk);

			HBRUSH hbrush1, hbrush2, hbrush3, hbrushold;
			hbrush1 = CreateSolidBrush(color[litcurrent[1] & 3]);
			hbrush2 = CreateSolidBrush(color[litcurrent[0] & 3]);
			hbrush3 = CreateSolidBrush(color[4 | (litcurrent[2] & 1)]);
			hbrushold = (HBRUSH) SelectObject(dis->hDC, hbrush1);
			Rectangle(dis->hDC, dis->rcItem.left+6, height/2 - 4, dis->rcItem.left + 24, height/2 + 5);
			SelectObject(dis->hDC, hbrush2);
			Rectangle(dis->hDC, dis->rcItem.left+32, height/2 - 4, dis->rcItem.left + 50, height/2 + 5);
			if (showfdstat)
			{
				SelectObject(dis->hDC, hbrush3);
				Rectangle(dis->hDC, dis->rcItem.left+58, height/2 - 4, dis->rcItem.left + 68, height/2 + 5);
			}
			SelectObject(dis->hDC, hbrushold);
			DeleteObject(hbrush1);
			DeleteObject(hbrush2);
			DeleteObject(hbrush3);
			break;
		}
	}
}

// ---------------------------------------------------------------------------
//	メッセージ追加
//
bool StatusDisplay::Show(int priority, int duration, char* msg, ...)
{
	CriticalSection::Lock lock(cs);
	
	if (currentpriority < priority)
		if (!duration || (GetTickCount() + duration - currentduration) < 0)
			return true;

	List* entry = new List;
	if (!entry)
		return false;
	memset(entry, 0, sizeof(List));

	va_list marker;
	va_start(marker, msg);
	int tl = wvsprintf(entry->msg, msg, marker);
	assert(tl < 128);
	va_end(marker);

	entry->duration = GetTickCount() + duration;
	entry->priority = priority;
	entry->next = list;
	entry->clear = duration != 0;
	list = entry;

	LOG3("reg : [%s] p:%5d d:%8d\n", entry->msg, entry->priority, entry->duration);
	updatemessage = true;
	return true;
}

// ---------------------------------------------------------------------------
//	更新
//
void StatusDisplay::Update()
{
	updatemessage = false;
	if (hwnd)
	{
		CriticalSection::Lock lock(cs);
		// find highest priority (0 == highest)
		int pc = 10000;
		List* entry = 0;
		int c = GetTickCount();
		for (List* l = list; l; l=l->next)
		{
//			LOG3("\t\t[%s] p:%5d d:%8d\n", l->msg, l->priority, l->duration);
			if ((l->priority < pc) && ((!l->clear) || (l->duration - c) > 0))
				entry = l, pc = l->priority;
		}
		if (entry)
		{
			LOG3("show: [%s] p:%5d d:%8d\n", entry->msg, entry->priority, entry->duration);
			memcpy(buf, entry->msg, 128);
			PostMessage(hwnd,  SB_SETTEXT, SBT_OWNERDRAW | 0, (LPARAM) buf);
			
			if (entry->clear)
			{
				timerid = ::SetTimer(hwndparent, 8, entry->duration - c, 0);
				currentpriority = entry->priority;
				currentduration = entry->duration;
			}
			else
			{
				currentpriority = 10000;
				if (timerid)
				{
					KillTimer(hwndparent, timerid);
					timerid = 0;
				}
			}
			Clean();
		}
		else
		{
			LOG0("clear\n");
			PostMessage(hwnd, SB_SETTEXT, 0, (LPARAM) " ");
			currentpriority = 10000;
			if (timerid)
			{
				KillTimer(hwndparent, timerid);
				timerid = 0;
			}
		}
	}
}

// ---------------------------------------------------------------------------
//	必要ないエントリの削除
//
void StatusDisplay::Clean()
{
	List** prev = &list;
	int c = GetTickCount();
	while (*prev)
	{
		if ((((*prev)->duration - c) < 0) || !(*prev)->clear)
		{
			List* de = *prev;
			*prev = de->next;
			delete de;
		}
		else
			prev = &(*prev)->next;
	}
}

// ---------------------------------------------------------------------------
//
//
void StatusDisplay::FDAccess(uint dr, bool hd, bool active)
{
	dr &= 1;
	if (!(litstat[dr] & 4))
	{
		litstat[dr] = (hd ? 0x22 : 0) | (active ? 0x11 : 0) | 4;
	}
	else
	{
		litstat[dr] = (litstat[dr] & 0x0f) | (hd ? 0x20 : 0) | (active ? 0x10 : 0) | 9;
	}
}

void StatusDisplay::UpdateDisplay()
{
	bool update = false;
	for (int d=0; d<3; d++)
	{
		if ((litstat[d] ^ litcurrent[d]) & 3)
		{
			litcurrent[d] = litstat[d];
			update = true;
		}
		if (litstat[d] & 8)
			litstat[d] >>= 4;
	}
	if (hwnd)
	{
		if (update)
			PostMessage(hwnd,  SB_SETTEXT, SBT_OWNERDRAW | 1, 0);
		if (updatemessage)
			Update();
	}
}

// 
