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

	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/VSplitView.h"
#include "v/VLayout.h"
#include "VApplication.h"
#include "vwin_error.h"


class VSplitViewInfo;
typedef void (*VSplitViewCallback)(VSplitViewInfo*,int,void*);

class VSplitViewInfo : public VContainerInfo
{
public:
	VSplitViewInfo(VObject *o, HWND h, int id, char vertical)
		: VContainerInfo(o,h,id,1,0,1,0,WndProc), vertical(vertical),
		  dragging(false), shrinkable0(0), shrinkable1(0), bar_hidden(0),
		  callback_func(0), callback_arg(0)
		{
			min = pos = 0;
			max = 0xffff;
			resized();
		}
//	virtual ~VSplitViewInfo()

	void begin_drag(short x, short y);
	void move_drag(short x, short y, bool draw_tmp=true);
	void end_drag(short x, short y);
	void cancel_drag();

	WORD get_pos(WORD *min, WORD *max);
	void set_pos(WORD p, bool redraw=true);
	void set_min_max(WORD min, WORD max);

	void set_visible(bool v) { bar_hidden = v?0:1; InvalidateRect(get_hwnd(),NULL,true); }
	void set_shrinkable(int pane, bool s) {
		if ( pane ) shrinkable1 = s?1:0; else shrinkable0 = s?1:0;
		set_pos(pos-m);
	}
	
	bool is_visible() { return !bar_hidden; }
	bool is_shrinkable(int pane) { return pane?shrinkable1:shrinkable0; }

	void set_callback_func(VSplitViewCallback func,void* arg) {
		callback_func = func;  callback_arg = arg;
	}

	static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

protected:
	void draw_split_bar(HDC hdc);
	void draw_tmp_bar();
	void resized();
	WORD calc_new_pos(short x);

	WORD min, max, pos, dpos;
	char vertical;
	char dragging;
	int m, n;

	VSplitViewCallback callback_func;
	void *callback_arg;

	unsigned char shrinkable0 : 1;
	unsigned char shrinkable1 : 1;
	unsigned char bar_hidden : 1;
};


WORD
VSplitViewInfo::get_pos(WORD *pmin, WORD *pmax)
{
	if ( pmin )
		*pmin = min - m;
	if ( pmax )
		*pmax = max - m;
	return pos - m;
}

void
VSplitViewInfo::set_pos(WORD p, bool redraw)
{
	WORD old_pos = pos;
	p += m;

	pos = calc_new_pos(p);

	if ( old_pos != pos ) {
		if ( redraw )
			InvalidateRect(get_hwnd(), NULL, true);
		if ( callback_func && !bar_hidden )
			callback_func(this,
				(shrinkable0&&pos==m)?0:(shrinkable0&&pos==n)?1:2, callback_arg);
	}
}

WORD
VSplitViewInfo::calc_new_pos(short x)
{
	if ( x < m )
		x = m;
	else if ( x > n )
		x = n;

	if ( shrinkable0 && x == m || shrinkable1 && x == n )
		return x;
	else if ( x < min )
		return min;
	else if ( x > max )
		return max;
	return x;
}

void
VSplitViewInfo::set_min_max(WORD min, WORD max)
{
	if ( min > max )
		max = min;
	this->min = min + m;
	this->max = max + m;
	set_pos(pos-m);
}


LRESULT CALLBACK
VSplitViewInfo::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	VSplitViewInfo *info;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (msg) {
		case WM_PAINT:
			info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
			hdc = BeginPaint(hWnd, &ps);
			if ( ps.fErase ) {
				HBRUSH bg = (HBRUSH)SendMessage(info->hAncestorDialog,
					WM_CTLCOLORSTATIC, (WPARAM)hdc, (LPARAM)hWnd);
				FillRect(hdc, &ps.rcPaint, bg);
			}
			info->draw_split_bar(hdc);
			EndPaint(hWnd, &ps);
			break;
		case WM_SIZE:
			if ( wParam != SIZE_MINIMIZED ) {
				info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
				if ( info )
					info->resized();
			}
			break;
		case WM_MOUSEMOVE:
			info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
			info->move_drag(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF);
			break;
		case WM_LBUTTONDOWN:
			info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
			info->begin_drag(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF);
			break;
		case WM_LBUTTONUP:
			info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
			info->end_drag(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF);
			break;
		case WM_RBUTTONDOWN:
			info = (VSplitViewInfo*)VSplitViewInfo::get_from_hwnd(hWnd);
			info->cancel_drag();
			break;
		default:
			return(DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0L);
}

void
VSplitViewInfo::draw_split_bar(HDC hdc)
{
	if ( bar_hidden )
		return;

	//static HPEN penHilight = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHIGHLIGHT));
	//static HPEN penShadow  = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
	static HPEN penBlack  = CreatePen(PS_SOLID, 1, 0);
	extern HPEN penHilight, penShadow;

	RECT rt;
	GetClientRect(get_hwnd(), &rt);

	HPEN hOldPen = (HPEN)SelectObject(hdc, penHilight);
	if ( !vertical ) {
		MoveToEx(hdc, rt.left+pos, rt.top, NULL);
		LineTo(hdc, rt.left+pos, rt.bottom);
	}
	else {
		MoveToEx(hdc, rt.left, rt.top+pos, NULL);
		LineTo(hdc, rt.right, rt.top+pos);
	}
	
	SelectObject(hdc, penShadow);
	if ( !vertical ) {
		MoveToEx(hdc, rt.left+pos+SPLIT_BAR_WIDTH-2, rt.top, NULL);
		LineTo(hdc, rt.left+pos+SPLIT_BAR_WIDTH-2, rt.bottom);
	}
	else {
		MoveToEx(hdc, rt.left, rt.top+pos+SPLIT_BAR_WIDTH-2, NULL);
		LineTo(hdc, rt.right, rt.top+pos+SPLIT_BAR_WIDTH-2);
	}

	SelectObject(hdc, penBlack);
	if ( !vertical ) {
		MoveToEx(hdc, rt.left+pos+SPLIT_BAR_WIDTH-1, rt.top, NULL);
		LineTo(hdc, rt.left+pos+SPLIT_BAR_WIDTH-1, rt.bottom);
	}
	else {
		MoveToEx(hdc, rt.left, rt.top+pos+SPLIT_BAR_WIDTH-1, NULL);
		LineTo(hdc, rt.right, rt.top+pos+SPLIT_BAR_WIDTH-1);
	}

	SelectObject(hdc, hOldPen);
}

void
VSplitViewInfo::draw_tmp_bar()
{
	HDC hdc;
	RECT rt;
	GetClientRect(get_hwnd(), &rt);
	if ( vertical ) {
		rt.top = dpos;
		rt.bottom = dpos + SPLIT_BAR_WIDTH;
	}
	else {
		rt.left = dpos;
		rt.right =dpos + SPLIT_BAR_WIDTH;
	}

	hdc = GetDC(get_hwnd());
	while ( 1 ) {
		DrawFocusRect(hdc, &rt);
		rt.left++;
		rt.right--;
		rt.top++;
		rt.bottom--;
		if ( rt.left >= rt.right || rt.top >= rt.bottom )
			break;
	}
	ReleaseDC(get_hwnd(), hdc);
}

void
VSplitViewInfo::resized()
{
	bool sh1 = false;
//	if ( pos == n )
//		sh1 = true;

	RECT rt;
	GetClientRect(get_hwnd(), &rt);
	m = vertical ? rt.top : rt.left;
	n = (vertical ? rt.bottom : rt.right) - SPLIT_BAR_WIDTH;

	if ( sh1 )
		set_pos(n-m);
	else
		set_pos(pos-m);
}

void
VSplitViewInfo::begin_drag(short x, short y)
{
	if ( bar_hidden )
		return;
	if ( vertical )
		x = y;
	if ( x < pos || x >= pos+SPLIT_BAR_WIDTH )
		return;

	SetCapture(get_hwnd());
	dragging = true;
	move_drag(x, y, false);
	draw_tmp_bar();
}

void
VSplitViewInfo::end_drag(short x, short y)
{
	if ( !dragging ) 
		return;
	move_drag(x, y);
	dragging = false;
	ReleaseCapture();
	draw_tmp_bar();

	set_pos(dpos-m);
}

void
VSplitViewInfo::move_drag(short x, short y, bool draw_tmp)
{
	if ( !dragging ) 
		return;
	if ( vertical )
		x = y;

	if ( draw_tmp )
		draw_tmp_bar();

	dpos = calc_new_pos(x);
	
	if ( draw_tmp )
		draw_tmp_bar();
}

void
VSplitViewInfo::cancel_drag()
{
	if ( !dragging ) 
		return;
	dragging = false;
	ReleaseCapture();
	draw_tmp_bar();
}




void
VSplitView::destroy_do(VObject *parent)
{
	parent->remove_child_do(this);
	if(info){
		v_serialized_exec_sub(::DestroyWindow, info->get_hwnd());
		delete info;
		info = NULL;
	}
	parent->redraw();
}

VSplitView::~VSplitView()
{
}

VExError
VSplitView::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
if(flags&VSF_VALUE)printf("ret st v : %d\n",sts.value);
	return err;
};

VExError
VSplitView::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);
	VSplitViewInfo *svi = dynamic_cast<VSplitViewInfo*>(info);

	if ( flags & VSF_ATTR ) {
		svi->set_shrinkable(0, s->attr & shrinkable0);
		svi->set_shrinkable(1, s->attr & shrinkable1);
	}
	if ( flags & VSF_VALUE ) {
		sts.value = 0x7fffffff; // force enable the value_changed handler
		svi->set_pos(s->value, true);
	}
	
	if ( flags & (VSF_CALC_MIN | VSF_LAYOUT) ) {
		unsigned short pos, min, max;
		if ( sts.value == -1 )
			pos = (unsigned short)-1;
		else
			pos = svi->get_pos(&min,&max);

		if ( flags & VSF_CALC_MIN ) {
			for ( VObjectList *list = sts.children ; list ; list = list->next ) {
				list->object->set_status(0, VSF_CALC_MIN);
			}
			VLayout layout;
			layout.layout_in_split_view(this, get_type()==VVSplitView::object_type,
						pos, min, max, child_status, true);
			sts.min_size = layout.parent_min_size();
			err.subcode1 &= ~VSF_CALC_MIN;
		}
		if ( flags & VSF_LAYOUT ) {
			VLayout layout;
			layout.layout_in_split_view(this, get_type()==VVSplitView::object_type,
						pos, min, max, child_status);
			sts.value = -1; // stop value_changed handler to avoid re-layout
			layout.do_layout(this);
			last_size = sts.size;
			svi->set_min_max(min, max);
			svi->set_pos(pos, true);
			sts.value = pos;
			err.subcode1 &= ~VSF_LAYOUT;
		}
	}

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

	V_OP_END

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

VExError VSplitView::add_child_do(VObject* child)
{
	if ( child0 == 0 )
		child0 = child;
	else if ( child1 == 0 )
		child1 = child;
	else
		er_panic("VSplitView::add_child_do");
	return initial_VExError(V_ER_NO_ERR,0,0);
}

void
VSplitView::remove_child_do(VObject* child)
{
	if ( child0 == child )
		child0 = 0;
	else if ( child1 == child )
		child1 = 0;
	else
		er_panic("VSplitView::remove_child_do");
}

void
VSplitView::child_status_changed(VObject* child, VInfo* info)
{
	// do nothing - object is marked when status is changed
}

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

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

void
VSplitView::value_changed()
{
	VSplitViewInfo *svi = dynamic_cast<VSplitViewInfo*>(info);
	if ( svi == 0 )
		return;
	int v = svi->get_pos(0,0);
	if ( sts.value >= 0 && sts.value != v
			&& is_pane_visible(0) && is_pane_visible(1) ) {
		if ( child_status[0] & child_shrinkable ) {
			if ( v == 0 && !(child_status[0] & child_shrinked) ) {
				child_status[0] |= child_shrinked;
				if ( child0 )
					ShowWindow(child0->get_info_this()->get_hwnd(), SW_HIDE);
			}
			else if ( v > 0 && (child_status[0] & child_shrinked) ) {
				child_status[0] &= ~child_shrinked;
				if ( child0 )
					ShowWindow(child0->get_info_this()->get_hwnd(), SW_SHOW);
			}
		}
		if ( child_status[1] & child_shrinkable ) {
			RECT rt;
			GetClientRect(svi->get_hwnd(), &rt);
			int width = rt.right - rt.left, height = rt.bottom - rt.top;
			short max = (get_type() == VHSplitView::object_type
							? width : height) - SPLIT_BAR_WIDTH;
			if ( v == max && !(child_status[1] & child_shrinked) ) {
				child_status[1] |= child_shrinked;
				if ( child1 )
					ShowWindow(child1->get_info_this()->get_hwnd(), SW_HIDE);
			}
			else if ( v < max && (child_status[1] & child_shrinked) ) {
				child_status[1] &= ~child_shrinked;
				if ( child1 )
					ShowWindow(child1->get_info_this()->get_hwnd(), SW_SHOW);
			}
		}

		vq_insert_callback(this, mark_window, 0, 0, 0, VSER_OFF);
	}
	if ( sts.value >= 0 )
		VObject::value_changed();
}

void
VSplitView::show_pane(int pane, int expand_window)
{
	_V_OP_START_VOID
	child_status[pane?1:0] &= ~child_hidden;

	VSplitViewInfo* svi = (VSplitViewInfo*)info;

	if ( pane == 0 ) {
		if ( child0 && !(child_status[0]&child_shrinked) )
			v_serialized_exec_sub(ShowWindow, child0->get_info_this()->get_hwnd(), SW_SHOW);
	}
	else {
		if ( child1 && !(child_status[1]&child_shrinked) )
			v_serialized_exec_sub(ShowWindow, child1->get_info_this()->get_hwnd(), SW_SHOW);
	}
	if ( !(child_status[0] & child_hidden) &&
		 !(child_status[1] & child_hidden) )
		svi->set_visible(1);
	
	V_OP_END

	if ( expand_window ) {
		if ( expand_window < 0 )
			expand_window = shrink_size[pane?1:0];

		VObjectStatus s;
		sts.window->get_status(&s, VSF_SIZE);
		unsigned short old_size =
			get_type() == VHSplitView::object_type?sts.size.w:sts.size.h;
		if ( get_type() == VHSplitView::object_type )
			s.size.w += expand_window+SPLIT_BAR_WIDTH;
		else
			s.size.h += expand_window+SPLIT_BAR_WIDTH;
		sts.window->set_status(&s, VSF_SIZE);
		last_size = v_default_size;
		sts.value = -2;	// stop value_changed handler
		VLayout::mark(sts.window);
		vobject_layout();
		svi->set_pos(pane?old_size:expand_window, false);
		sts.value = svi->get_pos(0,0);
	}

	VLayout::mark(this);
}

void
VSplitView::hide_pane(int pane, int shrink_window)
{
	_V_OP_START_VOID
	child_status[pane?1:0] |= child_hidden;
	
	VSplitViewInfo *svi = (VSplitViewInfo*)info;

	if ( pane == 0 ) {
		if ( child0 )
			v_serialized_exec_sub(ShowWindow, child0->get_info_this()->get_hwnd(), SW_HIDE);
	}
	else {
		if ( child1 )
			v_serialized_exec_sub(ShowWindow, child1->get_info_this()->get_hwnd(), SW_HIDE);
	}
	if ( (child_status[0] & child_hidden) ||
		 (child_status[1] & child_hidden) )
		svi->set_visible(0);

	V_OP_END

	if ( shrink_window ) {
		if ( shrink_window < 0 ) {
			if ( pane == 0 )
				shrink_window = sts.value;
			else {
				RECT rt;
				GetClientRect(svi->get_hwnd(), &rt);
				int width = rt.right - rt.left, height = rt.bottom - rt.top;
				shrink_window = (get_type() == VHSplitView::object_type
					? width : height) - SPLIT_BAR_WIDTH - sts.value;
			}
		}

		VObjectStatus s;
		sts.window->get_status(&s, VSF_SIZE);
		if ( get_type() == VHSplitView::object_type )
			s.size.w -= shrink_window+SPLIT_BAR_WIDTH;
		else
			s.size.h -= shrink_window+SPLIT_BAR_WIDTH;
		sts.window->set_status(&s, VSF_SIZE);
	}
	shrink_size[pane?1:0] = shrink_window;

	VLayout::mark(this);
}


static void
splitview_callback(VSplitViewInfo* info, int flag, void* arg)
{
	VSplitView *obj = dynamic_cast<VSplitView*>(info->get_obj());
	obj->value_changed();
}


char splitViewClassName[] = "splitView";

class SplitViewRegister{
public:
	
	SplitViewRegister(){
		WNDCLASSEX wc;
		wc.cbSize = sizeof(WNDCLASSEX);
		wc.style = CS_HREDRAW | CS_VREDRAW | CS_PARENTDC;
		wc.lpfnWndProc = VContainerInfo::WndProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = ::GetModuleHandle(NULL);
		wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
		wc.hCursor = LoadCursor(NULL,IDC_ARROW);
		wc.hbrBackground = NULL;
		wc.lpszMenuName = NULL;
		wc.lpszClassName = splitViewClassName;
		wc.hIconSm = NULL;
		
		if(!::RegisterClassEx(&wc)){
			VWinError();
			er_panic("SplitViewRegister");
		}
	}
}split_view_register;

static HWND CreateSplitWindow(int style, VContainerInfo *info, int id){
	return ::CreateWindowEx(
		WS_EX_CONTROLPARENT,
		splitViewClassName,
		splitViewClassName,
		style,
		0,
		0,
		0,
		0,
		info->get_hwnd(),
		(HMENU)id,
		theApp->get_instance(),
		NULL);
}

VInfo* make_SplitView(
	const VObjectStatus *s, VObject* obj, VContainerInfo *parent_info, int id, char vert)
{
	DWORD style=WS_CHILD;	
	if(!s->enabled)
		style |= WS_DISABLED;
	if(s->visible)
		style |= WS_VISIBLE;
	
	HWND hwnd = v_serialized_exec_func(CreateSplitWindow, style, parent_info, 0);
	VSplitViewInfo *info = new VSplitViewInfo(obj, hwnd, id, vert);
	info->set_callback_func(splitview_callback, 0);
	if(info->get_hwnd()==NULL){
		VWinError();
		er_panic("make_SplitView");
	}

	return info;
}


VExError
VHSplitView::create_do(const VObjectStatus* s, int flags,VObject * nmp, void * arg)
{
	child0 = child1 = 0; 
	child_status[0] = ( (flags & VSF_ATTR) && (s->attr & shrinkable0) ) ? child_shrinkable: 0;
	child_status[1] = ( (flags & VSF_ATTR) && (s->attr & shrinkable1) ) ? child_shrinkable: 0;
	short *ds = (short*)arg;
	def_size[0] = ds[0];
	def_size[1] = ds[1];
	shrink_size[0] = shrink_size[1] = 0;
	last_size = v_default_size;
	sts.value = -1;		// use default size in VLayout

	info = make_SplitView(s,this,VInfo::get_container_info(nmp),s->id,0);
	VExError err = nmp->add_child_do(this);
	err.subcode1 |= VSF_VALUE;
	return err;
}

VExError
VVSplitView::create_do(const VObjectStatus* s, int flags,VObject * nmp, void * arg)
{
	child0 = child1 = 0; 
	child_status[0] = ( (flags & VSF_ATTR) && (s->attr & shrinkable0) ) ? child_shrinkable: 0;
	child_status[1] = ( (flags & VSF_ATTR) && (s->attr & shrinkable1) ) ? child_shrinkable: 0;
	short *ds = (short*)arg;
	def_size[0] = ds[0];
	def_size[1] = ds[1];
	shrink_size[0] = shrink_size[1] = 0;
	last_size = v_default_size;
	sts.value = -1;		// use default size in VLayout

	info = make_SplitView(s,this,VInfo::get_container_info(nmp),s->id,1);
	VExError err = nmp->add_child_do(this);
	err.subcode1 |= VSF_VALUE;
	return err;
}
