/*
 * Copyright (C) 2007-2012 Geometer Plus <contact@geometerplus.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <ZLibrary.h>
#include <ZLLanguageUtil.h>
#include <ZLUnicodeUtil.h>

#include "W32DialogPanel.h"
#include "W32Control.h"
#include "../../../../core/src/win32/util/W32WCHARUtil.h"

#undef max

static const int FirstControlId = 2001;

std::map<HWND,W32DialogPanel*> W32DialogPanel::ourPanels;
const std::string W32DialogPanel::PANEL_SELECTED_EVENT = "Dialog Panel: Selected";
const std::string W32DialogPanel::APPLY_BUTTON_PRESSED_EVENT = "Dialog Panel: Apply Button Pressed";
UINT W32DialogPanel::LAYOUT_MESSAGE = 0;

static void allocateString(WORD *&p, const std::string &text) {
	ZLUnicodeUtil::Ucs2String ucs2Str;
	::createNTWCHARString(ucs2Str, text);
	memcpy(p, ::wchar(ucs2Str), 2 * ucs2Str.size());
	p += ucs2Str.size();
}

W32DialogPanel::W32DialogPanel(HWND mainWindow, const std::string &caption) : W32ControlCollection(FirstControlId), myMainWindow(mainWindow), myCaption(caption), myAddress(0), myPanelWindow(0) {
	if (LAYOUT_MESSAGE == 0) {
		LAYOUT_MESSAGE = RegisterWindowMessageA("layout");
	}
}

W32DialogPanel::~W32DialogPanel() {
	if (myAddress != 0) {
		delete[] myAddress;
	}
	if (myPanelWindow != 0) {
		ourPanels.erase(myPanelWindow);
	}
}

void W32DialogPanel::init(HWND dialogWindow) {
	myPanelWindow = dialogWindow;
	ShowScrollBar(myPanelWindow, SB_BOTH, false);
	ourPanels[myPanelWindow] = this;	
	myElement->init(dialogWindow, this);
}

void W32DialogPanel::calculateSize() {
	myMinimumSize = myElement->minimumSize();
	mySize = myMinimumSize;
}

W32Widget::Size W32DialogPanel::size() const {
	return mySize;
}

void W32DialogPanel::setSize(W32Widget::Size size) {
	mySize = size;
	myRealSize = size;
}

void W32DialogPanel::updateElementSize() {
	myElement->setPosition(0, 0, mySize);
}

DWORD W32DialogPanel::style() const {
	return DS_SHELLFONT | DS_CENTER | DS_MODALFRAME | WS_POPUPWINDOW | WS_CAPTION | WS_VSCROLL | WS_HSCROLL;
}

DLGTEMPLATE *W32DialogPanel::dialogTemplate() {
	if (myAddress != 0) {
		delete[] myAddress;
	}

	if ((mySize.Width == 0) && (mySize.Height == 0)) {
		calculateSize();
	}
	updateElementSize();

	const std::string fontName = "MS Shell Dlg";
	int size = 14 + ZLUnicodeUtil::utf8Length(myCaption) + ZLUnicodeUtil::utf8Length(fontName) + myElement->allocationSize();
	size += size % 2;
	myAddress = new WORD[size];

	static const DWORD extendedStyle = ZLLanguageUtil::isRTLLanguage(ZLibrary::Language()) ? WS_EX_LAYOUTRTL : 0;

	WORD *p = myAddress;
	*p++ = LOWORD(style());
	*p++ = HIWORD(style());
	*p++ = LOWORD(extendedStyle);
	*p++ = HIWORD(extendedStyle);
	*p++ = myElement->controlNumber();
	*p++ = 0; // X
	*p++ = 0; // Y
	*p++ = mySize.Width;
	*p++ = mySize.Height;
	*p++ = 0;
	*p++ = 0;
	allocateString(p, myCaption);
	*p++ = 8; // FONT SIZE -- should be always 8?
	allocateString(p, fontName);
	if ((p - myAddress) % 2 == 1) {
		p++;
	}

	short id = FirstControlId;
	myElement->allocate(p, id);

	return (DLGTEMPLATE*)myAddress;
}

void W32DialogPanel::setElement(W32WidgetPtr element) {
	myElement = element;
}

bool W32DialogPanel::commandCallback(WPARAM wParam) {
	W32Control *control = (*this)[LOWORD(wParam)];
	if (control != 0) {
		control->commandCallback(HIWORD(wParam));
		return true;
	}
	return false;
}

bool W32DialogPanel::notificationCallback(WPARAM wParam, LPARAM lParam) {
	W32Control *control = (*this)[LOWORD(wParam)];
	if (control != 0) {
		control->notificationCallback(lParam);
	}
	return false;
}

bool W32DialogPanel::drawItemCallback(WPARAM wParam, DRAWITEMSTRUCT &di) {
	W32Control *control = (*this)[LOWORD(wParam)];
	if (control != 0) {
		control->drawItemCallback(di);
		return true;
	}
	return false;
}

void W32DialogPanel::invalidate() {
	if (myPanelWindow != 0) {
		PostMessage(myPanelWindow, LAYOUT_MESSAGE, 0, 0);
		myDoLayout = true;
	}
}

void W32DialogPanel::layout() {
	if (myDoLayout) {
		calculateSize();
		if (myRealSize.Width != 0) {
			mySize.Width = std::max(mySize.Width, myRealSize.Width);
		}
		if (myPanelWindow != 0) {
			RECT rectangle;
			rectangle.left = 0;
			rectangle.top = 0;
			rectangle.right = mySize.Width;
			rectangle.bottom = mySize.Height;
			MapDialogRect(myPanelWindow, &rectangle);
			RECT realRectangle;
			GetClientRect(myPanelWindow, &realRectangle);
			bool hOversized = rectangle.right > realRectangle.right + 1;
			bool vOversized = rectangle.bottom > realRectangle.bottom + 1;
			int rightBound = realRectangle.right;
			int bottomBound = realRectangle.bottom;
			if (hOversized != vOversized) {
				SCROLLBARINFO info;
				info.cbSize = sizeof(info);
				if (hOversized) {
					GetScrollBarInfo(myPanelWindow, OBJID_HSCROLL, &info);
					vOversized = rectangle.bottom >= info.rcScrollBar.top;
					if (vOversized) {
						bottomBound = info.rcScrollBar.top - 1;
					}
				} else {
					GetScrollBarInfo(myPanelWindow, OBJID_VSCROLL, &info);
					hOversized = rectangle.right >= info.rcScrollBar.left;
					if (hOversized) {
						rightBound = info.rcScrollBar.left - 1;
					}
				}
			}
			ShowScrollBar(myPanelWindow, SB_HORZ, hOversized);
			if (hOversized) {
				SCROLLINFO info;
				info.cbSize = sizeof(info);
				info.fMask = SIF_RANGE | SIF_PAGE;
				info.nMin = 0;
				info.nMax = rectangle.right;
				info.nPage = rightBound;
				SetScrollInfo(myPanelWindow, SB_HORZ, &info, true);
			}
			ShowScrollBar(myPanelWindow, SB_VERT, vOversized);
			if (vOversized) {
				SCROLLINFO info;
				info.cbSize = sizeof(info);
				info.fMask = SIF_RANGE | SIF_PAGE;
				info.nMin = 0;
				info.nMax = rectangle.bottom;
				info.nPage = bottomBound;
				SetScrollInfo(myPanelWindow, SB_VERT, &info, true);
			}
		}
		updateElementSize();
		myDoLayout = false;
	}
}

const std::string &W32DialogPanel::caption() const {
	return myCaption;
}

void W32DialogPanel::scroll(WORD command, int direction) {
	SCROLLINFO info;
	info.cbSize = sizeof(info);
	info.fMask = SIF_ALL;
	GetScrollInfo(myPanelWindow, direction, &info);
	int position = info.nPos;
	switch (command) {
		case SB_LEFT: // == SB_TOP
			info.nPos = info.nMin;
			break;
		case SB_RIGHT: // == SB_BOTTOM
			info.nPos = info.nMax;
			break;
		case SB_LINELEFT: // == SB_LINEUP
			info.nPos -= 1;
			break;
		case SB_LINERIGHT: // == SB_LINEDOWN
			info.nPos += 1;
			break;
		case SB_PAGELEFT: // == SB_PAGEUP
			info.nPos -= info.nPage;
			break;
		case SB_PAGERIGHT: // == SB_PAGEDOWN
			info.nPos += info.nPage;
			break;
		case SB_THUMBTRACK:
			info.nPos = info.nTrackPos;
			break;
	}
	info.fMask = SIF_POS;
	SetScrollInfo(myPanelWindow, direction, &info, true);
	GetScrollInfo(myPanelWindow, direction, &info);
	if (info.nPos != position) {
		if (direction == SB_HORZ) {
			ScrollWindow(myPanelWindow, position - info.nPos, 0, 0, 0);
		} else {
			ScrollWindow(myPanelWindow, 0, position - info.nPos, 0, 0);
		}
		UpdateWindow(myPanelWindow);
	}
}
