/* AbiSource Application Framework
 * Copyright (C) 1998-2000 AbiSource, Inc.
 * BIDI Copyright (c) 2001-2004 Tomas Frydrych
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <direct.h>
#include "ut_assert.h"
#include "ut_debugmsg.h"
#include "ut_Win32Uuid.h"
#include "xap_Args.h"
#include "xap_Win32App.h"
#include "xap_Win32Clipboard.h"
#include "xap_Frame.h"
#include "xap_Win32FrameImpl.h"
#include "xap_Win32Toolbar_Icons.h"
#include "xap_Win32_TB_CFactory.h"
#include "xap_Win32Slurp.h"
#include "xap_Win32EncodingManager.h"
#include "xap_Prefs.h"
#include "gr_Win32Graphics.h"
#include "gr_Win32USPGraphics.h"

#include <locale.h>

#ifdef _MSC_VER
#pragma warning(disable:4355)	// 'this' used in base member initializer list
#endif

#ifdef __MINGW32__
#include <w32api.h>
#endif

#ifdef UNICODE
char XAP_Win32App::m_buffer[MAX_CONVBUFFER] = "";
WCHAR XAP_Win32App::m_wbuffer[MAX_CONVBUFFER] = L"";
#endif

// do not want to include wv.h here, since it defines some types that
// overlap with stuff in windov.h
extern "C" {const char * wvLIDToLangConverter(UT_uint16);}

/*****************************************************************/

XAP_Win32App::XAP_Win32App(HINSTANCE hInstance, XAP_Args * pArgs, const char * szAppName)
:	XAP_App(pArgs, szAppName),
	m_hInstance(hInstance),
	m_dialogFactory(this)
{
	UT_return_if_fail(hInstance);

	_setAbiSuiteLibDir();
	_setBidiOS();

	// create an instance of UT_UUIDGenerator or appropriate derrived class
	_setUUIDGenerator(new UT_Win32UUIDGenerator());

	// for some reason on win32 the locale used by things like
	// strftime is not set correctly; what's worse, MS setlocale does
	// not recognise standard ISO language and country codes. So, here
	// we retrieve the long language and country name for the users
	// default locale and pass these to setlocale. As long as folk use
	// UT_LocaleTransactor to temporarily change to some other locale,
	// this will ensure the C lib uses correct locale. Tomas Feb 5, 2004
	UT_String s;
	char buf[100];

	GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SENGLANGUAGE, &buf[0], 100);
	s += buf;
	s += "_";
	
	GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SENGCOUNTRY, &buf[0], 100);
	s += buf;
	setlocale(LC_ALL, s.c_str());

	// register graphics allocator
	GR_GraphicsFactory * pGF = getGraphicsFactory();
	UT_ASSERT_HARMLESS( pGF );

	if(pGF)
	{
		bool bSuccess = pGF->registerClass(GR_Win32Graphics::graphicsAllocator,
										   GR_Win32Graphics::graphicsDescriptor,
										   GR_Win32Graphics::s_getClassId());

		// we are in deep trouble if this did not succeed
		UT_return_if_fail( bSuccess );

		// this is our fall back ...
		pGF->registerAsDefault(GR_Win32Graphics::s_getClassId(), true);
		pGF->registerAsDefault(GR_Win32Graphics::s_getClassId(), false);
		
		// try to load Uniscribe; if we succeed we will make USP
		// graphics the default

#if ABI_OPT_DISABLE_USP
		HINSTANCE hUniscribe = NULL;
#else
		HINSTANCE hUniscribe = LoadLibrary("usp10.dll");
#endif

		if(hUniscribe)
		{
			// register Uniscribe graphics and make it the default
			bSuccess = pGF->registerClass(GR_Win32USPGraphics::graphicsAllocator,
										  GR_Win32USPGraphics::graphicsDescriptor,
										  GR_Win32USPGraphics::s_getClassId());

			UT_ASSERT_HARMLESS( bSuccess );
			if(bSuccess)
			{
				pGF->registerAsDefault(GR_Win32USPGraphics::s_getClassId(), true);
				pGF->registerAsDefault(GR_Win32USPGraphics::s_getClassId(), false);
			
				// now free the library (GR_Win32USPGraphics will load it
				// on its own behalf
			
				FreeLibrary(hUniscribe);
			}
		}
		else
		{
			UT_DEBUGMSG(("XAP_Win32App: could not load Uniscribe library"));
		}
	}
}

XAP_Win32App::~XAP_Win32App(void)
{
	m_pSlurp->disconnectSlurper();
	DELETEP(m_pSlurp);
}

HINSTANCE XAP_Win32App::getInstance() const
{
	return m_hInstance;
}

bool XAP_Win32App::initialize(const char * szKeyBindingsKey, const char * szKeyBindingsDefaultValue)
{
	// let our base class do it's thing.

	XAP_App::initialize(szKeyBindingsKey, szKeyBindingsDefaultValue);

	// do anything else we need here...

	m_pSlurp = new XAP_Win32Slurp(this);
	m_pSlurp->connectSlurper();
	char bufExePathname[4096];
	GetModuleFileNameA(NULL,bufExePathname,NrElements(bufExePathname)); //!TODO Using ANSI function

	// TODO these are Application-Specific values.  Move them out of here.
	m_pSlurp->stuffRegistry(".abw",getApplicationName(),bufExePathname,"application/abiword");
	m_pSlurp->stuffRegistry(".zabw",getApplicationName(),bufExePathname,"application/abiword-compressed");

	return true;
}

void XAP_Win32App::reallyExit(void)
{
	PostQuitMessage (0);
}

UT_sint32 XAP_Win32App::makeDirectory(const char * szPath, const UT_sint32 mode ) const { return mkdir(szPath); }

XAP_DialogFactory * XAP_Win32App::getDialogFactory(void)
{
	return &m_dialogFactory;
}

XAP_Toolbar_ControlFactory * XAP_Win32App::getControlFactory(void)
{
	return &m_controlFactory;
}

UT_uint32 XAP_Win32App::_getExeDir(char* pDirBuf, UT_uint32 iBufLen) // ansi only dirs for now
{
	UT_uint32 iResult = GetModuleFileNameA(NULL, pDirBuf, iBufLen); //!TODO Using ANSI function

	if (iResult > 0)
	{
		char* p = pDirBuf + strlen(pDirBuf);
		while (*p != '\\')
		{
			p--;
		}
		UT_ASSERT(p > pDirBuf);
		p++;
		*p = 0;
	}

	return iResult;
}

const char * XAP_Win32App::getUserPrivateDirectory(void) // ansi only dirs for now
{
	/* return a pointer to a static buffer */

	char * szAbiDir = "AbiSuite";

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

	static char buf[PATH_MAX];
	memset(buf,0,sizeof(buf));

	DWORD len, len1, len2;

	// On NT, USERPROFILE seems to be set to the directory containing per-user
	// information.  we'll try that first.

	len = GetEnvironmentVariableA("USERPROFILE",buf,PATH_MAX); //!TODO Using ANSI function
	if (len)
	{
		UT_DEBUGMSG(("Getting preferences directory from USERPROFILE [%s].\n",buf));
	}
	else
	{
		// If that doesn't work, look for HOMEDRIVE and HOMEPATH.  HOMEPATH
		// is mentioned in the GetWindowsDirectory() documentation at least.
		// These may be set if the SysAdmin did so in the Admin tool....

		len1 = GetEnvironmentVariableA("HOMEDRIVE",buf,PATH_MAX); //!TODO Using ANSI function
		len2 = GetEnvironmentVariableA("HOMEPATH",&buf[len1],PATH_MAX-len1); //!TODO Using ANSI function
		if (len1 && len2)
		{
			UT_DEBUGMSG(("Getting preferences directory from HOMEDRIVE and HOMEPATH [%s].\n",buf));
		}
		else
		{
			// If that doesn't work, let's just stick it in the WINDOWS directory.

			len = GetWindowsDirectoryA(buf,PATH_MAX); //!TODO Using ANSI function
			if (len)
			{
				UT_DEBUGMSG(("Getting preferences directory from GetWindowsDirectory() [%s].\n",buf));
			}
			else
			{
				// If that doesn't work, stick it in "C:\"...

				strcpy(buf,"C:\\");
			}
		}
	}

	if (strlen(buf)+strlen(szAbiDir)+2 >= PATH_MAX)
		return NULL;

	if (buf[strlen(buf)-1] != '\\')
		strcat(buf,"\\");
	strcat(buf,szAbiDir);
	return buf;
}

static void s_buildDirName(const UT_Vector& vDirComponents, UT_uint32 iComponentsNeeded, char* pDirBuf)
{
	UT_ASSERT(iComponentsNeeded <= vDirComponents.getItemCount());

	if(iComponentsNeeded == 0)
	{
		strcpy(pDirBuf, "\\");
		return;
	}

	UT_uint32 i = 0;
	strcpy(pDirBuf, (const char*) vDirComponents.getNthItem(i++));

	while(--iComponentsNeeded)
	{
		strcat(pDirBuf, "\\");
		strcat(pDirBuf, (const char*) vDirComponents.getNthItem(i++));
	}
}

void XAP_Win32App::_setAbiSuiteLibDir(void)
{
	char buf[PATH_MAX];
	char buf2[PATH_MAX];
	char szApplicationName[PATH_MAX];
	const char * sz = NULL;

	// see if a command line option [-lib <AbiSuiteLibraryDirectory>] was given

	int kLimit = m_pArgs->m_argc;

	for (int k=1; k<kLimit; k++)
		if ((*m_pArgs->m_argv[k] == '-') && (UT_stricmp(m_pArgs->m_argv[k],"-lib")==0) && (k+1 < kLimit))
		{
			strcpy(buf,m_pArgs->m_argv[k+1]);
			int len = strlen(buf);
			if (buf[len-1]=='\\')		// trim trailing slash
				buf[len-1] = 0;
			XAP_App::_setAbiSuiteLibDir(buf);
			return;
		}

	// if not, see if ABISUITE_HOME was set in the environment

	if (GetEnvironmentVariableA("ABISUITE_HOME",buf,sizeof(buf)) > 0) //!TODO Using ANSI function
	{
		char * p = buf;
		int len = strlen(p);
		if ( (p[0]=='"') && (p[len-1]=='"') )
		{
			// trim leading and trailing DQUOTES
			p[len-1]=0;
			p++;
			len -= 2;
		}
		if (p[len-1]=='\\')				// trim trailing slash
			p[len-1] = 0;
		XAP_App::_setAbiSuiteLibDir(p);
		return;
	}

	// [Win32 only] if not, use something relative to <exedir>
	// if we are in normal distribution format, we have:
	//
	// .../AbiSuite/AbiWord/bin/AbiWord.exe
	//                     /strings/*.strings
	//                     /help/en-US/*.html
	//                     /samples/en-US/*.abw
	//             /AbiShow/bin/AbiShow.exe
	//                     /strings/*.strings
	//                     /help/en-US/*.html
	//                     /samples/en-US/*.abw
	//             /dictionary/*.hash
	//
	// we want to set the library directory to the
	// directory which stand for .../AbiSuite
	// (aka "getExeDir()/../..")
	//
	// if this is a developer build in the canonical build
	// directory, we have:
	//
	// $(OUT)/$os_..._$dbg/bin/AbiWord.exe
	//                        /AbiShow.exe
	//                    /obj/*.obj
	//                    /AbiSuite/AbiWord/strings/*.strings
	//                                     /help/...
	//                                     /samples/...
	//                             /AbiShow/...
	//                             /dictionary/*.hash
	//
	// note that the bin directory is in a different place.
	// in this case, we want to set the library directory to
	// $(OUT)/$os_..._$dbg/AbiSuite
	// (aka "getExeDir()/../AbiSuite")

	if (_getExeDir(buf,sizeof(buf)) > 0)
	{
		int len = strlen(buf);
		if (buf[len-1]=='\\')
			buf[len-1] = 0;

		strcpy(buf2,buf);

		UT_Vector v;
		char * p = strtok(buf2,"\\");
		v.addItem(p);
		while ( (p=strtok(NULL,"\\")) )
			v.addItem(p);

		strcpy(szApplicationName, getApplicationName());
		strtok(szApplicationName, " ");

		int n = v.getItemCount();
		if (   (n > 2)
			&& (UT_stricmp((const char *)v.getNthItem(n-1),"bin")==0)
			&& (UT_stricmp((const char *)v.getNthItem(n-2),szApplicationName)==0))
		{
			s_buildDirName(v, n - 2, buf);
			XAP_App::_setAbiSuiteLibDir(buf);
			return;
		}

		if (   (n > 1)
			&& (UT_stricmp((const char *)v.getNthItem(n-1),"bin")==0))
		{
			s_buildDirName(v, n - 1, buf);
			strcat(buf,"\\AbiSuite");
			XAP_App::_setAbiSuiteLibDir(buf);
			return;
		}
	}

	// otherwise, use the hard-coded value
	XAP_App::_setAbiSuiteLibDir(getAbiSuiteHome());
	return;
}

void XAP_Win32App::enableAllTopLevelWindows(bool b)
{
	UT_uint32 iCount = m_vecFrames.getItemCount();

	for (UT_uint32 ndx=0; ndx<iCount; ndx++)
	{
		XAP_Frame * pFrame = (XAP_Frame *) m_vecFrames.getNthItem(ndx);

		EnableWindow(static_cast<XAP_Win32FrameImpl*>(pFrame->getFrameImpl())->getTopLevelWindow(), b);
	}
}

// This function gets saved geometry from preference file, attempts to validate
// and sets window's placement.  It returns the flag that should be called to
// ShowWindow on the first call.
UT_sint32 XAP_Win32App::setupWindowFromPrefs(UT_sint32 iCmdShow, HWND hwndFrame)
{
	UT_uint32 iHeight, iWidth;
	UT_sint32 x,y;
	UT_uint32 flag;

	// if width & height are <= 0 then assume invalid values so just use system defaults
	if (getGeometry(&x,&y,&iWidth,&iHeight,&flag) && (iWidth > 0) && (iHeight > 0))
	{
		WINDOWPLACEMENT wndPlacement;
		wndPlacement.length = sizeof(WINDOWPLACEMENT); // must do
		// get current windowplacement info, so anything we don't set is correct
		if (!GetWindowPlacement(hwndFrame, &wndPlacement))
		{
			memset(&wndPlacement, 0, sizeof(WINDOWPLACEMENT));
			wndPlacement.length = sizeof(WINDOWPLACEMENT);
		}
		if (flag && (flag <= SW_MAX)) // validate flag
		{
			wndPlacement.showCmd = flag;
			iCmdShow = flag; /* SW_SHOW; */
		}
		else
		{
			wndPlacement.showCmd = iCmdShow; /* SW_SHOWNORMAL; */
		}
		wndPlacement.rcNormalPosition.left = x;
		wndPlacement.rcNormalPosition.top = y;
		wndPlacement.rcNormalPosition.right = x + iWidth;
		wndPlacement.rcNormalPosition.bottom = y + iHeight;
		SetWindowPlacement(hwndFrame, &wndPlacement);
	}

	return iCmdShow;
}

/*
	most of the code in the following function comes from Mozilla
*/
void XAP_Win32App::_setBidiOS(void)
{

	m_eBidiOS = XAP_App::BIDI_SUPPORT_NONE;
/*
	I have run into problems with the built-in win32 bidi support -- it is inconsistent
	It treats some fonts (i.e., the MS fonts) correctly, but some common Hebrew fonts
	it will not reorder. Consequently, I felt that the best solution would be to disable
	the win32 bidi altogether and treating it as a non-bidi system, but there does
	not seem to be a simple way of doing this

	I am going to have another shot at disabling the built-in support
	for the main window, and leaving it for the GUI only. The main
	changes are in gr_Win32Graphics class. Tomas, Jan 19th, 2003
*/
	const UT_UCS2Char araAin  = 0x0639;
	const UT_UCS2Char one     = 0x0031;

	int distanceArray[2];
	UT_UCS2Char glyphArray[2];
	UT_UCS2Char outStr[] = {0, 0};

	GCP_RESULTSW gcpResult;
	gcpResult.lStructSize = sizeof(GCP_RESULTS);
	gcpResult.lpOutString = (LPWSTR) outStr;     // Output string
	gcpResult.lpOrder = NULL;			// Ordering indices
	gcpResult.lpDx = distanceArray;     // Distances between character cells
	gcpResult.lpCaretPos = NULL;		// Caret positions
	gcpResult.lpClass = NULL;         // Character classifications
// w32api changed lpGlyphs from UINT * to LPWSTR to match MS PSDK in w32api v2.4
#ifdef __MINGW32__
#if (__W32API_MAJOR_VERSION == 2 && __W32API_MINOR_VERSION < 4)
	gcpResult.lpGlyphs = (UINT *) glyphArray;    // Character glyphs
#else
	gcpResult.lpGlyphs = (LPWSTR) glyphArray;    // Character glyphs
#endif
#else	
	gcpResult.lpGlyphs = (unsigned short *) glyphArray;    // Character glyphs
#endif
	gcpResult.nGlyphs = 2;              // Array size

	UT_UCS2Char inStr[] = {araAin, one};

	HDC displayDC = GetDC(NULL);

	if(!displayDC)
	{
		return;
	}

	if (GetCharacterPlacementW(displayDC, (LPCWSTR)inStr, 2, 0, &gcpResult, GCP_REORDER)
		&& (inStr[0] == outStr[1]) )
	{
		m_eBidiOS = XAP_App::BIDI_SUPPORT_GUI;
		UT_DEBUGMSG(("System has bidi and glyph shaping\n"));
	}
	else
	{
		const UT_UCSChar hebAlef = 0x05D0;
		inStr[0] = hebAlef;
		inStr[1] = one;
		if (GetCharacterPlacementW(displayDC, (LPCWSTR)inStr, 2, 0, &gcpResult, GCP_REORDER)
			&& (inStr[0] == outStr[1]) )
		{
			m_eBidiOS = XAP_App::BIDI_SUPPORT_GUI;
			UT_DEBUGMSG(("System has bidi\n"));
		}
	}

	ReleaseDC(NULL,displayDC);
}

const char * XAP_Win32App::getDefaultEncoding () const
{
	#ifdef UNICODE
	return "UTF-8";
	#else
	XAP_EncodingManager * pEncodingManager = XAP_EncodingManager::get_instance();
	return pEncodingManager->getNativeSystemEncodingName();
	#endif
	
}

#ifdef UNICODE
const WCHAR * XAP_Win32App::getWideString (const char * utf8input)
{
	int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8input, -1, NULL, 0);
	UT_ASSERT(wlen);
	if (wlen && (wlen < MAX_CONVBUFFER)) {
		wlen = MultiByteToWideChar(CP_UTF8, 0, utf8input, -1, m_wbuffer, MAX_CONVBUFFER);
		UT_ASSERT(wlen);
		return m_wbuffer;
	}
	else 
	{
		UT_ASSERT(wlen < MAX_CONVBUFFER);
		UT_DEBUGMSG(("getWideString:converted string too long %d", wlen));
		return NULL;
	}
}

const char * XAP_Win32App::getUTF8String (const WCHAR * p_str)
{
	int len = WideCharToMultiByte(CP_UTF8, 0, p_str, -1, NULL, 0, NULL, NULL);
	UT_ASSERT(len);
	if (len && (len < MAX_CONVBUFFER)) {
		len = WideCharToMultiByte(CP_UTF8, 0, p_str, -1, m_buffer, MAX_CONVBUFFER, NULL, NULL);
		UT_ASSERT(len);
		return m_buffer;
	}
	else 
	{
		UT_ASSERT(len < MAX_CONVBUFFER);
		UT_DEBUGMSG(("getUTF8String:converted string too long %d", len));
		return NULL;
	}
}


#else
const char * XAP_Win32App::getWideString (const char * utf8input)
{
	return utf8input;
}

const char * XAP_Win32App::getUTF8String (const char * p_str)
{
	return p_str;
}

#endif


void XAP_Win32App::getDefaultGeometry(UT_uint32& width, UT_uint32& height, UT_uint32& flags)
{
	flags |= PREF_FLAG_GEOMETRY_MAXIMIZED;
	
	width = GetSystemMetrics(SM_CXFULLSCREEN);
	height = GetSystemMetrics(SM_CYFULLSCREEN);	
}

const char * XAP_Win32App::_getKbdLanguage()
{
	// make sure that m_hkl is in sync with the current value
	m_hkl = GetKeyboardLayout(0);
	
	WORD langID = LANGIDFROMLCID(LOWORD(m_hkl));
	const char * pszLang = wvLIDToLangConverter((unsigned short)langID);
	UT_DEBUGMSG(("XAP_Win32App::_getKbdLanguage: %s\n",pszLang));
	return pszLang;
}

// wrapper around XAP_App::setKbdLanguage(const char *)
void XAP_Win32App::setKbdLanguage(HKL hkl)
{
	m_hkl = hkl;

	WORD langID = LANGIDFROMLCID(LOWORD(hkl));
	const char * pszLang = wvLIDToLangConverter((unsigned short)langID);
	UT_DEBUGMSG(("XAP_Win32App::setKbdLanguage: %s\n",pszLang));

	// set the language
	XAP_App::setKbdLanguage(pszLang);
}