/*
    A really crappy viewer module.
    Copyright (C) 2002-2006 Randy Heit

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
#ifndef NO_MAP_VIEWER

#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include "resource.h"
#include "zdbsp.h"
#include "doomdata.h"
#include "templates.h"
#include "tarray.h"

static int MapWidthDiff, MapHeightDiff;
static int ButtonsLeftOffset, ButtonsTopOffset;
static int StaticNumberLeft, StaticComboLeft;
static FLevel *Level;
static RECT MapBounds;
static POINT MapSize;
static int Divisor;

enum ViewMode
{
	ViewNodes,
	ViewGLNodes,
	ViewSubsectors,
	ViewGLSubsectors,
	ViewReject,
	ViewPVS
};

ViewMode Viewing;

void ResetViews ();
void SetStaticText ();

void ResizeDialog (HWND hDlg)
{
	RECT client;
	HWND control;

	GetClientRect (hDlg, &client);
	control = GetDlgItem (hDlg, IDC_MAPVIEW);
	SetWindowPos (control, 0, 0, 0, client.right - MapWidthDiff, client.bottom - MapHeightDiff,
		SWP_NOMOVE|SWP_NOREPOSITION|SWP_NOZORDER);

	control = GetDlgItem (hDlg, IDOK);
	SetWindowPos (control, 0, ButtonsLeftOffset, client.bottom - ButtonsTopOffset, 0, 0,
		SWP_NOSIZE|SWP_NOREPOSITION|SWP_NOZORDER);

	control = GetDlgItem (hDlg, IDC_STATICNUMBER);
	SetWindowPos (control, 0, StaticNumberLeft, client.bottom - ButtonsTopOffset, 0, 0,
		SWP_NOSIZE|SWP_NOREPOSITION|SWP_NOZORDER);

	control = GetDlgItem (hDlg, IDC_COMBO1);
	SetWindowPos (control, 0, StaticComboLeft, client.bottom - ButtonsTopOffset, 0, 0,
		SWP_NOSIZE|SWP_NOREPOSITION|SWP_NOZORDER);
}

void AddToCombo (HWND control, const char *name, ViewMode mode)
{
	LRESULT lResult = SendMessage (control, CB_ADDSTRING, 0, (LPARAM)name);
	if (lResult != CB_ERR && lResult != CB_ERRSPACE)
	{
		SendMessage (control, CB_SETITEMDATA, lResult, (LPARAM)mode);
		if (Viewing == mode)
		{
			SendMessage (control, CB_SETCURSEL, (WPARAM)lResult, 0);
		}
	}
}

INT_PTR CALLBACK ViewDialogFunc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HWND control;
	RECT rect1, rect2;

	switch (uMsg)
	{
	case WM_INITDIALOG:
		if (GetClientRect (hDlg, &rect1))
		{
			control = GetDlgItem (hDlg, IDC_MAPVIEW);
			if (GetWindowRect (control, &rect2))
			{
				MapWidthDiff = rect1.right - rect2.right + rect2.left;
				MapHeightDiff = rect1.bottom - rect2.bottom + rect2.top;
			}
			control = GetDlgItem (hDlg, IDOK);
			if (GetWindowRect (control, &rect2))
			{
				POINT pt = { 0, rect1.bottom };
				ClientToScreen (hDlg, &pt);
				ButtonsTopOffset = pt.y - rect2.top;
				ButtonsLeftOffset = rect2.left - pt.x;
				control = GetDlgItem (hDlg, IDC_STATICNUMBER);
				if (GetWindowRect (control, &rect2))
				{
					StaticNumberLeft = rect2.left - pt.x;
				}
				control = GetDlgItem (hDlg, IDC_COMBO1);
				if (GetWindowRect (control, &rect2))
				{
					StaticComboLeft = rect2.left - pt.x;
				}
			}
		}

		control = GetDlgItem (hDlg, IDC_COMBO1);
		if (Level->Nodes)
		{
			AddToCombo (control, "Nodes", ViewNodes);
		}
		if (Level->Subsectors)
		{
			AddToCombo (control, "Subsectors", ViewSubsectors);
		}
		if (Level->Reject)
		{
			AddToCombo (control, "Reject", ViewReject);
		}
		if (Level->GLNodes)
		{
			AddToCombo (control, "GL Nodes", ViewGLNodes);
		}
		if (Level->GLSubsectors)
		{
			AddToCombo (control, "GL Subsectors", ViewGLSubsectors);
		}
		if (Level->GLPVS)
		{
			AddToCombo (control, "PVS", ViewPVS);
		}
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
		case IDCANCEL:
			EndDialog (hDlg, 1);
			break;

		case IDC_COMBO1:
			if (HIWORD(wParam) == CBN_SELENDOK)
			{
				ViewMode newmode;

				newmode = (ViewMode)SendMessage (HWND(lParam), CB_GETITEMDATA,
					SendMessage (HWND(lParam), CB_GETCURSEL, 0, 0), 0);

				if (newmode != Viewing)
				{
					Viewing = newmode;
					ResetViews ();
					InvalidateRect (GetDlgItem (hDlg, IDC_MAPVIEW), NULL, TRUE);
				}
			}
			break;
		}
		return TRUE;

	case WM_GETMINMAXINFO:
		((MINMAXINFO *)lParam)->ptMinTrackSize.x = 480;
		((MINMAXINFO *)lParam)->ptMinTrackSize.y = 200;
		return TRUE;

	case WM_SIZE:
		if (wParam != SIZE_MAXHIDE && wParam != SIZE_MAXSHOW && wParam != SIZE_MINIMIZED)
		{
			ResizeDialog (hDlg);
		}
		return TRUE;

	case WM_MOUSEWHEEL:
		control = GetDlgItem (hDlg, IDC_MAPVIEW);
		PostMessage (control, WM_MOUSEWHEEL, wParam, lParam);
		return 0;

	case WM_APP:
		control = GetDlgItem (hDlg, IDC_STATICNUMBER);
		char foo[16];
		wsprintf (foo, "%d", wParam);
		SendMessage (control, WM_SETTEXT, 0, (LPARAM)foo);
		return 0;
	}
	return FALSE;
}

static inline int VERTX (int i)
{
	return Level->Vertices[i].x >> FRACBITS;
}

static inline int VERTY (int i)
{
	return Level->Vertices[i].y >> FRACBITS;
}

static inline int GLVERTX (int i)
{
	return Level->GLVertices[i].x >> FRACBITS;
}

static inline int GLVERTY (int i)
{
	return Level->GLVertices[i].y >> FRACBITS;
}

static HPEN NotInNode;
static HPEN LeftOfSplitter, LeftPen1, LeftPen2;
static HPEN RightOfSplitter, RightPen1, RightPen2;
static HPEN OutPen1;
static HPEN Splitter;
static short DesiredNode;
static TArray<short> DesiredHistory;

static void DrawSubsector (HDC dc, int ssec)
{
	for (DWORD i = 0; i < Level->Subsectors[ssec].numlines; ++i)
	{
		int seg = Level->Subsectors[ssec].firstline + i;
		if (Level->Segs[seg].side == 0)
		{
			MoveToEx (dc, VERTX(Level->Segs[seg].v1), VERTY(Level->Segs[seg].v1), NULL);
			LineTo (dc, VERTX(Level->Segs[seg].v2), VERTY(Level->Segs[seg].v2));
		}
		else
		{
			MoveToEx (dc, VERTX(Level->Segs[seg].v2), VERTY(Level->Segs[seg].v2), NULL);
			LineTo (dc, VERTX(Level->Segs[seg].v1), VERTY(Level->Segs[seg].v1));
		}
	}
}

static void DrawSubsectorGL (HDC dc, int ssec, HPEN miniPen, HPEN badPen)
{
	int seg;

	seg = Level->GLSubsectors[ssec].firstline;
	MoveToEx (dc, GLVERTX(Level->GLSegs[seg].v1), GLVERTY(Level->GLSegs[seg].v1), NULL);
	for (DWORD i = 0; i < Level->GLSubsectors[ssec].numlines; ++i)
	{
		HPEN oldPen = NULL;
		seg = Level->GLSubsectors[ssec].firstline + i;
		if (Level->GLSegs[seg].linedef == NO_INDEX)
		{
			if (Level->GLSegs[seg].partner == NO_INDEX)
			{
				oldPen = (HPEN)SelectObject (dc, badPen);
			}
			else
			{
				oldPen = (HPEN)SelectObject (dc, miniPen);
			}
		}
		if (Level->GLSegs[seg].side == 0 || 1)
		{
			//MoveToEx (dc, GLVERTX(Level->GLSegs[seg].v1), GLVERTY(Level->GLSegs[seg].v1), NULL);
			LineTo (dc, GLVERTX(Level->GLSegs[seg].v2), GLVERTY(Level->GLSegs[seg].v2));
		}
		else
		{
			//MoveToEx (dc, GLVERTX(Level->GLSegs[seg].v2), GLVERTY(Level->GLSegs[seg].v2), NULL);
			LineTo (dc, GLVERTX(Level->GLSegs[seg].v1), GLVERTY(Level->GLSegs[seg].v1));
		}
		if (oldPen != NULL)
		{
			SelectObject (dc, oldPen);
		}
	}
}


static void RealDrawNode (HDC dc, int node, HPEN miniPen, HPEN badPen)
{
	if (Viewing == ViewNodes)
	{
		if (node & NFX_SUBSECTOR)
		{
			DrawSubsector (dc, node & ~NFX_SUBSECTOR);
		}
		else
		{
			RealDrawNode (dc, Level->Nodes[node].children[0], miniPen, badPen);
			RealDrawNode (dc, Level->Nodes[node].children[1], miniPen, badPen);
		}
	}
	else
	{
		if (node & NFX_SUBSECTOR)
		{
			DrawSubsectorGL (dc, node & ~NFX_SUBSECTOR, miniPen, badPen);
		}
		else
		{
			RealDrawNode (dc, Level->GLNodes[node].children[0], miniPen, badPen);
			RealDrawNode (dc, Level->GLNodes[node].children[1], miniPen, badPen);
		}
	}
}

static void DrawNode (HDC dc, int node)
{
	if (node & NFX_SUBSECTOR)
	{
		return;
	}
	else
	{
		if (Viewing == ViewNodes)
		{
			if (node == DesiredNode)
			{
				SelectObject (dc, LeftOfSplitter);
				RealDrawNode (dc, Level->Nodes[node].children[1], LeftPen1, LeftPen2);
				SelectObject (dc, RightOfSplitter);
				RealDrawNode (dc, Level->Nodes[node].children[0], RightPen1, RightPen2);
				return;
			}
			else
			{
				DrawNode (dc, Level->Nodes[node].children[0]);
				DrawNode (dc, Level->Nodes[node].children[1]);
			}
		}
		else
		{
			if (node == DesiredNode)
			{
				SelectObject (dc, LeftOfSplitter);
				RealDrawNode (dc, Level->GLNodes[node].children[1], LeftPen1, LeftPen2);
				SelectObject (dc, RightOfSplitter);
				RealDrawNode (dc, Level->GLNodes[node].children[0], RightPen1, RightPen2);
				return;
			}
			else
			{
				DrawNode (dc, Level->GLNodes[node].children[0]);
				DrawNode (dc, Level->GLNodes[node].children[1]);
			}
		}
	}
}

static void DrawOutsideNode (HDC dc, int node)
{
	if (node == DesiredNode)
	{
		return;
	}
	else if (node & NFX_SUBSECTOR)
	{
		if (Viewing == ViewNodes)
		{
			DrawSubsector (dc, node & ~NFX_SUBSECTOR);
		}
		else
		{
			DrawSubsectorGL (dc, node & ~NFX_SUBSECTOR, OutPen1, OutPen1);
		}
	}
	else
	{
		if (Viewing == ViewNodes)
		{
			DrawOutsideNode (dc, Level->Nodes[node].children[0]);
			DrawOutsideNode (dc, Level->Nodes[node].children[1]);
		}
		else
		{
			DrawOutsideNode (dc, Level->GLNodes[node].children[0]);
			DrawOutsideNode (dc, Level->GLNodes[node].children[1]);
		}
	}
}

static void DrawSplitter (HDC dc, MapNodeEx *node)
{
	int dx = node->dx, dy = node->dy;
	// If the splitter is particularly short, make it longer to stand out
	while (abs(dx) < (20 << 16) && abs(dy) < (20 << 16))
	{
		dx <<= 1;
		dy <<= 1;
	}
	SelectObject (dc, Splitter);
	MoveToEx (dc, (node->x - dx) >> 16, (node->y - dy) >> 16, NULL);
	LineTo (dc, (node->x + 2*dx) >> 16, (node->y + 2*dy) >> 16);
}

static void DrawLevelNodes (HDC dc)
{
	HPEN oldPen;

	NotInNode = CreatePen (PS_SOLID, 1, RGB(200,200,200));
	OutPen1 = CreatePen (PS_SOLID, 1, RGB(238,238,238));
	LeftOfSplitter = CreatePen (PS_SOLID, 1, RGB(255,0,0));
	LeftPen1 = CreatePen (PS_SOLID, 1, RGB(255,200,200));
	LeftPen2 = CreatePen (PS_SOLID, 1, RGB(128,0,0));
	RightOfSplitter = CreatePen (PS_SOLID, 1, RGB(0,255,0));
	RightPen1 = CreatePen (PS_SOLID, 1, RGB(200,255,200));
	RightPen2 = CreatePen (PS_SOLID, 1, RGB(0,128,0));
	Splitter = CreatePen (PS_DOT, 1, RGB(70,0,255));

	oldPen = (HPEN)SelectObject (dc, NotInNode);

	if (Viewing == ViewNodes)
	{
		DrawOutsideNode (dc, Level->NumNodes - 1);
		DrawNode (dc, Level->NumNodes - 1);
		DrawSplitter (dc, &Level->Nodes[DesiredNode]);
	}
	else
	{
		DrawOutsideNode (dc, Level->NumGLNodes - 1);
		DrawNode (dc, Level->NumGLNodes - 1);
		DrawSplitter (dc, &Level->GLNodes[DesiredNode]);
	}


	SelectObject (dc, oldPen);

	DeleteObject (NotInNode);
	DeleteObject (LeftOfSplitter);
	DeleteObject (LeftPen1);
	DeleteObject (LeftPen2);
	DeleteObject (RightOfSplitter);
	DeleteObject (RightPen1);
	DeleteObject (RightPen2);
	DeleteObject (Splitter);
}

static int DesiredSubsector;

static void DrawLevelSubsectors (HDC dc)
{
	HPEN oldPen, pen, mini, bad;
	int i;

	pen = CreatePen (PS_SOLID, 1, RGB(200,200,200));
	mini = CreatePen (PS_SOLID, 1, RGB(100,200,255));
	bad = CreatePen (PS_SOLID, 1, RGB(255,128,128));

	oldPen = (HPEN)SelectObject (dc, pen);

	if (Viewing == ViewGLSubsectors)
	{
		for (i = 0; i < Level->NumGLSubsectors; ++i)
		{
			if (i != DesiredSubsector)
			{
				DrawSubsectorGL (dc, i, mini, bad);
			}
		}
	}
	else
	{
		for (i = 0; i < Level->NumSubsectors; ++i)
		{
			if (i != DesiredSubsector)
			{
				DrawSubsector (dc, i);
			}
		}
	}
	SelectObject (dc, oldPen);
	DeleteObject (pen);
	DeleteObject (mini);
	DeleteObject (bad);

	pen = CreatePen (PS_SOLID, 1, RGB(0,0,0));
	mini = CreatePen (PS_SOLID, 1, RGB(0,0,255));
	bad = CreatePen (PS_SOLID, 1, RGB(255,0,0));
	SelectObject (dc, pen);
	if (Viewing == ViewGLSubsectors)
	{
		DrawSubsectorGL (dc, DesiredSubsector, mini, bad);
	}
	else
	{
		DrawSubsector (dc, DesiredSubsector);
	}
	SelectObject (dc, oldPen);
	DeleteObject (pen);
	DeleteObject (mini);
	DeleteObject (bad);
}

static inline int PointOnSide (int x, int y, const MapNodeEx &nd)
{
	int foo = DMulScale32 ((y<<16)-nd.y, nd.dx, nd.x-(x<<16), nd.dy);
	return foo >= 0;
}

static void SetDesiredSubsector (int x, int y)
{
	if (Viewing == ViewSubsectors)
	{
		int node = Level->NumNodes - 1;

		while (!(node & NFX_SUBSECTOR))
		{
			node = Level->Nodes[node].children[PointOnSide (x, y, Level->Nodes[node])];
		}
		DesiredSubsector = node & ~NFX_SUBSECTOR;
	}
	else
	{
		int node = Level->NumGLNodes - 1;

		while (!(node & NFX_SUBSECTOR))
		{
			node = Level->GLNodes[node].children[PointOnSide (x, y, Level->GLNodes[node])];
		}
		DesiredSubsector = node & ~NFX_SUBSECTOR;
	}
}

static void SetDesiredNode (int x, int y)
{
	int node, parent;
	unsigned int depth = 0;

	// Traverse the tree until we find a node that is not on the
	// path to the previous desired node.
	if (Viewing == ViewNodes)
	{
		node = Level->NumNodes - 1, parent = node;
		while (depth < DesiredHistory.Size() && node == DesiredHistory[depth])
		{
			parent = node;
			node = Level->Nodes[node].children[PointOnSide (x, y, Level->Nodes[node])];
			depth++;
		}
	}
	else
	{
		node = Level->NumGLNodes - 1, parent = node;
		while (depth < DesiredHistory.Size() && node == DesiredHistory[depth])
		{
			parent = node;
			node = Level->GLNodes[node].children[PointOnSide (x, y, Level->GLNodes[node])];
			depth++;
		}
	}

	// If we traversed all the way through the history, the new desired
	// node is a child of the old one.
	if (depth == DesiredHistory.Size())
	{
		if (!(node & NFX_SUBSECTOR))
		{
			DesiredNode = node;
			DesiredHistory.Push (DesiredNode);
		}
	}
	// If we didn't make it all the way through the history, set the
	// desired node to this node's parent, so that the new desired node
	// will include both the old node and the one just clicked.
	else
	{
		DesiredHistory.Resize (depth);
		DesiredNode = DesiredHistory[depth-1];;
	}
}

static int DesiredSector;

static void DrawLevelReject (HDC dc)
{
	int seeFromRow = DesiredSector * Level->NumSectors();
	HPEN oldPen;
	HPEN cantSee;
	HPEN canSee;
	HPEN seeFrom;

	cantSee = CreatePen (PS_SOLID, 1, RGB(200,200,200));
	canSee = CreatePen (PS_SOLID, 1, RGB(200,0,200));
	seeFrom = CreatePen (PS_SOLID, 1, RGB(255,128,0));

	oldPen = (HPEN)SelectObject (dc, canSee);
	enum { UNDECIDED, CANT_SEE, CAN_SEE, SEE_FROM } choice, prevchoice = CAN_SEE;

	for (int i = 0; i < Level->NumLines(); ++i)
	{
		choice = UNDECIDED;

		if (Level->Lines[i].sidenum[0] != NO_INDEX)
		{
			if (Level->Sides[Level->Lines[i].sidenum[0]].sector == DesiredSector)
			{
				choice = SEE_FROM;
			}
			else if (Level->Reject != NULL)
			{
				int pnum = seeFromRow + Level->Sides[Level->Lines[i].sidenum[0]].sector;
				if (Level->Reject[pnum>>3] & (1<<(pnum&7)))
				{
					choice = CANT_SEE;
				}
				else
				{
					choice = CAN_SEE;
				}
			}
			else
			{
				choice = CAN_SEE;
			}
		}
		if (Level->Lines[i].sidenum[1] != NO_INDEX && choice < SEE_FROM)
		{
			if (Level->Sides[Level->Lines[i].sidenum[1]].sector == DesiredSector)
			{
				choice = SEE_FROM;
			}
			else if (Level->Reject != NULL && choice < CAN_SEE)
			{
				int pnum = seeFromRow + Level->Sides[Level->Lines[i].sidenum[1]].sector;
				if (Level->Reject[pnum>>3] & (1<<(pnum&7)))
				{
					choice = CANT_SEE;
				}
				else
				{
					choice = CAN_SEE;
				}
			}
			else
			{
				choice = CAN_SEE;
			}
		}
		if (choice != UNDECIDED)
		{
			if (choice != prevchoice)
			{
				prevchoice = choice;
				switch (choice)
				{
				case CANT_SEE:	SelectObject (dc, cantSee);	break;
				case CAN_SEE:	SelectObject (dc, canSee); break;
				case SEE_FROM:	SelectObject (dc, seeFrom); break;
				default: break;
				}
			}
			MoveToEx (dc, VERTX(Level->Lines[i].v1), VERTY(Level->Lines[i].v1), NULL);
			LineTo (dc, VERTX(Level->Lines[i].v2), VERTY(Level->Lines[i].v2));
		}
	}
	SelectObject (dc, oldPen);
	DeleteObject (cantSee);
	DeleteObject (canSee);
	DeleteObject (seeFrom);
}

static void DrawLevelPVS (HDC dc)
{
	HPEN oldPen;
	HPEN pen;
	HPEN mini;

	pen = CreatePen (PS_SOLID, 1, RGB(200,200,200));
	mini = CreatePen (PS_SOLID, 1, RGB(100,200,255));

	oldPen = (HPEN)SelectObject (dc, pen);

	int i, row = DesiredSubsector * Level->NumGLSubsectors;

	for (i = 0; i < Level->NumGLSubsectors; ++i)
	{
		int l = (row + i) >> 3;
		int r = (row + i) & 7;

		if (!(Level->GLPVS[l] & (1 << r)))
		{
			DrawSubsectorGL (dc, i, mini, mini);
		}
	}

	SelectObject (dc, oldPen);
	DeleteObject (pen);
	DeleteObject (mini);

	pen = CreatePen (PS_SOLID, 1, RGB(200,0,200));
	mini = CreatePen (PS_SOLID, 1, RGB(200,100,200));
	SelectObject (dc, pen);

	for (i = 0; i < Level->NumGLSubsectors; ++i)
	{
		int l = (row + i) >> 3;
		int r = (row + i) & 7;

		if (Level->GLPVS[l] & (1 << r))
		{
			DrawSubsectorGL (dc, i, mini, mini);
		}
	}

	SelectObject (dc, oldPen);
	DeleteObject (pen);
	DeleteObject (mini);

	pen = CreatePen (PS_SOLID, 1, RGB(255,128,0));
	mini = CreatePen (PS_SOLID, 1, RGB(255,150,100));
	SelectObject (dc, pen);

	DrawSubsectorGL (dc, DesiredSubsector, mini, mini);

	SelectObject (dc, oldPen);
	DeleteObject (pen);
	DeleteObject (mini);
}

static void SetDesiredSector (int x, int y)
{
	int node = Level->NumNodes - 1;
	const MapSegEx *seg;

	while (!(node & NFX_SUBSECTOR))
	{
		node = Level->Nodes[node].children[PointOnSide (x, y, Level->Nodes[node])];
	}
	node &= ~NFX_SUBSECTOR;
	seg = &Level->Segs[Level->Subsectors[node].firstline];
	DesiredSector = Level->Sides[Level->Lines[seg->linedef].sidenum[seg->side]].sector;
}

static void DrawLevel (HDC dc)
{
	switch (Viewing)
	{
	case ViewNodes:
	case ViewGLNodes:
		DrawLevelNodes (dc);
		break;
	case ViewSubsectors:
	case ViewGLSubsectors:
		DrawLevelSubsectors (dc);
		break;
	case ViewReject:
		DrawLevelReject (dc);
		break;
	case ViewPVS:
		DrawLevelPVS (dc);
		break;
	}
	/*
	int i;

	for (i = 0; i < Level->NumSegs; ++i)
	{
		MoveToEx (dc, VERTX(Level->Segs[i].v1), VERTY(Level->Segs[i].v1), NULL);
		LineTo (dc, VERTX(Level->Segs[i].v2), VERTY(Level->Segs[i].v2));
	}
	*/
}

void SizeView (HWND wnd, bool firstTime)
{
	RECT rect;
	SCROLLINFO sinfo = { sizeof(SCROLLINFO) };

	GetClientRect (wnd, &rect);
	sinfo.fMask = SIF_PAGE|SIF_RANGE|SIF_POS;

	GetScrollInfo (wnd, SB_VERT, &sinfo);
	sinfo.nMin = 0;
	sinfo.nMax = MapBounds.bottom - MapBounds.top;
	sinfo.nPage = rect.bottom;
	if (firstTime)
	{
		sinfo.nPos = 0;
	}
	else if (sinfo.nPos > sinfo.nMax - (int)sinfo.nPage)
	{
		int delta = sinfo.nPos - sinfo.nMax + (int)sinfo.nPage;
		ScrollWindowEx (wnd, 0, (delta + Divisor-1) / Divisor, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
		sinfo.nPos = sinfo.nMax - sinfo.nPage;
	}
	SetScrollInfo (wnd, SB_VERT, &sinfo, TRUE);

	GetScrollInfo (wnd, SB_HORZ, &sinfo);
	sinfo.nMin = 0;
	sinfo.nMax = MapBounds.right - MapBounds.left;
	sinfo.nPage = rect.right;
	if (firstTime)
	{
		sinfo.nPos = 0;
	}
	else if (sinfo.nPos > sinfo.nMax - (int)sinfo.nPage)
	{
		int delta = sinfo.nPos - sinfo.nMax + (int)sinfo.nPage;
		ScrollWindowEx (wnd, (delta + Divisor-1) / Divisor, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
		sinfo.nPos = sinfo.nMax - sinfo.nPage;
	}
	SetScrollInfo (wnd, SB_HORZ, &sinfo, TRUE);
}

LRESULT CALLBACK MapViewFunc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static bool dragging = false;
	static union { POINTS dragpos; LPARAM dragpos_lp; };
	static int hitx, hity;
	SCROLLINFO sinfo = { sizeof(SCROLLINFO), };
	int pos;

	switch (uMsg)
	{
	case WM_CREATE:
		SizeView (hWnd, true);
		break;

	case WM_SIZE:
		SizeView (hWnd, false);
		//InvalidateRect (hWnd, NULL, TRUE);
		break;

	case WM_PAINT:
		if (GetUpdateRect (hWnd, NULL, FALSE))
		{
			PAINTSTRUCT paint;
			HDC dc = BeginPaint (hWnd, &paint);

			if (dc != NULL)
			{
				sinfo.fMask = SIF_POS;
				GetScrollInfo (hWnd, SB_HORZ, &sinfo);
				pos = sinfo.nPos;
				GetScrollInfo (hWnd, SB_VERT, &sinfo);
				SetMapMode (dc, MM_ANISOTROPIC);
				ScaleWindowExtEx (dc, Divisor, 1, Divisor, -1, NULL);
				SetWindowOrgEx (dc, pos + MapBounds.left, MapBounds.bottom - sinfo.nPos, NULL);
				DrawLevel (dc);
				EndPaint (hWnd, &paint);
			}
		}
		return 0;

	case WM_LBUTTONDOWN:
		sinfo.fMask = SIF_POS;
		GetScrollInfo (hWnd, SB_HORZ, &sinfo);
		pos = sinfo.nPos;
		GetScrollInfo (hWnd, SB_VERT, &sinfo);
		hitx = LOWORD(lParam)*Divisor + pos + MapBounds.left;
		hity = MapBounds.bottom - sinfo.nPos - HIWORD(lParam)*Divisor;
		switch (Viewing)
		{
		case ViewNodes:
		case ViewGLNodes:
			SetDesiredNode (hitx, hity);
			PostMessage (GetParent (hWnd), WM_APP, DesiredNode, 0);
			break;
		case ViewSubsectors:
		case ViewGLSubsectors:
		case ViewPVS:
			SetDesiredSubsector (hitx, hity);
			PostMessage (GetParent (hWnd), WM_APP, DesiredSubsector, 0);
			break;
		case ViewReject:
			SetDesiredSector (hitx, hity);
			PostMessage (GetParent (hWnd), WM_APP, DesiredSector, 0);
			break;
		}
		InvalidateRect (hWnd, NULL, TRUE);
		return 0;

	case WM_HSCROLL:
		sinfo.fMask = SIF_POS|SIF_TRACKPOS|SIF_RANGE|SIF_PAGE;
		GetScrollInfo (hWnd, SB_HORZ, &sinfo);
		switch (LOWORD(wParam))
		{
		case SB_PAGEUP:			pos = sinfo.nPos - sinfo.nPage;		break;
		case SB_PAGEDOWN:		pos = sinfo.nPos + sinfo.nPage;		break;
		case SB_LINEUP:			pos = sinfo.nPos - 10;				break;
		case SB_LINEDOWN:		pos = sinfo.nPos + 10;				break;
		case SB_THUMBTRACK:		pos = sinfo.nTrackPos;				break;
		default:				pos = sinfo.nPos;
		};

		pos = clamp<int> (pos, 0, sinfo.nMax - sinfo.nPage);
		if (pos != sinfo.nPos)
		{
			SetScrollPos (hWnd, SB_HORZ, pos, TRUE);
			int oldx = sinfo.nPos / Divisor;
			int newx = pos / Divisor;
			ScrollWindowEx (hWnd, oldx - newx, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
			UpdateWindow (hWnd);
		}
		return 0;

	case WM_VSCROLL:
		sinfo.fMask = SIF_POS|SIF_TRACKPOS|SIF_RANGE|SIF_PAGE;
		GetScrollInfo (hWnd, SB_VERT, &sinfo);
		switch (LOWORD(wParam))
		{
		case SB_PAGEUP:			pos = sinfo.nPos - sinfo.nPage;		break;
		case SB_PAGEDOWN:		pos = sinfo.nPos + sinfo.nPage;		break;
		case SB_LINEUP:			pos = sinfo.nPos - 10;				break;
		case SB_LINEDOWN:		pos = sinfo.nPos + 10;				break;
		case SB_THUMBTRACK:		pos = sinfo.nTrackPos;				break;
		default:				pos = sinfo.nPos;
		};

		pos = clamp<int> (pos, 0, sinfo.nMax - sinfo.nPage);
		if (pos != sinfo.nPos)
		{
			SetScrollPos (hWnd, SB_VERT, pos, TRUE);
			ScrollWindowEx (hWnd, 0, (sinfo.nPos - pos) / Divisor, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
			UpdateWindow (hWnd);
		}
		return 0;

	case WM_MOUSEWHEEL:
		sinfo.fMask = SIF_POS|SIF_RANGE|SIF_PAGE;
		GetScrollInfo (hWnd, SB_VERT, &sinfo);
		pos = sinfo.nPos - short(HIWORD(wParam))*64/WHEEL_DELTA;

		pos = clamp<int> (pos, 0, sinfo.nMax - sinfo.nPage);
		if (pos != sinfo.nPos)
		{
			SetScrollPos (hWnd, SB_VERT, pos, TRUE);
			ScrollWindowEx (hWnd, 0, (sinfo.nPos - pos) / Divisor, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
			UpdateWindow (hWnd);
		}
		return 0;

	case WM_RBUTTONDOWN:
		dragging = true;
		dragpos_lp = lParam;	// also sets dragpos; avoids type-punned dereference warning from GCC
		//dragpos = MAKEPOINTS(lParam);
		return 0;

	case WM_RBUTTONUP:
		dragging = false;
		return 0;

	case WM_MOUSEMOVE:
		if (!(wParam & MK_RBUTTON))
		{
			dragging = false;
		}
		else if (dragging)
		{
			union { POINTS newpos; LPARAM newpos_lp; };
			int delta;

			newpos_lp = lParam;
			delta = (newpos.x - dragpos.x) * 8;
			if (delta)
			{
				sinfo.fMask = SIF_POS|SIF_RANGE|SIF_PAGE;
				GetScrollInfo (hWnd, SB_HORZ, &sinfo);
				pos = sinfo.nPos - delta;

				pos = clamp<int> (pos, 0, sinfo.nMax - sinfo.nPage);
				if (pos != sinfo.nPos)
				{
					SetScrollPos (hWnd, SB_HORZ, pos, TRUE);
					ScrollWindowEx (hWnd, (sinfo.nPos - pos) / Divisor, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
					UpdateWindow (hWnd);
				}
			}
			delta = (newpos.y - dragpos.y) * 8;
			if (delta)
			{
				sinfo.fMask = SIF_POS|SIF_RANGE|SIF_PAGE;
				GetScrollInfo (hWnd, SB_VERT, &sinfo);
				pos = sinfo.nPos - delta;

				pos = clamp<int> (pos, 0, sinfo.nMax - sinfo.nPage);
				if (pos != sinfo.nPos)
				{
					SetScrollPos (hWnd, SB_VERT, pos, TRUE);
					ScrollWindowEx (hWnd, 0, (sinfo.nPos - pos) / Divisor, NULL, NULL, NULL, NULL, SW_INVALIDATE|SW_ERASE);
					UpdateWindow (hWnd);
				}
			}
			dragpos = newpos;
			return 0;
		}
	}
	return DefWindowProc (hWnd, uMsg, wParam, lParam);
}

void ShowView (FLevel *level)
{
	LOGBRUSH WhiteBrush = { BS_SOLID, RGB(255,255,255), 0 };
	WNDCLASS MapViewerClass =
	{
		0,
		MapViewFunc,
		0,
		0,
		GetModuleHandle(0),
		0,
		LoadCursor (0, IDC_ARROW),
		CreateBrushIndirect (&WhiteBrush),
		0,
		"MapViewer"
	};

	if (RegisterClass (&MapViewerClass))
	{
		Level = level;

		if (level->Blockmap != NULL)
		{
			MapBounds.left = short(level->Blockmap[0]) - 8;
			MapBounds.right = short(level->Blockmap[0]) + (level->Blockmap[2] << BLOCKBITS) + 8;
			MapBounds.top = short(level->Blockmap[1]) - 8;
			MapBounds.bottom = short(level->Blockmap[1]) + (level->Blockmap[3] << BLOCKBITS) + 8;
		}
		else
		{
			MapBounds.left = level->MinX >> FRACBITS;
			MapBounds.right = level->MaxX >> FRACBITS;
			MapBounds.top = level->MinY >> FRACBITS;
			MapBounds.bottom = level->MaxY >> FRACBITS;
		}
		MapSize.x = MapBounds.right - MapBounds.left;
		MapSize.y = MapBounds.bottom - MapBounds.top;
		Divisor = 1;

		if (Level->Subsectors == NULL)
		{
			Viewing = ViewGLSubsectors;
		}
		else
		{
			Viewing = ViewSubsectors;
		}

		ResetViews ();
		DialogBox (GetModuleHandle(0), MAKEINTRESOURCE(IDD_MAPVIEW), NULL, ViewDialogFunc);
		UnregisterClass ("MapViewer", GetModuleHandle(0));
	}
}

void ResetViews ()
{
	DesiredHistory.Clear ();
	if (Viewing == ViewNodes)
	{
		DesiredNode = Level->NumNodes - 1;
	}
	else
	{
		DesiredNode = Level->NumGLNodes - 1;
	}
	DesiredHistory.Push (DesiredNode);
	DesiredSubsector = 0;
	DesiredSector = 0;
}

#endif /* !NO_MAP_VIEWER */
