/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include "TSMProxy.h"
#include "proto.h"
#include "edt.h"
#include "uintl.h"
#include "intl_csi.h"


HoldUpdatesProxy::HoldUpdatesProxy(CEditView &inTextView) :
	mTextView(inTextView)
{
	mTextView.SetHoldUpdates(this);
	mStartY = 0;
	mHeight = 0;
}

HoldUpdatesProxy::~HoldUpdatesProxy()
{
	mTextView.SetHoldUpdates(nil);
	mTextView.DocumentChanged(mStartY, mHeight);
}

void HoldUpdatesProxy::DocumentChanged( int32 iStartY, int32 iHeight )
{
	if (mHeight == 0) {						// there is no range already
											// just set to the new range
		mStartY = iStartY;
		mHeight = iHeight;
		
	} else if (mHeight == -1) {				// the current range already extends to the bottom
											// should the top be moved up?
		if (iStartY < mStartY)
			mStartY = iStartY;
	
	} else if (iHeight == -1) {				// the new range extendes all the way to the bottom
											// should the top be moved up?
		mHeight = iHeight;
		if (iStartY < mStartY)
			mStartY = iStartY;
		
	} else {
	
		if (iStartY < mStartY) {
											// use the new top
			if (iStartY + iHeight > mStartY + mHeight) {
													// and the new height
				mStartY = iStartY;
				mHeight = iHeight;
				
			} else {
													// but the old height
				mHeight += mStartY - iStartY;
				mStartY = iStartY;
				
			}
			
		} else {
											// use the old top
			if (iStartY + iHeight > mStartY + mHeight) {
													// but use the new height
				mHeight = iStartY + iHeight - mStartY;
				
			}
			
		}
	}
}


AEEventHandlerUPP		HTMLInlineTSMProxy::sAEHandler 			= NewAEEventHandlerProc( AEHandlerTSM );
HTMLInlineTSMProxy	*HTMLInlineTSMProxy::sCurrentProxy 	= NULL;


#if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_
void HTMLInlineTSMProxy::PasteFromPtr(const Ptr thedata, int32 len, short hiliteStyle)
{
	if (len < 1)
		return;
	
	EDT_CharacterData *pData = EDT_NewCharacterData();
	if (pData) {
		pData->mask = TF_INLINEINPUT | TF_INLINEINPUTTHICK | TF_INLINEINPUTDOTTED;
		
		switch (hiliteStyle) {
			case kCaretPosition:
				pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK;		// this is just a guess actually: FIX ME!!
			break;
 
 			case kRawText:
				pData->values = 0;
			break;
 
			case kSelectedRawText:
				pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK | TF_INLINEINPUTDOTTED;
			break;
 
 			default:
 				XP_ASSERT(false);
			case kConvertedText:
				pData->values = TF_INLINEINPUT;
			break;
 
			case kSelectedConvertedText:
				pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK;
			break;
		}
		
		EDT_SetCharacterData( mContext ,pData );
		EDT_FreeCharacterData(pData);
	}
	
	// HACK HACK HACK
	// ok, so everyone has been really helpful and all but I'm going to put this in as a hack
	// rather than try to do it "right":  unicodeString will either be "thedata" or the result
	// if we need to do unicode conversion.  We'll free this below if the pointer address has changed
	char *unicodeString = thedata;
	INTL_CharSetInfo csi = LO_GetDocumentCharacterSetInfo(mContext);
	int16 win_csid = INTL_GetCSIWinCSID(csi);
	if ( (win_csid == CS_UTF8) || (win_csid==CS_UTF7) ) {
	
		INTL_Encoding_ID winCSID = ScriptToEncoding( ::GetScriptManagerVariable( smKeyScript ) );
		unicodeString = (char *)INTL_ConvertLineWithoutAutoDetect( winCSID, CS_UTF8, (unsigned char *)thedata, len );
       	len = strlen(unicodeString);

	}
	
	if (len < 16) {								// can we use a small static buffer?
	
		char smallbuffer[16];
		XP_MEMCPY(smallbuffer, unicodeString, len);
		smallbuffer[len] = '\0';
		EDT_InsertText(mContext, smallbuffer);
		
	} else {
	
		char *verytemp = (char *) XP_ALLOC(len + 1);
		if (verytemp) {
			XP_MEMCPY(verytemp, unicodeString, len);
			verytemp[len] = '\0';
			EDT_InsertText(mContext, verytemp);
			XP_FREE(verytemp);
		}
		
	}
	
	// see hack alert above
	if ( unicodeString != thedata )
		XP_FREEIF(unicodeString);

}
#endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_

HTMLInlineTSMProxy::HTMLInlineTSMProxy( CEditView &inTextView )

	:	mTextView( inTextView )

	{

		mTSMDocID = 0;
		
		OSType	supportedType = kTextService;
		OSErr	err = ::NewTSMDocument( 1, &supportedType, &mTSMDocID, (long)(void *)this );
		ThrowIfOSErr_(err);
		mInputHoleActive = false;
		mDocActive = false;
	
	}


HTMLInlineTSMProxy::~HTMLInlineTSMProxy()
	{
	
		if ( mDocActive )
			Deactivate();	//	for a bug in TSM.  See TE27

		OSErr	err = noErr;
	
		if ( mTSMDocID )
			err = ::DeleteTSMDocument(mTSMDocID);
	
		mTSMDocID = 0;
	//	Assert_(err == noErr);
	
	}
	
	
void
HTMLInlineTSMProxy::Activate( void )
	{
			
		OSErr	err = noErr;
	
		Assert_( mDocActive == false );
	
		InstallTSMHandlers();
	
		sCurrentProxy = this;

		#ifdef Debug_Signal
			//	check to see if a bug in TSM will be encountered
			ProcessSerialNumber	psn,
								csn;
			err = GetCurrentProcess(&psn);
//			ThrowIfOSErr_(err);
			err = GetFrontProcess(&csn);
//			ThrowIfOSErr_(err);
			Assert_((psn.highLongOfPSN == csn.highLongOfPSN) && (psn.lowLongOfPSN == csn.lowLongOfPSN));
		#endif

		if ( mTSMDocID )
			err = ::ActivateTSMDocument( mTSMDocID );
		else
			err = ::UseInputWindow(NULL, true);
//		ThrowIfOSErr_(err);

		if ( err == noErr )
			mDocActive = true;

	}
	
	
void
HTMLInlineTSMProxy::Deactivate( void )
	{
	
		OSErr	err = noErr;
	
		Assert_( mDocActive );
		
		RemoveTSMHandlers();
	
		sCurrentProxy = NULL;

		err = ::DeactivateTSMDocument( mTSMDocID );

		if (err != tsmDocNotActiveErr)  //	this just seems to happen too much -- it is okay if it happens
			{
			
				Assert_( err == noErr );
			
			} 		
			
		mDocActive = false;

	}
	
	
void
HTMLInlineTSMProxy::FlushInput( void )
	{
	
		OSErr	err = noErr;
		
		Assert_( mTSMDocID != 0 );
		
		if ( mTSMDocID != 0 ) 
			{
		
				err = ::FixTSMDocument( mTSMDocID );
			
			}
	
	}
	
	
void
HTMLInlineTSMProxy::InstallTSMHandlers( void )
	{
	
		OSErr	err = noErr;
		
		err = ::AEInstallEventHandler(kTextServiceClass, kUpdateActiveInputArea, sAEHandler, kUpdateActiveInputArea, false);
		ThrowIfOSErr_(err);
		err = ::AEInstallEventHandler(kTextServiceClass, kPos2Offset, sAEHandler, kPos2Offset, false);
		ThrowIfOSErr_(err);
		err = ::AEInstallEventHandler(kTextServiceClass, kOffset2Pos, sAEHandler, kOffset2Pos, false);
		ThrowIfOSErr_(err);
	
	}

	
void
HTMLInlineTSMProxy::RemoveTSMHandlers( void )
	{
	
		OSErr err = noErr;
		
		err = ::AERemoveEventHandler(kTextServiceClass, kUpdateActiveInputArea, sAEHandler, false);
		ThrowIfOSErr_(err);
		err = ::AERemoveEventHandler(kTextServiceClass, kPos2Offset, sAEHandler, false);
		ThrowIfOSErr_(err);
		err = ::AERemoveEventHandler(kTextServiceClass, kOffset2Pos, sAEHandler, false);
		ThrowIfOSErr_(err);
	
	}
	
	
pascal OSErr	
HTMLInlineTSMProxy::AEHandlerTSM( const AppleEvent *inAppleEvent, AppleEvent *outReply, Int32 inRefCon )
	{
	
		OSErr	err = noErr;
		
		THz	oldZone = ::LMGetTheZone(),	//	Apple bug #115424?
			appZone = ::LMGetApplZone();
		::LMSetTheZone(appZone);
	
#if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_
		try 
			{
			
				Assert_( sCurrentProxy != NULL );
				
				StHandleLocker	lock(inAppleEvent->dataHandle);
				LAESubDesc			appleEvent(*inAppleEvent);
				LAEStream				replyStream;
				
				ThrowIf_(((Int32)(void *)sCurrentProxy) != appleEvent.KeyedItem(keyAETSMDocumentRefcon).ToInt32());
				
				replyStream.OpenRecord();
				
				if ( sCurrentProxy != NULL )
					{
							
						switch( inRefCon )  
							{

								case kUpdateActiveInputArea:
								
									sCurrentProxy->AEUpdate(appleEvent);
									
								break;

								case kPos2Offset:
								
									sCurrentProxy->AEPos2Offset(appleEvent, replyStream);
									
								break;

								case kOffset2Pos:
								
									sCurrentProxy->AEOffset2Pos(appleEvent, replyStream);
									
								break;
								
							}
						
					}
				
				replyStream.CloseRecord();
				
				//	Transfer reply parameters to the real reply (hopefully MacOS 8 will have a way around this)
				//	ie, can simply say:
				//
				//		replyStream.Close(outReply);
				//
				StAEDescriptor	reply;
				replyStream.Close(reply);
				LAESubDesc		replySD(reply);
				AEKeyword		key;
				for (Int32 i = 1; i <= replySD.CountItems(); i++) {
					StAEDescriptor	parm;
					replySD.NthItem(i, &key).ToDesc(&parm.mDesc);
					err = ::AEPutParamDesc(outReply, key, &parm.mDesc);
					ThrowIfOSErr_(err);
				}		

			} 
			
		catch ( ExceptionCode inErr ) 
			{
			
				err = inErr;
			
			} 
			
		catch ( ... ) 
			{
			
				err = paramErr;
			
			}
#endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_

		::LMSetTheZone(oldZone);	//	Apple bug #115424?
		
		return err;
		
	}


#if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_
void	HTMLInlineTSMProxy::AEUpdate(
	const LAESubDesc	&inAppleEvent )
{
	CEditView::OutOfFocus(&mTextView);

	HoldUpdatesProxy stopUpdatesProxy(mTextView);
	
	// if we don't already have an input hole, remember where we are
	if (!mInputHoleActive) {
		mInputHoleActive = true;
		mInputHoleStart = EDT_GetInsertPointOffset(mContext);
		mInputHoleLen = 0;
	}
	
	// get the text in the input hole
	LAESubDesc	textSD(inAppleEvent.KeyedItem(keyAETheData), typeChar);
	Ptr thedata = (const Ptr)textSD.GetDataPtr();
	int32 len = textSD.GetDataLength();
			
	// fixLength is the number of characters which can be fixed into the buffer.
	Int32	fixLength = inAppleEvent.KeyedItem(keyAEFixLength).ToInt32();
	if (fixLength < 0)			// special signal to fix it all!!
		fixLength = len;

	mTextView.EraseCaret();
	mTextView.HideCaret(true);
	
	// if we do already have an input hole, select all the text and delete so that we start fresh
	if (mInputHoleLen) {
	
		EDT_CharacterData	*temp = EDT_GetCharacterData( mContext );

		EDT_SetInsertPointToOffset(mContext, mInputHoleStart, mInputHoleLen);
		EDT_DeletePreviousChar(mContext);
		
		if (temp) {
			if (len)	// if len == 0, then don't bother setting the character data because there is nothing left!
				EDT_SetCharacterData( mContext, temp );
			EDT_FreeCharacterData( temp );
		}
	}
		
	// we will handle this special case because it makes the algorithm easier to understand.
	// the input hole is going away because we are going to fix everything...
	if (fixLength == len) 
	{
		PasteFromPtr(thedata, fixLength, kRawText);		
		mInputHoleActive = false;
		CEditView::OutOfFocus(&mTextView);
		mTextView.HideCaret(false);
		return;
	}
	
	// we have already selected the old data, now paste in anything that needs to be fixed
	if (fixLength) {
		PasteFromPtr(thedata, fixLength, kRawText);
		mInputHoleStart = EDT_GetInsertPointOffset(mContext);	// a new starting point for our input hole
	}

	if (inAppleEvent.KeyExists(keyAEHiliteRange)) {
		LAESubDesc	hiliteSD(inAppleEvent.KeyedItem(keyAEHiliteRange), typeTextRangeArray);
		TextRangeArrayPtr	p = (TextRangeArrayPtr)hiliteSD.GetDataPtr();
		for (Int32 i = 0; i < p->fNumOfRanges; i++) {
		
			TextRange	record;
			// we don't care about any extra information which is supposed to be encoded in the sign of any of these numbers
			record.fStart = abs(p->fRange[i].fStart);
			record.fEnd = abs(p->fRange[i].fEnd);
			record.fHiliteStyle = abs(p->fRange[i].fHiliteStyle);
			
			PasteFromPtr(thedata + fixLength + record.fStart, record.fEnd - record.fStart, record.fHiliteStyle);
		}
	}
	
	mInputHoleLen = EDT_GetInsertPointOffset(mContext) - mInputHoleStart;	// a new length for our input hole

	
	//	output
	mTextView.HideCaret(false);
	CEditView::OutOfFocus(&mTextView);
}

//	so which is it?
#define	keyAELeadingEdge	keyAELeftSide

void	HTMLInlineTSMProxy::AEPos2Offset(
	const LAESubDesc	&inAppleEvent,
	LAEStream			&inStream) const
{
	//	input
	Point	where;
	Boolean	dragging = false;

	inAppleEvent.KeyedItem(keyAECurrentPoint).ToPtr(typeQDPoint, &where, sizeof(where));

	LAESubDesc	sd = inAppleEvent.KeyedItem(keyAEDragging);
	if (sd.GetType() != typeNull)	//	keyAEdragging is optional
		dragging = sd.ToBoolean();
	
	//	process
	CEditView::OutOfFocus(&mTextView);
	mTextView.FocusDraw();	//	for GlobalToLocal
	::GlobalToLocal(&where);
	CEditView::OutOfFocus(&mTextView);
	SPoint32	where32;
	mTextView.LocalToImagePoint(where, where32);
	
	LO_HitResult result;
	LO_Hit(mContext, where32.h, where32.v, false, &result, nil);
	
	if (result.type != LO_HIT_ELEMENT ||
//		result.lo_hitElement.region != LO_HIT_ELEMENT_REGION_MIDDLE ||
		result.lo_hitElement.position.element->type != LO_TEXT) {
		
			inStream.WriteKey(keyAEOffset);
			Int32	offset = -1;
			inStream.WriteDesc(typeLongInteger, &offset, sizeof(offset));
			
			inStream.WriteKey(keyAERegionClass);
			short	aShort = kTSMOutsideOfBody;
			inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort));
			
			return;
		}
		
	ED_BufferOffset newPosition = EDT_LayoutElementToOffset( mContext, result.lo_hitElement.position.element, result.lo_hitElement.position.position);
	
/*
	ED_BufferOffset saveSelStart, saveSelEnd;
	EDT_GetSelectionOffsets(mContext, &saveSelStart, &saveSelEnd);										// remember position
	
	EDT_PositionCaret(mContext, where32.h, where32.v );
	ED_BufferOffset newPosition = EDT_GetInsertPointOffset(mContext);
	
	EDT_SetInsertPointToOffset(mContext, saveSelStart, saveSelEnd - saveSelStart);						// restore position
*/	

	// restrict to the active range if you are dragging
	if (dragging) {
		if (newPosition < mInputHoleStart) newPosition = mInputHoleStart;
		if (newPosition > mInputHoleStart + mInputHoleLen) newPosition = mInputHoleStart + mInputHoleLen;
	}

	//	output
	inStream.WriteKey(keyAEOffset);
	Int32	offset = newPosition;
	offset -= mInputHoleStart;
	inStream.WriteDesc(typeLongInteger, &offset, sizeof(offset));
	
	inStream.WriteKey(keyAERegionClass);
	short	aShort = kTSMOutsideOfBody;
	
	SDimension32 sizeImage;
	SDimension16 sizeFrame;
	mTextView.GetImageSize(sizeImage);
	mTextView.GetFrameSize(sizeFrame);
	
	if ((0 <= where32.h) && (where32.h < sizeFrame.width) && (0 <= where32.v) && (where.v < sizeImage.height))
		{
		if (offset >= 0 && offset <= mInputHoleLen)
			aShort = kTSMInsideOfActiveInputArea;
		else
			aShort = kTSMInsideOfBody;
		}

	inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort));
}


void	HTMLInlineTSMProxy::AEOffset2Pos(
	const LAESubDesc	&inAppleEvent,
	LAEStream			&inStream) const
{
	//	input
	Int32	offset = inAppleEvent.KeyedItem(keyAEOffset).ToInt32();
	offset += mInputHoleStart;
	
	LO_Element * element;
	int32 caretPos;
	EDT_OffsetToLayoutElement(mContext, offset, &element, &caretPos);
		
	SPoint32	where32;
	int32		veryTemp;
	GetCaretPosition( mContext, element, caretPos, &where32.h, &veryTemp, &where32.v );

	Point		where;
	mTextView.ImageToLocalPoint(where32, where);
	CEditView::OutOfFocus(&mTextView);
	mTextView.FocusDraw();	//	for LocalToGlobal
	::LocalToGlobal(&where);
	
	//	output
	inStream.WriteKey(keyAEPoint);
	inStream.WriteDesc(typeQDPoint, &where, sizeof(where));
}
#endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_
