//================================================================
// File I/O
//
// Plugin for EQUINOX-3D
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-May-21 17:38:15
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <stdio.h>

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

#include <Xe/Label.h>
#include <Xe/Matrix.h>
#include <Xe/Menu.h>
#include <Xe/PushButton.h>
#include <Xe/ToggleButton.h>

#include <Xe/Dialog.h>
#include <Xe/Shell.h>
#include <Xe/FSBox.h>

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

#include <E3D/UI/ModeStack.h>    // E3dFIXME : for E3dp_ResetWorkModes (we shouldn't need it, it's just a work-around)
#include <E3D/UI/Panel.h>
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage
#include <E3D/UI/TimePanel.h>


#include "../EQXExt.h"


static EPlugin*      _Plugin = NULL;
static EPluginLock*  _SaveSceneDialogLock = NULL;


static EIOModuleGUI*  _SaveSceneFormatWidgets = NULL;


static XeDWidget  E3d_SaveSelectedHrcMenuButton = NULL,
                 E3d_SaveSceneMenuButton = NULL,
                 E3d_ReloadChangedMenuButton = NULL;



// "Save Scene" dialog box
//
static XeWidget    _SaveSceneDialogW = NULL;
static XeWidget    _SaveFormatsW = NULL, _FromExtensionBW = NULL;


//========================================
// Set Scene output file format
//========================================
static void _CB_3DSceneSaveFormat(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dIOModule*  LIOModule = (E3dIOModule*)(((XeOptionMenuCallbackStruct*)PCallData)->Value);
	char          LFName[MAXPATHLEN + 1];
	EBool         LCorrected = FALSE;

	Eqx_Prefs.SceneSaveFormat = LIOModule;

	E3dp_IOModuleShowGUI(LIOModule, _SaveSceneFormatWidgets, E3d_3DSceneFileFormats.Count, NULL);

	Eqx_Prefs.SceneFormatFromExtension = FALSE;
	XeToggleButton_SetState(_FromExtensionBW, FALSE, FALSE);

	char*  LFileName = XeFSBox_GetFile(_SaveSceneDialogW);if(LFileName)
	{
// See if the file name already has any of the extensions specified for any 3D-Scene format
//
		{
			const int  LLen = (int)strlen(LFileName);
			EBool      LFound = FALSE;

			int  LI = 0;const int  LN = E3d_3DSceneFileFormats.Count;for(;LI < LN;LI++)
			{
				E3dIOModule*  LTFileFormat = E3d_3DSceneFileFormats.Items[LI];if(LTFileFormat->FileNameExtensions)
				{
					int  LC1 = 0;for(;LTFileFormat->FileNameExtensions[LC1] != NULL;LC1++)
					{
						const char*  LExt = LTFileFormat->FileNameExtensions[LC1];
						const int    LEL = (int)strlen(LExt);

						if(LEL > 0)
						{
// Extension matched
//
							if(LLen > LEL && EStr_NEqual(LExt, LFileName + LLen - LEL, LEL))
							{
								LFound = TRUE;
								LFileName[LLen - LEL] = 0;
								sprintf(LFName, "%s%s", LFileName, LIOModule->FileNameExtensions[0]);
								LCorrected = TRUE;
								LI = LN;
								break;
							}
						}
					}
				}
			}
			if(!LFound)
			{
				sprintf(LFName, "%s%s", LFileName, LIOModule->FileNameExtensions[0]);LCorrected = TRUE;
			}
		}

		if(LCorrected) XeFSBox_SetFile(_SaveSceneDialogW, LFName);

		EFree(LFileName);
	}
}


//========================================
// Save Scene FSBox callbacks
//========================================
static void _FCB_SaveScene(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case XeCR_OK:
			{
				char*  LStringPtr = XeFSBox_GetFile(_SaveSceneDialogW);if(LStringPtr)
				{
					E3dp_Prefs.CurrentFrame    = E3dp_TimePanel->Frame;
					E3dp_Prefs.StartFrame      = E3dp_TimePanel->StartFrame;
					E3dp_Prefs.EndFrame        = E3dp_TimePanel->EndFrame;
					E3dp_Prefs.FramesPerSecond = E3dp_TimePanel->FramesPerSecond;

//{ EIndex  LI = 0;for(;LStringPtr[LI];LI++) printf("%02x ", LStringPtr[LI]);printf("\n");fflush(stdout); }

					E3dScene*  LScene = E3d_Scene;
					char       LActualName[MAXPATHLEN + 1];
					int        LResult;
					if(Eqx_Prefs.SceneFormatFromExtension) LResult = E3dScene_SaveToFile(LStringPtr, LActualName, MAXPATHLEN, LScene, E3dFileFormatFROM_EXTENSION, NULL);
					else LResult = E3dScene_SaveToFile(LStringPtr, LActualName, MAXPATHLEN, LScene, Eqx_Prefs.SceneSaveFormat, NULL);

					switch(LResult)
					{
						case E_SUCCESS:
							E3dp_PrintMessage(ELogMESSAGE, 10000, "Scene saved as '%s'", LActualName);
							XeFileSelectionDialog_Unmap(_SaveSceneDialogW);
							EPluginLock_Del(_Plugin, &_SaveSceneDialogLock, FALSE);
							Eqx_SetTitle(LActualName);
						break;

						case E_NO_IOFILTER_FOUND:
							if(Eqx_Prefs.SceneFormatFromExtension)
							{
								const char*  LExtension = EStr_GetFileNameExtension(LStringPtr);
								if(LExtension) E3dp_PrintMessage(ELogWARNING, 10000, "No exporter found for extension '%s'. Please correct it or turn off 'From extension'", LExtension);
								else E3dp_PrintMessage(ELogWARNING, 10000, "Please specify a file name extension or turn off 'From extension'");
							}
						break;

						default:  E3dp_PrintMessage(ELogMESSAGE, 10000, "%s '%s'", LStringPtr, EResult_ToString(LResult));break;
					}

					EFree(LStringPtr);
				}
			}
		break;

		case XeCR_CANCEL:
			XeFileSelectionDialog_Unmap(_SaveSceneDialogW);
			EPluginLock_Del(_Plugin, &_SaveSceneDialogLock, FALSE);
		break;

		case XeCR_APPLY:  // File name changed
			if(Eqx_Prefs.SceneFormatFromExtension)
			{
				char*  LStringPtr = XeFSBox_GetFile(_SaveSceneDialogW);if(LStringPtr)
				{
					const char*  LExtension = EStr_GetFileNameExtension(LStringPtr);if(LExtension)
					{
						E3dIOModule*  LIOModule = E3dIOModule_ByFileNameExtension(LExtension,  &E3d_3DSceneFileFormats,  EWRITE);

						if(LIOModule) E3dp_IOModuleShowGUI(LIOModule, _SaveSceneFormatWidgets, E3d_3DSceneFileFormats.Count, _SaveFormatsW);
					}

					EFree(LStringPtr);
				}
			}
		break;
	}
}


//========================================
// Dialog-box callback handler
//========================================
static void _DCB_Close(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeFileSelectionDialog_Unmap(_SaveSceneDialogW);
		EPluginLock_Del(_Plugin, &_SaveSceneDialogLock, FALSE);
	}
}


static int _SaveSceneDialogLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	XeFileSelectionDialog_Unmap(_SaveSceneDialogW);

	return(E_SUCCESS);
}


//========================================
// Dialog-box callback handler
//========================================
static void _DCB_FromExtension(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	Eqx_Prefs.SceneFormatFromExtension = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


//========================================
// Save Scene
//========================================
static void E3dMCB_SaveScene(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dp_ResetWorkModes();

	if(_SaveSceneDialogLock == NULL) _SaveSceneDialogLock = EPluginLock_Add(_Plugin, "SaveSceneDialog", _SaveSceneDialogLockDel);

	if(_SaveSceneDialogW == NULL)
	{
		EArg  Args[256];int  ArgCnt;

		if(Eqx_ModelPath == NULL) Eqx_ModelPath = EStr_CurrentDir(FALSE);

		EXeStart;
		EXeSetArg(XeNMinSizeX, 380);EXeSetArg(XeNMinSizeY, 400);
		EXeSetArg(XeNCenterToWidget, Eqx_TopShellW);
		EXeSetArg(XeNFileListVisibleItemNum, 22);
		EXeSetArg(XeNTitle, "Save Scene");
		EXeSetArg(XeNPath, Eqx_ModelPath);
		EXeSetArg(XeNFilterListVisibleItemNum, 4);
		EXeSetArg(XeNCustomItemType, XeMATRIX);
		EXeSetArg(XeNCustomItemPlacement, XePLACE_ABOVE_FILENAME);
		XeWidget  LDialogW = XeFileSelectionDialog_New(Eqx_TopShellW,  Args, ArgCnt);if(LDialogW)
		{
			XeWidget          LW;
			XeOptionMenuRec*  LOptionMenuItems;
			int               LNumOfFormatsFound = 0;

			_SaveSceneDialogW = LDialogW;

			XeAddCallbackN(LDialogW, XeNOkCallback,           _FCB_SaveScene, (EPointer)XeCR_OK);
			XeAddCallbackN(LDialogW, XeNCancelCallback,       _FCB_SaveScene, (EPointer)XeCR_CANCEL);
			XeAddCallbackN(LDialogW, XeNValueChangedCallback, _FCB_SaveScene, (EPointer)XeCR_APPLY);
			XeAddCallback(LDialogW,  XeNWMCallback,           _DCB_Close,     (EPointer)0);

			XeWidget  LTopMatrixW = XeFSBox_GetChild(LDialogW, XeDIALOG_CUSTOM_ITEM);
			EXeStart;
			EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
			EXeSetArg(XeNOrientation, XeVERTICAL);
			EXeSetArg(XeNShadowThickness, 2);
			EXeSetArg(XeNMarginSizeX, 7);EXeSetArg(XeNMarginSizeY, 5);
			EXeSetArg(XeNSpacingX, 12);EXeSetArg(XeNSpacingY, 7);
			Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

			const EIndex  LN = E3d_3DSceneFileFormats.Count;

			{
				EIndex  LI = 0;for(;LI < LN;LI++)
				{
					E3dIOModule*  LIOModule = E3d_3DSceneFileFormats.Items[LI];

					if(LIOModule->Save) LNumOfFormatsFound++;
				}
			}

			ENewN(LNumOfFormatsFound, LOptionMenuItems);

			{
				EIndex  LI = 0;XeOptionMenuRec*  LOptionMenuItem = LOptionMenuItems;for(;LI < LN;LI++)
				{
					E3dIOModule*	LIOModule = E3d_3DSceneFileFormats.Items[LI];if(LIOModule->Save)
					{
						LOptionMenuItem->Name = EStrDup(LIOModule->Name);
						LOptionMenuItem->Callback = NULL;
						LOptionMenuItem->Value = (EPointer)LIOModule;
						LOptionMenuItem->ClientData = (EPointer)LIOModule;
						LOptionMenuItem->Icon = NULL;
						LOptionMenuItem++;
					}
				}
			}

			if(LNumOfFormatsFound > 0) Eqx_Prefs.SceneSaveFormat = E3d_3DSceneFileFormats.Items[0];
			else Eqx_Prefs.SceneSaveFormat = NULL;


			{
				EXeStart;
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 12);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				XeWidget  LMatrixW = XeWidget_ManagedNew("LMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNButtonColorPtr, &Xe_ButtonRGBAiColor);
				EXeSetArg(XeNFontList, XeMenu_FontList);
				EXeSetArg(XeNLabelFontList, Xe_LabelFontList);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNMenuPaneBackgroundColor, &XeOptionMenu_RGBAiColor);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				_SaveFormatsW = XeOptionMenu_CreateAndInitManaged(LMatrixW, NULL, LOptionMenuItems, LNumOfFormatsFound, (EPointer)(Eqx_Prefs.SceneSaveFormat), "Format", _CB_3DSceneSaveFormat, NULL,  Args, ArgCnt);

				EIndex  LI = 0;XeOptionMenuRec*  LOptionMenuItem = LOptionMenuItems;for(;LI < LNumOfFormatsFound;LI++, LOptionMenuItem++)
				{
					if(LOptionMenuItem->Name) EFree(LOptionMenuItem->Name);
				}
				EFree(LOptionMenuItems);

				if(E3d_3DSceneFileFormats.Count > 0)
				{
// E3dFIXME
// This should be freed when the app exits
//
					ENewN(E3d_3DSceneFileFormats.Count, _SaveSceneFormatWidgets);

					for(LI = 0;LI < LN;LI++)
					{
						E3dIOModule*	LIOModule = E3d_3DSceneFileFormats.Items[LI];

						if(LIOModule->GUIForSave)
						{
							EXeStart;
							EXeSetArg(XeNOrientation, XeVERTICAL);
							_SaveSceneFormatWidgets[LI].MatrixW = XeWidget_ManagedNew("3DIOMtx", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);
						}
						else
						{
							_SaveSceneFormatWidgets[LI].MatrixW = NULL;
						}

						_SaveSceneFormatWidgets[LI].IOModule = LIOModule;
					}

					E3dp_IOModuleShowGUI(E3d_3DSceneFileFormats.Items[0], _SaveSceneFormatWidgets, E3d_3DSceneFileFormats.Count, NULL);
				}


				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, Eqx_Prefs.SceneFormatFromExtension);
				_FromExtensionBW = LW = XeWidget_ManagedNew("From extension", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_FromExtension, (EPointer)NULL);
			}

			XeWidget_Manage(LTopMatrixW);

			XeShell_CenterToWidget(LDialogW, Eqx_TopShellW);
			XeShell_Raise(LDialogW);
		}

	}
	else XeShell_Raise(_SaveSceneDialogW);
}


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


typedef struct
{
	EpImage*  Image;
	EListA(, E3d2DTexture*, Textures);
	EBool  NeedsReload;
} E3dImageAndTxs;

static E3dImageAndTxs* _FindTx(E3dImageAndTxs* PS, const EIndex PNS, E3d2DTexture* PTexture)
{
	EIndex  LIC = 0;E3dImageAndTxs*  LS = PS;for(;LIC < PNS;LIC++, LS++)
	{
		EIndex  LTC = 0;for(;LTC < LS->Textures_Count;LTC++)
		{
			if(LS->Textures[LTC] == PTexture) return(LS);
		}
	}
	return(NULL);
}


//========================================
// Reload changed image files
//========================================
static void E3dMCB_ReloadChanged(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

	EListAI(, E3dImageAndTxs, LImages);

	{
		EIndex  LI = 0;for(;LI < LScene->Textures_Count;LI++)
		{
			E3d2DTexture*  LTexture = LScene->Textures[LI];

			if(LTexture->Image && LTexture->Image->FileName)
			{
				E3dImageAndTxs*  LInTs = _FindTx(LImages, LImages_Count, LTexture);if(LInTs)
				{
					EList_PointerAddAChk(LInTs->Textures, 16, LTexture);
				}
				else
				{
					LInTs = EList_StructAddAClr(LImages, 16);
					LInTs->Image = (EpImage*)LTexture->Image;
					EList_PointerAddAChk(LInTs->Textures, 16, LTexture);
				}
			}
		}
	}

	if(LImages)
	{
		EIndex  LReloadC = 0;

		{
			EIndex  LIC = 0;E3dImageAndTxs*  LInTs = LImages;for(;LIC < LImages_Count;LIC++, LInTs++)
			{
				EpImage*  LImage = LInTs->Image;

				if(LImage->Loaded_ms >= 0)
				{
					struct stat  LFStat;if(lstat(LImage->FileName, &LFStat) == 0)
					{
						const ETime  LMTime_ms = ((long long)LFStat.st_mtime) * 1000;
//printf("%s() %s:%d  %s loaded at %lld  mtime %lld\n", __FUNCTION__, __FILE__, __LINE__, LImage->FileName, (long long)LImage->Loaded_ms, (long long)LMTime_ms);fflush(stdout);
						if(LMTime_ms > LImage->Loaded_ms) { LInTs->NeedsReload = TRUE;LReloadC++; }
					}
				}
			}
		}

		if(LReloadC > 0)
		{
			EBool  LRenderersStopped = FALSE;
			EIndex  LProggressC = 0;

			EIndex  LIC = 0;E3dImageAndTxs*  LInTs = LImages;for(;LIC < LImages_Count;LIC++, LInTs++)
			{
				if(LInTs->NeedsReload)
				{
					if(!LRenderersStopped) { E3dRenderers_Stop();LRenderersStopped = TRUE; }

					E3dProgressIndicator_SetMessage("Reloading %s", LInTs->Textures[0]->FileName);E3dProgressIndicator_Set((float)LProggressC / (float)LReloadC, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);

					E3dScene_TextureImageLoad(LScene, LInTs->Textures[0], E3dp_Prefs.TexturePath, E3dp_Prefs.CurrentFrame, TRUE,  NULL);LReloadC++;  // Do Image force reload for the first Texture, then just update the Texture for the rest
					EIndex  LTC = 1;for(;LTC < LInTs->Textures_Count;LTC++)
					{
						E3dScene_TextureImageLoad(LScene, LInTs->Textures[LTC], E3dp_Prefs.TexturePath, E3dp_Prefs.CurrentFrame, FALSE,  NULL);LReloadC++;
						E3dProgressIndicator_SetMessage("Reloading %s", LInTs->Textures[LTC]->FileName);E3dProgressIndicator_Set((float)LProggressC / (float)LReloadC, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);
					}
				}
			}
			E3dProgressIndicator_Set((float)LProggressC / (float)LReloadC, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);
			E3dProgressIndicator_SetMessage(NULL);E3dProgressIndicator_Set(-1.0, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);
		}

		{
			EIndex  LIC = 0;E3dImageAndTxs*  LInTs = LImages;for(;LIC < LImages_Count;LIC++, LInTs++) EList_FreeA(LInTs->Textures);
		}

		EList_FreeA(LImages);

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMaterialPARAM|E3dChgTextureMAPPING);
	}
}


//========================================
// Reload changed image files
//========================================
static void E3dKCB_ReloadChanged(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	E3dMCB_ReloadChanged(NULL, PClientData, (EPointer)NULL);
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

//	E3d_SaveSelectedHrcMenuButton = XePushButton_Add("Menu->File", "Save selected hierarchies", '\0', NULL, NULL, FALSE, NULL, EqxMCB_SaveSelectedHierarchies, (EPointer)0);

	const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;const char*  LCtrlName = Xe_CommandKeyForCtrl ? "Cmd" : "Ctrl";char                 LAccText[256];

	sprintf(LAccText, "Shift+%s+S", LCtrlName);E3d_SaveSceneMenuButton = XePushButton_Add(Xe_App, "Menu/File", "Save scene", '\0', NULL, LAccText, FALSE, NULL, E3dMCB_SaveScene, (EPointer)0);
	E_KeySetCallback("Save scene", 'S', LCtrlMask|EShiftMask, EKeyPRESS, E3dKCB_SaveScene, (EPointer)0);

	sprintf(LAccText, "%s+R", LCtrlName);E3d_ReloadChangedMenuButton = XePushButton_Add(Xe_App, "Menu/File", "Reload changed", '\0', NULL, LAccText, FALSE, NULL, E3dMCB_ReloadChanged, (EPointer)0);
	E_KeySetCallback("Reload changed", 'R', LCtrlMask, EKeyPRESS, E3dKCB_ReloadChanged, (EPointer)0);

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		XeItemFree(E3d_SaveSelectedHrcMenuButton);
		XeItemFree(E3d_SaveSceneMenuButton);
		XeItemFree(E3d_ReloadChangedMenuButton);
	}

	E_KeyRemoveCallback('S', EControlMask|EShiftMask, EKeyPRESS, E3dKCB_SaveScene, (EPointer)0);
	E_KeyRemoveCallback('R', EControlMask, EKeyPRESS, E3dKCB_ReloadChanged, (EPointer)0);

	return(E_SUCCESS);
}
