/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include <stdio.h>
#include <string.h>

#ifdef LINUX
#  ifndef __USE_XOPEN
#    define __USE_XOPEN
#  endif
#endif
#include <poll.h>

#include <tools/prex.h>
#include <X11/Xlocale.h>
#include <X11/Xlib.h>
#include <unx/XIM.h>
#include <tools/postx.h>

#include "unx/salunx.h"
#include "unx/saldisp.hxx"
#include "unx/i18n_im.hxx"
#include "unx/i18n_status.hxx"

#include <osl/thread.h>
#include <osl/process.h>

using namespace vcl;
#include "unx/i18n_cb.hxx"
#if defined(SOLARIS) ||  defined(LINUX)
extern "C" char * XSetIMValues(XIM im, ...);
#endif

// ------------------------------------------------------------------------------------
//
// kinput2 IME needs special key handling since key release events are filtered in
// preeditmode and XmbResetIC does not work
//
// ------------------------------------------------------------------------------------

Bool
IMServerKinput2 ()
{
    const static char* p_xmodifiers = getenv ("XMODIFIERS");
    const static Bool  b_kinput2    =    (p_xmodifiers != NULL)
                                      && (strcmp(p_xmodifiers, "@im=kinput2") == 0);

    return b_kinput2;
}

class XKeyEventOp : XKeyEvent
{
    private:
        void            init();

    public:
                        XKeyEventOp();
                        ~XKeyEventOp();

        XKeyEventOp&    operator= (const XKeyEvent &rEvent);
        void            erase ();
        Bool            match (const XKeyEvent &rEvent) const;
};

void
XKeyEventOp::init()
{
    type        = 0; /* serial = 0; */
    send_event  = 0; display   = 0;
    window      = 0; root      = 0;
    subwindow   = 0; /* time   = 0; */
 /* x           = 0; y         = 0; */
 /* x_root      = 0; y_root    = 0; */
    state       = 0; keycode   = 0;
    same_screen = 0;
}

XKeyEventOp::XKeyEventOp()
{
    init();
}

XKeyEventOp::~XKeyEventOp()
{
}

XKeyEventOp&
XKeyEventOp::operator= (const XKeyEvent &rEvent)
{
    type        = rEvent.type;     /* serial  = rEvent.serial; */
    send_event  = rEvent.send_event;  display = rEvent.display;
    window      = rEvent.window;      root    = rEvent.root;
    subwindow   = rEvent.subwindow;/* time    = rEvent.time;   */
 /* x           = rEvent.x,           y       = rEvent.y;      */
 /* x_root      = rEvent.x_root,      y_root  = rEvent.y_root; */
    state       = rEvent.state;       keycode = rEvent.keycode;
    same_screen = rEvent.same_screen;

    return *this;
}

void
XKeyEventOp::erase ()
{
    init();
}

Bool
XKeyEventOp::match (const XKeyEvent &rEvent) const
{
    return (   (type == XLIB_KeyPress   && rEvent.type == KeyRelease)
            || (type == KeyRelease && rEvent.type == XLIB_KeyPress  ))
         /* && serial      == rEvent.serial */
            && send_event  == rEvent.send_event
            && display     == rEvent.display
            && window      == rEvent.window
            && root        == rEvent.root
            && subwindow   == rEvent.subwindow
         /* && time        == rEvent.time
            && x           == rEvent.x
            && y           == rEvent.y
            && x_root      == rEvent.x_root
            && y_root      == rEvent.y_root */
            && state       == rEvent.state
            && keycode     == rEvent.keycode
            && same_screen == rEvent.same_screen;
}

// -------------------------------------------------------------------------
//
// locale handling
//
// -------------------------------------------------------------------------

//  Locale handling of the operating system layer

static char*
SetSystemLocale( const char* p_inlocale )
{
	char *p_outlocale;

	if ( (p_outlocale = setlocale(LC_ALL, p_inlocale)) == NULL )
	{
		fprintf( stderr, "I18N: Operating system doesn't support locale \"%s\"\n",
			p_inlocale );
	}

	return p_outlocale;
}

#ifdef SOLARIS
static void
SetSystemEnvironment( const rtl::OUString& rLocale )
{
    rtl::OUString LC_ALL_Var(RTL_CONSTASCII_USTRINGPARAM("LC_ALL"));
    osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData);

    rtl::OUString LANG_Var(RTL_CONSTASCII_USTRINGPARAM("LANG"));
    osl_setEnvironment(LANG_Var.pData, rLocale.pData);
}
#endif

static Bool
IsPosixLocale( const char* p_locale )
{
	if ( p_locale == NULL )
		return False;
	if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') )
		return True;
	if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 )
		return True;

	return False;
}

//  Locale handling of the X Window System layer

static Bool
IsXWindowCompatibleLocale( const char* p_locale )
{
	if ( p_locale == NULL )
		return False;

	if ( !XSupportsLocale() )
	{
		fprintf (stderr, "I18N: X Window System doesn't support locale \"%s\"\n",
				p_locale );
		return False;
	}
	return True;
}

// Set the operating system locale prior to trying to open an 
// XIM InputMethod.
// Handle the cases where the current locale is either not supported by the
// operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho) 
// by providing a fallback.
// Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents
// see i8988, i9188, i8930, i16318
// on Solaris the environment needs to be set equivalent to the locale (#i37047#)

Bool
SalI18N_InputMethod::SetLocale( const char* pLocale )
{
	// check whether we want an Input Method engine, if we don't we
	// do not need to set the locale
	if ( mbUseable )
	{
		char *locale = SetSystemLocale( pLocale );
		if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) )
		{
            osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1);
            locale = SetSystemLocale( "en_US" );
            #ifdef SOLARIS
            SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("en_US")) );
            #endif
		    if (! IsXWindowCompatibleLocale(locale))
            {
			    locale = SetSystemLocale( "C" );
                #ifdef SOLARIS
                SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("C")) );
                #endif
		        if (! IsXWindowCompatibleLocale(locale))
				    mbUseable = False;
            }
		}
		
        // must not fail if mbUseable since XSupportsLocale() asserts success
		if ( mbUseable && XSetLocaleModifiers("") == NULL )
		{
			fprintf (stderr, "I18N: Can't set X modifiers for locale \"%s\"\n",
				locale);
			mbUseable = False;
		}
	}

	return mbUseable;
}

Bool
SalI18N_InputMethod::PosixLocale()
{
    if (mbMultiLingual)
        return False;
    if (maMethod)
        return IsPosixLocale (XLocaleOfIM (maMethod));
    return False;
}

// ------------------------------------------------------------------------
//
// Constructor / Destructor / Initialisation
//
// ------------------------------------------------------------------------

SalI18N_InputMethod::SalI18N_InputMethod( ) : mbUseable( bUseInputMethodDefault ),
											  mbMultiLingual( False ),
                                              maMethod( (XIM)NULL ),
								  			  mpStyles( (XIMStyles*)NULL )
{
	const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" );
	if ( pUseInputMethod != NULL )
		mbUseable = pUseInputMethod[0] != '\0' ;
}

SalI18N_InputMethod::~SalI18N_InputMethod()
{
    ::vcl::I18NStatus::free();
	if ( mpStyles != NULL )
		XFree( mpStyles );
	if ( maMethod != NULL )
		XCloseIM ( maMethod );
}

//
// XXX
// debug routine: lets have a look at the provided method styles
//

#if OSL_DEBUG_LEVEL > 1

extern "C" char*
GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize)
{
	struct StyleName {
		const XIMStyle nStyle;
		const char    *pName;
		const int      nNameLen;
	};

	StyleName *pDescPtr;
	static const StyleName pDescription[] = {
		{ XIMPreeditArea, 	   "PreeditArea ", 	   sizeof("PreeditArea ")	},
		{ XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")},
		{ XIMPreeditPosition,  "PreeditPosition ", sizeof("PreeditPosition ") },
		{ XIMPreeditNothing,   "PreeditNothing ",  sizeof("PreeditNothing ")  },
		{ XIMPreeditNone, 	   "PreeditNone ",	   sizeof("PreeditNone ")	},
		{ XIMStatusArea, 	   "StatusArea ",      sizeof("StatusArea ")	},
		{ XIMStatusCallbacks,  "StatusCallbacks ", sizeof("StatusCallbacks ") },
		{ XIMStatusNothing,    "StatusNothing ",   sizeof("StatusNothing ")	},
		{ XIMStatusNone, 	   "StatusNone ",      sizeof("StatusNone ")	},
		{ 0, "NULL", 0 }
	};

	if ( nBufSize > 0 )
		pBuf[0] = '\0';

	char *pBufPtr = pBuf;
	for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ )
	{
		int nSize = pDescPtr->nNameLen - 1;
		if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) )
		{
			strncpy( pBufPtr, pDescPtr->pName, nSize + 1);
			pBufPtr  += nSize;
			nBufSize -= nSize;
		}
	}

	return pBuf;
}

extern "C" void
PrintInputStyle( XIMStyles *pStyle )
{
	char pBuf[ 128 ];
	int  nBuf = sizeof( pBuf );

	if ( pStyle == NULL )
		fprintf( stderr, "no input method styles\n");
	else
	for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ )
	{
		fprintf( stderr, "style #%i = %s\n", nStyle,
		  	GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf) );
	}
}

#endif

//
// this is the real constructing routine, since locale setting has to be done
// prior to xopendisplay, the xopenim call has to be delayed
//

Bool
SalI18N_InputMethod::CreateMethod ( Display *pDisplay )
{
	if ( mbUseable )
	{
        const bool bTryMultiLingual =
        #ifdef LINUX
                        false;
        #else
                        true;
        #endif
		if ( bTryMultiLingual && getenv("USE_XOPENIM") == NULL )
		{
			mbMultiLingual = True; // set ml-input flag to create input-method
	    	maMethod = XvaOpenIM(pDisplay, NULL, NULL, NULL,
					XNMultiLingualInput, mbMultiLingual, /* dummy */
			 		(void *)0);
			// get ml-input flag from input-method
			if ( maMethod == (XIM)NULL )
				mbMultiLingual = False;
			else
			if ( XGetIMValues(maMethod,
					XNMultiLingualInput, &mbMultiLingual, NULL ) != NULL )
				mbMultiLingual = False;
            if( mbMultiLingual )
            {
                XIMUnicodeCharacterSubsets* subsets;
                if( XGetIMValues( maMethod,
                                  XNQueryUnicodeCharacterSubset, &subsets, NULL ) == NULL )
                {
#if OSL_DEBUG_LEVEL > 1
                    fprintf( stderr, "IM reports %d subsets: ", subsets->count_subsets );
#endif
                    I18NStatus& rStatus( I18NStatus::get() );
                    rStatus.clearChoices();
                    for( int i = 0; i < subsets->count_subsets; i++ )
                    {
#if OSL_DEBUG_LEVEL > 1
                        fprintf( stderr,"\"%s\" ", subsets->supported_subsets[i].name );
#endif
                        rStatus.addChoice( String( subsets->supported_subsets[i].name, RTL_TEXTENCODING_UTF8 ), &subsets->supported_subsets[i] );
                    }
#if OSL_DEBUG_LEVEL > 1
                    fprintf( stderr, "\n" );
#endif
                }
#if OSL_DEBUG_LEVEL > 1
                else
                    fprintf( stderr, "query subsets failed\n" );
#endif
            }
		}
		else
	    {
		    maMethod = XOpenIM(pDisplay, NULL, NULL, NULL);
			mbMultiLingual = False;
        }

        if ((maMethod == (XIM)NULL) && (getenv("XMODIFIERS") != NULL))
        {
				rtl::OUString envVar(RTL_CONSTASCII_USTRINGPARAM("XMODIFIERS"));
				osl_clearEnvironment(envVar.pData);
                XSetLocaleModifiers("");
                maMethod = XOpenIM(pDisplay, NULL, NULL, NULL);
			    mbMultiLingual = False;
        }

		if ( maMethod != (XIM)NULL )
		{
			if (   XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, NULL)
				!= NULL)
				mbUseable = False;
            #if OSL_DEBUG_LEVEL > 1
			fprintf(stderr, "Creating %s-Lingual InputMethod\n",
				mbMultiLingual ? "Multi" : "Mono" );
			PrintInputStyle( mpStyles );
			#endif
		}
		else
		{
			mbUseable = False;
		}
	}

    #if OSL_DEBUG_LEVEL > 1
	if ( !mbUseable )
		fprintf(stderr, "input method creation failed\n");
	#endif

    maDestroyCallback.callback	  = (XIMProc)IM_IMDestroyCallback;
	maDestroyCallback.client_data = (XPointer)this;
    if (mbUseable && maMethod != NULL)
        XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, NULL);

	return mbUseable;
}

//
// give IM the opportunity to look at the event, and possibly hide it
//

Bool
SalI18N_InputMethod::FilterEvent( XEvent *pEvent, XLIB_Window window	)
{
	if (!mbUseable)
        return False;

    Bool bFilterEvent = XFilterEvent (pEvent, window);

    if (pEvent->type != XLIB_KeyPress && pEvent->type != KeyRelease)
        return bFilterEvent;

    /*
     * fix broken key release handling of some IMs
     */
    XKeyEvent*         pKeyEvent = &(pEvent->xkey);
    static XKeyEventOp maLastKeyPress;

    if (bFilterEvent)
    {
        if (pKeyEvent->type == KeyRelease)
            bFilterEvent = !maLastKeyPress.match (*pKeyEvent);
        maLastKeyPress.erase();
    }
    else /* (!bFilterEvent) */
    {
        if (pKeyEvent->type == XLIB_KeyPress)
            maLastKeyPress = *pKeyEvent;
        else
            maLastKeyPress.erase();
    }

    return bFilterEvent;
}

void
SalI18N_InputMethod::HandleDestroyIM()
{
    mbUseable       = False;
    mbMultiLingual  = False;
    maMethod        = NULL;
}

// ------------------------------------------------------------------------
//
// add a connection watch into the SalXLib yieldTable to allow iiimp
// connection processing: soffice waits in select() not in XNextEvent(), so
// there may be requests pending on the iiimp internal connection that will
// not be processed until XNextEvent is called the next time. If we do not
// have the focus because the atok12 lookup choice aux window has it we stay
// deaf and dump otherwise.
//
// ------------------------------------------------------------------------

int
InputMethod_HasPendingEvent(int nFileDescriptor, void *pData)
{
	if (pData == NULL)
		return 0;

	struct pollfd aFileDescriptor;
	#ifdef SOLARIS
	nfds_t 		  nNumDescriptor = 1;
	#else
	unsigned int	  nNumDescriptor = 1;
	#endif
	aFileDescriptor.fd      = nFileDescriptor;
	aFileDescriptor.events  = POLLRDNORM;
	aFileDescriptor.revents = 0;

	int nPoll = poll (&aFileDescriptor, nNumDescriptor, 0 /* timeout */ );

	if (nPoll > 0)
	{
		/* at least some conditions in revent are set */
		if (   (aFileDescriptor.revents & POLLHUP)
			|| (aFileDescriptor.revents & POLLERR)
			|| (aFileDescriptor.revents & POLLNVAL))
			return 0; /* oops error condition set */

		if (aFileDescriptor.revents & POLLRDNORM)
			return 1; /* success */
	}

	/* nPoll == 0 means timeout, nPoll < 0 means error */
	return 0;
}

int
InputMethod_IsEventQueued(int nFileDescriptor, void *pData)
{
	return InputMethod_HasPendingEvent (nFileDescriptor, pData);
}

int
InputMethod_HandleNextEvent(int nFileDescriptor, void *pData)
{
	if (pData != NULL)
		XProcessInternalConnection((Display*)pData, nFileDescriptor);

	return 0;
}

extern "C" void
InputMethod_ConnectionWatchProc (Display *pDisplay, XPointer pClientData,
	int nFileDescriptor, Bool bOpening, XPointer*)
{
	SalXLib *pConnectionHandler = (SalXLib*)pClientData;

	if (pConnectionHandler == NULL)
		return;

	if (bOpening)
	{
		pConnectionHandler->Insert (nFileDescriptor, pDisplay,
									InputMethod_HasPendingEvent,
									InputMethod_IsEventQueued,
									InputMethod_HandleNextEvent);
	}
	else
	{
		pConnectionHandler->Remove (nFileDescriptor);
	}
}

Bool
SalI18N_InputMethod::AddConnectionWatch(Display *pDisplay, void *pConnectionHandler)
{
	// sanity check
	if (pDisplay == NULL || pConnectionHandler == NULL)
		return False;

	// if we are not ml all the extended text input comes on the stock X queue,
	// so there is no need to monitor additional file descriptors.
#ifndef SOLARIS
 	if (!mbMultiLingual || !mbUseable)
 		return False;
#endif

	// pConnectionHandler must be really a pointer to a SalXLib
	Status nStatus = XAddConnectionWatch (pDisplay, InputMethod_ConnectionWatchProc,
										  (XPointer)pConnectionHandler);
	return (Bool)nStatus;
}
