#include <windows.h>
#include <commctrl.h>
#include <TCHAR.H>
#include <SWI-Prolog.h>

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

extern DataListView *g_lpDlv;
static int CALLBACK ElvSort(LPARAM, LPARAM, LPARAM);

EpisodeListView::EpisodeListView() : ListView((HMENU)IDC_EPISODELISTVIEW, 0)
{
	LVCOLUMN lvc;

	lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
	lvc.iSubItem = ELVSIEPISODE;
	lvc.pszText = TEXT("#");
	lvc.cx = Dpi(42);
	ListView_InsertColumn(m_hWnd, ELVSIEPISODE, &lvc);

	lvc.iSubItem = ELVSITITLE;
	lvc.pszText = TEXT("Title");
	lvc.cx = 500;
	ListView_InsertColumn(m_hWnd, ELVSITITLE, &lvc);

	lvc.iSubItem = ELVSIRATING;
	lvc.pszText = TEXT("/");
	lvc.cx = Dpi(30);
	ListView_InsertColumn(m_hWnd, ELVSIRATING, &lvc);

	if (!Pl("cfg","get_sort","i",&m_iSort))
		m_iSort = 1;
}

void
EpisodeListView::DoSort()
{
	ListView_SortItemsEx(m_hWnd, ElvSort, (LPARAM)this);
}

void
EpisodeListView::EnsureFocusVisible()
{
	int iEpFocus = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED);
	if (iEpFocus == -1) return;
	ListView_EnsureVisible(m_hWnd, iEpFocus, TRUE);
}

LRESULT
EpisodeListView::HandleNotify(LPARAM lParam)
{
	LPNMLISTVIEW lpNmLv = (LPNMLISTVIEW)lParam;

	switch (lpNmLv->hdr.code) {
	case LVN_ITEMCHANGED:	/* Select/focus episode. */
		if ((lpNmLv->uChanged & LVIF_STATE) &&
		    (lpNmLv->uNewState & LVIS_FOCUSED)) {
			m_lviFocus.iItem = lpNmLv->iItem;
			m_lviFocus.lParam = lpNmLv->lParam;
			UpdateItem(&m_lviFocus);
			g_lpDlv->ShowEpisode(lpNmLv->lParam);
		}
		break;
	case LVN_COLUMNCLICK:	/* Sort by column. */
	    {
		int iColumn = lpNmLv->iSubItem+1;
		m_iSort = abs(m_iSort) == iColumn? -m_iSort: iColumn;
		Pl("cfg","set_sort","I",m_iSort);
		DoSort();
		ShowFocus();
		break;
	    }
	case LVN_KEYDOWN:	/* Navigate episodes by keyboard. */
	    {
		LPNMLVKEYDOWN lpNmLvKd = (LPNMLVKEYDOWN)lParam;
		switch (lpNmLvKd->wVKey) {
		case VK_LEFT:
			SelectUnwatched(-1);
			break;
		case VK_RIGHT:
			SelectUnwatched(1);
			break;
		}
		break;
	    }
	case NM_CUSTOMDRAW:	/* Make unwatched episodes bold. */
	    {
		LPNMLVCUSTOMDRAW lpLvCd = (LPNMLVCUSTOMDRAW)lParam;
		switch (lpLvCd->nmcd.dwDrawStage) {
		case CDDS_PREPAINT:
			return CDRF_NOTIFYITEMDRAW;
			break;
		case CDDS_ITEMPREPAINT:
		    {
			extern HFONT g_fBold;
			if (!Pl("track_episodes","watched","I",lpLvCd->nmcd.lItemlParam)) {
				SelectObject(lpLvCd->nmcd.hdc, g_fBold);
				return CDRF_NEWFONT;
			}
			break;
		    }
		}
		break;
	    }
	case NM_DBLCLK:		/* Open clicked episode. */
	    {
		if (!Pl("local_episodes","open_episode_locally","I",
			m_lviFocus.lParam))
			Pl("local_episodes","open_episode_online","I",
			    m_lviFocus.lParam);
		break;
	    }
	case NM_RETURN:		/* Open all selected episodes. */
	    {
		LVITEM lvi;
		lvi.mask = LVIF_PARAM;
		lvi.iItem = -1;
		while ((lvi.iItem = ListView_GetNextItem(
			    m_hWnd, lvi.iItem, LVNI_SELECTED)) != -1) {
			if (!ListView_GetItem(m_hWnd, &lvi)) goto b;
			if (!Pl("local_episodes","open_episode_locally","I",
				lvi.lParam))
				Pl("local_episodes","open_episode_online","I",
				    lvi.lParam);
		}
	    b:	break;
	    }
	case NM_RCLICK:
	    {
		extern HWND g_hWnd;
		extern HMENU g_hPopupMenu;
		DWORD dwPos = GetMessagePos();
		TrackPopupMenu(g_hPopupMenu, TPM_RIGHTBUTTON,
		    LOWORD(dwPos), HIWORD(dwPos), 0,
		    g_hWnd, NULL);
		break;
	    }
	}

	return 0;
}

int
EpisodeListView::ISort() const
{
	return m_iSort;
}

void
EpisodeListView::Redraw()
{
	RedrawWindow(m_hWnd, NULL, NULL,
	    RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN);
}

void
EpisodeListView::SaveFocus()
{
	LVITEM lvi;
	lvi.mask = LVIF_PARAM;
	if ((lvi.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1
	    && ListView_GetItem(m_hWnd, &lvi))
		Pl("cfg","set_focus","I",lvi.lParam);
}

void
EpisodeListView::SetTop(int iItem)
{
	int iLast = ListView_GetItemCount(m_hWnd)-1;
	ListView_EnsureVisible(m_hWnd, iLast, TRUE);
	ListView_EnsureVisible(m_hWnd, iItem, TRUE);
}

/* Select previously focused episode. */
void
EpisodeListView::RestoreFocus()
{
	int i, iEpisode, iItem;
	LVFINDINFO lvfi;

	iItem = 0;
	if (!Pl("cfg","get_focus","i",&iEpisode)) return;

	lvfi.flags = LVFI_PARAM;
	lvfi.lParam = iEpisode;
	i = 0;
	while ((iItem = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 && i++ < 100)
		lvfi.lParam = ++iEpisode;
	if (iItem != -1) goto s;

	iEpisode -= 100;
	lvfi.lParam = iEpisode;
	i = 0;
	while ((iItem = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 && i++ < 100)
		lvfi.lParam = --iEpisode;
	if (iItem != -1) goto s;
	return;

s:	ListView_SetItemState(m_hWnd, -1, LVIF_STATE, LVIS_SELECTED);
	SetTop(iItem > 5? iItem-5: 0);
	ListView_SetItemState(m_hWnd, iItem,
	    LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
	m_lviFocus.iItem = iItem;
	m_lviFocus.lParam = iEpisode;
	UpdateItem(&m_lviFocus);
	g_lpDlv->ShowEpisode(iEpisode);
}

/* Select next/previous unwatched episode. */
void
EpisodeListView::SelectUnwatched(int iDir)
{
	/* Get focused episode. */
	LVITEM lviFocus;
	lviFocus.mask = LVIF_PARAM;
	if ((lviFocus.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1
	    && ListView_GetItem(m_hWnd, &lviFocus))
		;
	else
		return;

	LVFINDINFO lvfi;
	int i, iEpNew, iItemNew;
	i = 0;
	lvfi.flags = LVFI_PARAM;
	lvfi.lParam = lviFocus.lParam;

	do {
		if (!Pl("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched",
			"Ii",lvfi.lParam,&iEpNew))
			return;

		lvfi.lParam = iEpNew;
		if ((iItemNew = ListView_FindItem(m_hWnd, -1, &lvfi)) != -1) {
			ListView_SetItemState(m_hWnd,-1,LVIF_STATE,LVIS_SELECTED);
			ListView_SetSelectionMark(m_hWnd, iItemNew);
			ListView_SetItemState(m_hWnd, iItemNew,
			    LVIS_SELECTED|LVIS_FOCUSED,
			    LVIS_SELECTED|LVIS_FOCUSED);
			Redraw();
			ListView_EnsureVisible(m_hWnd, iItemNew, TRUE);
			return;
		}
	} while (i++ < 1000);
}

void
EpisodeListView::ShowFocus()
{
	int iEpFocus = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED);
	if (iEpFocus == -1) return;
	ListView_EnsureVisible(m_hWnd, iEpFocus, TRUE);
}

/* Update episode list. */
void
EpisodeListView::Update()
{
	int cEp, i, iEp, iEpFocus, iItem, iItemMark, iItemTopNew;
	LVITEM lvi, lviEpisode, lviTop;
	LVFINDINFO lvfi;
	extern HWND g_hWndStatus;
	static TCHAR tszDisp[16], tszEpisode[16];
	static int aEpSel[2048];

	lviEpisode.mask = LVIF_TEXT|LVIF_PARAM;

	/* Save scrolling position. */
	lviTop.iItem = ListView_GetTopIndex(m_hWnd);
	lviTop.mask = LVIF_PARAM;
	ListView_GetItem(m_hWnd, &lviTop);

	/* Save selected episodes. */
	i = 0;
	lvi.mask = LVIF_PARAM;
	lvi.iItem = -1;
	while ((lvi.iItem = ListView_GetNextItem(
		    m_hWnd, lvi.iItem, LVNI_SELECTED)) != -1
	    && i < 2048)
		if (ListView_GetItem(m_hWnd, &lvi))
			aEpSel[i++] = lvi.lParam;
	aEpSel[i] = 0;
	iItemMark = ListView_GetSelectionMark(m_hWnd);

	/* Save focus. */
	iEpFocus = 0;
	if ((lvi.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1
	    && ListView_GetItem(m_hWnd, &lvi))
		iEpFocus = lvi.lParam;

	SendMessage(m_hWnd, WM_SETREDRAW, FALSE, 0);
	ListView_DeleteAllItems(m_hWnd);

	if (!Pl("episode_data","ensure_episode_data","")) return;
	if (!Pl("episode_data","episode_count","i",&cEp)) return;

	for (iEp = 1, iItem = 0; iEp <= cEp; iEp++) {
		extern char g_szLimitScreenwriter[];
		extern int g_bViewTVOriginal, g_bViewWatched;

		if (g_szLimitScreenwriter[0])
			if (!Pl("episode_data","episode_datum","ISS",
				iEp,"Screenwriter",g_szLimitScreenwriter))
				continue;
		
		if (!g_bViewWatched)
			if (Pl("track_episodes","watched","I",iEp)) continue;

		if (!g_bViewTVOriginal)
			if (Pl("episode_data","tv_original","I",iEp)) continue;

		/* Format episode number string. */
		_stprintf_s(tszEpisode, sizeof(tszEpisode), TEXT("%d"), iEp);

		/* Insert item. */
		lviEpisode.iItem = iItem++;
		lviEpisode.iSubItem = ELVSIEPISODE;
		lviEpisode.pszText = tszEpisode;
		lviEpisode.lParam = iEp;
		ListView_InsertItem(m_hWnd, &lviEpisode);
		UpdateItem(&lviEpisode);
	}

	DoSort();

	lvfi.flags = LVFI_PARAM;

	/* Reset selection. */
	for (i = 0; aEpSel[i]; i++) {
		int iItemSel;
		lvfi.lParam = aEpSel[i];
		if ((iItemSel = ListView_FindItem(m_hWnd, -1, &lvfi)) != -1)
			ListView_SetItemState(m_hWnd, iItemSel,
			    LVIS_SELECTED, LVIS_SELECTED);
	}
	if (iItemMark != -1)
		ListView_SetSelectionMark(m_hWnd, iItemMark);

	/* Reset focus. */
	if (iEpFocus) {
		int iItemFocus;
		i = 0;
		do
			lvfi.lParam = iEpFocus+i;
		while ((iItemFocus = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1
		    && i++ < 100);
		if (iItemFocus != -1)
			ListView_SetItemState(m_hWnd, iItemFocus,
			    LVIS_FOCUSED, LVIS_FOCUSED);
	}

	/* Try to reset scrolling position. Note that this must be
	 * done last, as focusing an item scrolls it into view. */
	i = 0;
	do
		lvfi.lParam = lviTop.lParam+i;
	while ((iItemTopNew = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1
	    && i++ < 100);
	if (iItemTopNew != -1)
		SetTop(iItemTopNew);

	_stprintf_s(tszDisp, sizeof(tszDisp), TEXT("%d"), iItem);
	SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), (LPARAM)tszDisp);
	SendMessage(m_hWnd, WM_SETREDRAW, TRUE, 0);
}

/* Update episode name and rating. */
void
EpisodeListView::UpdateItem(LPLVITEM lpLvi)
{
	char *szName;
	int iRating;
	TCHAR *tszName;
	static TCHAR tszRating[3];

	tszName = NULL;
	if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) {
		if (!Pl("episode_data","update_episode_data","")) goto r;
		if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName))
			goto r;
	}
	tszName = TszFromSz(szName, CP_UTF8);
	if (tszName)
		ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSITITLE, tszName);

r:	if (!Pl("episode_data","episode_rating","Ii",lpLvi->lParam,&iRating)) {
		ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, TEXT(""));
		goto f;
	}

	_stprintf_s(tszRating, sizeof(tszRating), TEXT("%d"), iRating);
	ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, tszRating);

f:	if (tszName) free(tszName);
}

LRESULT CALLBACK
EpisodeListView::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_GETDLGCODE:
	    {
		LRESULT lResult;

		/* For the episode list view, the Enter key should not
		 * be handled by the dialog manager, but instead be sent
		 * along to the main window procedure, so that it may be
		 * handled by the NM_RETURN case in ElvHandleNotify. */

		lResult = CallWindowProc(m_prevProc, hWnd, uMsg, wParam, lParam);
		if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN
		    && ((MSG *)lParam)->wParam == VK_RETURN)
			return DLGC_WANTMESSAGE;
		return lResult;
	    }
	}

	return ListView::WndProc(hWnd, uMsg, wParam, lParam);
}

/* Sort list view items, iSort being the 1-based index of the column
 * to sort by. If iSort is negative, the order is descending. */
int CALLBACK
ElvSort(LPARAM iItem1, LPARAM iItem2, LPARAM lpLv)
{
	EpisodeListView *lpThis = (EpisodeListView *)lpLv;

	LVITEM lvi1, lvi2;
	lvi1.mask = lvi2.mask = LVIF_PARAM;
	lvi1.iItem = iItem1; lvi2.iItem = iItem2;
	if (!ListView_GetItem(lpThis->HWnd(), &lvi1)) return 0;
	if (!ListView_GetItem(lpThis->HWnd(), &lvi2)) return 0;

	int iOrder = Cmp(lpThis->ISort(), 0);

	switch (abs(lpThis->ISort())-1) {
	case ELVSIEPISODE:
		return iOrder*Cmp(lvi1.lParam, lvi2.lParam);
		break;
	case ELVSIRATING:
	    {
		int iRating1, iRating2;
		iRating1 = lpThis->ISort() > 0? 99: -1;
		iRating2 = lpThis->ISort() > 0? 99: -1;
		Pl("episode_data","episode_rating","Ii",lvi1.lParam,&iRating1);
		Pl("episode_data","episode_rating","Ii",lvi2.lParam,&iRating2);
		if (iRating1 == iRating2)
			return Cmp(lvi1.lParam, lvi2.lParam);
		return iOrder*Cmp(iRating1, iRating2);
		break;
	    }
	case ELVSITITLE:
	    {
		char *sz1, *sz2;
		int cch, cch1, cch2;
		if (!Pl("episode_data","episode_title","Is",lvi1.lParam,&sz1))
			return 0;
		if (!Pl("episode_data","episode_title","Is",lvi2.lParam,&sz2))
			return 0;
		cch1 = strlen(sz1);
		cch2 = strlen(sz2);
		cch = cch1 > cch2? cch2: cch1;
		return iOrder*_strnicmp(sz1, sz2, cch);
		break;
	    }
	default:
		return 0;
	}
}