/**********************************************************************
 
	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 <windows.h>
#include <wingdi.h>

#include "v/VDraw.h"
#include "VApplication.h"
#include "vobject_main.h"
#include "vwin_control.h"


static
v_mouse_event_set(VMouseEvent *event, MSG *msg, HWND hwnd)
{
	if ( ! (msg->message >= WM_MOUSEFIRST && msg->message <= WM_MOUSELAST) )
		er_panic("not mouse message");

	event->button = 
			((msg->wParam & MK_LBUTTON) ? V_BUTTON_1 : 0) |
			((msg->wParam & MK_RBUTTON) ? V_BUTTON_2 : 0) |
			((msg->wParam & MK_MBUTTON) ? V_BUTTON_3 : 0);
	event->modifiers =
			(GetKeyState(VK_SHIFT)	? V_MODKEY_SHIFT : 0) |
			(GetKeyState(VK_CAPITAL)? V_MODKEY_CAPS  : 0) |
			(GetKeyState(VK_MENU)	? V_MODKEY_ALT   : 0) |
			(GetKeyState(VK_CONTROL)? V_MODKEY_CTRL  : 0) |
			(GetKeyState(VK_LWIN)	? V_MODKEY_META  : 0) |
			(GetKeyState(VK_RWIN)	? V_MODKEY_META  : 0);
	POINT pt = msg->pt;
	ScreenToClient(hwnd, &pt);
	event->point.x = pt.x;
	event->point.y = pt.y;
	event->time = msg->time;
}


class VDrawInfo : public VTrackedInfo {

public:
	VDrawInfo(VObject *o, HWND h, int id, WNDPROC DefWndProc=NULL)
		: VTrackedInfo(o,h,id,1,0,DefWndProc),img(NULL),move_suspended(0) {}

	virtual LRESULT	dispatch_message(MSG *msg);
	void			set_image(VImage *img) { this->img = img; }
	static void		redraw(VDrawInfo *info);
	void			suspend_move(bool suspend);

protected:
	virtual void	mouse_enter(MSG *msg);
	virtual void	mouse_leave(MSG *msg);
	virtual void	mouse_move(MSG *msg);
	virtual void	mouse_down(MSG *msg);
	virtual void	mouse_up(MSG *msg);

	void			save_msg(MSG *msg)	// back up last mouse move event
	{
		if ( msg->message == WM_MOUSEMOVE ) {
			if ( ! move_suspended || last_msg.message == WM_MOUSEMOVE ) // update
				last_msg = *msg;
		}
		else if ( last_msg.message == WM_MOUSEMOVE )
			last_msg = *msg;	// indicates last message is not WM_MOUSEMOVE
	}

private:
	VImage	*img;
	bool	move_suspended;
	MSG		last_msg, saved_msg;
};


static V_CALLBACK_D(redraw_draw_area)
{
	VImage *img = ((VDraw*)object)->draw_start();
	if ( img && img->buf_32 ) {
		v_serialized_exec_sub(VDrawInfo::redraw, (VDrawInfo*)user_arg);
		((VDraw*)object)->draw_end();
	}
}

LRESULT
VDrawInfo::dispatch_message(MSG *msg)
{
	VDraw* draw = dynamic_cast<VDraw*>(get_obj());
	if ( draw == 0 )
		er_panic("VDrawInfo::dispatch_message");

	VMouseEvent event;
	switch(msg->message){
		case WM_PAINT:
		{
			if ( img ) {
				if ( v_image_draw_start(img, 1) == 0 ) {
					redraw(this);
					v_image_draw_end(img);
					return 0;
				}
				else
					vq_insert_callback(draw, redraw_draw_area, this, 0, 0);
			}
		}
		break;
		case WM_SIZE:
		{
			RECT r;
			::GetClientRect(msg->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;
				VSize s = {size.w, size.h};
				draw->resize_event(s);
			}
		}
		break;
		case WM_LBUTTONDBLCLK:
		case WM_RBUTTONDBLCLK:
		case WM_MBUTTONDBLCLK:
		{
			v_mouse_event_set(&event, msg, get_hwnd());
			event.type = V_EV_MOUSE_DOUBLE_CLICK;
			event.button =
				msg->message == WM_LBUTTONDBLCLK ? V_BUTTON_1 :
				msg->message == WM_RBUTTONDBLCLK ? V_BUTTON_2 : V_BUTTON_3;
			event.point.x = LOWORD(msg->lParam);
			event.point.y = HIWORD(msg->lParam);
			dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
		}
		break;
	}
	return VContainerInfo::dispatch_message(msg);
}

void
VDrawInfo::redraw(VDrawInfo *info)
{
	int w = info->img->size.w, h = info->img->size.h;
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(info->get_hwnd() , &ps);
	HWND hParent = GetParent(info->get_hwnd());
	if ( ps.fErase ) {
		FillRect(hdc, &ps.rcPaint,
			(HBRUSH)SendMessage(hParent, WM_CTLCOLORSTATIC,
			(WPARAM)hdc, (LPARAM)info->get_hwnd()));
	}
	HDC hBuffer = CreateCompatibleDC(hdc);
	SelectObject(hBuffer , info->img->info);
	if ( dynamic_cast<VDraw*>(info->get_obj())->get_draw_use_alpha() ) {
		HDC hCopyDC = CreateCompatibleDC(hdc);
		BITMAPINFO bmi;
		bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		bmi.bmiHeader.biWidth = w;
		bmi.bmiHeader.biHeight = -h;
		bmi.bmiHeader.biPlanes = 1;
		bmi.bmiHeader.biBitCount = 32;
		bmi.bmiHeader.biCompression = BI_RGB;
		bmi.bmiHeader.biSizeImage = w*h*4;
		bmi.bmiHeader.biXPelsPerMeter = 0;
		bmi.bmiHeader.biYPelsPerMeter = 0;
		bmi.bmiHeader.biClrUsed = 0;
		bmi.bmiHeader.biClrImportant = 0;
		HBITMAP hCopy = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS,
								NULL, NULL, 0);
		SelectObject(hCopyDC, hCopy);

		RECT rc = {0, 0, w, h};
		FillRect(hCopyDC, &rc,
			(HBRUSH)SendMessage(hParent, WM_CTLCOLORSTATIC,
			(WPARAM)hCopyDC, (LPARAM)info->get_hwnd()));

		BLENDFUNCTION blend = {AC_SRC_OVER,0,0xff,0x01/*AC_SRC_ALPHA*/};
		if ( AlphaBlend(hCopyDC, 0, 0, w, h, hBuffer, 0, 0, w, h, blend) == 0 )
			VWinError();

		if ( BitBlt(hdc, 0, 0, w, h, hCopyDC, 0, 0, SRCCOPY) == 0 )
			er_panic("BitBlt failure");

		DeleteObject(hCopy);
		DeleteDC(hCopyDC);
	}
	else {
		if ( BitBlt(hdc, 0, 0, w, h, hBuffer, 0, 0, SRCCOPY) == 0 )
			er_panic("BitBlt failure");
	}
	DeleteDC(hBuffer);
	EndPaint(info->get_hwnd() , &ps);
}

void
VDrawInfo::suspend_move(bool suspend)
{
	if ( ! move_suspended && suspend ) {
		saved_msg = last_msg;
		move_suspended = suspend;
	}
	else if ( move_suspended && ! suspend ) {
		move_suspended = suspend;
		if ( last_msg.message == WM_MOUSEMOVE &&
				(last_msg.pt.x != saved_msg.pt.x || 
				 last_msg.pt.y != saved_msg.pt.y) ) {
			mouse_move(&last_msg);
		}
	}
}

void
VDrawInfo::mouse_move(MSG *msg)
{
	save_msg(msg);
	if ( ! move_suspended ) {
		VMouseEvent event;
		event.type = V_EV_MOUSE_MOVE;
		v_mouse_event_set(&event, msg, get_hwnd());
		dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
	}
}

void
VDrawInfo::mouse_enter(MSG *msg)
{
	save_msg(msg);
	VMouseEvent event;
	event.type = V_EV_MOUSE_ENTER;
	v_mouse_event_set(&event, msg, get_hwnd());
	dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
}

void
VDrawInfo::mouse_leave(MSG *msg)
{
	save_msg(msg);
	VMouseEvent event;
	event.type = V_EV_MOUSE_LEAVE;
	v_mouse_event_set(&event, msg, get_hwnd());
	dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
}
	
void
VDrawInfo::mouse_down(MSG *msg)
{
	save_msg(msg);
	VMouseEvent event;
	event.type = V_EV_MOUSE_DOWN;
	v_mouse_event_set(&event, msg, get_hwnd());
	switch ( msg->message ) {
	  case WM_LBUTTONDOWN:
		event.button = V_BUTTON_1;
		break;
	  case WM_RBUTTONDOWN:
		event.button = V_BUTTON_2;
		break;
	  case WM_MBUTTONDOWN:
		event.button = V_BUTTON_3;
		break;
	}
	dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
}

void
VDrawInfo::mouse_up(MSG *msg)
{
	save_msg(msg);
	VMouseEvent event;
	event.type = V_EV_MOUSE_UP;
	v_mouse_event_set(&event, msg, get_hwnd());
	switch ( msg->message ) {
	  case WM_LBUTTONUP:
		event.button = V_BUTTON_1;
		break;
	  case WM_RBUTTONUP:
		event.button = V_BUTTON_2;
		break;
	  case WM_MBUTTONUP:
		event.button = V_BUTTON_3;
		break;
	}
	dynamic_cast<VDraw*>(get_obj())->mouse_event(&event);
}



class DrawAreaRegister{
public:
	
	DrawAreaRegister(){
		WNDCLASSEX wc;
		wc.cbSize = sizeof(WNDCLASSEX);
		wc.style = CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;
		wc.lpfnWndProc = VDrawInfo::WndProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = sizeof(DWORD);
		wc.hInstance = ::GetModuleHandle(NULL);
		wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
		wc.hCursor = LoadCursor(NULL,IDC_ARROW);
		wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
		wc.lpszMenuName = NULL;
		wc.lpszClassName = "vobj_draw_area";
		wc.hIconSm = NULL;
		
		if(!::RegisterClassEx(&wc)){
			VWinError();
			er_panic("DrawAreaRegister");
		}
	}
}draw_area_register;

static HWND CreateDrawWindow(int style, HWND hwnd, int id){
	return ::CreateWindowEx(
		0, 
		"vobj_draw_area",
		"",
		style,
		0,
		0,
		0,
		0,
		hwnd,
		(HMENU)id,
		theApp->get_instance(),
		NULL);
}


VExError
VDraw::create_do(const VObjectStatus* s, int flags, VObject * parent ,void * arg)
{
	mouse_event_handler = 0;
	resize_event_handler = 0;
	img = 0;
	img_locked = 0;
	VSize zero = {0,0};
	sts.size = sts.min_size = zero;

	DWORD style=WS_CHILD;
	if(!s->enabled){
		style |= WS_DISABLED;
	}
	if(s->visible){
		style |= WS_VISIBLE;
	}
	
	/* find container window that will own this button.*/
	VContainerInfo *container_info = VInfo::get_container_info(parent);
	int id = container_info->get_next_control_id();
	HWND hwnd = v_serialized_exec_func(CreateDrawWindow, style, container_info->get_hwnd(), id);
	info = new VDrawInfo(this, hwnd, id);
	
	return parent->add_child_do(this);
}

void
VDraw::destroy_do(VObject *parent)
{
	parent->remove_child_do(this);
	if ( img_locked )
		er_panic("VDraw::destroy while img_lock");
	if ( img ) {
		v_image_unref(img);
		img = 0;
	}
	if(info){
		v_serialized_exec_sub(::DestroyWindow, info->get_hwnd());
		delete info;
		info = NULL;
	}
	sts.parent->redraw();
}

VDraw::~VDraw()
{
}


VExError
VDraw::get_status(VObjectStatus *s, int flags) const
{
	V_OP_START_EX
	VExError err = VObject::get_status(s,flags);
	win_control_default_get_status(info->get_hwnd(), s, flags, &err);
	V_OP_END
	return err;
};

VExError
VDraw::set_status(const VObjectStatus *s, int flags)
{
	V_OP_START_EX
	VExError err = VObject::set_status(s,flags);
	HWND hwnd = info->get_hwnd();
	win_control_default_set_status(hwnd, s, flags, &err);

	if ( flags & VSF_CALC_MIN ) {
		// min must be specified by user
		err.subcode1 &= ~VSF_CALC_MIN;
	}

	if ( flags & VSF_VISIBLE ) {
		if ( s->visible != (::IsWindowVisible(info->get_hwnd())==TRUE) ){
			v_serialized_exec_sub(::ShowWindow, hwnd, s->visible ? SW_SHOW : SW_HIDE);
		}
		err.subcode1 &= ~VSF_VISIBLE;
	}

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

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

VImage*
VDraw::draw_start(bool img_draw_start)
{
	_V_OP_START(NULL)
	if ( img == 0 || img_locked ) {
		V_OP_END
		return NULL;
	}
	img_locked = 1;
	if ( img_draw_start )
		v_image_draw_start(img, 0);
	return img;
}

void
VDraw::draw_end(bool img_draw_end)
{
	if ( ! img_locked )
		er_panic("illegal VDraw::draw_end");
	img_locked = 0;
	if ( img_draw_end )
		v_image_draw_end(img);
	V_OP_END
}

VError
VDraw::set_image(VImage* img, VSize min_size)
{
	V_OP_START
	VError err = V_ER_NO_ERR;
	bool size_f = false;
	if ( img_locked )
		err = V_ER_CANT_SET;
	else {
		VImage* old = this->img;
		if ( img ) {
			v_image_ref(img);
			if ( min_size.w != v_default_size.w && min_size.h != v_default_size.h ) {
				sts.min_size = min_size;
				size_f = true;
			}
			else if ( sts.min_size.w == v_default_size.w || sts.min_size.h == v_default_size.h ) {
				sts.min_size = img->size;
				size_f = true;
			}
		}
		this->img = img;
		static_cast<VDrawInfo*>(info)->set_image(img);
		if ( old )
			v_image_unref(old);
	}
	V_OP_END
	if ( size_f )
		VLayout::mark(this);
	return err;
}

bool
VDraw::mouse_event(VMouseEvent *event)
{	// called from OS on mouse event
	if (sts.attr & mouse_move_wait) {
		if ( event->type == V_EV_MOUSE_MOVE )
			dynamic_cast<VDrawInfo*>(info)->suspend_move(1);
		if ( event->type == V_EV_MOUSE_ENTER )
			dynamic_cast<VDrawInfo*>(info)->suspend_move(0);
	}
	if (mouse_event_handler)
		vq_insert_callback(this, mouse_event_handler, user_arg_mouse, event, sizeof(*event));
	return false;
}

void
VDraw::mouse_move_event_done()
{
	if ( ! (sts.attr & mouse_move_wait) )
		return;
	dynamic_cast<VDrawInfo*>(info)->suspend_move(0);
}
