/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.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 <LPane.h>

#include "v/VDraw.h"
#include "VApplication.h" // appBusyLv
#include "VMouseTracker.h"
#include "CClickCheckAttachment.h"

extern "C" {
#include "task.h"
#include "pri_level.h"
LTabGroup* GetTabGroupForWindow(LView* view);
}

short mac_ev_to_modifiers(EventModifiers mod);


short
mac_ev_to_modifiers(EventModifiers mod)
{
	short ret = 0;
	if ( mod & shiftKey )
		ret |= V_MODKEY_SHIFT;
	if ( mod & alphaLock )
		ret |= V_MODKEY_CAPS;
	if ( mod & optionKey )
		ret |= V_MODKEY_ALT;
	if ( mod & controlKey )
		ret |= V_MODKEY_CTRL;
	if ( mod & cmdKey )
		ret |= V_MODKEY_META;
	return ret;
}

class CCustomDraw : public LPane
{
public:
	CCustomDraw(const SPaneInfo &pi, VDraw* draw)
		: LPane(pi),
		  mVDraw(draw), emuMouseButton(0),
		  mouseMoveWait(0), lastEventBuffered(0) {}
	
	void				SetMouseMoveWait(bool wait) {
		mouseMoveWait = wait;
		if (  ! wait && lastEventBuffered &&
				lastPoint.h != lastEventBuffer.where.h &&
				lastPoint.v != lastEventBuffer.where.v ) {
			GlobalToPortPoint(lastEventBuffer.where);
			MouseWithin(lastEventBuffer.where, lastEventBuffer);
		}
		lastEventBuffered = false;
	}
	
	virtual void		CopyImage(VImage *img) {
		StGDeviceSaver saver;
		{
			StGrafPortSaver saver2;
			if ( FocusDraw() ) {
				RGBForeColor(&RGB_black);
				RGBBackColor(&RGB_white);
				Rect r;
				CalcLocalFrameRect(r);

				// transfer bitmap image using QuickTime
				ImageDescriptionHandle imageDesc;
				MakeImageDescriptionForPixMap(
					GetPortPixMap(img->info->GetMacGWorld()), &imageDesc);
				Rect rect = {r.top, r.left, r.top+img->size.h, r.left+img->size.w};
				RgnHandle rgn = NewRgn();
				RectRgn(rgn, &r);
				DecompressImage(GetPixBaseAddr(GetPortPixMap(img->info->GetMacGWorld())),
					imageDesc, GetPortPixMap(GetMacPort()), nil, &rect,
					mVDraw->get_draw_use_alpha() ? graphicsModeStraightAlpha : srcCopy, rgn);
				DisposeRgn(rgn);
				DisposeHandle((Handle)imageDesc);
			}
		}
	}
	
	static V_CALLBACK_D(RedrawAgain) {
		VDraw *draw = (VDraw*)object;
		VImage *img = draw->draw_start(false);
		if ( img ) {
			((CCustomDraw*)user_arg)->CopyImage(img);
			draw->draw_end(false);
		}
	}

	virtual void		DrawSelf() {
		VImage *img = mVDraw->get_image();
		if ( img ) {
			if ( v_image_draw_start(img, 1) == 0 ) {
				CopyImage(img);
				v_image_draw_end(img);
			}
			else
				vq_insert_callback(mVDraw, RedrawAgain, this, 0, 0);
			v_image_unref(img);
		}
	}
	
	virtual void		ClickSelf( const SMouseDownEvent& md ) {
		lastEventBuffered = false;
		mouseMoveWait = false;
		VMouseTracker::DontForceQuitTracking();

		Rect r;
		CalcLocalFrameRect(r);
		if ( md.macEvent.modifiers & controlKey )
			emuMouseButton = V_BUTTON_2;
		else if ( md.macEvent.modifiers & cmdKey )
			emuMouseButton = V_BUTTON_3;
		else
			emuMouseButton = V_BUTTON_1;
		mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_DOWN, md.macEvent.when,
										{md.whereLocal.h-r.left,md.whereLocal.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(md.macEvent.modifiers)});
		if ( sClickCount == 2 )
			mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_DOUBLE_CLICK, md.macEvent.when,
										{md.whereLocal.h-r.left,md.whereLocal.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(md.macEvent.modifiers)});
		appBusyLv++;
	}
	virtual void		EventMouseUp( const EventRecord& me ) {
		if ( ! emuMouseButton )
			return;
		lastEventBuffered = false;
		mouseMoveWait = false;

		Point point = me.where;
		GlobalToPortPoint(point);
		Rect r;
		CalcPortFrameRect(r);
//printf("DDD %d %d %d \n",me.when % 1000, point.h-r.left, point.v-r.top);
		mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_UP, me.when,
										{point.h-r.left,point.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(me.modifiers)});
		emuMouseButton = 0;
		appBusyLv--;
	}								
	virtual void		MouseEnter( Point point, const EventRecord& me ) {
		lastEventBuffered = false;
		mouseMoveWait = false;

		Rect r;
		CalcPortFrameRect(r);
		mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_ENTER, me.when,
										{point.h-r.left,point.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(me.modifiers)});
	}
	virtual void		MouseLeave() {
		lastEventBuffered = false;
		mouseMoveWait = false;

		const EventRecord* me = VMouseTracker::CurrentMacEvent();
		if ( !me )
			return;
		Point point = me->where;
		GlobalToPortPoint(point);
		Rect r;
		CalcPortFrameRect(r);
		mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_LEAVE, me->when,
										{point.h-r.left,point.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(me->modifiers)});
	}
	virtual void		MouseWithin( Point point, const EventRecord& me ) {
		Rect r;
		CalcPortFrameRect(r);
		if ( mouseMoveWait ) {
			lastEventBuffered = true;
			lastEventBuffer = me;
			return;
		}
		lastPoint = point;

//printf("EEE %d %d %d %d [%d]\n", emuMouseButton, me.when % 1000, point.h-r.left, point.v-r.top, me.what);
		mVDraw->mouse_event(&(VMouseEvent){V_EV_MOUSE_MOVE, me.when,
										{point.h-r.left,point.v-r.top},
										emuMouseButton,
										mac_ev_to_modifiers(me.modifiers)});
	}

	virtual void		ResizeFrameBy(
								SInt16		inWidthDelta,
								SInt16		inHeightDelta,
								Boolean		inRefresh)
	{
		LPane::ResizeFrameBy(inWidthDelta, inHeightDelta, inRefresh);
		mVDraw->resize_event((VSize){mFrameSize.width, mFrameSize.height});
	}
	VDraw *		mVDraw;
	short		emuMouseButton;
	bool		mouseMoveWait;
	bool		lastEventBuffered;
	EventRecord	lastEventBuffer;
	Point		lastPoint;
};




VExError
VDraw::create_do(const VObjectStatus* s, int flags,VObject * nmp, void * arg)
{
	mouse_event_handler = 0;
	resize_event_handler = 0;
	sts.size = sts.min_size = (VSize){0,0};
	
	SPaneInfo paneInfo;
	paneInfo.superView = dynamic_cast<LView*>(nmp->get_info_this());
	if ( paneInfo.superView == 0 )
		er_panic("non-LView object cannot be parent");
	paneInfo.paneID = sts.id;
	paneInfo.width = 0;
	paneInfo.height = 0;
	paneInfo.visible = true;
	paneInfo.enabled = true;
	paneInfo.left = 0;
	paneInfo.top = 0;
	paneInfo.bindings = (SBooleanRect){false,false,false,false};
	paneInfo.userCon = (long)this;

	LWindow *win;
	LView *view = paneInfo.superView;
	while ( view && (win = dynamic_cast<LWindow*>(view)) == 0 ) // get super window
		view = view->GetSuperView();
	
	CCustomDraw *v = new CCustomDraw(paneInfo, this);
	v->AddAttachment(new CClickCheckAttachment);
	v->FinishCreate();
	info = v;

	img = 0;
	img_locked = 0;
	return nmp->add_child_do(this);
}

void
VDraw::destroy_do(VObject * nmp)
{
	if ( img_locked )
		er_panic("VDraw::destroy while img_lock");
	if ( img ) {
		v_image_unref(img);
		img = 0;
	}
	nmp->remove_child_do(this);
	delete info;
	nmp->redraw();
}

VDraw::~VDraw()
{
}


VExError
VDraw::get_status(VObjectStatus *s, int flags) const
{
	V_OP_START_EX
	VExError err = v_get_status_standard(s, &flags, info);
	VExError err2 = VObject::get_status(s,flags);
	if ( err2.code )
		err = merge_VExError_vstatus_type(err,err2);
	V_OP_END
	return err;
};

VExError
VDraw::set_status(const VObjectStatus *s, int flags)
{
	V_OP_START_EX
	VExError err = v_set_status_standard(s, flags&~VSF_ENABLED, &sts, info);
	VExError err2 = VObject::set_status(s,flags&~VSF_MIN_SIZE);
	if ( err2.code )
		err = merge_VExError_vstatus_type(err,err2);

	if ( flags & VSF_MIN_SIZE ) {
		int w = v_default_size.w, h = v_default_size.h;
		if ( s->min_size.w != v_default_size.w ) 
			w = s->min_size.w;
		else if ( img )
			w = img->size.w;
		if ( s->min_size.h != v_default_size.h )
			h = s->min_size.h;
		else if ( img )
			h = img->size.h;
printf("[%s] min size : %d x %d\n", describe_self(), w, h);
		sts.min_size = (VSize){w,h};
		err.subcode1 &= ~VSF_MIN_SIZE;
	}

	V_OP_END

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

void
VDraw::redraw(VRect* rect) const
{

	_V_OP_START()
	Rect r;
	if ( rect )
		r = (Rect){rect->t,rect->l,rect->b,rect->r};
	else
		info->CalcPortFrameRect(r);
	info->InvalPortRect(&r);
	RgnHandle rgn = NewRgn();
	RectRgn(rgn, &r);
	info->Draw(rgn);
	DisposeRgn(rgn);
	V_OP_END
}

VImage*
VDraw::draw_start(bool img_draw_start)
{

	_V_OP_START(NULL)
//printf(" --- --- start %s\n",describe_self());
	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)
{

//printf(" --- --- end %s\n",describe_self());
	if ( ! img_locked )
		er_panic("illegal VDraw::draw_end");
	if ( img_draw_end )
		v_image_draw_end(img);
	img_locked = 0;
	V_OP_END
}

VError
VDraw::set_image(VImage* img, VSize min_size)
{

	V_OP_START
//printf("draw set img %s %x\n",describe_self(), img);
	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;
		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 Framework on mouse event
	if ( event->type == V_EV_MOUSE_MOVE && (sts.attr & mouse_move_wait)  )
		((CCustomDraw*)info)->SetMouseMoveWait(true);
	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()
{
	((CCustomDraw*)info)->SetMouseMoveWait(false);
}
