//
//	Copyright © 2005 Nemetschek North America 
//	All Rights Reserved.
//
// Created by Vladislav Stanev

#include "StdAfx.h"

using namespace VWFC::Math;
using namespace VWFC::Tools;
using namespace VWFC::VWObjects;

VWObject::VWObject()
{
	fhObject	= NULL;
}

VWObject::VWObject(const VWObject& src)
{
	fhObject	= src.fhObject;
}

VWObject::VWObject(MCObjectHandle hObj)
{
	fhObject	= hObj;
}

VWObject::~VWObject()
{
}

VWObject& VWObject::operator=(const VWObject&)
{
	VWFC_ASSERT( false );
	return *this;
}

VWObject::operator MCObjectHandle() const
{
	return fhObject;
}

void VWObject::DeleteObject(bool bUseUndo/*=true*/)
{
	VWFC_ASSERT( fhObject );
	::GS_DeleteObject( gCBP, fhObject, bUseUndo );
	fhObject		= NULL;
}

void VWObject::AddObjectToContainer(MCObjectHandle container)
{
	VWFC_ASSERT( fhObject );
	::GS_AddObjectToContainer( gCBP, fhObject, container );
}

void VWObject::InsertAfterObject(MCObjectHandle afterObj)
{
	VWFC_ASSERT( fhObject );
	::GS_InsertObjectAfter( gCBP, fhObject, afterObj );
}

void VWObject::InsertBeforeObject(MCObjectHandle beforeObj)
{
	VWFC_ASSERT( fhObject );
	::GS_InsertObjectBefore( gCBP, fhObject, beforeObj );
}

MCObjectHandle VWObject::GetThisObject() const
{
	return fhObject;
}

MCObjectHandle VWObject::GetNextObject() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_NextObject( gCBP, fhObject );
}

MCObjectHandle VWObject::GetPrevObject() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_PrevObject( gCBP, fhObject );
}

MCObjectHandle VWObject::GetParentObject() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_ParentObject( gCBP, fhObject );
}

MCObjectHandle VWObject::GetParentLayer() const
{
	MCObjectHandle	hLayer		= fhObject;
	do {
		MCObjectHandle	hParent		= ::GS_ParentObject( gCBP, hLayer );
		if ( hParent == NULL ) {
			// there is no parent, get the AUX owner
			hParent		= ::GS_AuxOwner( gCBP, hLayer );
		}

		hLayer		= hParent;
	} while ( hLayer && ! VWLayerObj::IsLayerObject( hLayer ) );
	return hLayer;
}

void VWObject::GetContainerInfo(MCObjectHandle* pOutContainerHandle, short* pOutContainerType, double* pOutContainerScale) const
{
	MCObjectHandle		hContainer		= fhObject;
	MCObjectHandle		h				= fhObject;
	short				type			= ::GS_GetObjectType( gCBP, h );
	// Stop when you get to the document header or a nil parent.
	while ( h != NULL && type != kHeaderNode && type != kTermNode ) {
		hContainer		= h;
		h				= ::GS_ParentObject( gCBP, h );
		type			= ::GS_GetObjectType( gCBP, h );
	}

	double				dContainerScale	= 1;
	short				nContainerType	= ::GS_GetObjectType( gCBP, hContainer );
	short				containerType	= nContainerType;
	switch( nContainerType ) {
		case kGroupNode: {
				containerType	= kViewportNode;

				hContainer		= ::GS_GetViewportGroupParent( gCBP, hContainer );

				TVariableBlock		value;
				::GS_GetObjectVariable( gCBP, hContainer, ovViewportScale, value );
				VERIFYN( kVStanev, value.GetReal64( dContainerScale ) );
			} break;
		case kLayerNode: {
				::GS_GetLayerScaleN( gCBP, hContainer, dContainerScale );

				TVariableBlock		value;
				::GS_GetObjectVariable( gCBP, hContainer, ovLayerType, value );
				short				sheetType;
				VERIFYN( kVStanev, value.GetSint16( sheetType ) );

				if ( sheetType == 2 )
					containerType = - containerType;	// sheet layer
			} break;
		case symListNode:
		default:	{
				dContainerScale				= 1;

				// find the first selected layer
				MCObjectHandle	hDrawing	= ::GS_GetDrawingHeader( gCBP );
				MCObjectHandle	hLayer		= ::GS_FirstMemberObj( gCBP, hDrawing );
				while( hLayer ) {
					short	type	= ::GS_GetObjectType( gCBP, hLayer );
					if ( type == kLayerNode ) {
						// check if this layer is selected
						if ( ::GS_IsSelected( gCBP, hLayer ) ) {
							hContainer		= hLayer;
							::GS_GetLayerScaleN( gCBP, hContainer, dContainerScale );
							break;
						}
					}

					hLayer		= ::GS_NextObject( gCBP, hLayer );
				}
			} break;
	}

	// set output
	if ( pOutContainerType )
		*pOutContainerType			= containerType;
	if ( pOutContainerHandle )
		*pOutContainerHandle		= hContainer;
	if ( pOutContainerScale )
		*pOutContainerScale			= dContainerScale;
}

void VWObject::ResetObject()
{
	VWFC_ASSERT( fhObject );
	::GS_ResetObject( gCBP, fhObject );
}

short VWObject::GetType() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_GetObjectType( gCBP, fhObject );
}

VWClass VWObject::GetObjectClass() const
{
	VWFC_ASSERT( fhObject );
	return VWClass( ::GS_GetObjectClass( gCBP, fhObject ) );
}

void VWObject::SetObjectClass(const VWClass& clas)
{
	VWFC_ASSERT( fhObject );
	::GS_SetObjectClass( gCBP, fhObject, clas );
}

VWObjectAttr& VWObject::GetObjectAttribs()
{
	fObjAttribs.fhObject	= fhObject;
	return fObjAttribs;
}

// XXX Vlado
// Singleton fix for Mac OS X 10.3 -- it has problems with static local variables
static VWObjectAttr		g_staticProps( NULL );
const VWObjectAttr& VWObject::GetObjectAttribs() const
{
	g_staticProps			= fObjAttribs;
	g_staticProps.fhObject	= fhObject;
	return g_staticProps;
}

bool VWObject::IsVisible() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_IsVisible( gCBP, fhObject ) ? true : false;
}

void VWObject::SetVisible(bool bVisible /*=true*/)
{
	VWFC_ASSERT( fhObject );
	return ::GS_SetVisibility( gCBP, fhObject, bVisible );
}

bool VWObject::IsLocked() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_IsLocked( gCBP, fhObject ) ? true : false;
}

void VWObject::SetLock(bool bLock /*=true*/)
{
	VWFC_ASSERT( fhObject );
	::GS_LockObject( gCBP, fhObject, bLock );
}

bool VWObject::IsSelected() const
{
	VWFC_ASSERT( fhObject );
	return ::GS_IsSelected( gCBP, fhObject ) ? true : false;
}

void VWObject::SetSelected(bool bSelect /*=true*/)
{
	VWFC_ASSERT( fhObject );
	::GS_SelectObject( gCBP, fhObject, bSelect );
}

TXString VWObject::GetObjectName() const
{
	VWFC_ASSERT( fhObject );
	InternalName		intName;
	::GS_GetObjectName( gCBP, fhObject, intName );
	return TXString( intName );
}

void VWObject::SetObjectName(const TXString& name)
{
	VWFC_ASSERT( fhObject );
	InternalName		intName;
	VWFC_ASSERT( name.GetLength() < sizeof(InternalName) );
	if ( name.GetLength() < sizeof(InternalName) ) {
		name.CopyInto( intName );
		GSError	err	= ::GS_SetObjectName( gCBP, fhObject, intName );

		err;	// dumb use
		//bool b1 = err == 0;		// no err
		//bool b2 = err == nameNotUniqueErr;
		//bool b3 = err == nameTableFullErr;
	}
}


void VWObject::GetObjectBounds(VWRectangle2D& bounds) const
{
	WorldRect	worldBounds;
	::GS_GetObjectBounds( gCBP, fhObject, worldBounds );

	bounds.SetRect( worldBounds.Left(), worldBounds.Right(), worldBounds.Top(), worldBounds.Bottom() );
}

void VWObject::GetObjectMatrix(VWTransformMatrix& outMatrix) const
{
	TransformMatrix			theMat;
	::GS_GetEntityMatrix( gCBP, fhObject, theMat );
	outMatrix		= theMat;
}


// --------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------
// faking the new VW12.5.2 function so it works on older versions without update of the Core
// ::GS_SetEntityMatrix( gCBP, h, mat );
#pragma pack(push, 2) // BEGIN 2 BYTE STRUCTURE ALIGNMENT (.mcd file format)
struct MeshPoint {
	WorldPt3					vertex;
	short						flags;
	char						unused[2];
};
#pragma pack(pop) // END 2 BYTE STRUCTURE ALIGNMENT (.mcd file format)

MCObjectHandle Fake_GS_SetEntityMatrix(MCObjectHandle h, const TransformMatrix& mat)
{
	MCObjectHandle	hRes		= h;
	// ::GS_SetEntityMatrix( gCBP, h, mat );
	short			objType		= ::GS_GetObjectType( gCBP, h );
	if ( objType == kMeshNode ) {
		if ( false ) {
			::GS_SetEntityMatrix( gCBP, h, mat );
		}

		UInt8*		pMeshType	= (UInt8*) *h;

		short*		pVertices	= (short*)		( pMeshType + 0x00000088 );
		MeshPoint*	pVertexList	= (MeshPoint*)	( pMeshType + 0x0000008c );
		for(short i=0; i<*pVertices; i++) {
			MeshPoint&	v	= pVertexList[ i ];
			v.vertex	= PointTransformN( v.vertex, mat );
		}
	}
	else if ( objType == kCSGTreeNode ) {
		if ( false ) {
			::GS_SetEntityMatrix( gCBP, h, mat );
		}

		/*UInt8*				pSolidType		= (UInt8*) *h;
		_TransformMatrix*	pSoildMatrix	= (_TransformMatrix*) (pSolidType + 0x00000084);

		*pSoildMatrix						= mat;

		// since we cannot reset directly
		// we call this only to reset the solid
		TVariableBlock	value( 0.5 );
		::GS_SetObjectVariable( gCBP, h, ovSolidXScale, value );	*/	

		// since there is no way to reset the CSG note after we set matrix
		// so we are forced to convert it to mesh, and apply matrix to the mesh
		MCObjectHandle	hMesh	= ::GS_ConvertTo3DPolygons( gCBP, h, false );		
		if ( ::GS_GroupToMesh( gCBP, hMesh ) == kMeshNoError ) {
			hRes	= ::Fake_GS_SetEntityMatrix( hMesh, mat );
		}
	}

	return hRes;
}
// --------------------------------------------------------------------------------------------------

void VWObject::SetObjectMatrix(const VWTransformMatrix& matrix)
{
	bool	bSetForOlderVersion	= false;
	short	objType				= ::GS_GetObjectType( gCBP, fhObject );
	if ( objType == kMeshNode || objType == kCSGTreeNode ) {
		short		hostID;
		Byte		version, majorRev, minorRev;
		Str255		productName;
		Str255		productVersion;
		::GS_GetHostInfo( gCBP, hostID, version, majorRev, minorRev, productName, productVersion );
		if ( version == 12 && majorRev <= 5 && minorRev <= 1 ) {
			// change the object handle in 'this' since this fake fucntion may change it
			fhObject				= ::Fake_GS_SetEntityMatrix( fhObject, matrix );
			bSetForOlderVersion		= true;
		}
	}

	if ( ! bSetForOlderVersion ) {
		::GS_SetEntityMatrix( gCBP, fhObject, matrix );
	}
}

void VWObject::ApplyObjectMatrix(const VWTransformMatrix& matrix)
{
	VWTransformMatrix	objMatrix;
	this->GetObjectMatrix( objMatrix );
	this->SetObjectMatrix( objMatrix * matrix );
}

void VWObject::MoveObject(const VWPoint2D& offset)
{
	::GS_MoveObject( gCBP, fhObject, offset.x, offset.y );
}

void VWObject::MoveObject(double dx, double dy)
{
	::GS_MoveObject( gCBP, fhObject, dx, dy );
}

void VWObject::MoveObject3D(const VWPoint3D& offset)
{
	::GS_MoveObject3D( gCBP, fhObject, offset.x, offset.y, offset.z );
}

void VWObject::MoveObject3D(double dx, double dy, double dz)
{
	::GS_MoveObject3D( gCBP, fhObject, dx, dy, dz );
}

void VWObject::RotateObject(const VWPoint2D& pt, double dAngle)
{
	::GS_RotateObjectN( gCBP, fhObject, pt, dAngle );
}

void VWObject::AttachAuxObject(MCObjectHandle hAuxObject)
{
	::GS_AttachAuxObject( gCBP, hAuxObject, fhObject );
}

void VWObject::DeleteAuxObject(MCObjectHandle hAuxObject)
{
	::GS_DeleteAuxObject( gCBP, hAuxObject, fhObject );
}

MCObjectHandle VWObject::FindAuxObject(short type, MCObjectHandle hLastAuxObject) const
{
	if ( hLastAuxObject == NULL )
		return ::GS_FindAuxObject( gCBP, fhObject, type );
	else {
		MCObjectHandle	hNextAux	= ::GS_NextObject( gCBP, hLastAuxObject );
		return ::GS_NextAuxObject( gCBP, hNextAux, type );
	}
}

MCObjectHandle VWObject::GetFirstAuxObject() const
{
	return ::GS_FirstAuxObject( gCBP, fhObject );
}

VWObjectIterator VWObject::GetFirstAuxObjectIterator() const
{
	return VWObjectIterator( ::GS_FirstAuxObject( gCBP, fhObject ) );
}

/*static*/void VWObject::ResetObjectsVisibility()
{
	::GS_ResetAllObjectsVisibility( gCBP );
}
//	extern "C" InternalIndex GS_CALLBACK GS_GetObjectClass(CallBackPtr, MCObjectHandle h);
//	extern "C" void GS_CALLBACK GS_SetObjectClass(CallBackPtr, MCObjectHandle h, InternalIndex classID);

/*static*/bool VWObject::IsSelected(MCObjectHandle hObj)
{
	return ::GS_IsSelected( gCBP, hObj );
}

// XXX Vlado This should be in VWDocument
/*static*/void VWObject::DeselectAll()
{
	::GS_DeselectAll( gCBP );
}

/*static*/void VWObject::DeleteObject(MCObjectHandle hObj, bool bUseUndo)
{
	::GS_DeleteObject( gCBP, hObj, bUseUndo );
}

/*static*/void VWObject::ResetObject(MCObjectHandle hObj)
{
	::GS_ResetObject( gCBP, hObj );
}

/*static*/ MCObjectHandle VWObject::GetNamedObject(const TXString& objName)
{
	InternalName		intName;
	objName.CopyInto( intName );

	return ::GS_GetNamedObject( gCBP, intName );
}
