//
//	ObjectEventingObject.cpp
//
//	Main entry point for VectorWorks Plug-in Object code.
//
//	Copyright © Nemetschek North America, Inc  2005.
//	All Rights Reserved.
//
//

#include "StdAfx.h"


#include "ObjectEventingData.h"

static MCObjectHandle CreateBigText(const TXString& text, const WorldPt& textPt)
{
	MCObjectHandle	hText	= NULL;

	size_t			textLen	= text.GetLength();
	if ( textLen == 0 ) {
		hText			= ::GS_CreateTextBlock( gCBP, TXString("_"), textPt, false, 0 );

		// now delete the extra character so the string is empty
		::GS_DeleteText( gCBP, hText, 0, 1 );
	}
	else {
		// Note 'GS_AddTextFromBuffer' doesnt work if 'GS_CreateTextBlock' creates empty text!
		// That is why we add extra space at the end just to create the text block, and then delete it
		// NOTE! You cannot extract the last char and use it with GS_CreateTextBlock
		//	because that breaks Japaneese string drawing
		hText					= ::GS_CreateTextBlock( gCBP, TXString("_"), textPt, false, 0 );

		// now delete the extra character so the string that is being added below is the only one in the text object
		::GS_DeleteText( gCBP, hText, 0, 1 );
	
		// now add the text in the text object
		::GS_AddTextFromBuffer( gCBP, hText, 0, (void*) (const char*) text, DemoteTo<long>( kVStanev, textLen ) );
	}

	return hText;
}

static TXString GetValueAsString(const TVariableBlock& value)
{
	Boolean	theValue_bool;
	Real64	theValue_real64;
	Uint8	theValue_uint8;
	Sint16	theValue_sint16;
	Sint32	theValue_sint32;
	Uint32	theValue_uint32;
	Str255	theValue_str255;

	TXString			result	= "Unknown";
	if ( value.GetBoolean( theValue_bool ) ) {
		result	= theValue_bool ? "TRUE" : "FALSE";
	}
	else if ( value.GetReal64( theValue_real64 ) ) {
		result.Format( "%f", theValue_real64 );
	}
	else if ( value.GetUInt8( theValue_uint8 ) ) {
		result.Format( "%d", theValue_uint8 );
	}
	else if ( value.GetSint16( theValue_sint16 ) ) {
		result.Format( "%d", theValue_sint16 );
	}
	else if ( value.GetSint32( theValue_sint32 ) ) {
		result.Format( "%d", theValue_sint32 );
	}
	else if ( value.GetUint32( theValue_uint32 ) ) {
		result.Format( "%d", theValue_uint32 );
	}
	else if ( value.GetStr255( theValue_str255 ) ) {
		result	= theValue_str255;
	}

	return result;
}

long ParametricMain(long action, MCObjectHandle hParametricObj, long message, long &userData)
//
//	Main entry point for parametric object regeneration external code resource.
//	See MiniCadHookIntf.h for constants.
//
{
	message; // Eliminate warning

    // initialize VCOM mechanizm
    ::GS_InitializeVCOM( gCBP );

	long	reply = kParametricNoErr;

	CObjectEventingData		objData( userData );

	switch (action) {
		case kParametricInitGlobals : {
				/*
					This action is called when an overlay is activated and serves the
					primary purpose of allowing the external function to define any
					defaults and global information and to initialize data space that
					may be stored in userData.
				*/
				objData.InitUserdata( true );
				break;
			}



		case kParametricDisposeGlobals : {
				/*
					This action is called when an overlay is deactivated and serves the
					primary purpose of allowing the external function to dispose data
					space that may be stored in userData.
				*/
				objData.InitUserdata( false );
				break;
			}


		case kObjOnInitXProperties: {
			//
			// This action is called to allow the plug-in to set up the extended properties of the parametric type
			//
				CodeRefID objectID	= (CodeRefID) message;

				using namespace VectorWorks::PluginSupport;
				VCOMPtr<IExtendedProps>	pExtProps( IID_ExtendedProps );
				if ( pExtProps ) {
					// prepare flags for the state event that is to be received
					short		stateEventFlags	= kParametricStateEvent_ResetStatesEvent|kParametricStateEvent_InternalStatesEvent;
					::GS_SetProgramVariable( gCBP, varParametricEnableStateEventing, & stateEventFlags );
					VERIFYN( kVStanev, VCOM_SUCCEEDED( pExtProps->SetObjectProperty( objectID, kObjXPropAcceptStates, true ) ) );
					VERIFYN( kVStanev, VCOM_SUCCEEDED( pExtProps->SetObjectProperty( objectID, kObjXPropAcceptStatesInternal, true ) ) );

					// init states manager -- add states to monitor
					CStatesManager&	statesManager	= objData.GetData().GetStateManager();
					statesManager.AddStateToMonitor( ObjectState::kCreatedReset );
					statesManager.AddStateToMonitor( ObjectState::kMovedReset );
					statesManager.AddStateToMonitor( ObjectState::kRotatedReset );
					statesManager.AddStateToMonitor( ObjectState::kParameterChangedReset );
					statesManager.AddStateToMonitor( ObjectState::kObjectChangedReset );
					statesManager.AddStateToMonitor( ObjectState::kLayerChangedReset );
					statesManager.AddStateToMonitor( ObjectState::kExitFromEditGroup );
					statesManager.AddStateToMonitor( ObjectState::kNameChanged );
					statesManager.AddStateToMonitor( ObjectState::kObjectUndoRestore );
					statesManager.AddStateToMonitor( ObjectState::kObjectUndoRemove );

					// this extended prop is set just to demonstrate "ObjectState::kExitFromEditGroup" event
					if ( false ) {
						VERIFYN( kVStanev, VCOM_SUCCEEDED( pExtProps->SetObjectProperty( objectID, kObjXPropEditGroup, Sint8(kObjXPropEditGroupProfile) ) ) );
					}
					else {
						VERIFYN( kVStanev, VCOM_SUCCEEDED( pExtProps->SetObjectProperty( objectID, kObjXPropEditGroup, Sint8(kObjXPropEditGroupPath) ) ) );
					}

					// this extended prop is set just to demonstrate "ObjectState::kLayerChangedReset" evet
					VERIFYN( kVStanev, VCOM_SUCCEEDED( pExtProps->SetObjectProperty( objectID, kObjXPropHasLayerScaleDeps, true ) ) );
				}
				break;
			}

		case kObjOnAddState: {
			//
			// This action is called to inform the parametric of change in it's state
			// This is generally called before regeneration, but it may be called several times
			// and over different handles.
			// The user plug-in should store the events to be used during regeneration
			//
			// NOTE! THIS EVENT IS NOT TO BE USED FOR REGENERATION OR DOCUMENT CHANGE
			//	-- THIS IS INFORMATIVE EVENT, NOT ACTION EVENT
			//
				if ( message != 0 ) {
					CStatesManager&	statesManager	= objData.GetData().GetStateManager();

					statesManager.AddState( hParametricObj, (ObjectState*) message );
				}

				break;
			}

		case kParametricRecalculate : {
			//
			//	This action is called to regenerate the look of a parametric object in response to a change
			//	in one of the parameter values on the object info palette, or a rotate, move, attribute change.
			//
			//	The message argument contains a handle to a wall, if we are inserted in a wall directly. Only for this action.
			//

			CStatesManager&	statesManager	= objData.GetData().GetStateManager();

			::GS_CreateRectangle( gCBP, WorldRect(0,0, 200, -100 ) );

			::CreateBigText( TXString("State events received after the last kParametricRecalculate:"), WorldPt(5,8) );

			if ( statesManager.PopState( hParametricObj, ObjectState::kCreatedReset ) ) {
				::CreateBigText( TXString("Object just created!"), WorldPt(0,0) );
			}

			ObjectStateData_Position	dataPos;
			if ( statesManager.PopState( hParametricObj, dataPos, true ) ) {
				WorldPt3		offset	= dataPos.fTransformOffset.P();
				TXString		text;
				text.Format( "offset=(%.2f,%.2f,%.2f) is3DMove=%s", offset.x, offset.y, offset.z, (dataPos.fbIs3DMove?"TRUE":"FALSE") );

				::CreateBigText( TXString("Object just moved! ") + text, WorldPt(0,-10) );
			}

			ObjectStateData_Position	dataRot;
			if ( statesManager.PopState( hParametricObj, dataRot, false ) ) {
				double	mySin	= dataRot.fTransformOffset.mat[0][1];
				double	deg		= asin(mySin) * 180.0 / 3.1415;
				TXString		text;
				text.Format( "diff=%.2f deg is3DMove=%s", deg, (dataRot.fbIs3DMove?"TRUE":"FALSE") );

				::CreateBigText( TXString("Object just rotated! ") + text, WorldPt(0,-20) );
			}

			ObjectStateData_ParamChanged	dataParamChanged;
			if ( statesManager.PopState( hParametricObj, dataParamChanged ) ) {
				TXString		text;
				text.Format( "widgetID=%d paramIndex=%d", dataParamChanged.fWidgetID, dataParamChanged.fParameterIndex );
				if ( dataParamChanged.fParameterValidOldValue ) {
					text	+= " Old value=";
					text	+= GetValueAsString( dataParamChanged.fParameterOldValue );
				}
				else {
					text	+= " No old value!";
				}

				::CreateBigText( TXString("Object parameter just changed! ") + text, WorldPt(0,-30) );
			}

			ObjectStateData_ExitFromEditGroup	dataExitGrp;
			if ( statesManager.PopState( hParametricObj, dataExitGrp ) ) {
				TXString		text;
				switch( dataExitGrp.fGroupType ) {
					case kObjXPropEditGroupDefault:		text	= "Exited from: Default Group!";	break;
					case kObjXPropEditGroupProfile:		text	= "Exited from: Profile Group!";	break;
					case kObjXPropEditGroupPath:		text	= "Exited from: Path Group!";		break;
					case kObjXPropEditGroupCustom:		text	= "Exited from: Custom Group!";		break;
					default:							text	= "Exited from: Uknown Group!";		break;
				}

				::CreateBigText( TXString("Object exited from edit group! ") + text, WorldPt(0,-40) );
			}

			ObjectStateData_ObjectChanged	dataObjChanged;
			if ( statesManager.PopState( hParametricObj, dataObjChanged ) ) {
				TXString		text;
				text.Format( "fChangeID=%d - sorry there are no constants for those values yet!", dataObjChanged.fChangeID );

				::CreateBigText( TXString("Object changed! ") + text, WorldPt(0,-50) );
			}

			ObjectStateData_LayerChanged	dataLayerChanged;
			if ( statesManager.PopState( hParametricObj, dataLayerChanged ) ) {
				TXString		text;
				text.Format( "oldScale=%f newScale=%f scaleText=%s", dataLayerChanged.fdOldLayerScale, dataLayerChanged.fdLayerScale, (dataLayerChanged.fbScaleText ? "YES" : "NO" ) );

				::CreateBigText( TXString("Layer changed! ") + text, WorldPt(0,-60) );
			}

			ObjectStateData_ObjectNameChanged	objNameChanged;
			if ( statesManager.PopState( hParametricObj, objNameChanged ) ) {
				TXString		text;
				text.Format( "oldName=%s newName=%s", (const char*) TXString(objNameChanged.fOldName), (const char*) TXString(objNameChanged.fNewName) );

				::CreateBigText( TXString("Object name changed! ") + text, WorldPt(0,-70) );
			}

			if ( statesManager.PopState( hParametricObj, ObjectState::kObjectUndoRestore ) ) {
				::CreateBigText( TXString("Internal! Undo restore!"), WorldPt(0,-80) );
			}

			if ( statesManager.PopState( hParametricObj, ObjectState::kObjectUndoRemove ) ) {
				::CreateBigText( TXString("Internal! Undo remove!"), WorldPt(0,-90) );
			}

			// clear out states for the next recalculate
			statesManager.ClearStates( hParametricObj );

			break;
		}

		case kParametricPreference : {
			//	This action gives the parametric an opportunity to pose a custom dialog for parameter value editting.
			//	If this parametric handles the request and poses its own dialog, then it returns kParametricNoErr regardless of
			//	whether chose ok or cancel.  If this parametric does not have a custom dialog, it returns kParametricNotImplemented.
			// 	Most parametrics will simply set reply to kParametricNotImplemented and let MiniCAD pose the standard dialog.

			reply = kParametricNoErr;


			break;
		}
	}

	userData	= objData.GetUserData();
	return reply;
}
