
#include "WINGsP.h"

typedef struct W_ScrollView {
	W_Class widgetClass;
	WMView *view;

	WMView *contentView;
	WMView *viewport;

	WMScroller *vScroller;
	WMScroller *hScroller;

	short lineScroll;
	short pageScroll;

	struct {
		WMReliefType relief:3;
		unsigned int hasVScroller:1;
		unsigned int hasHScroller:1;

	} flags;

} ScrollView;

static void destroyScrollView(ScrollView * sPtr);

static void paintScrollView(ScrollView * sPtr);
static void handleEvents(XEvent * event, void *data);
static void handleViewportEvents(XEvent * event, void *data);
static void resizeScrollView();
static void updateScrollerProportion();

W_ViewDelegate _ScrollViewViewDelegate = {
	NULL,
	NULL,
	resizeScrollView,
	NULL,
	NULL
};

WMScrollView *WMCreateScrollView(WMWidget * parent)
{
	ScrollView *sPtr;

	sPtr = wmalloc(sizeof(ScrollView));
	memset(sPtr, 0, sizeof(ScrollView));

	sPtr->widgetClass = WC_ScrollView;

	sPtr->view = W_CreateView(W_VIEW(parent));
	if (!sPtr->view) {
		wfree(sPtr);
		return NULL;
	}
	sPtr->viewport = W_CreateView(sPtr->view);
	if (!sPtr->viewport) {
		W_DestroyView(sPtr->view);
		wfree(sPtr);
		return NULL;
	}
	sPtr->view->self = sPtr;
	sPtr->viewport->self = sPtr;

	sPtr->view->delegate = &_ScrollViewViewDelegate;

	sPtr->viewport->flags.mapWhenRealized = 1;

	WMCreateEventHandler(sPtr->view, StructureNotifyMask | ExposureMask, handleEvents, sPtr);
	WMCreateEventHandler(sPtr->viewport, SubstructureNotifyMask, handleViewportEvents, sPtr);

	sPtr->lineScroll = 4;

	sPtr->pageScroll = 0;

	return sPtr;
}

static void applyScrollerValues(WMScrollView * sPtr)
{
	int x, y;

	if (sPtr->contentView == NULL)
		return;

	if (sPtr->flags.hasHScroller) {
		float v = WMGetScrollerValue(sPtr->hScroller);
		int size;

		size = sPtr->contentView->size.width - sPtr->viewport->size.width;

		x = v * size;
	} else {
		x = 0;
	}

	if (sPtr->flags.hasVScroller) {
		float v = WMGetScrollerValue(sPtr->vScroller);

		int size;

		size = sPtr->contentView->size.height - sPtr->viewport->size.height;

		y = v * size;
	} else {
		y = 0;
	}

	x = WMAX(0, x);
	y = WMAX(0, y);

	W_MoveView(sPtr->contentView, -x, -y);

	W_RaiseView(sPtr->viewport);
}

static void reorganizeInterior(WMScrollView * sPtr)
{
	int hx, hy, hw;
	int vx, vy, vh;
	int cx, cy, cw, ch;

	cw = hw = sPtr->view->size.width;
	vh = ch = sPtr->view->size.height;

	if (sPtr->flags.relief == WRSimple) {
		cw -= 2;
		ch -= 2;
		cx = 1;
		cy = 1;
	} else if (sPtr->flags.relief != WRFlat) {
		cw -= 3;
		ch -= 3;
		cx = 2;
		cy = 2;
	} else {
		cx = 0;
		cy = 0;
	}

	if (sPtr->flags.hasHScroller) {
		int h = 20;

		ch -= h;

		if (sPtr->flags.relief == WRSimple) {
			hx = 0;
			hy = sPtr->view->size.height - h;
		} else if (sPtr->flags.relief != WRFlat) {
			hx = 1;
			hy = sPtr->view->size.height - h - 1;
			hw -= 2;
		} else {
			hx = 0;
			hy = sPtr->view->size.height - h;
		}
	} else {
		/* make compiler shutup */
		hx = 0;
		hy = 0;
	}

	if (sPtr->flags.hasVScroller) {
		int w = 20;
		cw -= w;
		cx += w;
		hx += w - 1;
		hw -= w - 1;

		if (sPtr->flags.relief == WRSimple) {
			vx = 0;
			vy = 0;
		} else if (sPtr->flags.relief != WRFlat) {
			vx = 1;
			vy = 1;
			vh -= 2;
		} else {
			vx = 0;
			vy = 0;
		}
	} else {
		/* make compiler shutup */
		vx = 0;
		vy = 0;
	}

	W_ResizeView(sPtr->viewport, cw, ch);
	W_MoveView(sPtr->viewport, cx, cy);

	if (sPtr->flags.hasHScroller) {
		WMResizeWidget(sPtr->hScroller, hw, 20);
		WMMoveWidget(sPtr->hScroller, hx, hy);
	}
	if (sPtr->flags.hasVScroller) {
		WMResizeWidget(sPtr->vScroller, 20, vh);
		WMMoveWidget(sPtr->vScroller, vx, vy);
	}

	applyScrollerValues(sPtr);
}

static void resizeScrollView(W_ViewDelegate * self, WMView * view)
{
	reorganizeInterior(view->self);
	updateScrollerProportion(view->self);
}

void WMResizeScrollViewContent(WMScrollView * sPtr, unsigned int width, unsigned int height)
{
	int w, h, x;

	w = width;
	h = height;

	x = 0;
	if (sPtr->flags.relief == WRSimple) {
		w += 2;
		h += 2;
	} else if (sPtr->flags.relief != WRFlat) {
		w += 4;
		h += 4;
		x = 1;
	}

	if (sPtr->flags.hasVScroller) {
		WMResizeWidget(sPtr->vScroller, 20, h);
		width -= W_VIEW(sPtr->vScroller)->size.width;
	}

	if (sPtr->flags.hasHScroller) {
		WMResizeWidget(sPtr->hScroller, w, 20);
		WMMoveWidget(sPtr->hScroller, x, h);
		height -= W_VIEW(sPtr->hScroller)->size.height;
	}

	W_ResizeView(sPtr->view, w, h);

	W_ResizeView(sPtr->viewport, width, height);
}

void WMSetScrollViewLineScroll(WMScrollView * sPtr, int amount)
{
	assert(amount > 0);

	sPtr->lineScroll = amount;
}

void WMSetScrollViewPageScroll(WMScrollView * sPtr, int amount)
{
	assert(amount >= 0);

	sPtr->pageScroll = amount;
}

WMRect WMGetScrollViewVisibleRect(WMScrollView * sPtr)
{
	WMRect rect;

	rect.pos.x = -sPtr->contentView->pos.x;
	rect.pos.y = -sPtr->contentView->pos.y;
	rect.size = sPtr->viewport->size;

	return rect;
}

void WMScrollViewScrollPoint(WMScrollView * sPtr, WMPoint point)
{
	float xsize, ysize;
	float xpos, ypos;

	xsize = sPtr->contentView->size.width - sPtr->viewport->size.width;
	ysize = sPtr->contentView->size.height - sPtr->viewport->size.height;

	xpos = point.x / xsize;
	ypos = point.y / ysize;

	if (sPtr->hScroller)
		WMSetScrollerParameters(sPtr->hScroller, xpos, WMGetScrollerKnobProportion(sPtr->hScroller));
	if (sPtr->vScroller)
		WMSetScrollerParameters(sPtr->vScroller, ypos, WMGetScrollerKnobProportion(sPtr->vScroller));

	W_MoveView(sPtr->contentView, -point.x, -point.y);
}

static void doScrolling(WMWidget * self, void *data)
{
	ScrollView *sPtr = (ScrollView *) data;
	float value;
	int pos;
	int vpsize;
	float size;

	if (sPtr->hScroller == (WMScroller *) self) {
		pos = -sPtr->contentView->pos.x;
		size = sPtr->contentView->size.width - sPtr->viewport->size.width;
		vpsize = sPtr->viewport->size.width - sPtr->pageScroll;
	} else {
		pos = -sPtr->contentView->pos.y;
		size = sPtr->contentView->size.height - sPtr->viewport->size.height;
		vpsize = sPtr->viewport->size.height - sPtr->pageScroll;
	}
	if (vpsize <= 0)
		vpsize = 1;

	switch (WMGetScrollerHitPart(self)) {
	case WSDecrementLine:
		if (pos > 0) {
			pos -= sPtr->lineScroll;
			if (pos < 0)
				pos = 0;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;
	case WSIncrementLine:
		if (pos < size) {
			pos += sPtr->lineScroll;
			if (pos > size)
				pos = size;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;

	case WSKnob:
		value = WMGetScrollerValue(self);
		pos = value * size;
		break;

	case WSDecrementPage:
		if (pos > 0) {
			pos -= vpsize;
			if (pos < 0)
				pos = 0;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;

	case WSDecrementWheel:
		if (pos > 0) {
			pos -= vpsize / 3;
			if (pos < 0)
				pos = 0;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;

	case WSIncrementPage:
		if (pos < size) {
			pos += vpsize;
			if (pos > size)
				pos = size;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;

	case WSIncrementWheel:
		if (pos < size) {
			pos += vpsize / 3;
			if (pos > size)
				pos = size;
			value = (float)pos / size;
			WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
		}
		break;

	case WSNoPart:
	case WSKnobSlot:
		break;
	}

	if (sPtr->hScroller == (WMScroller *) self) {
		W_MoveView(sPtr->contentView, -pos, sPtr->contentView->pos.y);
	} else {
		W_MoveView(sPtr->contentView, sPtr->contentView->pos.x, -pos);
	}
}

WMScroller *WMGetScrollViewHorizontalScroller(WMScrollView * sPtr)
{
	return sPtr->hScroller;
}

WMScroller *WMGetScrollViewVerticalScroller(WMScrollView * sPtr)
{
	return sPtr->vScroller;
}

void WMSetScrollViewHasHorizontalScroller(WMScrollView * sPtr, Bool flag)
{
	if (flag) {
		if (sPtr->flags.hasHScroller)
			return;
		sPtr->flags.hasHScroller = 1;

		sPtr->hScroller = WMCreateScroller(sPtr);
		WMSetScrollerAction(sPtr->hScroller, doScrolling, sPtr);
		/* make it a horiz. scroller */
		WMResizeWidget(sPtr->hScroller, 2, 1);

		if (W_VIEW_REALIZED(sPtr->view)) {
			WMRealizeWidget(sPtr->hScroller);
		}

		reorganizeInterior(sPtr);

		WMMapWidget(sPtr->hScroller);
	} else {
		if (!sPtr->flags.hasHScroller)
			return;

		WMUnmapWidget(sPtr->hScroller);
		WMDestroyWidget(sPtr->hScroller);
		sPtr->hScroller = NULL;
		sPtr->flags.hasHScroller = 0;

		reorganizeInterior(sPtr);
	}
}

void WMSetScrollViewHasVerticalScroller(WMScrollView * sPtr, Bool flag)
{
	if (flag) {
		if (sPtr->flags.hasVScroller)
			return;
		sPtr->flags.hasVScroller = 1;

		sPtr->vScroller = WMCreateScroller(sPtr);
		WMSetScrollerAction(sPtr->vScroller, doScrolling, sPtr);
		WMSetScrollerArrowsPosition(sPtr->vScroller, WSAMaxEnd);
		/* make it a vert. scroller */
		WMResizeWidget(sPtr->vScroller, 1, 2);

		if (W_VIEW_REALIZED(sPtr->view)) {
			WMRealizeWidget(sPtr->vScroller);
		}

		reorganizeInterior(sPtr);

		WMMapWidget(sPtr->vScroller);
	} else {
		if (!sPtr->flags.hasVScroller)
			return;
		sPtr->flags.hasVScroller = 0;

		WMUnmapWidget(sPtr->vScroller);
		WMDestroyWidget(sPtr->vScroller);
		sPtr->vScroller = NULL;

		reorganizeInterior(sPtr);
	}
}

void WMSetScrollViewContentView(WMScrollView * sPtr, WMView * view)
{
	assert(sPtr->contentView == NULL);

	sPtr->contentView = view;

	W_ReparentView(sPtr->contentView, sPtr->viewport, 0, 0);

	if (sPtr->flags.hasHScroller) {
		float prop;

		prop = (float)sPtr->viewport->size.width / sPtr->contentView->size.width;
		WMSetScrollerParameters(sPtr->hScroller, 0, prop);
	}
	if (sPtr->flags.hasVScroller) {
		float prop;

		prop = (float)sPtr->viewport->size.height / sPtr->contentView->size.height;

		WMSetScrollerParameters(sPtr->vScroller, 0, prop);
	}
}

void WMSetScrollViewRelief(WMScrollView * sPtr, WMReliefType type)
{
	sPtr->flags.relief = type;

	reorganizeInterior(sPtr);

	if (sPtr->view->flags.mapped)
		paintScrollView(sPtr);

}

static void paintScrollView(ScrollView * sPtr)
{
	W_DrawRelief(sPtr->view->screen, sPtr->view->window, 0, 0,
		     sPtr->view->size.width, sPtr->view->size.height, sPtr->flags.relief);
}

static void updateScrollerProportion(ScrollView * sPtr)
{
	float prop, value;
	float oldV, oldP;

	if (sPtr->flags.hasHScroller) {
		oldV = WMGetScrollerValue(sPtr->hScroller);
		oldP = WMGetScrollerKnobProportion(sPtr->hScroller);

		prop = (float)sPtr->viewport->size.width / (float)sPtr->contentView->size.width;

		if (oldP == 1.0)
			value = 0;
		else
			value = (prop * oldV) / oldP;
		WMSetScrollerParameters(sPtr->hScroller, value, prop);
	}
	if (sPtr->flags.hasVScroller) {
		oldV = WMGetScrollerValue(sPtr->vScroller);
		oldP = WMGetScrollerKnobProportion(sPtr->vScroller);

		prop = (float)sPtr->viewport->size.height / (float)sPtr->contentView->size.height;

		if (oldP == 1.0)
			value = 0;
		else
			value = (prop * oldV) / oldP;
		WMSetScrollerParameters(sPtr->vScroller, value, prop);
	}
	applyScrollerValues(sPtr);
}

static void handleViewportEvents(XEvent * event, void *data)
{
	ScrollView *sPtr = (ScrollView *) data;

	if (sPtr->contentView && event->xconfigure.window == sPtr->contentView->window)
		updateScrollerProportion(sPtr);
}

static void handleEvents(XEvent * event, void *data)
{
	ScrollView *sPtr = (ScrollView *) data;

	CHECK_CLASS(data, WC_ScrollView);

	switch (event->type) {
	case Expose:
		if (event->xexpose.count != 0)
			break;
		if (event->xexpose.serial == 0)	/* means it's artificial */
			W_RedisplayView(sPtr->contentView);
		else
			paintScrollView(sPtr);
		break;

	case DestroyNotify:
		destroyScrollView(sPtr);
		break;

	}
}

static void destroyScrollView(ScrollView * sPtr)
{
	wfree(sPtr);
}
