// 29 november 2014

static LONG rowHeight(struct table *t)
{
	HFONT thisfont, prevfont;
	TEXTMETRICW tm;
	HDC dc;
	LONG ret;

	dc = GetDC(t->hwnd);
	if (dc == NULL)
		abort();
	thisfont = t->font;		// in case WM_SETFONT happens before we return
	prevfont = (HFONT) SelectObject(dc, thisfont);
	if (prevfont == NULL)
		abort();
	if (GetTextMetricsW(dc, &tm) == 0)
		abort();
	if (SelectObject(dc, prevfont) != (HGDIOBJ) (thisfont))
		abort();
	if (ReleaseDC(t->hwnd, dc) == 0)
		abort();
	ret = tm.tmHeight;
	if (ret < t->imagelistHeight)
		ret = t->imagelistHeight;
	if (ret < t->checkboxHeight)
		ret = t->checkboxHeight;
	return ret;
}

static void redrawAll(struct table *t)
{
	if (InvalidateRect(t->hwnd, NULL, TRUE) == 0)
		abort();
	if (UpdateWindow(t->hwnd) == 0)
		abort();
}

static RECT realClientRect(struct table *t)
{
	RECT r;

	if (GetClientRect(t->hwnd, &r) == 0)
		abort();
	r.top += t->headerHeight;
	return r;
}

static void repositionHeader(struct table *t)
{
	RECT r;
	HDLAYOUT headerlayout;
	WINDOWPOS headerpos;

	if (GetClientRect(t->hwnd, &r) == 0)		// use the whole client rect
		abort();
	// grow the rectangle to the left to fake scrolling
	r.left -= t->hpos;
	headerlayout.prc = &r;
	headerlayout.pwpos = &headerpos;
	if (SendMessageW(t->header, HDM_LAYOUT, 0, (LPARAM) (&headerlayout)) == FALSE)
		abort();
	if (SetWindowPos(t->header, headerpos.hwndInsertAfter, headerpos.x, headerpos.y, headerpos.cx, headerpos.cy, headerpos.flags | SWP_SHOWWINDOW) == 0)
		abort();
	t->headerHeight = headerpos.cy;
}

// this counts partially visible rows
// for all fully visible rows use t->pagesize
// cliprect and rowHeight must be specified here to avoid recomputing things multiple times
static intptr_t lastVisible(struct table *t, RECT cliprect, LONG rowHeight)
{
	intptr_t last;

	last = ((cliprect.bottom + rowHeight - 1) / rowHeight) + t->firstVisible;
	if (last >= t->count)
		last = t->count;
	return last;
}

static void redrawRow(struct table *t, intptr_t row)
{
	RECT r;
	intptr_t height;

	r = realClientRect(t);
	height = rowHeight(t);
	if (row < t->firstVisible || row > lastVisible(t, r, height))		// not visible; don't bother
		return;
	r.top = (row - t->firstVisible) * height + t->headerHeight;
	r.bottom = r.top + height;
	// keep the width and height the same; it spans the client area anyway
	if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
		abort();
	if (UpdateWindow(t->hwnd) == 0)
		abort();
}

static intptr_t hitTestColumn(struct table *t, int x)
{
	HDITEMW item;
	intptr_t i;

	// TODO count dividers
	for (i = 0; i < t->nColumns; i++) {
		ZeroMemory(&item, sizeof (HDITEMW));
		item.mask = HDI_WIDTH;
		if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE)
			abort();
		if (x < item.cxy)
			return i;
		x -= item.cxy;		// not yet
	}
	// no column
	return -1;
}

static void lParamToRowColumn(struct table *t, LPARAM lParam, intptr_t *row, intptr_t *column)
{
	int x, y;
	LONG h;

	x = GET_X_LPARAM(lParam);
	y = GET_Y_LPARAM(lParam);
	h = rowHeight(t);
	y += t->firstVisible * h;
	y -= t->headerHeight;
	y /= h;
	if (row != NULL) {
		*row = y;
		if (*row >= t->count)
			*row = -1;
	}
	if (column != NULL)
		*column = hitTestColumn(t, x);
}