/**********************************************************************

	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomohito Nakajima <nakajima@zeta.co.jp>
	Tomoki Sekiyama <sekiyama@yahoo.co.jp>

	This program is free software; you can redistribute it
	and/or modify it under the terms of the GLOBALBASE
	Library General Public License (G-LGPL) as published by

	http://www.globalbase.org/

	This program is distributed in the hope that it will be
	useful, but WITHOUT ANY WARRANTY; without even the
	implied warranty of MERCHANTABILITY or FITNESS FOR A
	PARTICULAR PURPOSE.

**********************************************************************/


#include "machine/gb_windows.h"
#include "vwin_control.h"
#include "v/VWindow.h"
#include "v/VButton.h"
#include "v/VMenu.h"
#include "VApplication.h"
#include "vwin_error.h"
#include "v/VLayout.h"
#include "XPStyle.h"

extern "C" {
#include "machine/lc_util.h"
#include "utils.h"
#include "pri_level.h"
#include "memory_debug.h"

bool idle_task();
}

VWindowsList *windows_list;


static V_CALLBACK_D(mark_window)
{
	VLayout::mark(object);
}

class VWindowInfo : public VContainerInfo{
public:
	VWindowInfo(VObject *o, HWND h, int id)
		: VContainerInfo(o,h,id,0,1),menu_info(0) {}
	virtual ~VWindowInfo() {
		if ( menu_info )
			delete menu_info;
	}

	static BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
	BOOL dispatch_message(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
	static int translate_accel(MSG *msg);

	static HWND get_active_dialog() { return active_dialog; }
	void	set_menu_info(VMenuInfoM *info);

private:
	static HWND	active_dialog;
	VMenuInfoM	*menu_info;
};

HWND VWindowInfo::active_dialog = NULL;

HWND
get_active_dialog()
{
	return VWindowInfo::get_active_dialog();
}

int
translate_accel(MSG *msg)
{
	return VWindowInfo::translate_accel(msg);
}

int
VWindowInfo::translate_accel(MSG *msg)
{
	HWND parent, toplevel = msg->hwnd;
	while ( (parent = GetParent(toplevel)) != 0 )
		toplevel = parent;
	VWindowInfo *info = dynamic_cast<VWindowInfo*>(get_from_hwnd(toplevel));
	if ( info == 0 )
		return 0;
	return TranslateAccelerator(toplevel, info->menu_info->get_haccel(), msg);
}

void
VWindowInfo::set_menu_info(VMenuInfoM *info)
{
	menu_info = new VMenuInfoM(*info);
}

BOOL
VWindowInfo::dispatch_message(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	VWindow* vwin = dynamic_cast<VWindow*>(get_obj());
	VInfo *info;
	HDC hdc;
	PAINTSTRUCT ps;
	RECT r;
	
	switch (message) {
	case WM_CTLCOLORSTATIC:
		return FALSE;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		if ( ps.fErase ) {
			HBRUSH bg = CreateSolidBrush(GetSysColor(COLOR_3DFACE));
			FillRect(hdc, &ps.rcPaint, bg);
			DeleteObject(bg);
		}
		EndPaint(hWnd, &ps);
		return 1;
	case WM_ACTIVATE:
		if ( LOWORD(wParam) == WA_ACTIVE ||
			 LOWORD(wParam) == WA_CLICKACTIVE )
			active_dialog = get_hwnd();
		return FALSE;
	case WM_INITMENU:
		VCustomizedMenuBarImp::init_menu((HMENU)wParam, menu_info);
		return FALSE;
	case WM_COMMAND:
		if ( lParam == 0 )	{
			if ( LOWORD(wParam) >= 10 ) { // from menu
				VCustomizedMenuBarImp::menu_choosed(vwin, LOWORD(wParam));
				return TRUE;
			}
			return FALSE;
		}
		break;
	case WM_GETMINMAXINFO:
		LPMINMAXINFO lpmm;
		lpmm = (LPMINMAXINFO)lParam;
		r.left = r.top = 0;
		r.right = min_size.w;
		r.bottom = min_size.h;
		AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, menu_info != 0);
		lpmm->ptMinTrackSize.x = r.right-r.left;
		lpmm->ptMinTrackSize.y = r.bottom-r.top;
		break;
	case WM_SIZE:
		::GetClientRect(hWnd, &r);
		if ( (r.top || r.left || r.right || r.bottom )
				&& ( size.w != r.right-r.left 
				  || size.h != r.bottom-r.top ) ) {
			size.w = r.right-r.left;
			size.h = r.bottom-r.top;
			vq_insert_callback(vwin, mark_window, 0, 0, 0, VSER_OFF);

			// while resizing, pick up ms task and execute here in main thread...
/*			extern bool doing_layout;
			int max_loop = 100;
			do {
				while (idle_task()) {}
				Sleep(1);
			} while (doing_layout && max_loop--);
*/
		}
		return FALSE;
	case WM_CLOSE:
		vwin->attempt_close();
		return TRUE;	// don't close window here
	}

	MSG msg;
	memset(&msg, 0, sizeof(MSG));
	msg.hwnd = hWnd;
	msg.message = message;
	msg.lParam = lParam;
	msg.wParam = wParam;
	return VContainerInfo::dispatch_message(&msg);
}

BOOL CALLBACK
VWindowInfo::DlgProc(HWND hwnd , UINT message , WPARAM wp , LPARAM lp)
{
	VInfo *info = get_from_hwnd(hwnd);
	VWindowInfo *win_info = info ? dynamic_cast<VWindowInfo*>(info) : 0;
	if ( win_info )
		return win_info->dispatch_message(hwnd, message, wp, lp);
	return FALSE;
}

static HWND v_create_dialog(DWORD style, HWND parent)
{
	struct {
		DLGTEMPLATE	h;
		USHORT		menu;
		USHORT		cls;
		USHORT		title;
	} tmp;
	tmp.h.style = style;
	tmp.h.dwExtendedStyle = 0;
	tmp.h.cdit = 0;
	tmp.h.x = 0;
	tmp.h.y = 0;
	tmp.h.cx = 10;
	tmp.h.cy = 10;
	tmp.menu = 0;
	tmp.cls = 0;
	tmp.title = 0;
	HICON hIcon;
	hIcon = LoadIcon(theApp->get_instance(), MAKEINTRESOURCE(IDI_WINDOW));
	HWND hWnd = CreateDialogIndirect(GetModuleHandle(NULL),
		(DLGTEMPLATE*)&tmp, parent, VWindowInfo::DlgProc);
	SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
	return hWnd;
}

static HWND v_create_window_ex(CREATESTRUCT *create_struct){
	HWND ret = v_create_dialog(
		create_struct->style, create_struct->hwndParent);
	SetMenu(ret, create_struct->hMenu);
	SetWindowText(ret, create_struct->lpszName);
	MoveWindow(ret, create_struct->x, create_struct->y,
		create_struct->cx, create_struct->cy, TRUE);
	VWindowsList *list = new VWindowsList;
	list->hwnd = ret;
	list->next = windows_list;
	windows_list = list;
	return ret;
}

void v_destroy_window(HWND hwnd)
{
	VWindowsList *list, *prev = 0;
	for ( list = windows_list ; list ; prev = list, list = list->next ) {
		if ( list->hwnd == hwnd ) {
			if ( prev )
				prev->next = list->next;
			else
				windows_list = list->next;
			delete list;
			break;
		}
	}
	DestroyWindow(hwnd);
}

static V_CALLBACK_D(layout_and_show_window)
{
	VLayout::layout_marked_window();
	v_serialized_exec_sub(::ShowWindow,(HWND)user_arg, SW_SHOWNORMAL);
}

VExError 
VWindow::create_do(const VObjectStatus *s, int flags, VObject *parent, void * arg)
{
	window_close_handler = 0;
	sts.min_size.w = V_WINDOW_MIN_WIDTH;
	sts.min_size.h = V_WINDOW_MIN_HEIGHT;

	category = *(int*)arg;
	menu_bar = VCustomizedMenuBar::create(category, this);
	HMENU hmenu = NULL;
	VMenuInfoM menu_info;
	memset(&menu_info, 0, sizeof(menu_info));
	if ( menu_bar )
		hmenu = v_serialized_exec_func(VCustomizedMenuBarImp::make_menu_items,
						menu_bar->get_items(), &menu_info, 0);

	if ( flags & VSF_ATTR )
		sts.attr = s->attr;
	else
		sts.attr = 0;

	HWND hwnd_parent=NULL;
	if((flags & VSF_PARENT) && s->parent){
		VInfo *info = s->parent->get_info_this();
		if(info && ::IsWindow(info->get_hwnd()))
			hwnd_parent = info->get_hwnd();
		else
			hwnd_parent = NULL;
	}
	else{
		hwnd_parent = NULL;
	}

	CREATESTRUCT create_struct;
	create_struct.lpCreateParams = NULL;
	create_struct.hInstance = ::GetModuleHandle(NULL);
	create_struct.hMenu = hmenu;
	create_struct.hwndParent = hwnd_parent;
	create_struct.cx = V_WINDOW_MIN_WIDTH;
	create_struct.cy = V_WINDOW_MIN_HEIGHT;
	create_struct.x = 0;
	create_struct.y = 0;
	create_struct.style = WS_OVERLAPPEDWINDOW;
	
	create_struct.lpszName = "vobject_defaultwnd";
	create_struct.lpszClass = "vobject_defaultwnd";
	create_struct.dwExStyle = 0;

	HWND hwnd = v_serialized_exec_func(v_create_window_ex, &create_struct);
	info = new VWindowInfo(this,hwnd,s->id);
	static_cast<VWindowInfo*>(info)->set_menu_info(&menu_info);
	return initial_VExError(V_ER_NO_ERR,0,0);
}

void
VWindow::destroy_do(VObject *parent)
{
	parent->remove_child_do(this);
	if(info){
		VLayout::unmark(this);
		v_serialized_exec_sub(v_destroy_window, info->get_hwnd());
		delete info;
		info = NULL;
	}

	if ( menu_bar )
		delete menu_bar;

}

VWindow::~VWindow()
{
}

VExError
VWindow::get_status(VObjectStatus *s, int flags) const
{
	VExError err = initial_VExError(V_ER_NO_ERR,flags,0);
	V_OP_START_EX

	HWND hwnd = info->get_hwnd();

	RECT r;
	if ( flags & VSF_SIZE ) {
		::GetClientRect(hwnd, &r);
		s->size.w = r.right-r.left;
		s->size.h = r.bottom-r.top;
		flags &= ~VSF_SIZE;
	}

	win_control_default_get_status(hwnd, s, flags, &err);
	
	VExError err2 = VObject::get_status(s,flags);
	if ( err2.code )
		err =  merge_VExError_vstatus_type(err,err2);
	
	V_OP_END
	return err;
}


VExError
VWindow::set_status(const VObjectStatus *s, int flags)
{
	V_OP_START_EX
	VExError err = VObject::set_status(s,flags);
	if ( err.code ) {
		V_OP_END
		return err;
	}
	
	HWND hwnd = info->get_hwnd();
	LONG window_style;

	if ( flags & VSF_ATTR ) {
		window_style = ::GetWindowLong(hwnd, GWL_STYLE);
		if ( s->attr & resizable )
			window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
		else
			window_style &= ~(WS_SIZEBOX | WS_MAXIMIZEBOX);
		v_serialized_exec_sub(::SetWindowLong, hwnd, GWL_STYLE, window_style);

		window_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
		if ( s->attr & floating )
			window_style |= WS_EX_PALETTEWINDOW;
		else
			window_style &= ~WS_EX_PALETTEWINDOW;
		v_serialized_exec_sub(::SetWindowLong, hwnd, GWL_EXSTYLE, window_style);

		if ( s->attr & (modal | floating) )
			v_serialized_exec_sub(::SetWindowPos, hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		else
			v_serialized_exec_sub(::SetWindowPos, hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		
		err.subcode1 &= ~VSF_ATTR;
	}

	if ( flags & VSF_DESC ) {
		if ( s->descriptor ) {
			char *cstr;
			wchar_t *wstr;
			l2native(&cstr, &wstr, s->descriptor);
			if(cstr){
				v_serialized_exec_sub(::SetWindowTextA, hwnd, cstr);
				d_f_ree(cstr);
			}
			else if(wstr){
				v_serialized_exec_sub(::SetWindowTextW, hwnd, wstr);
				d_f_ree(wstr);
			}
		}
		else{
			v_serialized_exec_sub(::SetWindowTextW, hwnd, L"");
		}
		err.subcode1 &= ~VSF_DESC;
	}

	if ( flags & VSF_ENABLED ) {
		if( (::IsWindowEnabled(hwnd)==TRUE) != s->enabled ){
			v_serialized_exec_sub(::EnableWindow, hwnd, (BOOL)s->enabled);
		}
		err.subcode1 &= ~VSF_ENABLED;
	}

	if ( flags & VSF_MIN_SIZE ) {
		static_cast<VContainerInfo*>(info)->
			set_min_size(s->min_size.w, s->min_size.h);
		err.subcode1 &= ~VSF_ENABLED;
	}
	if ( flags & (VSF_SIZE | VSF_POSITION) ) {
		RECT window_rect;
		RECT client_rect;
	
		::GetWindowRect(hwnd, &window_rect);
		::GetClientRect(hwnd, &client_rect);
		int client_width = client_rect.right-client_rect.left;
		int client_height = client_rect.bottom-client_rect.top;
		int window_width = window_rect.right-window_rect.left;
		int window_height = window_rect.bottom-window_rect.top;
		if ( (flags & VSF_SIZE) && 
			(client_width !=s->size.w ||
			(client_height)!=s->size.h)) {
	
			::SetWindowPos(hwnd, (HWND)NULL, 0, 0, 
				s->size.w + (window_width-client_width), 
				s->size.h + (window_height-client_height), 
				SWP_NOZORDER | SWP_NOMOVE);
			err.subcode1 &= ~VSF_SIZE;
		}
		if ( (flags & VSF_POSITION) &&
			((window_rect.left != s->position.x) || 
			(window_rect.top != s->position.y))) {
			::SetWindowPos(hwnd, (HWND)NULL, s->position.x, s->position.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
			err.subcode1 &= ~VSF_POSITION;
		}
	}
	if ( flags & VSF_CALC_MIN ) {
		if (sts.children)
			sts.children->object->set_status(0, VSF_CALC_MIN);
		VLayout layout;
		layout.layout_in_frame(this, true);
		sts.min_size = layout.parent_min_size();
	}
	if ( flags & VSF_LAYOUT ) {
		if ( sts.visible ) {
			VLayout layout;
			layout.layout_in_frame(this);
			layout.do_layout(this);
		}
		err.subcode1 &= ~VSF_LAYOUT;
	}
	
	if ( flags & VSF_VISIBLE ) {
		if ( s->visible ) {
			if(!::IsWindowVisible(hwnd)){
				vq_insert_callback(NULL, layout_and_show_window, hwnd, 0, 0, VSER_OFF);
			}
		}
		else{
			if(::IsWindowVisible(hwnd)){
				v_serialized_exec_sub(::ShowWindow, hwnd, SW_HIDE);
			}
		}
		err.subcode1 &= ~VSF_VISIBLE;
	}
	V_OP_END

	if ( flags & ( VSF_PADDING | VSF_ALIGN | VSF_VISIBLE ) ) {
		VLayout::mark(this);
	}
	return err;

}

bool
VWindow::attempt_close()
{
	if ( window_close_handler == 0 ) {
		vq_insert_callback(this, v_object_destroyer, 0, 0, 0, VSER_OFF);
		return true;
	}
	vq_insert_callback(this, window_close_handler, user_arg, 0, 0, VSER_OFF);
	return false;
}


void
VWindow::redraw(VRect* rect) const
{
	if (!info)
		return;
	_V_OP_START_VOID
	win_redraw(info->get_hwnd(), rect);
	V_OP_END
}


VExError 
VWindow::add_child_do(VObject* child)
{
	v_serialized_exec_sub(::SetParent, child->get_info_this()->get_hwnd(),
		this->info->get_hwnd());
	VLayout layout;	// calc min size
	layout.layout_in_frame(this, true);
	return initial_VExError(V_ER_NO_ERR, 0, 0);
}

void
VWindow::remove_child_do(VObject* child)
{

}

void
VWindow::child_status_changed(VObject* child, VInfo* info)
{
	
}

