//========================================================================
// EQUINOX-3D plugin
//
// Information about the selected Models and Objects.
// This plugin is great for debugging things in EQUINOX-3D, as well
// as for learning how objects are stored inside EQUINOX-3D.
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 22:13:15
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//========================================================================
#include <stdio.h>

#include <EMalloc.h>
#include <EPlugins.h>

#include <Xe/PushButton.h>

#include <E3D/Matrix.h>
#include <E3D/Scene.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/StatusPanel.h>


static XeDWidget  _MenuButtons[16];static int     _MenuButtons_Count = 0;
static XeDWidget  _ToolBarButtons[16];static int  _ToolBarButtons_Count = 0;


//static EBool     _E3dp_ShowMaterials = TRUE;

enum
{
	CR_INFO_ON_SELECTION,
	CR_INFO_LIST_ROOTS,
	CR_INFO_LIST_NODES,
	CR_INFO_LIST_MATERIALS,
	CR_INFO_LIST_ANIMATIONS
};


//========================================
// Print information about a Spline
//========================================
static void _PointSet_PrintInfo(E3dPointSet* PPointSet)
{
	{
		const EIndex  LN = PPointSet->Vertices.Count;printf(" %sPPointSet%s: Vertices: %lld\n {\n", ECodeCyan, ECodeNormal, (long long)LN);
	}

// VertexAttributes
//
	{
		printf("  VertexAttributes (bytes per Vertes: %lld)\n", (long long)PPointSet->VertexSize);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PPointSet->VertexAttributes.Count;const EResource*  LAttr = PPointSet->VertexAttributes.Items;for(;LI < LN;LI++, LAttr++)
		{
			const EIndex  LCount = LAttr->Ext.Count > 0 ? LAttr->Ext.Count : 1;
			char  LStr[36];printf("   Name %-8s  Semantic  %-8s %s x %lld\n", LAttr->Name, (char*)LAttr->Semantic, ERes_TypeToString(LAttr->Type, LStr, 32), (long long)LCount);fflush(stdout);
		}
	}

	{
		E3dMaterial*  LMaterial = PPointSet->Material;if(LMaterial)
		{
			printf("  Material:\n");E3dMaterial_Print(LMaterial, 3);
		}
		else
		{
			LMaterial = E3dObject_GetDrawingMaterial(NULL, (E3dObject*)PPointSet, FALSE);
			if(LMaterial == NULL) printf("  No Material (using default)\n");
			else if(LMaterial)
			{
				printf("  Inherited Material\n");fflush(stdout);
				E3dMaterial_Print(LMaterial, 4);
			}
		}
	}

	if(PPointSet->TextureMappers.Count > 0)
	{
		printf("  TextureMappers: %lld\n", (long long)PPointSet->TextureMappers.Count);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PPointSet->TextureMappers.Count;for(;LI < LN;LI++)
		{
			printf("   %s\n", PPointSet->TextureMappers.Items[LI]->Class->Name);fflush(stdout);
		}
	}

	printf(" }\n");fflush(stdout);
}


//========================================
// Print information about a Spline
//========================================
static void _Spline_PrintInfo(E3dSpline* PSpline)
{
	const EIndex  LN = PSpline->CVs.Count;

	printf(" %sSpline%s: CVs: %lld  Type: ", ECodeCyan, ECodeNormal, (long long)LN);

	switch(PSpline->SplineType)
	{
		case E3dSplineBEZIER:
			printf("BEZIER\n");fflush(stdout);
		break;

		case E3dSplineBSPLINE:
			printf("B-SPLINE\n");fflush(stdout);
		break;

		case E3dSplineCARDINAL:
			printf("CARDINAL\n");fflush(stdout);
		break;

		case E3dSplineLINEAR:
			printf("LINEAR\n");fflush(stdout);
		break;
	}

	switch(PSpline->SplineType)
	{
		case E3dSplineLINEAR:
			{
				EIndex  LI = 0;const E3dSplineCV*  LSplineCV = PSpline->CVs.Items;for(;LI < LN;LI++, LSplineCV++)
				{
					printf(" Position: %f,%f,%f\n", LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);
				}
			}
		break;
	}
	fflush(stdout);
}


//================================================
// Accumulate the number of Polygons of a Mesh
//================================================
static void _MeshAddPolyCounts(E3dMesh* PMesh, EIndex* PTotalPolyNumP, EIndex* PSelectedPolyNumP)
{
	EIndex  LPGI = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
	{
		E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];

		if((PMesh->Selection == E3dObjectSelPOLYGROUP && LPolyGroup->Selected) || PMesh->Selection != E3dObjectSelPOLYGROUP)
		{
			(*PTotalPolyNumP) += LPolyGroup->Polygons.Count;

			const EIndex  LPN = LPolyGroup->Polygons.Count;if(LPN)
			{
				EIndex  LPI = 0;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++)
				{
					if(LPolygon->VertexNodes)
					{
						if(LPolygon->Flags & E3dPolygonSELECTED)
						{
							(*PSelectedPolyNumP)++;
						}
					}
				}
			}
		}
	}
}


//========================================
// Print information about a Face
//========================================
static void _Face_PrintInfo(E3dFace* PFace)
{
	printf(" %sFace%s: Holes(s): %lld\n {\n", ECodeCyan, ECodeNormal, (long long)PFace->Holes_Count);

// VertexAttributes
//
	{
		printf("  VertexAttributes\n");fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PFace->VertexAttributes.Count;const EResource*  LAttr = PFace->VertexAttributes.Items;for(;LI < LN;LI++, LAttr++)
		{
			const EIndex  LCount = LAttr->Ext.Count > 0 ? LAttr->Ext.Count : 1;
			char  LStr[36];printf("   Name %-8s  Semantic  %-8s %s x %lld\n", LAttr->Name, (char*)LAttr->Semantic, ERes_TypeToString(LAttr->Type, LStr, 32), (long long)LCount);fflush(stdout);
		}
	}

//printf("%s() %s:%d  %sTextureMappers.Count%s  %lld  %sPolyGroup.TextureMappers.Count%s  %lld\n", __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, (long long)PFace->TextureMappers.Count,  ECodeGreen, ECodeNormal, (long long)PFace->PolyGroup.TextureMappers.Count);fflush(stdout);

	if(PFace->TextureMappers.Count > 0)
	{
		printf("  TextureMappers: %lld\n", (long long)PFace->TextureMappers.Count);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PFace->TextureMappers.Count;for(;LI < LN;LI++)
		{
			printf("   %s\n", PFace->TextureMappers.Items[LI]->Class->Name);fflush(stdout);
		}
	}

	if(PFace->PolyGroup && PFace->PolyGroup->TextureMappers.Count > 0)
	{
		printf("  PolyGroup.TextureMappers: %lld\n", (long long)PFace->PolyGroup->TextureMappers.Count);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PFace->PolyGroup->TextureMappers.Count;for(;LI < LN;LI++)
		{
			printf("   %s\n", PFace->PolyGroup->TextureMappers.Items[LI]->Class->Name);fflush(stdout);
		}
	}

	printf(" }\n");	// Close Face
}


//========================================
// Print information about a Mesh
//========================================
static void _Mesh_PrintInfo(E3dMesh* PMesh, EIndex* PTotalPolyNumP, EIndex* PSelectedPolyNumP)
{
	printf(" %sMesh%s: Vertices: %lld, PolyGroup(s): %lld\n {\n", ECodeCyan, ECodeNormal, (long long)PMesh->Vertices.Count, (long long)PMesh->PolyGroups.Count);

// VertexAttributes
//
	if(PMesh->Selection != E3dObjectSelPOLYGROUP)
	{
		printf("  VertexAttributes (bytes per Vertes: %lld)\n", (long long)PMesh->VertexSize);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PMesh->VertexAttributes.Count;const EResource*  LAttr = PMesh->VertexAttributes.Items;for(;LI < LN;LI++, LAttr++)
		{
			const EIndex  LCount = LAttr->Ext.Count > 0 ? LAttr->Ext.Count : 1;
			char  LStr[36];printf("  Name %-8s  Semantic  %-8s %s x %lld\n", LAttr->Name, (char*)LAttr->Semantic, ERes_TypeToString(LAttr->Type, LStr, 32), (long long)LCount);fflush(stdout);
		}
	}

	EIndex  LPGI = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
	{
		E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];

		if(LPolyGroup->Selected || PMesh->Selection == E3dObjectSelOBJECT)
		{
			if(PTotalPolyNumP) *PTotalPolyNumP += LPolyGroup->Polygons.Count;
			E3dPolyGroup_PrintInfo(LPolyGroup, LPGI,   PMesh->Vertices.Items, PMesh->Vertices.Count, PMesh->VertexSize,  PSelectedPolyNumP, 2);
		}
	}

// Show info about selected Edges
//
	if(PMesh->Selection != E3dObjectSelPOLYGROUP && E3dMesh_NumOfSelectedEdges(PMesh) > 0)
	{
		EBool  LWingedEdgesCreated = FALSE;

		if(PMesh->WingedEdges == NULL) { E3dMesh_CreateWingedEdgesFromEdges(PMesh, FALSE, FALSE);LWingedEdgesCreated = TRUE; }

		if(PMesh->WingedEdges)
		{
			EIndex  LEI = 0;const EIndex  LEN = PMesh->Edges.Count;const E3dEdge*  LEdge = PMesh->Edges.Items;const E3dWingedEdge*  LWingedEdge = PMesh->WingedEdges;for(;LEI < LEN;LEI++, LEdge++, LWingedEdge++)
			{
				if(LEdge->Flags & E3dEdgeSELECTED)
				{
					printf(" Edge  % 5lld  Polygons: %d", (long long)LEI, LWingedEdge->Polygons_Count);
/*
					int32_t  LPI = 0;const int32_t  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
					{
						printf(" ", LEdgePolygonInfo);fflush(stdout);
					}
*/
					printf("\n");fflush(stdout);
				}
			}
		}

		if(LWingedEdgesCreated) E3dMesh_WingedEdgesFree(PMesh);

		printf("\n");fflush(stdout);
	}

	if(PMesh->Selection != E3dObjectSelPOLYGROUP && PMesh->TextureMappers.Count > 0)
	{
		printf("  TextureMappers: %lld\n", (long long)PMesh->TextureMappers.Count);fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = PMesh->TextureMappers.Count;for(;LI < LN;LI++)
		{
			printf("   %s\n", PMesh->TextureMappers.Items[LI]->Class->Name);fflush(stdout);
		}
	}

	if(PMesh->Selection != E3dObjectSelPOLYGROUP)
	{
		if(E3dMesh_Check(PMesh, 2) == E_SUCCESS) { printf("  E3dMesh_Check() %sOK%s\n", ECodeGreen, ECodeNormal);fflush(stdout); }
	}

	printf(" }\n");  // Close Mesh scope
}


//================================================================
// Print information about a specified link member of an E3dNode
//================================================================
#define _M_NodeLinkPrint(pLink, pPrinted)\
{\
	if(LNode->pLink)\
	{\
		printf(" %s: [%s]", #pLink, LNode->pLink->Name);fflush(stdout);\
		pPrinted = TRUE;\
	}\
}


//================================================================
// Print information about an E3dObject
//================================================================
static void _ObjectInfo(const E3dObject* PObject, EIndex* PTotalPolyNumP, EIndex* PSelectedPolyNumP)
{
	printf("{\n");fflush(stdout);
	if(EObject_IsSubClass(PObject, E3dGeometry))
	{
		E3dGeometry*  LGeometry = (E3dGeometry*)PObject;

		printf(" Models: %lld", (long long)PObject->Models.Count);
		printf(" Selection: %08x", PObject->Selection);fflush(stdout);
		switch(PObject->Selection)
		{
			case E3dObjectSelOBJECT:          printf(" (OBJECT)");break;
			case E3dObjectSelPOLYGROUP:       printf(" (POLYGROUP)");break;
			case E3dObjectSelSPLINE_SEGMENT:  printf(" (SPLINE_SEGMENT)");break;
			case E3dObjectSelFACE_CONTOUR:    printf(" (FACE_CONTOUR)");break;
			default:  break;
		}
		printf("\n");fflush(stdout);


		{
			printf(" Inputs:  %2lld:", (long long)PObject->Inputs_Count);
			EIndex  LI = 0;for(;LI < PObject->Inputs_Count;LI++)
			{
				const EInterface*  LInterface = PObject->Inputs[LI];
				printf("  %p", LInterface);
				if(LInterface->Object) printf(" <%s>", LInterface->Object->Name ? LInterface->Object->Name : "-");
				if(LInterface->Internal) printf(" (Internal)");
			}
			printf("\n");fflush(stdout);
		}


		{
			printf(" Outputs: %2lld:", (long long)PObject->Outputs_Count);
			EIndex  LI = 0;for(;LI < PObject->Outputs_Count;LI++)
			{
				const EInterface*  LInterface = PObject->Outputs[LI];
				printf("  %p", LInterface);
				if(LInterface->Object) printf(" <%s>", LInterface->Object->Name ? LInterface->Object->Name : "-");
				if(LInterface->Internal) printf(" (Internal)");
			}
			printf("\n");fflush(stdout);
		}


		if(EObject_IsSubClass(PObject, E3dMesh))
		{
			E3dMesh*  LMesh = (E3dMesh*)LGeometry;

			_Mesh_PrintInfo(LMesh, PTotalPolyNumP, PSelectedPolyNumP);
		}
		else if(EObject_IsSubClass(PObject, E3dFace))
		{
			_Face_PrintInfo((E3dFace*)PObject);
		}
		else if(EObject_IsSubClass(PObject, E3dSpline))
		{
			_Spline_PrintInfo((E3dSpline*)PObject);
		}
		else if(EObject_IsSubClass(PObject, E3dPointSet))
		{
			_PointSet_PrintInfo((E3dPointSet*)PObject);
		}
	}

	if(PObject->Selection == E3dObjectSelOBJECT)
	{
		if(PObject->Components_Count > 0)
		{
			const EIndex  LIN = PObject->Components_Count;

			printf(" Components: %lld\n {\n", (long long)LIN);fflush(stdout);

			EIndex  LIC = 0;for(;LIC < LIN;LIC++)
			{
				E3dObjectComponent*  LInfo = PObject->Components[LIC];

				printf("  %s%p%s  Class {%s%s%s}\n", ECodeCyan, LInfo, ECodeNormal, ECodeGreen, LInfo->Class->Name, ECodeNormal);fflush(stdout);

				if(LInfo->Class->Resources.Count > 0)
				{
					printf("  {\n");fflush(stdout);
					ERes_ObjectPrintResources(LInfo, &LInfo->Class->Resources, 3);
					printf("  }\n");fflush(stdout);
				}
			}
			printf(" }\n");fflush(stdout);
		}
	}
	printf("}\n");fflush(stdout);
}


//========================================
// Info about the selection
//========================================
static void _SelInfo(E3dScene* PScene)
{
	EIndex  LTotalPolyNum = 0, LSelectedPolyNum = 0;


// Collect selected Objects
// Note that E3dScene_GetObjects() collects a list with non-repeating entries (even if an Object is instantiated from multiple Models)
//
	{
		EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(PScene, E3dItemANYOBJECT, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
		{
			EIndex  LObjI = 0;for(;LObjI < LNObjects;LObjI++)
			{
				E3dObject*  LObject = LObjects.Items[LObjI];

// Check if the Object directly, or because one of its instantiating Models are selected.
// Only print info if it's a direct selection, because we list selected Models separately, later.
//
//				if(LObject->Selection == E3dObjectSelOBJECT)
				if(LObject->Selection)
				{
					printf("Object #%2lld <%s%p%s:%s::%s%s%s>\n", (long long)LObjI, ECodeCyan, LObject, ECodeNormal, LObject->Class->Name, ECodeCyan, LObject->Name, ECodeNormal);fflush(stdout);
					_ObjectInfo(LObject, &LTotalPolyNum, &LSelectedPolyNum);
				}
/*
				else if(LObject->Selection == E3dObjectSelPOLYGROUP)
				{
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
				}
*/
			}

// We passed FALSE for PAddReferences, to E3dScene_GetObjects(), so the Objects in the LObjects array are not reference-assigned.
// This means, we can just shallow-free the LObjects.
// Otherwise, we would have to use EList_ObjectsFree().
// You should do the latter method, if you expect that other functions might release/free one or more of those Objects (via E_Delete), while you have a pointer to them.
//
			EList_FreeA_s(LObjects);
		}
	}

// Walk through the Node hierarchies of PScene
//
	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = PScene->RootNodes.Items[LRI];for(;LNode;LNode = LNode->Next)
		{
			const EBool  LModelSelected = E3dNode_IsSelected(LNode);if(LModelSelected)
			{
				printf("{%s} [%s%p%s:%s%s%s]  RefCnt: %d\n{\n", LNode->Class->Name, ECodeCyan, LNode, ECodeNormal, ECodeGreen, LNode->Name, ECodeNormal, LNode->RefCnt);
				printf(" Selection: %08x", LNode->Selection);fflush(stdout);
				switch(LNode->Selection)
				{
					case E3dNodeSelOBJECT:       printf(" (OBJECT)");break;
					case E3dNodeSelNODE:         printf(" (NODE)");break;
					case E3dNodeSelBRANCH:       printf(" (BRANCH)");break;
					case E3dNodeSelBRANCH_ROOT:  printf(" (BRANCH_ROOT)");break;
					default:  break;
				}
				printf("\n");fflush(stdout);


// Print hierarchy link members of LNode
//
				EBool  LPrinted = FALSE;
				_M_NodeLinkPrint(Parent, LPrinted);
				_M_NodeLinkPrint(Child, LPrinted);
				_M_NodeLinkPrint(Prev, LPrinted);
				_M_NodeLinkPrint(Next, LPrinted);
				_M_NodeLinkPrint(PrevSibling, LPrinted);
				_M_NodeLinkPrint(NextSibling, LPrinted);

				if(LPrinted) { printf("\n");fflush(stdout); }

// Print info about the Component objects of LNode
//
				if(LNode->Components_Count)
				{
					const EIndex  LIN = LNode->Components_Count;

					printf(" Components: %d\n", (int)LIN);fflush(stdout);
					EIndex  LIC = 0;for(;LIC < LIN;LIC++)
					{
						E3dNodeComponent*  LInfo = LNode->Components[LIC];

						const E3dNodeComponentClass*  LClasses[EMAX_CLASS_DEPTH];
						EIndex                        LCI = 0;

// Collect superclasses
//
						{
							const E3dNodeComponentClass*  LClass = LInfo->Class;
							while(LClass)
							{
								LClasses[LCI] = LClass;
								LClass = LClass->SuperClass;
								LCI++;
							}
						}

						printf(" {\n");fflush(stdout);

						int  LIndent = 2;

						printf("%*c%s%p%s\n",  LIndent, ' ',  ECodeCyan, LInfo, ECodeNormal);fflush(stdout);
						do
						{
							const E3dNodeComponentClass*  LClass = LClasses[LCI - 1];

							printf("%*c{%s%s%s}\n",  LIndent, ' ',  ECodeGreen, LClass->Name, ECodeNormal);fflush(stdout);
							if(LClass->Resources.Count > 0)
							{
								printf("%*c{\n",  LIndent, ' ');fflush(stdout);
								ERes_ObjectPrintResources(LInfo, &LClass->Resources, LIndent + 1);
								printf("%*c}\n",  LIndent, ' ');fflush(stdout);
							}
							LIndent++;
						} while(--LCI);
						printf(" }\n");fflush(stdout);
					}
				}

/*
				if(_E3dp_ShowMaterials)
				{
					LMaterial = E3dNode_InheritMaterial(LNode, TRUE);

					if(LModel->Material) { printf("   ModelMaterial:\n");E3dMaterial_Print(LMaterial, 4); }
					else
					{
						if(LMaterial == E3d_DefaultMaterial) printf("   No Material (using default)\n");
						else { printf("   Inherited ModelMaterial:\n");E3dMaterial_Print(LMaterial, 4); }
					}
				}
*/


// Check if LNode is a Model - note the dynamic cast
//
				E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel && LModel->Objects.Count > 0)
				{
					printf(" Objects(s): %lld\n {\n", (long long)LModel->Objects.Count);

					EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LObjects[LObjI];

						printf("  Object #%2lld <%s%p%s:%s::%s%s%s>\n", (long long)LObjI, ECodeCyan, LObject, ECodeNormal, LObject->Class->Name, ECodeGreen, LObject->Name, ECodeNormal);fflush(stdout);

						if(EObject_IsSubClass(LObject, E3dMesh))
						{
							E3dMesh*  LMesh = (E3dMesh*)LObject;

							_MeshAddPolyCounts(LMesh, &LTotalPolyNum, &LSelectedPolyNum);
						}
					}

					printf(" }\n");  // Close Object list
				}

				printf("}\n");  // Close Model
				if(LModel->Next) printf("\n");
			}
		}
	}
	printf("Total # of Polygons: %lld  selected: %lld\n", (long long)LTotalPolyNum, (long long)LSelectedPolyNum);fflush(stdout);
}


//========================================
// List RootNodes
//========================================
static void _RootsInfo(const E3dScene* PScene)
{
printf("RootNodes:\n");fflush(stdout);
	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = PScene->RootNodes.Items[LRI];
printf("[%s] %p RefCnt: %d\n", LNode->Name, LNode, LNode->RefCnt);fflush(stdout);
	}
}


//================================================================================
// Get the next Node of a Node, based on depth-first walk of the hierarchy
//================================================================================
E3dNode* E3dNodeHrc_NextNodeDepthFirstD(E3dNode* PNode, int* PDepth)
{
	if(PNode->Child) { PNode = PNode->Child;*PDepth += 1; }  // Does the current Node have a child ? (1)
	                                                         // (1Y) Yes, let the child be the next...
	else                                                     // (1N) No, does it have a sibling ? (2)
	{
		if(PNode->NextSibling) PNode = PNode->NextSibling;     // (2Y) Yes, let the sibling be the next...
		else                                                   // (2N) No, go back upward in this branch until
		{                                                      // we find a Node that has a sibling
			while((PNode->NextSibling == NULL) && PNode->Parent)
			{
				PNode = PNode->Parent;*PDepth -= 1;
			}
			if(PNode) PNode = PNode->NextSibling;
		}
	}
	return(PNode);
}


//========================================
// List all Nodes
//========================================
static void _NodesInfo(const E3dScene* PScene)
{
	printf("Nodes:\n");fflush(stdout);
	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = PScene->RootNodes.Items[LRI];

		int  PDepth = 0;

		if(LRI > 0) { printf("\n");fflush(stdout); }

		while(LNode)
		{
			char  LIndent[256];if(PDepth < 256) { memset(LIndent, ' ', PDepth);LIndent[PDepth] = 0; } else { memset(LIndent, ' ', 255);LIndent[255] = 0; }

			printf("%s[%p:%s%s%s]\n", LIndent, LNode, ECodeCyan, LNode->Name, ECodeNormal);fflush(stdout);

//			const int  LOldDepth = PDepth;
			LNode = E3dNodeHrc_NextNodeDepthFirstD(LNode, &PDepth);
		}
	}
}


//========================================
// List Materials
//========================================
static void _MaterialsInfo(const E3dScene* PScene)
{
	printf("Materials:");fflush(stdout);

	EIndex  LI = 0;const EIndex  LN = PScene->Materials_Count;for(;LI < LN;LI++)
	{
		E3dMaterial*  LMaterial = PScene->Materials[LI];

		printf("\n");fflush(stdout);
		E3dMaterial_Print(LMaterial, 1);
	}

	printf("PScene->Materials_Count: %lld\n", (long long)(PScene->Materials_Count));fflush(stdout);
}


//========================================
// List Animations
//========================================
static void _AnimationsInfo(const E3dScene* PScene)
{
	printf("Animations:");fflush(stdout);

	EIndex  LASI = 0;const EIndex  LASN = PScene->AnimStacks.Count;for(;LASI < LASN;LASI++)
	{
		E3dAnimStack*  LAnimStack = PScene->AnimStacks.Items[LASI];
		printf("Stack [%s] animations: %lld\n", LAnimStack->Name, (long long)(LAnimStack->Animations_Count));fflush(stdout);
		EIndex  LI = 0;const EIndex  LN = LAnimStack->Animations_Count;for(;LI < LN;LI++)
		{
			E3dAnimation*  LAnimation = LAnimStack->Animations[LI];E3dKeyFrameAnimation*  LKeyFrameAnimation = E_DynamicCast(LAnimation, E3dKeyFrameAnimation);
			printf(" |%s| {%s} Channels_Count %lld\n", LAnimation->Name, LAnimation->Class->Name, (long long)LAnimation->Channels_Count);fflush(stdout);

			EIndex  LCI = 0;const EIndex  LCN = LAnimation->Channels_Count;for(;LCI < LCN;LCI++)
			{
				E3dAnimationChannel*  LChannel = LAnimation->Channels[LCI];
				printf("%s() %s:%d  %3lld %s", __FUNCTION__, __FILE__, __LINE__, (long long)LCI, LChannel->Name);fflush(stdout);
				if(LKeyFrameAnimation)
				{
					printf(" Curve %p\n", ((E3dKeyFrameAnimationChannel*)LChannel)->Curve);fflush(stdout);
				}
				printf("\n");fflush(stdout);
			}
		}
	}
}


//========================================
// Append a number string to POutStr
//========================================
static void _AddCountString(const char* PName, const int PCount, char* POutStr)
{
	if(PCount)
	{
		char  LTmpStr[1024];

		if(PCount > 1) sprintf(LTmpStr, " %d %ss", PCount, PName);
		else sprintf(LTmpStr, " 1 %s", PName);
		strcat(POutStr, LTmpStr);
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_Info(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_INFO_ON_SELECTION:
			_SelInfo(E3d_Scene);
		break;

		case CR_INFO_LIST_ROOTS:
			_RootsInfo(E3d_Scene);
		break;

		case CR_INFO_LIST_NODES:
			_NodesInfo(E3d_Scene);
		break;

		case CR_INFO_LIST_MATERIALS:
			_MaterialsInfo(E3d_Scene);
		break;

		case CR_INFO_LIST_ANIMATIONS:
			_AnimationsInfo(E3d_Scene);
		break;
	}
}


//========================================
// Key callback
//========================================
static void _KCB_Info(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	_MCB_Info(NULL, PClientData, NULL);
}


/*
//========================================
// Menu callback handler
//========================================
static void _MCB_Ruler(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
// Just create a Spline with a "Ruler" ObjectComponent and its custom GLDraw function
// then interactively print the Spline's length on the StatusPanel
}
*/


//========================================================================================
// Remove NodeInfo from selected Nodes and GeoInfo from selected Geometries
//========================================================================================
static void _RemoveNodeAndObjectComponents(E3dScene* PScene)
{
	EIndex LAttDelNodeN = 0, LAttDelObjectN = 0;

	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;E3dNode**  LRootNodes = PScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
		{
			if(LNode->Selection != E3dNodeSelNONE)
			{
				const EBool  LNodeSelected = E3dNode_IsSelected(LNode);if(LNodeSelected)
				{
					if(LNode->Components)
					{
						EIndex  LIC = 0;const EIndex  LIN = LNode->Components_Count;for(;LIC < LIN;LIC++)
						{
							E3dNodeComponentClass*  LClass = LNode->Components[LIC]->Class;
							if(LClass->Free) LClass->Free(LNode, LNode->Components[LIC]);
							EFree(LNode->Components[LIC]);
						}

						EList_Free(LNode->Components);

						LAttDelNodeN++;
					}
				}

				{
					E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
					{
						EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
						{
							E3dObject*  LObject = LObjects[LObjI];

							if(LNodeSelected || LObject->Selection == E3dObjectSelOBJECT)
							{
								if(LObject->Components)
								{
									EIndex  LIC = 0;EIndex  LIN = LObject->Components_Count;for(;LIC < LIN;LIC++)
									{
										E3dObjectComponentClass*  LClass = LObject->Components[LIC]->Class;
										if(LClass->Free) LClass->Free(LObject, LObject->Components[LIC]);
										EFree(LObject->Components[LIC]);
									}

									EList_Free(LObject->Components);

									LAttDelObjectN++;
								}
							}

							if(LNodeSelected || LObject->Selection == E3dObjectSelPOLYGROUP)
							{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
								E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
								{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
									EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
									{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
										E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];if(LPolyGroup->Selected)
										{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
											if(LPolyGroup->Components)
											{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
												EIndex  LIC = 0;EIndex  LIN = LPolyGroup->Components_Count;for(;LIC < LIN;LIC++)
												{
													E3dPolyGroupComponentClass*  LClass = LPolyGroup->Components[LIC]->Class;
													if(LClass->Free) LClass->Free(LPolyGroup, LPolyGroup->Components[LIC]);
													EFree(LPolyGroup->Components[LIC]);
												}

												EList_Free(LPolyGroup->Components);

												LAttDelObjectN++;
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	if(LAttDelObjectN > 0 || LAttDelNodeN > 0)
	{
		char  LTmpStr[1024] = "Removed attributes from";

		_AddCountString("Node", LAttDelNodeN, LTmpStr);
		_AddCountString("Object", LAttDelObjectN, LTmpStr);

		E3dp_PrintMessage(ELogMESSAGE, 5000, LTmpStr);
		E3dWindows_RedrawByChangeFlags(PScene, E3dChgWIREFRAME);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "No Node or Object with attributes was selected");
}


//========================================
// Menu callback
//========================================
static void _MCB_RemoveAttributes(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_RemoveNodeAndObjectComponents(E3d_Scene);
}


//========================================
// Key callback
//========================================
static void _KCB_RemoveAttributes(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	_MCB_RemoveAttributes(NULL, PClientData, NULL);
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* LPlugin)
{
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Debug", "Selected objects", '\0', NULL, NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_ON_SELECTION);
//	E_KeySetCallback("Info about selected objects", 'I', EControlMask, EKeyPRESS, _KCB_Info, (EPointer)CR_INFO_ON_SELECTION);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Debug", "Roots", '\0', "~Shift ~Ctrl <Key>R", NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_LIST_ROOTS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Debug", "Nodes", '\0', "~Shift ~Ctrl <Key>N", NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_LIST_NODES);
//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Debug", "List Scene Materials", '\0', "~Shift ~Ctrl <Key>R", NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_LIST_MATERIALS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Debug", "List Scene Animations", '\0', NULL, NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_LIST_ANIMATIONS);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "Attributes", '\0', "Alt <Key>Delete", "Alt+Del", FALSE, "Remove special attributes such as construction history,\nfrom selected Nodes and Objects", _MCB_RemoveAttributes, (EPointer)NULL);
	E_KeySetCallback("Delete attributes", EKeyDELETE, EAltMask, EKeyPRESS, _KCB_RemoveAttributes, (EPointer)NULL);

// _MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Info", "Ruler", '\0', NULL, NULL, FALSE, NULL, _MCB_Ruler, (EPointer)NULL);



// _ToolBarButtons[_ToolBarButtons_Count++] = XePushButton_Add("Tool->Transform", "Anims", '\0', NULL, NULL, FALSE, NULL, _MCB_Info, (EPointer)CR_INFO_LIST_ANIMATIONS);
	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LI;

		for(LI = 0;LI < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0;
		for(LI = 0;LI < _ToolBarButtons_Count;LI++) { XeItemFree(_ToolBarButtons[LI]); } _ToolBarButtons_Count = 0;
	}

	E_KeyRemoveCallback('I',        EControlMask, EKeyPRESS, _KCB_Info, (EPointer)CR_INFO_ON_SELECTION);
	E_KeyRemoveCallback(EKeyDELETE, EAltMask,     EKeyPRESS, _KCB_RemoveAttributes, (EPointer)NULL);
	return(E_SUCCESS);
}
