/*
 * Copyright (c) 2001, 2002, 2003 Gary R. Van Sickle.
 * 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 Gary R. Van Sickle <g.r.vansickle@worldnet.att.net>
 * Written by Tom N Harris <telliamed@whoopdedo.org>
 *
 */

// This is the implementation of the Window class.  It serves both as a window class
// in its own right and as a base class for other window-like classes (e.g. PropertyPage,
// PropSheet).

#include "Window.h"

using namespace Setup;

ATOM Window::WindowClassAtom = 0;
HINSTANCE Window::AppInstance = NULL;

Window::Window()
	: m_windowHandle(NULL),
	  m_parent(NULL)
{
}

Window::~Window()
{
	// Delete any fonts we created.
	FontList::iterator i;
	for (i = m_fonts.begin(); i != m_fonts.end(); ++i)
	{
		::DeleteObject(*i);
	}

	// FIXME: Maybe do some reference counting and do this Unregister
	// when there are no more of us left.  Not real critical unless
	// we're in a DLL which we're not right now.
	//::UnregisterClass(WindowClassAtom, InstanceHandle);
}

LRESULT CALLBACK
Window::FirstWindowProcReflector(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	Window *wnd = NULL;

	if(uMsg == WM_NCCREATE)
	{
		// This is the first message a window gets (so MSDN says anyway).
		// Take this opportunity to "link" the HWND to the 'this' ptr, steering
		// messages to the class instance's WindowProc().
		wnd = reinterpret_cast<Window*>(reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams);

		// Set a backreference to this class instance in the HWND.
		::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(wnd));

		// Set a new WindowProc now that we have the peliminaries done.
		// We could instead simply do the contents of Window::WindowProcReflector
		// in the 'else' clause below, but this way we eliminate an unnecessary 'if/else' on
		// every message.  Yeah, it's probably not worth the trouble.
		::SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&Window::WindowProcReflector));
		// Finally, store the window handle in the class.
		wnd->m_windowHandle = hwnd;
	}
	else
	{
		// Should never get here.
		abort();
	}

  return wnd->windowProc(uMsg, wParam, lParam);
}

LRESULT CALLBACK
Window::WindowProcReflector(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	Window *This;

	// Get our this pointer
	This = reinterpret_cast<Window*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));

	if (uMsg == WM_CREATE)
	{
		This->init();
	}

	return This->windowProc(uMsg, wParam, lParam);
}

bool
Window::create(Window * Parent, DWORD Style)
{
	// First register the window class, if we haven't already
	if (registerWindowClass() == false)
	{
		// Registration failed
		return false;
	}

	// Save our parent, we'll probably need it eventually.
	m_parent = Parent;

	// Create the window instance
	m_windowHandle = ::CreateWindowEx (
			   // Extended Style
			   0,
			   TEXT("MainWindowClass"),	//MAKEINTATOM(WindowClassAtom),     // window class atom (name)
			   TEXT("Setup"),	// no title-bar string yet
			   // Style bits
			   Style,
			   // Default positions and size
			   CW_USEDEFAULT, CW_USEDEFAULT,
			   CW_USEDEFAULT, CW_USEDEFAULT,
			   // Parent Window 
			   (Parent) ? Parent->getHWND() : (HWND)NULL,
			   // use class menu 
			   (HMENU)NULL,
			   // The application instance 
			   getInstance(),
			   // The this ptr, which we'll use to set up the WindowProc reflection.
			   reinterpret_cast<LPVOID>(this));

	if (m_windowHandle == NULL)
	{
		// Failed
		return false;
	}

	return true;
}

bool
Window::registerWindowClass()
{
	if (WindowClassAtom == 0)
	{
		// We're not registered yet
		WNDCLASSEX
		wc;

		wc.cbSize = sizeof(wc);
		// Some sensible style defaults
		wc.style = CS_DBLCLKS | 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 = NULL;
		wc.hIconSm = NULL;
		wc.hCursor = NULL;
		wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
		// No menu
		wc.lpszMenuName = NULL;
		// We'll get a little crazy here with the class name
		wc.lpszClassName = TEXT("MainWindowClass");

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

		if (WindowClassAtom == 0)
		{
			// Failed
			return false;
		}
	}

	// We're registered, or already were before the call,
	// return success in either case.
	return true;
}

void Window::init()
{
}

void
Window::show(int State)
{
	::ShowWindow(getHWND(), State);
}

LRESULT
Window::windowProc (UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return ::DefWindowProc(m_windowHandle, uMsg, wParam, lParam);
}

bool
Window::messageLoop()
{
	MSG
	msg;

	while (::GetMessage(&msg, NULL, 0, 0) != 0
	    && ::GetMessage(&msg, (HWND) NULL, 0, 0) != -1)
	{
		if (!::IsWindow(m_windowHandle) || !::IsDialogMessage(m_windowHandle, &msg))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
	}

	return true;
}

void
Window::postMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  ::PostMessage(getHWND(), uMsg, wParam, lParam);
}

RECT
Window::getWindowRect() const
{
	RECT retval;
	::GetWindowRect(getHWND(), &retval);
	return retval;
}

RECT
Window::getClientRect() const
{
	RECT retval;
	::GetClientRect(getHWND(), &retval);
	return retval;
}

RECT
Window::screenToClient(const RECT &r) const
{
	RECT ret;

	ret.left = r.left;
	ret.top = r.top;
	::ScreenToClient(getHWND(), reinterpret_cast<POINT*>(&ret.left));
	ret.right = r.right;
	ret.bottom = r.bottom;
	::ScreenToClient(getHWND(), reinterpret_cast<POINT*>(&ret.right));

	return ret;
}

RECT Window::getDlgItemRect(int id) const
{
	RECT ret;

	::GetWindowRect(getDlgItem(id), &ret);
	::ScreenToClient(getHWND(), reinterpret_cast<POINT*>(&ret.left));
	::ScreenToClient(getHWND(), reinterpret_cast<POINT*>(&ret.right));

	return ret;
}

bool
Window::moveWindow(long x, long y, long w, long h, bool Repaint)
{
  return ::MoveWindow(getHWND(), x, y, w, h, Repaint);
}

bool
Window::moveWindow(const RECTWrapper &r, bool Repaint)
{
  return ::MoveWindow(getHWND(), r.left, r.top, r.width(), r.height(), Repaint);
}

void
Window::centerWindow()
{
  RECT WindowRect, ParentRect;
  int WindowWidth, WindowHeight;
  POINT p;

	// Get the window rectangle
	WindowRect = getWindowRect();

	if (getParent() == NULL)
	{
		// Center on desktop window
		::GetWindowRect(::GetDesktopWindow(), &ParentRect);
	}
	else
	{
		// Center on client area of parent
		::GetClientRect(getParent()->getHWND(), &ParentRect);
	}

	WindowWidth = WindowRect.right - WindowRect.left;
	WindowHeight = WindowRect.bottom - WindowRect.top;

	// Find center of area we're centering on
	p.x = (ParentRect.right - ParentRect.left) / 2;
	p.y = (ParentRect.bottom - ParentRect.top) / 2;

	// Convert that to screen coords
	if (getParent() == NULL)
	{
		::ClientToScreen(::GetDesktopWindow(), &p);
	}
	else
	{
		::ClientToScreen(getParent()->getHWND(), &p);
	}

	// Calculate new top left corner for window
	p.x -= WindowWidth / 2;
	p.y -= WindowHeight / 2;

	// And finally move the window
	moveWindow(p.x, p.y, WindowWidth, WindowHeight);
}

UINT
Window::isButtonChecked(int nIDButton) const
{
  return ::IsDlgButtonChecked(getHWND(), nIDButton);
}

bool
Window::setDlgItemFont(int id, const TCHAR * fontname, int Pointsize,
		       int Weight, bool Italic, bool Underline, bool Strikeout)
{
	HWND ctrl;

	ctrl = getDlgItem(id);
	if (ctrl == NULL)
	{
		// Couldn't get that ID
		return false;
	}

	// We need the DC for the point size calculation.
	HDC hdc = ::GetDC(ctrl);

	// Create the font.  We have to keep it around until the dialog item
	// goes away - basically until we're destroyed.
	HFONT hfnt;
	hfnt = ::CreateFont(- ::MulDiv(Pointsize, ::GetDeviceCaps(hdc, LOGPIXELSY), 72),
		     0, 0, 0, Weight, Italic ? TRUE : FALSE,
		     Underline ? TRUE : FALSE, Strikeout ? TRUE : FALSE,
		     ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
		     PROOF_QUALITY, DEFAULT_PITCH | FF_DONTCARE, fontname);
	if (hfnt == NULL)
	{
		// Font creation failed
		return false;
	}

	// Set the new font, and redraw any text which was already in the item.
	::SendMessage(ctrl, WM_SETFONT, (WPARAM) hfnt, TRUE);

	m_fonts.push_back(hfnt);

	return true;
}

void
Window::setWindowText(const String & s)
{
	::SetWindowText(getHWND(), s.c_str());
}

void
Window::setDlgItemText(int id, const String & s)
{
	::SetWindowText(getDlgItem(id), s.c_str());
}

String
Window::getDlgItemText(int id)
{
	String str;
	HWND h = getDlgItem(id);
	int len = ::GetWindowTextLength(h) + 1;
	TCHAR* tmp = reinterpret_cast<TCHAR*>(::operator new(len*sizeof(TCHAR)));
	if (::GetWindowText(h, tmp, len))
		str = tmp;
	::operator delete(tmp);
	return str;
}

void 
Window::refresh()
{
	::UpdateWindow(getHWND());
}
