#include <windows.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <SWI-Prolog.h>

#include "resource.h"
#include "defs.h"

char SzLimitScreenwriter[64] = {0};
HFONT HfNormal;
HFONT HfBold;
HMENU HPopupMenu;
HWND HFocus;
HWND HWnd;
HWND HWndStatus;
int BViewTVOriginal = 1;
int BViewWatched = 1;
int IDPI = -1;
static int BThemes;
static int CxVScroll;
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
static HWND CreateStatusBar(HWND, HINSTANCE);
static int Attach(void);
static void SetupFonts(void);
static void UpdateTheme(void);

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, INT nCmdShow)
{
	char *argv[2];
	HMODULE hModule;
	HWND hWnd;
	LPTSTR tszErr;
	MSG msg;
	INITCOMMONCONTROLSEX icc;
	term_t t;
	WNDCLASSEX wc;

	/* Set constant values. */
	hModule = LoadLibrary(TEXT("uxtheme.dll"));
	BThemes = hModule && GetProcAddress(hModule,"SetWindowTheme");
	if (hModule) FreeLibrary(hModule);
	CxVScroll = GetSystemMetrics(SM_CXVSCROLL);

	/* Initialize Prolog. */
	argv[0] = "EpisodeBrowser";
	argv[1] = NULL;
	tszErr = TEXT("Could not initialize Prolog.");
	if (!PL_initialise(1, argv)) goto f;
	tszErr = TEXT("Could not attach databases.");
	if (!Attach()) goto f;

	/* Initialize common controls, load menu and register window class. */
	icc.dwSize = sizeof(icc);
	icc.dwICC = ICC_WIN95_CLASSES;
	tszErr = TEXT("Could not initialize common controls.");
	if (!InitCommonControlsEx(&icc)) goto f;

	HPopupMenu = LoadMenu(NULL, MAKEINTRESOURCE(IDR_POPUPMENU));
	HPopupMenu = GetSubMenu(HPopupMenu, 0);

	memset(&wc, 0, sizeof(WNDCLASSEX));
	wc.cbSize	 = sizeof(WNDCLASSEX);
	wc.lpfnWndProc	 = WndProc;
	wc.hInstance	 = hInstance;
	wc.hIcon	 = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor	 = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.lpszMenuName	 = MAKEINTRESOURCE(IDR_MENU);
	wc.lpszClassName = TEXT("Episode Browser");
	wc.hIconSm	 = LoadIcon(NULL, IDI_APPLICATION);
	tszErr = TEXT("Could not register window class.");
	if (!RegisterClassEx(&wc)) goto f;

	/* Create window. */
	hWnd = CreateWindowEx(
	    0,
	    TEXT("Episode Browser"),
	    TEXT("Episode Browser"),
	    WS_OVERLAPPEDWINDOW,
	    CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
	    NULL, NULL, hInstance, NULL
	);
	tszErr = TEXT("Could not create main window.");
	if (!hWnd) goto f;
	HWndStatus = CreateStatusBar(hWnd, hInstance);
	ShowWindow(hWnd, nCmdShow);

	/* Populate episode list view. */
	t = T(0);
	P("track_episodes","update_tracked_episodes",0,t);
	ElvUpdate();
	ElvSelectRecent();

	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		if (IsDialogMessage(hWnd, &msg)) continue;
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	PL_halt(0);
	return 0;
f:	MessageBox(NULL, tszErr, TEXT("Error"), MB_ICONERROR);
	PL_halt(1);
	return 1;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_CREATE:
	    {
		HMODULE hModule;
		FARPROC GetDpiForWindow;
		extern HWND HWnd, HElv;
		HWnd = hWnd;
		IDPI = 96;
		hModule = LoadLibrary(TEXT("User32.dll"));
		if (hModule && (GetDpiForWindow = GetProcAddress(hModule, "GetDpiForWindow"))) {
			IDPI = GetDpiForWindow(HWnd);
			FreeLibrary(hModule);
		}
		SetWindowPos(hWnd, NULL, -1, -1, Dpi(510), Dpi(400), SWP_NOMOVE);
		{
			term_t t;
			t = T(1);
			P("cfg","get_view_watched",1,t) goto s;
			GI(t,&BViewWatched) goto s;
			CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED,
			    BViewWatched? MF_CHECKED: MF_UNCHECKED);
		}
	    s:	{
			term_t t;
			t = T(1);
			P("cfg","get_view_tv_original",1,t) goto t;
			GI(t,&BViewTVOriginal) goto t;
			CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL,
			    BViewTVOriginal? MF_CHECKED: MF_UNCHECKED);
		}
	    t:	{
			char *sz;
			term_t t;
			t = T(1);
			P("cfg","get_limit_screenwriter",1,t) goto u;
			GAC(t,&sz) goto u;
			strcpy_s(SzLimitScreenwriter,
			    sizeof(SzLimitScreenwriter), sz);
			CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
			    SzLimitScreenwriter[0]? MF_UNCHECKED: MF_CHECKED);
		}
	    u:	SetupFonts();
		DlvCreate();
		ElvCreate();
		UpdateTheme();
		SetFocus(HElv);
		break;
	    }
	case WM_CLOSE:
		DestroyWindow(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_SIZE:
		SendMessage(HWndStatus, WM_SIZE, wParam, lParam);
		UpdateLayout();
		break;
	case WM_GETMINMAXINFO:
	    {
		LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
		extern HWND HDlv;
		lpMMI->ptMinTrackSize.y = LvHeight(HDlv, 0)+80;
		break;
	    }
	case WM_THEMECHANGED:
		UpdateTheme();
		break;
	case 0x02E0: /* WM_DPICHANGED */
	    {
		LPRECT lpr;
		lpr = (LPRECT)lParam;
		IDPI = HIWORD(wParam);
		SetWindowPos(hWnd, NULL,
		    lpr->left, lpr->top,
		    lpr->right-lpr->left,
		    lpr->bottom-lpr->top,
		    SWP_NOZORDER | SWP_NOACTIVATE);
		UpdateLayout();
		break;
	    }
	case WM_ACTIVATE:
		switch (wParam) {
		case WA_INACTIVE:
			HFocus = GetFocus();
			break;
		case WA_ACTIVE:
		case WA_CLICKACTIVE:
		    {
			term_t t;
			SetFocus(HFocus);
			t = T(0);
			P("track_episodes","update_tracked_episodes",0,t);
			ElvRedraw();
		    }
		}
		break;
	case WM_NOTIFY:
		switch (((LPNMHDR)lParam)->idFrom) {
		case IDC_EPISODELISTVIEW:
			return ElvHandleNotify(lParam);
		}
		break;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDM_FILE_EXIT:
			PostMessage(hWnd, WM_CLOSE, 0, 0);
			break;
		case IDM_FILE_REFRESH:
			ElvUpdate();
			break;
		case IDM_FILE_RESET:
			ElvSelectRecent();
			break;
		case IDM_FILE_FETCH_DATA:
		    {
			term_t t;
			t = T(0);
			P("episode_data","update_episode_data",0,t);
			ElvUpdate();
			break;
		    }
		case IDM_FILE_FETCH_SCREENWRITERS:
		    {
			term_t t;
			t = T(0);
			P("episode_data","update_screenwriters",0,t);
			ElvUpdate();
			break;
		    }
		case IDM_FILE_ABOUT:
			DialogBox(
			    GetModuleHandle(NULL),
			    MAKEINTRESOURCE(IDD_ABOUT),
			    hWnd,
			    AboutDlgProc
			);
			break;
		case IDM_VIEW_WATCHED:
		    {
			int iEpFocus;
			term_t t;
			extern HWND HElv;
			CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED,
			    BViewWatched? MF_UNCHECKED: MF_CHECKED);
			BViewWatched = !BViewWatched;
			ElvUpdate();
			t = T(1);
			PI(t,BViewWatched) break;
			P("cfg","set_view_watched",1,t);
			iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED);
			if (iEpFocus == -1) break;
			ListView_EnsureVisible(HElv, iEpFocus, TRUE);
			break;
		    }
		case IDM_VIEW_TV_ORIGINAL:
		    {
			int iEpFocus;
			term_t t;
			extern HWND HElv;
			CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL,
			    BViewTVOriginal? MF_UNCHECKED: MF_CHECKED);
			BViewTVOriginal = !BViewTVOriginal;
			ElvUpdate();
			t = T(1);
			PI(t,BViewTVOriginal) break;
			P("cfg","set_view_tv_original",1,t);
			iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED);
			if (iEpFocus == -1) break;
			ListView_EnsureVisible(HElv, iEpFocus, TRUE);
			break;
		    }
		case IDM_VIEW_OTHERS:	/* Show/hide other screenwriters. */
		    {
			atom_t a;
			int iEpFocus;
			term_t t;
			extern HWND HElv;
			if (SzLimitScreenwriter[0]) {
				CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
				    MF_CHECKED);
				SzLimitScreenwriter[0] = 0;
			} else {
				atom_t a;
				char *sz;
				LVITEM lvi;
				term_t t;
				iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED);
				if (iEpFocus == -1) break;
				lvi.iItem = iEpFocus;
				lvi.mask = LVIF_PARAM;
				if (!ListView_GetItem(HElv, &lvi)) break;
				t = T(3);
				a = A("Screenwriter");
				PI(t,lvi.lParam) break;
				PA(t+1,a) break;
				P("episode_data","episode_datum",3,t) break;
				GAC(t+2,&sz) break;
				strcpy_s(SzLimitScreenwriter,
				    sizeof(SzLimitScreenwriter), sz);
				CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
				    MF_UNCHECKED);
			}
			ElvUpdate();
			t = T(1);
			a = A(SzLimitScreenwriter);
			PA(t,a) break;
			P("cfg","set_limit_screenwriter",1,t);
			iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED);
			if (iEpFocus == -1) break;
			ListView_EnsureVisible(HElv, iEpFocus, TRUE);
			break;
		    }
		case IDM_WATCH_LOCALLY:
		case IDM_WATCH_ONLINE:
		case IDM_TOGGLE:
		case IDM_FORGET:
		case IDM_LOOKUP:
		case IDM_WIKI:
		case IDM_RATE10:
		case IDM_RATE9:
		case IDM_RATE8:
		case IDM_RATE7:
		case IDM_RATE6:
		case IDM_RATE5:
		case IDM_RATE4:
		case IDM_RATE3:
		case IDM_RATE2:
		case IDM_RATE1:
		case IDM_RATE0:
		    {
			LVITEM lvi;
			term_t t;
			extern HWND HElv;

			/* Look through selected items, applying the
			 * selected command to each one. */

			lvi.mask = LVIF_PARAM;
			lvi.iItem = -1;
			t = T(2);
			while ((lvi.iItem = ListView_GetNextItem(
				    HElv, lvi.iItem, LVNI_SELECTED)) != -1) {
				if (!ListView_GetItem(HElv, &lvi)) goto b;
				PI(t,lvi.lParam) goto b;

				switch (LOWORD(wParam)) {
				case IDM_WATCH_LOCALLY:
					P("local_episode","open_episode_locally",1,t);
					break;
				case IDM_WATCH_ONLINE:
					P("local_episode","open_episode_online",1,t);
					break;
				case IDM_TOGGLE:
					P("track_episodes","toggle_episode",1,t);
					ElvRedraw();
					break;
				case IDM_FORGET:
					P("track_episodes","forget_episode",1,t);
					P("track_episodes","update_tracked_episodes",0,t);
					ElvRedraw();
					break;
				case IDM_LOOKUP:
					P("episode_data","retract_episode",1,t);
					ElvUpdateItem(&lvi);
					ElvRedraw();
					DlvShowEpisode(lvi.lParam);
					break;
				case IDM_WIKI:
					P("episode_data","open_episode_wiki",1,t);
					break;
				case IDM_RATE10:
					PI(t+1,10) break;
					goto r;
				case IDM_RATE9:
					PI(t+1,9) break;
					goto r;
				case IDM_RATE8:
					PI(t+1,8) break;
					goto r;
				case IDM_RATE7:
					PI(t+1,7) break;
					goto r;
				case IDM_RATE6:
					PI(t+1,6) break;
					goto r;
				case IDM_RATE5:
					PI(t+1,5) break;
					goto r;
				case IDM_RATE4:
					PI(t+1,4) break;
					goto r;
				case IDM_RATE3:
					PI(t+1,3) break;
					goto r;
				case IDM_RATE2:
					PI(t+1,2) break;
					goto r;
				case IDM_RATE1:
					PI(t+1,1) break;
					goto r;
				case IDM_RATE0:
					PI(t+1,0) break;
				r:	P("episode_data","rate_episode",2,t);
					ElvUpdateItem(&lvi);
					break;
				}
			}
			switch (LOWORD(wParam)) {
			case IDM_RATE10:
			case IDM_RATE9:
			case IDM_RATE8:
			case IDM_RATE7:
			case IDM_RATE6:
			case IDM_RATE5:
			case IDM_RATE4:
			case IDM_RATE3:
			case IDM_RATE2:
			case IDM_RATE1:
			case IDM_RATE0:
				ElvRedraw();
				ElvDoSort();
				ElvShowFocus();
			}
		    b:	break;
		    }
		}
		break;
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	return 0;
}

INT_PTR CALLBACK
AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_CLOSE:
		EndDialog(hWnd, IDOK);
		break;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			EndDialog(hWnd, IDOK);
			break;
		}
		break;
	default:
		return FALSE;
	}

	return TRUE;
}

HWND
CreateStatusBar(HWND hWndParent, HINSTANCE hInstance)
{
	HWND hWnd;
	hWnd = CreateWindowEx(
	    0,
	    STATUSCLASSNAME,
	    (LPCTSTR) NULL,
	    WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,
	    0, 0, 0, 0,
	    hWndParent,
	    (HMENU)ID_STATUS,
	    hInstance,
	    NULL
	);
	return hWnd;
}

/***/

/* Attach persistent databases. */
int
Attach()
{
	term_t t;
	t = T(0);
	P("track_episodes","attach",0,t) return 0;
	P("episode_data","attach",0,t) return 0;
	return 1;
}

void
SetupFonts()
{
	HMODULE hModule;
	LOGFONT lf;

	hModule = LoadLibrary(TEXT("User32.dll"));
	if (hModule && GetProcAddress(hModule, "SystemParametersInfoW")) {
		NONCLIENTMETRICS m;
		m.cbSize = sizeof(NONCLIENTMETRICS);
		SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
		    sizeof(NONCLIENTMETRICS), &m, 0);
		HfNormal = CreateFontIndirect(&m.lfMessageFont);
		FreeLibrary(hModule);
	} else
		HfNormal = GetStockObject(DEFAULT_GUI_FONT);

	GetObject(HfNormal, sizeof(LOGFONT), &lf);
	lf.lfWeight = FW_BOLD;
	HfBold = CreateFontIndirect(&lf);
}

/***/

void
UpdateLayout()
{
	int cxColumn, cyDlv, yStatus;
	RECT rc, rcStatus;
	extern HWND HDlv, HElv;

	GetClientRect(HWnd, &rc);
	GetClientRect(HWndStatus, &rcStatus);
	yStatus = rcStatus.bottom-rcStatus.top;

	/* Resize data list view. */
	SendMessage(HDlv, WM_SETREDRAW, FALSE, 0);
	SendMessage(HElv, WM_SETREDRAW, FALSE, 0);
	cyDlv = rc.bottom-yStatus-LvHeight(HDlv, DLVSIKEY);
	MoveWindow(HDlv, 0, cyDlv, rc.right, rc.bottom-yStatus-cyDlv, TRUE);
	ListView_SetColumnWidth(HDlv, DLVSIKEY, LVSCW_AUTOSIZE);
	cxColumn = ListView_GetColumnWidth(HDlv, 0)+4;
	ListView_SetColumnWidth(HDlv, DLVSIKEY, cxColumn);
	ListView_SetColumnWidth(HDlv, DLVSIVALUE, rc.right-cxColumn-CxVScroll-4);

	/* Resize episode list view. */
	MoveWindow(HElv, 0, 0, rc.right, cyDlv+1, TRUE);
	ListView_SetColumnWidth(HElv, ELVSIEPISODE, LVSCW_AUTOSIZE);
	cxColumn = ListView_GetColumnWidth(HElv, ELVSIEPISODE)+4;
	ListView_SetColumnWidth(HElv, ELVSIEPISODE, cxColumn);
	cxColumn += ListView_GetColumnWidth(HElv, ELVSIRATING);
	ListView_SetColumnWidth(HElv, ELVSITITLE, rc.right-cxColumn-CxVScroll-4);
	SendMessage(HElv, WM_SETREDRAW, TRUE, 0);
	SendMessage(HDlv, WM_SETREDRAW, TRUE, 0);

	/* Resize status bar parts. */
	{
		int aParts[] = {rc.right-Dpi(100), rc.right-Dpi(50), rc.right};
		SendMessage(HWndStatus, SB_SETPARTS,
		    (WPARAM)sizeof(aParts), (LPARAM)aParts);
	}
}

/* Try to style application according to current Windows theme. */
void
UpdateTheme()
{
	DWORD dwStyle;
	LPTSTR tszTheme;
	WORD wAction;
	extern HWND HElv, HDlv;

	if (!BThemes) return;
	if (IsThemeActive()) {
		dwStyle = LVS_EX_DOUBLEBUFFER;
		tszTheme = TEXT("Explorer");
		wAction = UIS_SET;
	} else {
		dwStyle = 0;
		tszTheme = NULL;
		wAction = UIS_CLEAR;
	}

	/* Use modern "Explorer" theme. */
	SetWindowTheme(HElv, tszTheme, NULL);
	SetWindowTheme(HDlv, tszTheme, NULL);

	/* The modern theme requires double buffering. */
	ListView_SetExtendedListViewStyleEx(HElv, LVS_EX_DOUBLEBUFFER, dwStyle);
	ListView_SetExtendedListViewStyleEx(HDlv, LVS_EX_DOUBLEBUFFER, dwStyle);

	/* Hide focus rectangles. */
	SendMessage(HElv, WM_UPDATEUISTATE, MAKEWPARAM(wAction, UISF_HIDEFOCUS), 0);
	SendMessage(HDlv, WM_UPDATEUISTATE, MAKEWPARAM(wAction, UISF_HIDEFOCUS), 0);
}