#include "stdafx.h"
#include "WindowsManager.h"
#include "AsSingleWindow.h"

void sWindowInfo::saveState()
{
	WINDOWPLACEMENT wndPlace;
	wndPlace.length = sizeof(wndPlace);
	if (!GetWindowPlacement(this->hWnd, &wndPlace))
		return;

	switch (wndPlace.showCmd) {
	case SW_HIDE:
		this->eState = WINDOW_STATE_HIDDEN;
		break;

	case SW_MINIMIZE:
	case SW_SHOWMINIMIZED:
	case SW_SHOWMINNOACTIVE:
		this->eState = WINDOW_STATE_MINIMIZED;
		break;

	case SW_MAXIMIZE:
	case SW_RESTORE:
	case SW_SHOW:
	case SW_SHOWNA:
	case SW_SHOWNOACTIVATE:
	case SW_SHOWNORMAL:
		this->eState = WINDOW_STATE_NORMAL;
		break;
	}
}

void sWindowInfo::saveRect()
{
	switch (this->eState) {
	case WINDOW_STATE_HIDDEN:
	case WINDOW_STATE_MINIMIZED:
		WINDOWPLACEMENT wndPlace;
		wndPlace.length = sizeof(wndPlace);
		if (GetWindowPlacement(this->hWnd, &wndPlace))
			this->rLastSavedPosition = wndPlace.rcNormalPosition;
		break;

	default:
		GetWindowRect(this->hWnd, &this->rLastSavedPosition);
		break;
	}
}

void pluginSetProgress()
{
	EnterCriticalSection(&pluginVars.m_CS);
	pluginVars.IsUpdateInProgress = true;
	LeaveCriticalSection(&pluginVars.m_CS);
}

void pluginSetDone()
{
	EnterCriticalSection(&pluginVars.m_CS);
	pluginVars.IsUpdateInProgress = false;
	LeaveCriticalSection(&pluginVars.m_CS);
}

bool pluginIsAlreadyRunning()
{
	EnterCriticalSection(&pluginVars.m_CS);
	bool result = pluginVars.IsUpdateInProgress;
	LeaveCriticalSection(&pluginVars.m_CS);
	return result;
}

/**
 * Поиск окна в списке
 * возвращается указатель на структуру с информацией
 */
sWindowInfo* windowFind(HWND hWnd)
{
	for (auto itr = pluginVars.allWindows.begin(); itr != pluginVars.allWindows.end(); ++itr)
		if (itr->hWnd == hWnd)
			return &*itr;

	return nullptr;
}

/**
 * Поиск окна в списке
 * возвращается итератор
 */
windowsList::iterator windowFindItr(HWND hWnd)
{
	for (auto itr = pluginVars.allWindows.begin(); itr != pluginVars.allWindows.end(); ++itr)
		if (itr->hWnd == hWnd)
			return itr;

	return pluginVars.allWindows.end();
}

/**
 * Поиск окна в списке
 * возвращается реверсный итератор
 */
windowsList::reverse_iterator windowFindRevItr(HWND hWnd)
{
	for (auto ritr = pluginVars.allWindows.rbegin(); ritr != pluginVars.allWindows.rend(); ++ritr)
		if (ritr->hWnd == hWnd)
			return ritr;

	return pluginVars.allWindows.rend();
}

/**
 * Добавление окна в список окон и выставление всех начальных значений
 * здесь же выставляется хук на wndProc
 */
void windowAdd(HWND hWnd, bool IsMain)
{
	sWindowInfo thisWindowInfo;

	hWnd = windowGetRoot(hWnd);

	// Если окно уже есть в списке (могло быть спрятано)
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		wndInfo->eState = WINDOW_STATE_NORMAL; // если окно пряталось ранее
		windowReposition(hWnd);
		return;
	}

	thisWindowInfo.hWnd = hWnd;
	thisWindowInfo.eState = WINDOW_STATE_NORMAL;
	mir_subclassWindow(hWnd, wndProcSync);

	pluginVars.allWindows.push_back(thisWindowInfo);

	if (IsMain)
		pluginVars.contactListHWND = hWnd;

	windowReposition(hWnd);
}

/**
 * Поиск окна верхнего уровня
 */
HWND windowGetRoot(HWND hWnd)
{
	HWND hWndParent = GetParent(hWnd);
	while (IsWindow(hWndParent) && (hWndParent != GetDesktopWindow())) // IsWindowVisible() ?
	{
		hWnd = hWndParent;
		hWndParent = GetParent(hWnd);
	}
	return hWnd;
}

void windowListUpdate()
{
	bool isRemoved = false;
	windowsList::iterator itr = pluginVars.allWindows.begin();
	while (itr != pluginVars.allWindows.end())
		// Не удаляем КЛ в принципе, нет необходимости
		if (!IsWindow(itr->hWnd) && itr->hWnd != pluginVars.contactListHWND) {
			itr = pluginVars.allWindows.erase(itr);
			isRemoved = true;
		}
		else
			++itr;

	if (isRemoved)
		if (!pluginVars.allWindows.empty())
			// TODO: разобраться, почему после этого КЛ пропадает в трей
			allWindowsMoveAndSize(pluginVars.contactListHWND);
}

/**
 * Установка стартовых координат и размера окна
 * базируется на основе координат другого окна в списке
 */
void windowReposition(HWND hWnd)
{
	// TODO: Подумать, нужен ли тут hWnd вообще
	RECT prevWindowPos;

	hWnd = windowGetRoot(hWnd);

	// TODO: Проверить, возможно нужен выход из цикла
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		for (auto itr = pluginVars.allWindows.begin(); itr != pluginVars.allWindows.end(); ++itr)
			if (itr->hWnd != hWnd) // TODO: очень странная логика
				if (GetWindowRect(itr->hWnd, &prevWindowPos)) {
					SendMessage(itr->hWnd, WM_MOVE, 0, MAKELPARAM(prevWindowPos.left, prevWindowPos.top));
					break;
				}
	}
	else if (!pluginVars.allWindows.empty()) {
		auto itr = pluginVars.allWindows.begin();
		if (GetWindowRect(itr->hWnd, &prevWindowPos))
			SendMessage(itr->hWnd, WM_MOVE, 0, MAKELPARAM(prevWindowPos.left, prevWindowPos.top));
	}
}

/**
 * Перемещение и ресайз всех окон списка
 * hWnd - окно-источник события перемещения/ресайза
 */
void allWindowsMoveAndSize(HWND hWnd)
{
	// Если склеивание отключено
	if (pluginVars.Options.DrivenWindowPos == ASW_CLWINDOWPOS_DISABLED)
		return;

	// Окно должно быть в списке и иметь нормальное состояние
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		if (wndInfo->eState != WINDOW_STATE_NORMAL)
			return;
	}
	else
		return;

	// Отключаем связь, если окон больше двух и выбрана соотв. опция
	if (pluginVars.Options.WindowsMerging == ASW_WINDOWS_MERGEDISABLE)
		if (pluginVars.allWindows.size() > 2)
			return;

	// Просмотр окон от текущего до конца
	windowsList::iterator itrC = windowFindItr(hWnd);
	if (itrC != pluginVars.allWindows.end()) {
		windowsList::iterator itrN = ++windowFindItr(hWnd);
		for (; itrC != pluginVars.allWindows.end(), itrN != pluginVars.allWindows.end(); ++itrC, ++itrN) {
			// Режим только двух окон
			if (pluginVars.Options.WindowsMerging == ASW_WINDOWS_MERGEONE)
				if ((itrC->hWnd != pluginVars.contactListHWND) && (itrN->hWnd != pluginVars.contactListHWND))
					continue;

			// itrC проверяется в начале функции
			UINT incCount = 0;
			bool isItrInList = true;
			while (itrN->eState != WINDOW_STATE_NORMAL) {
				++itrN;
				++incCount;
				if (itrN == pluginVars.allWindows.end()) {
					isItrInList = false;
					break;
				}
			}
			if (!isItrInList)
				break;

			sWndCoords wndCoord;
			if (calcNewWindowPosition(itrC->hWnd, itrN->hWnd, &wndCoord, (pluginVars.Options.DrivenWindowPos == ASW_CLWINDOWPOS_RIGHT) ? WINDOW_POSITION_RIGHT : WINDOW_POSITION_LEFT))
				SetWindowPos(itrN->hWnd, itrC->hWnd, wndCoord.x, wndCoord.y, wndCoord.width, wndCoord.height, SWP_NOACTIVATE);

			itrN->saveRect();

			for (; incCount != 0; incCount--)
				++itrC;
		}
	}

	// Просмотр окон от текущего до начала
	windowsList::reverse_iterator ritrC = windowFindRevItr(hWnd);
	if (ritrC != pluginVars.allWindows.rend()) {
		windowsList::reverse_iterator ritrN = ++windowFindRevItr(hWnd);
		for (; ritrC != pluginVars.allWindows.rend(), ritrN != pluginVars.allWindows.rend(); ++ritrC, ++ritrN) {
			// Режим только двух окон
			if (pluginVars.Options.WindowsMerging == ASW_WINDOWS_MERGEONE)
				if ((ritrC->hWnd != pluginVars.contactListHWND) && (ritrN->hWnd != pluginVars.contactListHWND))
					continue;

			UINT incCount = 0;
			bool isItrInList = true;
			while (ritrN->eState != WINDOW_STATE_NORMAL) {
				++ritrN;
				++incCount;
				if (ritrN == pluginVars.allWindows.rend()) {
					isItrInList = false;
					break;
				}
			}
			if (!isItrInList)
				break;

			sWndCoords wndCoord;
			if (calcNewWindowPosition(ritrC->hWnd, ritrN->hWnd, &wndCoord, (pluginVars.Options.DrivenWindowPos == ASW_CLWINDOWPOS_RIGHT) ? WINDOW_POSITION_LEFT : WINDOW_POSITION_RIGHT))
				SetWindowPos(ritrN->hWnd, ritrC->hWnd, wndCoord.x, wndCoord.y, wndCoord.width, wndCoord.height, SWP_NOACTIVATE);

			ritrN->saveRect();

			for (; incCount != 0; incCount--)
				++ritrC;
		}
	}

	if (sWindowInfo* wndInfo = windowFind(hWnd))
		wndInfo->saveRect();
}

void allWindowsActivation(HWND hWnd)
{
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		WindowState wndState = wndInfo->eState;
		for (auto itr = pluginVars.allWindows.begin(); itr != pluginVars.allWindows.end(); ++itr) {
			if (itr->hWnd == hWnd)
				continue;

			switch (wndState) {
				// Восстанавливаем все окна и выстраиваем на переднем плане
			case WINDOW_STATE_NORMAL:
				ShowWindow(itr->hWnd, SW_SHOWNA);
				ShowWindow(itr->hWnd, SW_RESTORE);
				SetWindowPos(itr->hWnd, hWnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
				itr->eState = WINDOW_STATE_NORMAL;
				break;

				// Прячем окна диалогов, окно КЛ - скрываем
			case WINDOW_STATE_MINIMIZED:
				if (itr->hWnd != pluginVars.contactListHWND)
					ShowWindow(itr->hWnd, SW_MINIMIZE);
				else
					ShowWindow(itr->hWnd, SW_HIDE);
				itr->eState = WINDOW_STATE_MINIMIZED;
				break;

				// Прячем все окна
			case WINDOW_STATE_HIDDEN:
			case WINDOW_STATE_CLOSED:
				ShowWindow(itr->hWnd, SW_HIDE);
				itr->eState = WINDOW_STATE_HIDDEN;
				break;
			}
		}
	}
}

void windowChangeState(HWND hWnd, WPARAM cmd, LPARAM)
{
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		switch (cmd) {
		case SC_CLOSE:
			wndInfo->eState = WINDOW_STATE_CLOSED;
			windowReposition(hWnd);
			break;

		case SC_MAXIMIZE:
			wndInfo->eState = WINDOW_STATE_MAXIMIZED;
			windowReposition(hWnd);
			break;

		case SC_MINIMIZE:
			wndInfo->eState = WINDOW_STATE_MINIMIZED;
			allWindowsActivation(hWnd);
			break;

		case SC_RESTORE:
		case SC_MOVE:
		case SC_SIZE:
			wndInfo->eState = WINDOW_STATE_NORMAL;
			allWindowsActivation(hWnd);
			windowReposition(hWnd);
			break;
		}
	}
}

void windowChangeState(HWND hWnd, WindowState newState)
{
	if (sWindowInfo* wndInfo = windowFind(hWnd)) {
		wndInfo->eState = newState;
		switch (newState) {
		case WINDOW_STATE_NORMAL:
		case WINDOW_STATE_HIDDEN:
			allWindowsActivation(hWnd);
			break;
		}
	}
}

void windowActivation(HWND hWnd, HWND prevhWnd)
{
	// Не активируем окно, если предыдущим активным было уже привязанное окно
	if (sWindowInfo* wndInfo = windowFind(prevhWnd))
		return;

	// Почему-то этот код приводит к скукоживанию КЛа / двойному восстановлению
	/*
	hWnd = windowGetRoot(hWnd);
	if (sWindowInfo* wndInfo = windowFind(hWnd))
	{
		 if (wndInfo->eState == WINDOW_STATE_MINIMIZED)
			  ShowWindow(wndInfo->hWnd, SW_RESTORE);
		 if (wndInfo->eState == WINDOW_STATE_HIDDEN)
			  ShowWindow(wndInfo->hWnd, SW_SHOW);
		 wndInfo->eState = WINDOW_STATE_NORMAL;

		 allWindowsActivation(hWnd);
	}
	*/

	for (auto itr = pluginVars.allWindows.begin(); itr != pluginVars.allWindows.end(); ++itr) {
		if (itr->hWnd != hWnd) {
			if (itr->eState == WINDOW_STATE_MINIMIZED) {
				ShowWindow(itr->hWnd, SW_RESTORE);
				// itr->eState = WINDOW_STATE_NORMAL; - ведет к глюкам
			}
			SetWindowPos(itr->hWnd, hWnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
		}
	}
}

LRESULT CALLBACK wndProcSync(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (!pluginIsAlreadyRunning()) {
		pluginSetProgress();

		windowListUpdate();

		switch (msg) {
		case WM_SYSCOMMAND:
			windowChangeState(hWnd, wParam, lParam);
			break;

		case WM_SIZE:
			allWindowsMoveAndSize(hWnd);
			break;

		case WM_MOVE:
			allWindowsMoveAndSize(hWnd);
			break;

		case WM_SHOWWINDOW:
			windowChangeState(hWnd, wParam ? WINDOW_STATE_NORMAL : WINDOW_STATE_HIDDEN);
			allWindowsMoveAndSize(hWnd);
			break;

		case WM_ACTIVATE:
			if (wParam)
				windowActivation(hWnd, (HWND)lParam);
			break;
		}

		pluginSetDone();
	}

	return mir_callNextSubclass(hWnd, wndProcSync, msg, wParam, lParam);
}

bool calcNewWindowPosition(HWND hWndLeading, HWND hWndDriven, sWndCoords* wndCoord, eWindowPosition wndPos)
{
	RECT rWndLeading, rWndDriven;

	if (!GetWindowRect(hWndLeading, &rWndLeading) || !GetWindowRect(hWndDriven, &rWndDriven))
		return false;

	wndCoord->width = rWndDriven.right - rWndDriven.left;
	wndCoord->height = rWndLeading.bottom - rWndLeading.top;
	wndCoord->y = rWndLeading.top;
	if (wndPos == WINDOW_POSITION_RIGHT)
		wndCoord->x = rWndLeading.left - wndCoord->width;
	else if (wndPos == WINDOW_POSITION_LEFT)
		wndCoord->x = rWndLeading.right;

	return true;
}