/*
 * Copyright (c) 2002 Robert Collins.
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     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.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Robert Collins <robertc@hotmail.com>
 * Written by Tom N Harris <telliamed@whoopdedo.org>
 *
 */

#include "PickView.h"
#include <commctrl.h>
#include "PickItemLine.h"
#include "PickCategoryLine.h"
#include "resource.h"
#include <algorithm>

using namespace Setup;
using namespace std;


ATOM PickView::WindowClassAtom = 0;

// DoInsertItem - inserts an item into a header control.
// Returns the index of the new item.
// hwndHeader - handle to the header control.
// iInsertAfter - index of the previous item.
// nWidth - width of the new item.
// lpsz - address of the item string.
static int
SetColumn(HWND hwndHeader, int iInsertAfter, int nWidth, LPCTSTR lpsz, int textlen)
{
	HDITEM hdi;
	int index;

	hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
	hdi.pszText = const_cast<TCHAR*>(lpsz);
	hdi.cxy = nWidth;
	hdi.cchTextMax = textlen;
	hdi.fmt = HDF_LEFT | HDF_STRING;

	index = ::SendMessage(hwndHeader, HDM_INSERTITEM,
			    static_cast<WPARAM>(iInsertAfter),
			    reinterpret_cast<LPARAM>(&hdi));

	return index;
}

static int
SizeColumn(HWND hwndHeader, int iColumnNum, int nWidth)
{
	HDITEM hdi;
	int index;

	hdi.mask = HDI_WIDTH;
	hdi.cxy = nWidth;

	index = ::SendMessage(hwndHeader, HDM_SETITEM,
			    static_cast<WPARAM>(iColumnNum),
			    reinterpret_cast<LPARAM>(&hdi));

	return index;
}


/*
 * LRESULT CALLBACK
 * PickView::listview_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 */
LRESULT
PickView::windowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_HSCROLL:
		scroll(SB_HORZ, &m_scrollX, LOWORD(wParam));
		return 0;
	case WM_VSCROLL:
		scroll(SB_VERT, &m_scrollY, LOWORD(wParam));
		return 0;
	case WM_LBUTTONDOWN:
		click(LOWORD(lParam), HIWORD(lParam));
		return 0;
	case WM_PAINT:
		paint();
		return 0;
	case WM_NOTIFY:
	{
		// pnmh = (LPNMHDR) lParam
		LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(lParam);
		switch (phdr->hdr.code)
		{
		case HDN_ITEMCHANGED:
			if (phdr->hdr.hwndFrom == getListHeader())
			{
				if (phdr->pitem && phdr->pitem->mask & HDI_WIDTH)
					m_headers[phdr->iItem].width = phdr->pitem->cxy;
				for (size_t i = phdr->iItem + 1; i < m_headers.size(); ++i)
					m_headers[i].x =
						m_headers[i - 1].x + m_headers[i - 1].width;
				setScroll(SB_HORZ);
			}
			break;
		default:
			break;
		}
	}
	case WM_SIZE:
	{
		// Note: WM_SIZE msgs only appear when 'just' scrolling the window
		RECTWrapper clientRect = getWindowRect();
		if (m_hasClientRect)
		{
			int dx = clientRect.width() - m_lastClientRect.width();
			if (dx != 0)
			{
				//m_headers.back().width += dx;
				//setHeaders();
				::MoveWindow(m_listheader, -m_scrollX, 0,
					    clientRect.width(), 
					    m_headerheight, TRUE);
				setScroll(SB_HORZ);
			}
		}
		else
			m_hasClientRect = true;
		m_lastClientRect = clientRect;
		return 0;
	}

	default:
		break;
	}
	return ::DefWindowProc(getHWND(), message, wParam, lParam);
}

bool PickView::registerWindowClass()
{
	if (WindowClassAtom != 0)
		return true;

	// We're not registered yet
	WNDCLASSEX wc;

	wc.cbSize = sizeof(wc);
	// Some sensible style defaults
	wc.style = CS_HREDRAW | CS_VREDRAW;
	// Our default window procedure.  This replaces itself
	// on the first call with the simpler Window::WindowProcReflector().
	wc.lpfnWndProc = Window::FirstWindowProcReflector;
	// No class bytes
	wc.cbClsExtra = 0;
	// One pointer to REFLECTION_INFO in the extra window instance bytes
	wc.cbWndExtra = 4;
	// The app instance
	wc.hInstance = getInstance();
	// Use a bunch of system defaults for the GUI elements
	wc.hIcon = ::LoadIcon(0, IDI_APPLICATION);
	wc.hIconSm = NULL;
	wc.hCursor = ::LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	// No menu
	wc.lpszMenuName = NULL;
	// We'll get a little crazy here with the class name
	wc.lpszClassName = TEXT("listview");

	// All set, try to register
	WindowClassAtom = ::RegisterClassEx(&wc);
	return WindowClassAtom != 0;
}


PickView::PickView()
	: m_hasClientRect(false),
	  m_sysFont(NULL),
	  m_bitmapDC(NULL),
	  m_listheader(NULL)
{
	m_contents = new PickCategoryLine(*this);
}

PickView::~PickView()
{
	delete m_contents;
	if (m_bitmapDC)
		::DeleteDC(m_bitmapDC);
}

bool 
PickView::create(Window * Parent, DWORD Style, RECT *r, LPCTSTR CtlId)
{

	// First register the window class, if we haven't already
	if (!registerWindowClass())
	{
		// Registration failed
		return false;
	}

	// Save our parent, we'll probably need it eventually.
	setParent(Parent);

	// Create the window instance
	::CreateWindowEx(
			// Extended Style
			WS_EX_CLIENTEDGE,
			TEXT("listview"), //MAKEINTATOM(WindowClassAtom),     // window class atom (name)
			TEXT("listviewwindow"), // no title-bar string yet
			// Style bits
			Style,
			(r) ? r->left : CW_USEDEFAULT, r ? r->top : CW_USEDEFAULT, 
			(r) ? r->right - r->left + 1 : CW_USEDEFAULT, 
			(r) ? r->bottom - r->top + 1 :CW_USEDEFAULT,
			// Parent Window 
			(Parent) ? Parent->getHWND() : (HWND)NULL,
			// use class menu 
			(HMENU)CtlId,
			// The application instance 
			getInstance(),
			// The this ptr, which we'll use to set up the WindowProc reflection.
			reinterpret_cast<void *>(static_cast<Window*>(this)));
	if (getHWND() == NULL)
	{
		//log(LOG_BABBLE) << "Failed to create PickView " << ::GetLastError() << endLog;
		return false;
	}

	return true;
}


void
PickView::init()
{
	HDC dc = ::GetDC(getHWND());
	m_sysFont = ::GetStockObject(DEFAULT_GUI_FONT);
	::SelectObject(dc, m_sysFont);
	::GetTextMetrics(dc, &m_textmetrics);

	m_bitmapDC = ::CreateCompatibleDC(dc);
	m_bitmaps[VIEW_BITMAPCHECKYES] = ::LoadImage(getInstance(), MAKEINTRESOURCE(IDB_CHECK_YES), IMAGE_BITMAP, 0, 0, 0);
	m_bitmaps[VIEW_BITMAPCHECKNO] = ::LoadImage(getInstance(), MAKEINTRESOURCE(IDB_CHECK_NO), IMAGE_BITMAP, 0, 0, 0);
	m_bitmaps[VIEW_BITMAPCHECKNA] = ::LoadImage(getInstance(), MAKEINTRESOURCE(IDB_CHECK_NA), IMAGE_BITMAP, 0, 0, 0);
	//m_bmSpin = ::LoadImage(getInstance(), MAKEINTRESOURCE(IDB_SPIN), IMAGE_BITMAP, 0, 0, 0);
	//m_bmRtArrow = ::LoadImage(getInstance(), MAKEINTRESOURCE(IDB_RTARROW), IMAGE_BITMAP, 0, 0, 0);

	m_rowheight = (m_textmetrics.tmHeight + m_textmetrics.tmExternalLeading + ROW_MARGIN);
	int irh = m_textmetrics.tmExternalLeading + m_textmetrics.tmDescent + CHECK_SIZE + ROW_MARGIN;
	if (m_rowheight < irh)
		m_rowheight = irh;

	HDLAYOUT hdl;
	WINDOWPOS wp;

	// Ensure that the common control DLL is loaded, and then create
	// the header control.
	INITCOMMONCONTROLSEX controlinfo = { sizeof(INITCOMMONCONTROLSEX), ICC_LISTVIEW_CLASSES };
	::InitCommonControlsEx(&controlinfo);

	if (NULL == (m_listheader = ::CreateWindowEx(0, WC_HEADER, (LPCTSTR)NULL,
				    WS_CHILD | WS_BORDER | CCS_NORESIZE | 
				    HDS_HORZ /*| HDS_BUTTONS*/, 
				    0, 0, 0, 0, getHWND(),
				    (HMENU)IDC_CHOOSE_LISTHEADER, getInstance(),
				    (LPVOID)NULL)))
		// FIXME: throw an exception
		exit (10);

	// Retrieve the bounding rectangle of the parent window's
	// client area, and then request size and position values
	// from the header control.
	RECT rcParent = getClientRect();

	hdl.prc = &rcParent;
	hdl.pwpos = &wp;
	if (!::SendMessage(m_listheader, HDM_LAYOUT, 0, reinterpret_cast<LPARAM>(&hdl)))
		// FIXME: throw an exception
		exit (11);

	// Set the font of the listheader, but don't redraw, because its not shown
	// yet.This message does not return a value, so we are not checking it as we
	// do above.
	::SendMessage(m_listheader, WM_SETFONT, reinterpret_cast<WPARAM>(m_sysFont), FALSE);

	// Set the size, position, and visibility of the header control.
	SetWindowPos(m_listheader, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, 
		     wp.flags | SWP_SHOWWINDOW);

	m_headerheight = wp.cy;

	//initHeaders(dc);
	//setHeaders();
	::ReleaseDC(getHWND(), dc);
}

void
PickView::setScroll(int which)
{
	RECT r = getClientRect();
	SCROLLINFO si;
	memset(&si, 0, sizeof(si));
	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
	si.nMin = 0;

	if (which == SB_HORZ || which == SB_BOTH)
	{
		si.nMax = m_headers.back().x + m_headers.back().width; // + HMARGIN;
		si.nPage = r.right;
		if (si.nMax <= int(si.nPage))
			m_scrollX = 0;
		si.nPos = m_scrollX;
		::SetScrollInfo(getHWND(), SB_HORZ, &si, TRUE);
	}
	if (which == SB_VERT || which == SB_BOTH)
	{
		si.nMax = m_contents->itemCount() * m_rowheight;
		si.nPage = r.bottom - m_headerheight;
		if (si.nMax <= int(si.nPage))
			m_scrollY = 0;
		si.nPos = m_scrollY;
		::SetScrollInfo(getHWND(), SB_VERT, &si, TRUE);
	}
	::InvalidateRect(getHWND(), &r, TRUE);
}

void
PickView::scroll(int which, int *var, int code)
{
	SCROLLINFO si;
	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
	::GetScrollInfo(getHWND(), which, &si);

	switch (code)
	{
	  case SB_THUMBTRACK:
		si.nPos = si.nTrackPos;
		break;
	  case SB_THUMBPOSITION:
		break;
	  case SB_BOTTOM:
		si.nPos = si.nMax;
		break;
	  case SB_TOP:
		si.nPos = 0;
		break;
	  case SB_LINEDOWN:
		si.nPos += m_rowheight;
		break;
	  case SB_LINEUP:
		si.nPos -= m_rowheight;
		break;
	  case SB_PAGEDOWN:
		si.nPos += si.nPage * 9 / 10;
		break;
	  case SB_PAGEUP:
		si.nPos -= si.nPage * 9 / 10;
		break;
	}

	if ((int)si.nPos < 0)
		si.nPos = 0;
	if (si.nPos + si.nPage > (unsigned int)si.nMax)
		si.nPos = si.nMax - si.nPage;

	si.fMask = SIF_POS;
	::SetScrollInfo(getHWND(), which, &si, TRUE);

	int ox = m_scrollX;
	int oy = m_scrollY;
	*var = si.nPos;

	RECT cr, sr;
	cr = getClientRect();
	sr = cr;
	sr.top += m_headerheight;
	refresh();
	::ScrollWindow(getHWND(), ox - m_scrollX, oy - m_scrollY, &sr, &sr);
	/*
	   sr.bottom = sr.top;
	   sr.top = cr.top;
	   ScrollWindow (hwnd, ox - scroll_ulc_x, 0, &sr, &sr);
	 */
	if (ox - m_scrollX)
	{
		::GetClientRect(m_listheader, &cr);
		sr = cr;
		//UpdateWindow (htmp);
		::MoveWindow(m_listheader, -m_scrollX, 0,
                  m_headers.back().x +
                  m_headers.back().width, m_headerheight, TRUE);
	}
	refresh();
}

void
PickView::click(int x, int y)
{
	int row, do_refresh;

	if (m_contents->itemCount() == 0)
		return;
	if (y < m_headerheight)
		return;

	x += m_scrollX;
	y += m_scrollY - m_headerheight;

	row = (y + ROW_MARGIN / 2) / m_rowheight;

	if (row < 0 || row >= m_contents->itemCount())
		return;

	do_refresh = clickRow(row, x);

	// XXX we need a method to query the database to see if more
	// than just one package has changed! Until then...
#if 0
	if (do_refresh)
	{
#endif
	setScroll(SB_VERT);
#if 0
	}
	else
	{
		RECT rect;
		rect.left = -m_scrollX;
		rect.right = m_headers.back().x - m_scrollX;
		rect.top = m_headerheight + row * m_rowheight - m_scrollY;
		rect.bottom = rect.top + m_rowheight;
		::InvalidateRect(getHWND(), &rect, TRUE);
	}
#endif
}

void
PickView::paint()
{
	HDC hdc;
	PAINTSTRUCT ps;
	int x, y;

	hdc = ::BeginPaint(getHWND(), &ps);

	::SelectObject(hdc, m_sysFont);
	::SetBkColor(hdc, ::GetSysColor(COLOR_WINDOW));
	::SetTextColor(hdc, ::GetSysColor(COLOR_WINDOWTEXT));

	RECT cr = getClientRect();

	x = cr.left - m_scrollX;
	y = cr.top - m_scrollY + m_headerheight;

	::IntersectClipRect(hdc, cr.left, cr.top + m_headerheight, cr.right, cr.bottom);

	m_contents->paint(hdc, x, y, 0, 0);

	if (m_contents->itemCount() == 0)
	{
		String msg = String::Load(IDS_NOTHING_TO_INSTALL);
		::TextOut(hdc, x + HMARGIN, y, msg.c_str(), msg.size());
	}

	::EndPaint(getHWND(), &ps);
}

HDC
PickView::GetBitmapDC(int bitmap)
{
	if (bitmap >= VIEW_NUMBITMAPS)
		return NULL;
	::SelectObject(m_bitmapDC, m_bitmaps[bitmap]);
	return m_bitmapDC;
}

void
PickView::noteWidth(String const &str, int addend, int column)
{
	if (column >= static_cast<int>(m_headers.size()))
		return;
	if (str.empty())
	{
		if (m_headers[column].width >= addend)
			return;
		m_headers[column].width = addend;
	}
	else
	{
		HDC dc = ::GetDC(getHWND());
		SIZE s;
		::GetTextExtentPoint32(dc, str.c_str(), str.size(), &s);
		::ReleaseDC(getHWND(), dc);
		if (m_headers[column].width >= s.cx + addend)
			return;
		m_headers[column].width = s.cx + addend;
	}
	for (size_t n=column + 1; n < m_headers.size(); ++n)
	{
		m_headers[n].x = m_headers[n - 1].x + m_headers[n - 1].width;
	/*
		SizeColumn(m_listheader, n, m_headers[n].width);
	*/
	}
	SizeColumn(m_listheader, column, m_headers[column].width);
	setScroll(SB_HORZ);
}

void
PickView::initHeaders(HDC dc)
{
	/*
	m_headers.clear();
	m_headers.resize(3);

	m_headers[0].text = String::Load(IDS_PICK_COLUMN_NAME);
	noteWidth(dc, m_headers[0].text, HMARGIN, 0);
	m_headers[1].text = String::Load(IDS_PICK_COLUMN_GAME);
	noteWidth(dc, m_headers[1].text, HMARGIN, 1);
	m_headers[2].text = String::Load(IDS_PICK_COLUMN_VERSION);
	noteWidth(dc, m_headers[2].text, HMARGIN, 2);
	
	noteWidth(dc, String::Load(IDS_DARKGAME_THIEF1), HMARGIN, 1);
	noteWidth(dc, String::Load(IDS_DARKGAME_THIEF2), HMARGIN, 1);
	noteWidth(dc, String::Load(IDS_DARKGAME_SSHOCK2), HMARGIN, 1);

	headers[0].x = 0;
	for (int i = 1; i < m_headers.size(); ++i)
		m_headers[i].x = m_headers[i - 1].x + m_headers[i - 1].width;
	*/
}

void
PickView::setHeaders()
{
	while (int n = ::SendMessage(m_listheader, HDM_GETITEMCOUNT, 0, 0))
		::SendMessage(m_listheader, HDM_DELETEITEM, n - 1, 0);
	for (size_t i = 0; i < m_headers.size(); ++i)
		SetColumn(m_listheader, i, m_headers[i].width, m_headers[i].text.c_str(), m_headers[i].text.size());
}

void PickView::insertColumn(String const & text)
{
	HDC dc = ::GetDC(m_listheader);
	SIZE s;
	::GetTextExtentPoint32(dc, text.c_str(), text.size(), &s);
	::ReleaseDC(m_listheader, dc);
	insertColumn(text, s.cx + HMARGIN);
}

void PickView::insertColumn(String const & text, int width)
{
	int new_col = m_headers.size();
	m_headers.resize(new_col+1);
	m_headers[new_col].text = text;
	m_headers[new_col].width = width;
	if (new_col > 0)
	{
		m_headers[new_col].x = m_headers[new_col - 1].x + m_headers[new_col - 1].width;
	}
	else
	{
		m_headers[new_col].x = 0;
		m_headers[new_col].width += CHECK_SIZE + ICON_MARGIN;
	}
	SetColumn(m_listheader, new_col, width, text.c_str(), text.size());
}

void
PickView::insertItem(String const & cat, const char* key, ...)
{
	va_list va;
	PickLine * item = new PickItemLine(*this, string(key));

	if (m_headers.size() > 0)
	{
		va_start(va, key);
		int margin = HMARGIN + ICON_MARGIN + CHECK_SIZE;
		for (size_t n = 0; n < m_headers.size(); ++n)
		{
			String const& str = *(reinterpret_cast<String const*>(va_arg(va, String const *)));
			noteWidth(str, margin, n);
			item->setColumn(n, str);
			margin = HMARGIN;
		}

		va_end(va);
	}

	if (! cat.empty())
	{
		PickLine * catline = m_contents->find(string(cat).c_str());
		if (catline == NULL)
		{
			catline = new PickCategoryLine(*this, cat);
			m_contents->insert(catline);
		}
		catline->insert(item);
	}
	else
	{
		m_contents->insert(item);
	}
	/*
  if (view_mode != views::Category)
    {
      PickLine & line = *new PickPackageLine (*this, pkg);
      contents.insert (line);
    }
  else
    {
      for (set <String, String::caseless>::const_iterator x
	   = pkg.categories.begin (); x != pkg.categories.end (); ++x)
        {
	  packagedb db;
	  // Special case - yuck
	  if (x->casecompare ("All") == 0)
	    continue;

	  PickCategoryLine & catline = 
	    *new PickCategoryLine (*this,* db.categories.find (*x), 1);
	  PickLine & line = *new PickPackageLine(*this, pkg);
	  catline.insert (line);
	  contents.insert (catline);
        }
    }
	*/
}

void
PickView::insertCategory(String const & name)
{
	PickCategoryLine * catline = new PickCategoryLine(*this, name);
	m_contents->insert(catline);
}

void
PickView::clearList(void)
{
	m_contents->clear();
	refreshList();
}

void
PickView::refreshList()
{
	setScroll(SB_BOTH);
}

int
PickView::clickRow(int row, int x)
{
	int refresh = m_contents->click(0, row, x, 0);
	if (refresh)
	{
		SendNotifyMessage(getParent()->getHWND(), WM_COMMAND, 
				  MAKELONG(::GetWindowLong(getHWND(),GWL_ID),VIEW_SELCHANGED),
				  reinterpret_cast<LPARAM>(getHWND()));
	}
	return refresh;
}

int
PickView::getNumRows()
{
	return m_contents->itemCount();
}

PickLine *
PickView::getRow(int row)
{
	return m_contents->getRow(row);
}

