//-----------------------------------------------------------------------------
// File: useful.cpp
//
// Desc: Contains various utility classes and functions to help the
//       UI carry its operations more easily.
//
// Copyright (C) 1999-2001 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------

#include <windows.h>
#include <tchar.h>
#include <wchar.h>
#include <stdio.h>
#include <stdarg.h>

// assert include (make sure assert actually works with build)
#ifdef DBG
#undef NDEBUG
#endif
#include <assert.h>

#include "useful.h"
#include "collections.h"


/*--------- \/ stuff for collections.h \/ ---------*/
BOOL AfxIsValidAddress( const void* lp, UINT nBytes, BOOL bReadWrite)
{
//	return lp != NULL;

	// simple version using Win-32 APIs for pointer validation.
	return (lp != NULL && !IsBadReadPtr(lp, nBytes) &&
		(!bReadWrite || !IsBadWritePtr((LPVOID)lp, nBytes)));
}

CPlex* PASCAL CPlex::Create(CPlex*& pHead, UINT nMax, UINT cbElement)
{
	assert(nMax > 0 && cbElement > 0);
	CPlex* p = (CPlex*) new BYTE[sizeof(CPlex) + nMax * cbElement];
			// may throw exception
	if (p)
	{
		p->pNext = pHead;
		pHead = p;  // change head (adds in reverse order for simplicity)
	}
	return p;
}

void CPlex::FreeDataChain()     // free this one and links
{
	CPlex* p = this;
	while (p != NULL)
	{
		BYTE* bytes = (BYTE*) p;
		CPlex* pNext = p->pNext;
		delete[] bytes;
		p = pNext;
	}
}
/*--------- /\ stuff for collections.h /\ ---------*/


int ConvertVal(int x, int a1, int a2, int b1, int b2)
{
	assert(a1 != a2 && a2 - a1);
	return MulDiv(x - a1, b2 - b1, a2 - a1) + b1;
}

double dConvertVal(double x, double a1, double a2, double b1, double b2)
{
	assert(a1 != a2 && a2 - a1);
	return (x - a1) * (b2 - b1) / (a2 - a1) + b1;
}

SIZE GetRectSize(const RECT &rect)
{
	SIZE size = {
		rect.right - rect.left,
		rect.bottom - rect.top};
	return size;
}

SIZE GetTextSize(LPCTSTR tszText, HFONT hFont)
{
	if (!tszText)
	{
		SIZE z = {0, 0};
		return z;
	}
	RECT trect = {0, 0, 1, 1};
	HDC hDC = CreateCompatibleDC(NULL);
	if (hDC != NULL)
	{
		HGDIOBJ hOld = NULL;
		if (hFont)
			hOld = SelectObject(hDC, hFont);
		DrawText(hDC, tszText, -1, &trect, DT_CALCRECT | DT_NOPREFIX);
		if (hFont)
			SelectObject(hDC, hOld);
		DeleteDC(hDC);
	}
	SIZE size = {trect.right - trect.left, trect.bottom - trect.top};
	return size;
}

int GetTextHeight(HFONT hFont)
{
	static const TCHAR str[] = _T("Happy Test!  :D");
	SIZE size = GetTextSize(str, hFont);
	return size.cy;
}

int vFormattedMsgBox(HINSTANCE hInstance, HWND hParent, UINT uType, UINT uTitle, UINT uMsg, va_list args)
{
	int i;
	const int len = 1024;
	static TCHAR title[len], format[len], msg[len];

	if (!LoadString(hInstance, uTitle, title, len))
		_tcscpy(title, _T("(could not load title string)"));
	
	if (!LoadString(hInstance, uMsg, format, len))
		return MessageBox(hParent, _T("(could not load message/format string)"), title, uType);

#ifdef WIN95
	{
		char *psz = NULL;
		char szDfs[1024]={0};
		strncpy(szDfs,format, 1024);					// make a local copy of format string
		while (psz = strstr(szDfs,"%p"))				// find each %p
			*(psz+1) = 'x';								// replace each %p with %x
		i = _vsntprintf(msg, sizeof(msg)/sizeof(TCHAR), szDfs, args);			// use the local format string
	}
#else
	{
		i = _vsntprintf(msg, sizeof(msg)/sizeof(TCHAR), format, args);
	}
#endif
	// _vsntprintf may not null-terminate the buffer if the count of characters it writes is equal to the buffer size;
	// so null-terminate it just in case
	msg[len-1] = 0;

	if (i < 0)
		return MessageBox(hParent, _T("(could not format message)"), title, uType);

	if (i < 1)
		msg[0] = 0;

	return MessageBox(hParent, msg, title, uType);
}

int FormattedMsgBox(HINSTANCE hInstance, HWND hParent, UINT uType, UINT uTitle, UINT uMsg, ...)
{
	va_list args;
	va_start(args, uMsg);
	int i = vFormattedMsgBox(hInstance, hParent, uType, uTitle, uMsg, args);
	va_end(args);
	return i;
}

BOOL UserConfirm(HINSTANCE hInstance, HWND hParent, UINT uTitle, UINT uMsg, ...)
{
	va_list args;
	va_start(args, uMsg);
	int i = vFormattedMsgBox(hInstance, hParent, MB_ICONQUESTION | MB_YESNO, uTitle, uMsg, args);
	va_end(args);
	return i == IDYES;
}

int FormattedErrorBox(HINSTANCE hInstance, HWND hParent, UINT uTitle, UINT uMsg, ...)
{
	va_list args;
	va_start(args, uMsg);
	int i = vFormattedMsgBox(hInstance, hParent, MB_OK | MB_ICONSTOP, uTitle, uMsg, args);
	va_end(args);
	return i;
}

int FormattedLastErrorBox(HINSTANCE hInstance, HWND hParent, UINT uTitle, UINT uMsg, DWORD dwError)
{
	// format an error message from GetLastError().
	LPVOID lpMsgBuf = NULL;
	DWORD result = FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dwError,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf,
		0,
		NULL);

	if (!result || lpMsgBuf == NULL)
		return FormattedErrorBox(hInstance, hParent, uTitle, uMsg, 
			_T("An unknown error occured (could not format the error code)."));

	int i = FormattedErrorBox(hInstance, hParent, uTitle, uMsg, (LPCTSTR)lpMsgBuf);

	LocalFree(lpMsgBuf);

	return i;
}

LPTSTR AllocLPTSTR(LPCWSTR wstr)
{
	if (wstr == NULL)
		return NULL;

#ifdef UNICODE
	return _tcsdup(wstr);
#else
	int len = wcslen(wstr) * 2 + 1;
	char *ret = (char *)malloc(len);
	if (!ret)
		return NULL;
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, ret, len, NULL, NULL);
	ret[len-1] = '\0';
	return ret;
#endif
}

LPTSTR AllocLPTSTR(LPCSTR str)
{
	if (str == NULL)
		return NULL;

#ifndef UNICODE
	return _tcsdup(str);
#else
	int len = strlen(str);
	WCHAR *ret = (WCHAR *)malloc((len + 1) * sizeof(WCHAR));
	if (!ret)
		return NULL;
	mbstowcs(ret, str, len);
	ret[len] = L'\0';
	return ret;
#endif
}

void CopyStr(LPWSTR dest, LPCWSTR src, size_t max)
{
	if (dest == NULL || src == NULL)
		return;

	wcsncpy(dest, src, max);
}

void CopyStr(LPSTR dest, LPCSTR src, size_t max)
{
	if (dest == NULL || src == NULL)
		return;

	strncpy(dest, src, max);
}

void CopyStr(LPWSTR dest, LPCSTR src, size_t max)
{
	if (dest == NULL || src == NULL)
		return;

	mbstowcs(dest, src, max);
}

void CopyStr(LPSTR dest, LPCWSTR src, size_t max)
{
	if (dest == NULL || src == NULL)
		return;

	WideCharToMultiByte(CP_ACP, 0, src, -1, dest, max, NULL, NULL);
}

LPWSTR AllocLPWSTR(LPCWSTR wstr)
{
	if (wstr == NULL)
		return NULL;

	return _wcsdup(wstr);
}

LPWSTR AllocLPWSTR(LPCSTR str)
{
	if (str == NULL)
		return NULL;

	size_t len = strlen(str);
	size_t retsize = mbstowcs(NULL, str, len);
	WCHAR *ret = (WCHAR *)malloc((retsize + 1) * sizeof(WCHAR));
	if (!ret)
		return NULL;
	mbstowcs(ret, str, len);
	ret[retsize] = L'\0';
	return ret;
}

LPSTR AllocLPSTR(LPCWSTR wstr)
{
	if (wstr == NULL)
		return NULL;

	size_t len = wcslen(wstr);
	size_t retsize = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
	CHAR *ret = (CHAR *)malloc((retsize + 1) * sizeof(CHAR));
	if (!ret)
		return NULL;
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, ret, retsize, NULL, NULL);
	ret[retsize] = '\0';
	return ret;
}

LPSTR AllocLPSTR(LPCSTR str)
{
	if (str == NULL)
		return NULL;

	return _strdup(str);
}

LPTSTR AllocFileNameNoPath(LPTSTR path)
{
	TCHAR fname[_MAX_FNAME];
	TCHAR ext[_MAX_EXT];

	if (path == NULL) return NULL;

	_tsplitpath(path, NULL, NULL, fname, ext);

	LPTSTR ret = (LPTSTR)malloc(sizeof(TCHAR) * (_tcslen(fname) + _tcslen(ext) + 1));
	if (ret != NULL)
	{
		_tcscpy(ret, fname);
		_tcscat(ret, ext);
	}

	return ret;
}

LPTSTR utilstr::Eject()
{
	LPTSTR str = m_str;
	m_str = NULL;
	m_len = 0;
	return str;
}

void utilstr::Empty()
{
	if (m_str != NULL)
		free(m_str);
	m_len = 0;
}

bool utilstr::IsEmpty() const
{
	return !GetLength();
}

int utilstr::GetLength() const
{
	if (m_str == NULL)
		return 0;
	if (!m_len)
		return 0;

	return _tcslen(m_str);
}

void utilstr::Format(LPCTSTR format, ...)
{
	static TCHAR buf[2048];
	va_list args;
	va_start(args, format);
#ifdef WIN95
	{
		char *psz = NULL;
		char szDfs[1024]={0};
		strncpy(szDfs,format, 1024);  // make a local copy of format string
		while (psz = strstr(szDfs,"%p"))  // find each %p
			*(psz+1) = 'x';  // replace each %p with %x
		_vsntprintf(buf, sizeof(buf)/sizeof(TCHAR), szDfs, args);  // use the local format string
	}
#else
	{
		_vsntprintf(buf, sizeof(buf)/sizeof(TCHAR), format, args);
	}
#endif
	// _vsntprintf may not null-terminate the buffer if the count of characters it writes is equal to the buffer size
	// so null-terminate it just in case
	buf[2047] = 0;

	va_end(args);
	equal(buf);
}

void utilstr::equal(LPCTSTR str)
{
	Empty();
	if (str == NULL)
		return;
	m_len = _tcslen(str);
	m_str = (LPTSTR)malloc(sizeof(TCHAR) * (m_len + 1));
	if (m_str != NULL)
		_tcscpy(m_str, str);
	else
		m_len = 0;
}

void utilstr::add(LPCTSTR str)
{
	if (str == NULL)
		return;
	if (IsEmpty())
	{
		equal(str);
		return;
	}
	int len = _tcslen(str);
	int newlen = m_len + len;
	LPTSTR newstr = (LPTSTR)malloc(sizeof(TCHAR) * (newlen + 1));
	if (newstr == NULL)
		return;
	_tcscpy(newstr, m_str);
	_tcscat(newstr, str);
	Empty();
	m_str = newstr;
	m_len = newlen;
}

LPTSTR AllocFlagStr(DWORD value, const AFS_FLAG flag[], int flags)
{
	utilstr ret;	// return string
	DWORD allknownbits = 0;  // check value to see if there are any bits
	                         // set for which we don't have a define

	// handle each flag
	bool bflagfound = false;
	for (int i = 0; i < flags; i++)
	{
		// set bit for this flag in allknownbits
		allknownbits |= flag[i].value;

		// if this bit is set in the passed value, or the value
		// is zero and we're on the zero flag,
		// add the define for this bit/flag to the return string
		if (value ? value & flag[i].value : !flag[i].value)
		{
			// adding binary or operators between flags
			if (bflagfound)
				ret += _T(" | ");
			ret += flag[i].name;
			bflagfound = true;
		}
	}

	// now see if there are any unknown bits in passed flag
	DWORD unknownbits = value & ~allknownbits;
	if (unknownbits)
	{
		// add hex number for unknown bits
		utilstr unk;
		unk.Format(_T("0x%08X"), unknownbits);
		if (bflagfound)
			ret += _T(" | ");
		ret += unk;
	}

	// if value is zero (and no flags for zero) we should just set the string to "0"
	if (!value && !bflagfound)
		ret = _T("0");

	// now the string should definitely not be empty, in any case
	assert(!ret.IsEmpty());

	// finally, add a comment that has hex number for entire value
	// (for debugging)
	utilstr temp;
	temp.Format(_T(" /* 0x%08X */"), value);
	ret += temp;

	// done
	return ret.Eject();
}

void PutLinePoint(HDC hDC, POINT p)
{
	MoveToEx(hDC, p.x, p.y, NULL);
	LineTo(hDC, p.x + 1, p.y);
}

void PolyLineArrowShadow(HDC hDC, POINT *p, int i)
{
	PolyLineArrow(hDC, p, i, TRUE);
}

void PolyLineArrow(HDC hDC, POINT *rgpt, int nPoints, BOOL bDoShadow)
{
	int i;

	if (rgpt == NULL || nPoints < 1)
		return;

	if (nPoints > 1)
		for (i = 0; i < nPoints - 1; i++)
		{
			SPOINT a = rgpt[i], b = rgpt[i + 1];

			if (bDoShadow)
			{
				int rise = abs(b.y - a.y), run = abs(b.x - a.x);
				bool vert = rise > run;
				int ord = vert ? 1 : 0;
				int nord = vert ? 0 : 1;
				
				for (int o = -1; o <= 1; o += 2)
				{
					SPOINT c(a), d(b);
					c.a[nord] += o;
					d.a[nord] += o;
					MoveToEx(hDC, c.x, c.y, NULL);
					LineTo(hDC, d.x, d.y);
				}

				bool reverse = a.a[ord] > b.a[ord];
				SPOINT e(reverse ? b : a), f(reverse ? a : b);
				e.a[ord] -= 1;
				f.a[ord] += 1;
				PutLinePoint(hDC, e);
				PutLinePoint(hDC, f);
			}
			else
			{
				MoveToEx(hDC, a.x, a.y, NULL);
				LineTo(hDC, b.x, b.y);
			}
		}

	POINT z = rgpt[nPoints - 1];

	if (bDoShadow)
	{
		POINT pt[5] = {
			{z.x, z.y + 2},
			{z.x + 2, z.y},
			{z.x, z.y - 2},
			{z.x - 2, z.y}, };
		pt[4] = pt[0];
		Polyline(hDC, pt, 5);
	}
	else
	{
		MoveToEx(hDC, z.x - 1, z.y, NULL);
		LineTo(hDC, z.x + 2, z.y);
		MoveToEx(hDC, z.x, z.y - 1, NULL);
		LineTo(hDC, z.x, z.y + 2);
	}
}

BOOL bEq(BOOL a, BOOL b)
{
	bool c = !a, d = !b;
	return (c == d) ? TRUE : FALSE;
}

void DrawArrow(HDC hDC, const RECT &rect, BOOL bVert, BOOL bUpLeft)
{
	SRECT srect = rect;
	srect.right--;
	srect.bottom--;
	int ord = bVert ? 1 : 0;
	int nord = bVert ? 0 : 1;
	SPOINT p(!bUpLeft ? srect.lr : srect.ul), b(!bUpLeft ? srect.ul : srect.lr);
	b.a[ord] += bUpLeft ? -1 : 1;
	SPOINT t = p;
	t.a[nord] = (p.a[nord] + b.a[nord]) / 2;
	SPOINT u;
	u.a[ord] = b.a[ord];
	u.a[nord] = p.a[nord];
	POINT poly[] = { {t.x, t.y}, {u.x, u.y}, {b.x, b.y} };
	Polygon(hDC, poly, 3);
}

BOOL ScreenToClient(HWND hWnd, LPRECT rect)
{
	if (rect == NULL)
		return FALSE;

	SRECT sr = *rect;

	if (ScreenToClient(hWnd, &sr.ul.p) &&
	    ScreenToClient(hWnd, &sr.lr.p))
	{
		*rect = sr;
		return TRUE;
	}

	return FALSE;
}

BOOL ClientToScreen(HWND hWnd, LPRECT rect)
{
	if (rect == NULL)
		return FALSE;

	SRECT sr = *rect;

	if (ClientToScreen(hWnd, &sr.ul.p) &&
	    ClientToScreen(hWnd, &sr.lr.p))
	{
		*rect = sr;
		return TRUE;
	}

	return FALSE;
}

#define z ((L"\0")[0])

int StrLen(LPCWSTR s)
{
	if (s == NULL)
		return 0;

	return wcslen(s);
}

int StrLen(LPCSTR s)
{
	if (s == NULL)
		return 0;

	return strlen(s);
}

