// 13 december 2014

// damn winsock
static void doselect(struct table *t, intptr_t row, intptr_t column)
{
	RECT r, client;
	intptr_t oldrow;
	LONG width, height;
	struct rowcol rc;
	BOOL dovscroll;
	intptr_t i;
	intptr_t xpos;
	LONG clientWidth;

	// check existing selection to see if it's valid
	if (t->selectedRow == -1 && t->selectedColumn != -1)
		panic("sanity check failure: old Table selection invalid (row == -1, column != -1)");
	if (t->selectedRow != -1 && t->selectedColumn == -1)
		panic("sanity check failure: old Table selection invalid (row != -1, column == -1)");
	if (t->selectedRow >= t->count)
		panic("sanity check failure: old Table selection invalid (row out of range)");
	if (t->selectedColumn >= t->nColumns)
		panic("sanity check failure: old Table selection invalid (column out of range)");

	oldrow = t->selectedRow;
	t->selectedRow = row;
	t->selectedColumn = column;

	// check new selection to see if it's valid
	if (t->selectedRow == -1 && t->selectedColumn != -1)
		panic("sanity check failure: new Table selection invalid (row == -1, column != -1)");
	if (t->selectedRow != -1 && t->selectedColumn == -1)
		panic("sanity check failure: new Table selection invalid (row != -1, column == -1)");
	if (t->selectedRow >= t->count)
		panic("sanity check failure: new Table selection invalid (row out of range)");
	if (t->selectedColumn >= t->nColumns)
		panic("sanity check failure: new Table selection invalid (column out of range)");

	// do this even if we don't scroll before; noScroll depends on it
	if (GetClientRect(t->hwnd, &client) == 0)
		panic("error getting Table client rect in doselect()");
	client.top += t->headerHeight;
	height = rowht(t);

	// only scroll if we selected something
	if (t->selectedRow == -1 || t->selectedColumn == -1)
		goto noScroll;

	// first vertically scroll to the new row to make it fully visible (or as visible as possible)
	if (t->selectedRow < t->vscrollpos)
		vscrollto(t, t->selectedRow);
	else {
		rc.row = t->selectedRow;
		rc.column = t->selectedColumn;
		// first assume entirely outside the client area
		dovscroll = TRUE;
		if (rowColumnToClientRect(t, rc, &r))
			// partially outside the client area?
			if (r.bottom <= client.bottom)		// <= here since we are comparing bottoms (which are the first pixels outside the rectangle)
				dovscroll = FALSE;
		if (dovscroll)
			vscrollto(t, t->selectedRow - t->vpagesize + 1);		// + 1 because apparently just t->selectedRow - t->vpagesize results in no scrolling (t->selectedRow - t->vpagesize == t->vscrollpos)...
	}

	// now see if the cell we want is to the left of offscreen, in which case scroll to its x-position
	xpos = 0;
	for (i = 0; i < t->selectedColumn; i++)
		xpos += columnWidth(t, i);
	if (xpos < t->hscrollpos)
		hscrollto(t, xpos);
	else {
		// if the full cell is not visible, scroll to the right just enough to make it fully visible (or as visible as possible)
		width = columnWidth(t, t->selectedColumn);
		clientWidth = client.right - client.left;
		if (xpos + width > t->hscrollpos + clientWidth)			// > because both sides deal with the first pixel outside
			// if the column is too wide, then just make it occupy the whole visible area (left-aligned)
			if (width > clientWidth)			// TODO >= ?
				hscrollto(t, xpos);
			else
				// TODO don't use t->hpagesize here? depends if other code uses it
				hscrollto(t, (xpos + width) - t->hpagesize);
	}

noScroll:
	// now redraw the old and new /rows/
	// we do this after scrolling so the rectangles to be invalidated make sense
	r.left = client.left;
	r.right = client.right;
	if (oldrow != -1 && oldrow >= t->vscrollpos) {
		r.top = client.top + ((oldrow - t->vscrollpos) * height);
		r.bottom = r.top + height;
		if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
			panic("error queueing previously selected row for redraw in doselect()");
	}
	// t->selectedRow must be visible by this point; we scrolled to it
	if (t->selectedRow != -1 && t->selectedRow != oldrow) {
		r.top = client.top + ((t->selectedRow - t->vscrollpos) * height);
		r.bottom = r.top + height;
		if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
			panic("error queueing newly selected row for redraw in doselect()");
	}

	// TODO what about deselect/defocus?
	// TODO notify on the old row too?
	NotifyWinEvent(EVENT_OBJECT_SELECTION, t->hwnd, OBJID_CLIENT, t->selectedRow);
	// TODO send EVENT_OBJECT_STATECHANGED too?
	// TODO send EVENT_OBJECT_FOCUS
}

// TODO make this needless
HANDLER(checkboxMouseDownHandler);

// TODO which WM_xBUTTONDOWNs?
HANDLER(mouseDownSelectHandler)
{
	struct rowcol rc;

	// TODO separate this from here
	// TODO other mouse buttons?
	// don't check SetFocus()'s error (http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see)
	// TODO NotifyWinEvent() here?
	SetFocus(t->hwnd);
	rc = lParamToRowColumn(t, lParam);
	// don't check if lParamToRowColumn() returned row -1 or column -1; we want deselection behavior
	doselect(t, rc.row, rc.column);
	// TODO separate this from here
	checkboxMouseDownHandler(t, uMsg, wParam, lParam, lResult);
	*lResult = 0;
	return TRUE;
}

/*
the routine below is intended to simulate the comctl32.dll listview keyboard navigation rules, at least as far as vertical navigation is concerned.
horizontal scrolling is different because unlike the comctl32 listview, we say that a single column in each row has the keyboard focus, so left and right navigate between columns here, instead of scrolling left/right by pixels.
	TODO provide an override for scrolling by pixels?
	TODO any other keyboard shortcuts?
		TODO browser keys
		TODO media navigation keys
			TODO XBUTTON1/2?
		TODO clear keys?

keyboard selection behaviors of the windows 7 listview:
with 100 items (0-99), the window currently shows items 30 through 47 as well as having item 48 partially visible
-  item 30:
	- page up -> item 13
	- page down -> item 47
- item 31:
	- page up -> item 30
	- page down -> item 47
- item 42:
	- page up -> item 30
	- page down -> item 47
- item 46:
	- page up -> item 30
	- page down -> item 47
- item 47:
	- page up: -> item 30
	- page down: -> item 64

when nothing is selected:
- down selects item 0 regardless of scroll
- up selects nothing regardless of scroll
- page down selects the last fully visible item depending on scroll
	- so with the above configuration:
		- item 0 -> item 17
		- item 30 -> item 47
		- item 80 -> item 97
- page up selects item 0 regardless of scroll
- home selects item 0 regardless of scroll
- end selects the last item regardless of scroll

for left and right we will simulate up and down, respectively (so right selects row 0 column 0); remember that you can't have either row or column be -1 but not both

TODO what happens if page up and page down are pressed with an item selected and the scroll in a different position?
*/

HANDLER(keyDownSelectHandler)
{
	intptr_t row;
	intptr_t column;

	if (t->count == 0 || t->nColumns == 0)		// no items to select
		return FALSE;
	row = t->selectedRow;
	column = t->selectedColumn;
	switch (wParam) {
	case VK_UP:
		if (row == -1)
			return FALSE;
		row--;
		if (row < 0)
			row = 0;
		break;
	case VK_DOWN:
		if (row == -1) {
			row = 0;
			column = 0;
		} else {
			row++;
			if (row >= t->count)
				row = t->count - 1;
		}
		break;
	case VK_LEFT:
		if (column == -1)
			return FALSE;
		column--;
		if (column < 0)
			column = 0;
		break;
	case VK_RIGHT:
		if (column == -1) {
			row = 0;
			column = 0;
		} else {
			column++;
			if (column >= t->nColumns)
				column = t->nColumns - 1;
		}
		break;
	case VK_HOME:
		row = 0;
		if (column == -1)
			column = 0;
		break;
	case VK_END:
		row = t->count - 1;
		if (column == -1)
			column = 0;
		break;
	case VK_PRIOR:
		if (row == -1) {
			row = 0;
			column = 0;
		} else {
			row = t->vscrollpos;
			if (row == t->selectedRow)
				// TODO investigate why the - 1 is needed here and below
				// TODO if this is a misunderstanding of how t->vpagesize works, figure out what happens if there is no partially visible row, and what is supposed to happen
				row -= t->vpagesize - 1;
			if (row < 0)
				row = 0;
		}
		break;
	case VK_NEXT:
		if (row == -1) {
			row = t->vscrollpos + t->vpagesize - 1;
			// TODO ensusre this is the case with the real list view
			if (row >= t->count)
				row = t->count - 1;
			column = 0;
		} else {
			row = t->vscrollpos + t->vpagesize - 1;
			if (row == t->selectedRow)
				row += t->vpagesize - 1;
			if (row >= t->count)
				row = t->count - 1;
		}
		break;
	default:
		return FALSE;
	}
	doselect(t, row, column);
	*lResult = 0;
	return TRUE;
}
