/*======================================================================*/
/* 3DLib								*/
/*									*/
/* General 3D file I/O							*/
/*									*/
/* AUTHOR:	Gabor Nagy						*/
/* DATE:	1996-Dec-24 23:46:22					*/
/*									*/
/* 3DLib(TM) Copyright (C) 1995 by Gabor Nagy. All rights reserved.	*/
/*======================================================================*/
#include <dlfcn.h>	// For runtime linking (plug-ins)
#include <stdio.h>

#include <ELists.h>
#include <EMalloc.h>
#include <EParam.h>
#include <EPrefs.h>
#include <EStrings.h>

#include <E3D/IO.h>

#include <E3D/Scene.h>


int	E3d_IOStatus;

E3dIOModule**	E3d_3DModelFileFormats=NULL;
unsigned int		E3d_NumOf3DModelFileFormats=0;

E3dIOModule**	E3d_3DHierarchyFileFormats=NULL;
unsigned int		E3d_NumOf3DHierarchyFileFormats=0;

E3dIOModule**	E3d_3DSceneFileFormats=NULL;
unsigned int		E3d_NumOf3DSceneFileFormats=0;

void		(*E3d_LogFunction)(int, char*, char*, ...);

char**		E3d_Extensions=NULL;
unsigned int	E3d_NumOfExtensions=0;



//========================================================
// Find FormatIO pointer by it's file name extension
//========================================================
E3dIOModule* E3d_GetIOFilterFromFileNameExtension(char* LExtension, E3dIOModule** LFormats, unsigned int LNumOfFormats)
{
 unsigned int		LC;
 E3dIOModule*	LFormatIORec;

 for(LC=0;LC<LNumOfFormats;LC++)
 {
  LFormatIORec=LFormats[LC];
  if(LFormatIORec->FileNameExtensions)
  {
   if(EStr_StringsEqual(LFormatIORec->FileNameExtensions[0], LExtension))
   {
    return(LFormatIORec);
   }
  }
 }
 return(NULL);
}


//========================================================================================
// Strip file name of its extension, if the extension is one of the known formats
//========================================================================================
char* E3d_GetBaseFileName(char* LFileName, char* LNameRet, unsigned int LMaxLen)
{
 E3dIOModule*	LFileFormat;
 char*			LExt;
 unsigned int		LLen=strlen(LFileName),
			LC, LN=E3d_NumOf3DModelFileFormats,
			LC1, LEL;


// Check all formats for the possible extensions
//
 for(LC=0;LC<LN;LC++)
 {
  LFileFormat=E3d_3DModelFileFormats[LC];

  if(LFileFormat->FileNameExtensions)
  {
// See if the file name has any of the extensions specified for this format
//
   for(LC1=0;LFileFormat->FileNameExtensions[LC1];LC1++)
   {
    LExt=LFileFormat->FileNameExtensions[LC1];

    if((LEL=strlen(LExt))>0)
    {
     if((LLen>LEL)&&EStr_StringsNEqual(LExt, LFileName+LLen-LEL, LEL))
     {
      if((LLen-LEL+1)<=LMaxLen)
      {
       if(LNameRet!=LFileName) memmove(LNameRet, LFileName, LLen-LEL);
       LNameRet[LLen-LEL]=0;
       return(LNameRet);
      }
      else return(NULL);
     }
    }
   }
  }
 }

 if(LNameRet==LFileName) return(LFileName);
 if((LLen+1)<=LMaxLen)
 {
  memmove(LNameRet, LFileName, LLen);
  LNameRet[LLen]=0;
  return(LNameRet);
 }
 else return(NULL);
}


//========================================
// Add 3D Model I/O module
//========================================
void* E3d_3DModelIOFilterAdd(char* LName, E3dCheckProc LCheckProc, E3dModelReadProc LReadProc, E3dModelWriteProc LWriteProc, char** LFileNameExtensions)
{
 E3dIOModule*	LFormatIORec;
 E3dIOModule**	LFormats=E3d_3DModelFileFormats;


 if(LFormats)
 {
  unsigned int LC, LN;

  for(LC=0, LN=E3d_NumOf3DModelFileFormats;LC<LN;LC++)
  {
   LFormatIORec=LFormats[LC];
   if(EStr_StringsEqual(LFormatIORec->Name, LName))
   {
    LFormatIORec->DataTypes=E3dFileTypeMODEL;
    LFormatIORec->FileNameExtensions=LFileNameExtensions;
    LFormatIORec->CheckProc=LCheckProc;
    LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
    LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;

    return((void*)LFormatIORec);
   }
  }
 }

 if((LFormatIORec=(E3dIOModule*)EMalloc(sizeof(E3dIOModule)))!=NULL)
 {
  ELst_AddPointer((void***)(&E3d_3DModelFileFormats), &E3d_NumOf3DModelFileFormats, LFormatIORec);

  if(LName) LFormatIORec->Name=EStrDup(LName);
  else LFormatIORec->Name=NULL;

  LFormatIORec->DataTypes=E3dFileTypeMODEL;
  LFormatIORec->FileNameExtensions=LFileNameExtensions;
  LFormatIORec->CheckProc=LCheckProc;
  LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
  LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;
 }
 return((void*)LFormatIORec);
}


//========================================
// Remove a 3D Model I/O module
//========================================
void E3d_3DModelIOFilterRemove(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 ELst_RemovePointer((void***)(&E3d_3DModelFileFormats), &E3d_NumOf3DModelFileFormats, LFileFormat);
 if(LFilter->Name) EFree(LFilter->Name);
 EFree(LFilter);
}


//========================================
// Deactivate a 3D Model I/O module
//========================================
void E3d_3DModelIOFilterDeactivate(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 LFilter->FileNameExtensions=NULL;
 LFilter->DataTypes=0;
 LFilter->CheckProc=NULL;
 LFilter->ReadProc=NULL;
 LFilter->WriteProc=NULL;
}



//========================================
// Add 3D Hierarchy I/O module
//========================================
void* E3d_3DHierarchyIOFilterAdd(char* LName, E3dCheckProc LCheckProc, E3dModelReadProc LReadProc, E3dModelWriteProc LWriteProc, char** LFileNameExtensions)
{
 E3dIOModule*	LFormatIORec;
 E3dIOModule**	LFormats;

 if((LFormats=E3d_3DHierarchyFileFormats)!=NULL)
 {
  unsigned int LC, LN;

  for(LC=0, LN=E3d_NumOf3DHierarchyFileFormats;LC<LN;LC++)
  {
   LFormatIORec=LFormats[LC];
   if(EStr_StringsEqual(LFormatIORec->Name, LName))
   {
    LFormatIORec->DataTypes=E3dFileTypeMODEL;
    LFormatIORec->FileNameExtensions=LFileNameExtensions;
    LFormatIORec->CheckProc=LCheckProc;
    LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
    LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;

    return((void*)LFormatIORec);
   }
  }
 }

 if((LFormatIORec=(E3dIOModule*)EMalloc(sizeof(E3dIOModule)))!=NULL)
 {
  ELst_AddPointer((void***)(&E3d_3DHierarchyFileFormats), &E3d_NumOf3DHierarchyFileFormats, LFormatIORec);

  if(LName) LFormatIORec->Name=EStrDup(LName);
  else LFormatIORec->Name=NULL;

  LFormatIORec->DataTypes=E3dFileTypeHIERARCHY;
  LFormatIORec->FileNameExtensions=LFileNameExtensions;
  LFormatIORec->CheckProc=LCheckProc;
  LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
  LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;
 }
 return((void*)LFormatIORec);
}


//========================================
// Remove a 3D Hierarchy I/O filter
//========================================
void E3d_3DHierarchyIOFilterRemove(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 ELst_RemovePointer((void***)(&E3d_3DHierarchyFileFormats), &E3d_NumOf3DHierarchyFileFormats, LFileFormat);
 if(LFilter->Name) EFree(LFilter->Name);
 EFree(LFilter);
}


//========================================
// Deactivate a 3D Hierarchy I/O module
//========================================
void E3d_3DHierarchyIOFilterDeactivate(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 LFilter->FileNameExtensions=NULL;
 LFilter->DataTypes=0;
 LFilter->CheckProc=NULL;
 LFilter->ReadProc=NULL;
 LFilter->WriteProc=NULL;
}



//========================================
// Add 3D Scene I/O filter
//========================================
void* E3d_3DSceneIOFilterAdd(char* LName, E3dCheckProc LCheckProc, E3dSceneReadProc LReadProc, E3dSceneWriteProc LWriteProc, char** LFileNameExtensions)
{
 E3dIOModule*	LFormatIORec;
 E3dIOModule**	LFormats;

 if((LFormats=E3d_3DSceneFileFormats)!=NULL)
 {
  unsigned int LC, LN;

  for(LC=0, LN=E3d_NumOf3DSceneFileFormats;LC<LN;LC++)
  {
   LFormatIORec=LFormats[LC];
   if(EStr_StringsEqual(LFormatIORec->Name, LName))
   {
    LFormatIORec->DataTypes=E3dFileTypeMODEL;
    LFormatIORec->FileNameExtensions=LFileNameExtensions;
    LFormatIORec->CheckProc=LCheckProc;
    LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
    LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;

    return((void*)LFormatIORec);
   }
  }
 }

 if((LFormatIORec=(E3dIOModule*)EMalloc(sizeof(E3dIOModule)))!=NULL)
 {
  ELst_AddPointerChk((void***)(&E3d_3DSceneFileFormats), &E3d_NumOf3DSceneFileFormats, LFormatIORec);

  if(LName) LFormatIORec->Name=EStrDup(LName);
  else LFormatIORec->Name=NULL;

  LFormatIORec->DataTypes=E3dFileTypeSCENE;
  LFormatIORec->FileNameExtensions=LFileNameExtensions;
  LFormatIORec->CheckProc=LCheckProc;
  LFormatIORec->ReadProc=(E3dReadProc)LReadProc;
  LFormatIORec->WriteProc=(E3dWriteProc)LWriteProc;
 }
 return((void*)LFormatIORec);
}


//========================================
// Remove a 3D Scene I/O module
//========================================
void E3d_3DSceneIOFilterRemove(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 ELst_RemovePointer((void***)(&E3d_3DSceneFileFormats), &E3d_NumOf3DSceneFileFormats, LFileFormat);
 if(LFilter->Name) EFree(LFilter->Name);
 EFree(LFilter);
}


//========================================
// Deactivate a 3D Scene I/O filter
//========================================
void E3d_3DSceneIOFilterDeactivate(void* LFileFormat)
{
 E3dIOModule*	LFilter=(E3dIOModule*)LFileFormat;

 LFilter->FileNameExtensions=NULL;
 LFilter->DataTypes=0;
 LFilter->CheckProc=NULL;
 LFilter->ReadProc=NULL;
 LFilter->WriteProc=NULL;
}



//========================================
// Read a Model from a file
//========================================
int E3d_ModelReadFromFile(char* LFileName, E3dIOModule* LFileFormat, E3dIOModule** LFileFormatRet, E3dModel** LModel)
{
 FILE*			LInFile;
 E3dCheckProc		LCheckProc;
 E3dModelReadProc	LReadProc;
 E3dIOModule*		LFormatIORec;
 unsigned int		LC, LN;
 int			LResult;


 LN=E3d_NumOf3DModelFileFormats;
 if(LFileFormat==NULL)	 	// Check all supported file formats
 {
  if(LFileFormatRet) *LFileFormatRet=NULL;
  if((LInFile=fopen(LFileName, "r"))!=NULL)
  {
   for(LC=0;LC<LN;LC++)
   {
    LFormatIORec=E3d_3DModelFileFormats[LC];
    if((LCheckProc=LFormatIORec->CheckProc)!=NULL)
    {
     if(LCheckProc(LFileName, LInFile))
     {
      if((LReadProc=(E3dModelReadProc)(LFormatIORec->ReadProc))!=NULL)
      {
       if(LFileFormatRet) *LFileFormatRet=LFormatIORec;
       LResult=LReadProc(LFileName, LInFile, LModel);
       fclose(LInFile);
       return(LResult);
      }
     }
    }
   }
   fclose(LInFile);
   return(EIO_UNSUPPORTED_FORMAT);
  }
  else return(EIO_COULD_NOT_OPEN_FILE);
 }
 else
 {
  if((LReadProc=(E3dModelReadProc)(LFileFormat->ReadProc))!=NULL)
  {
   if((LInFile=fopen(LFileName, "r"))!=NULL)
   {
    if(LFileFormatRet) *LFileFormatRet=LFileFormat;
    LResult=LReadProc(LFileName, LInFile, LModel);
    fclose(LInFile);
    return(LResult);
   }
   else return(EIO_COULD_NOT_OPEN_FILE);
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 }
}




//========================================================
// Write a Model to a file in the specified format
//========================================================
int E3d_ModelWriteToFile(char* LFileName, char* LNameReturn, int LNameMaxLen, E3dModel* LModel, E3dIOModule* LFileFormatIn, E3dIOModule** LFileFormatRet)
{
 char			LFName[MAXPATHLEN+1];
 char*			LExt;
 E3dModelWriteProc	LWriteProc;
 E3dIOModule*		LFileFormat=NULL;
 unsigned int		LEL;


 if(LFileFormatIn==NULL) LFileFormat=E3d_GetIOFilterFromFileNameExtension(EStr_GetFileNameExtension(LFileName), E3d_3DModelFileFormats, E3d_NumOf3DModelFileFormats);
 else LFileFormat=LFileFormatIn;

 if(LFileFormatRet) *LFileFormatRet=LFileFormat;

 if(LFileFormat)
 {
  if((LWriteProc=(E3dModelWriteProc)(LFileFormat->WriteProc))!=NULL)
  {
   if(LFileFormat->FileNameExtensions)
   {
    unsigned int	LC, LLen=EStr_GetFileNameLength(LFileName);
    EBool		LFound=FALSE;

// See if the file name already has any of the extensions specified for this format
//
    for(LC=0;LFileFormat->FileNameExtensions[LC]!=NULL;LC++)
    {
     LExt=LFileFormat->FileNameExtensions[LC];

     if((LEL=strlen(LExt))>0)
     {
      if((LLen>LEL)&&EStr_StringsNEqual(LExt, LFileName+strlen(LFileName)-LEL, LEL))
      {
       strcpy(LFName, LFileName);
       LFound=TRUE;
       break;
      }
     }
    }
    if(!LFound) sprintf(LFName, "%s%s", LFileName, LFileFormat->FileNameExtensions[0]);
   }
   else strcpy(LFName, LFileName);

   if(strlen(LFName)<=LNameMaxLen) strcpy(LNameReturn, LFName);
   return(LWriteProc(LFName, LModel, E3dMDL_BINARY));
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 }
 return(EIO_ERROR);
}


//========================================
// Read Hierarchy from a file
//========================================
int E3d_HierarchyReadFromFile(char* LFileName, E3dIOModule* LFileFormatIn, E3dIOModule** LFileFormatRet, E3dModel** LModel)
{
 FILE*			LInFile;
 E3dCheckProc		LCheckProc;
 E3dModelReadProc	LReadProc;
 E3dIOModule*		LFormatIORec;
 unsigned int		LC, LN;
 int			LResult;


 LN=E3d_NumOf3DHierarchyFileFormats;
 if(LFileFormatIn==NULL)	 	// Check all supported file formats
 {
  if(LFileFormatRet) *LFileFormatRet=NULL;
  if((LInFile=fopen(LFileName, "r"))!=NULL)
  {
   for(LC=0;LC<LN;LC++)
   {
    LFormatIORec=E3d_3DHierarchyFileFormats[LC];
    if((LCheckProc=LFormatIORec->CheckProc)!=NULL)
    {
     if(LCheckProc(LFileName, LInFile))
     {
      if((LReadProc=(E3dModelReadProc)(LFormatIORec->ReadProc))!=NULL)
      {
       if(LFileFormatRet) *LFileFormatRet=LFormatIORec;
       LResult=LReadProc(LFileName, LInFile, LModel);
       if(LResult==EIO_SUCCESS) { if(*LModel) E3d_ModelHrcRefreshHierarchy(*LModel); }
       fclose(LInFile);

       return(LResult);
      }
     }
    }
   }
   fclose(LInFile);
   return(EIO_UNSUPPORTED_FORMAT);
  }
  return(EIO_COULD_NOT_OPEN_FILE);
 }
 else
 {
  if((LReadProc=(E3dModelReadProc)(LFileFormatIn->ReadProc))!=NULL)
  {
   if((LInFile=fopen(LFileName, "r"))!=NULL)
   {
    if(LFileFormatRet) *LFileFormatRet=LFileFormatIn;
    LResult=LReadProc(LFileName, LInFile, LModel);
    if(LResult==EIO_SUCCESS) { if(*LModel) E3d_ModelHrcRefreshHierarchy(*LModel); }
    fclose(LInFile);
    return(LResult);
   }
   else return(EIO_COULD_NOT_OPEN_FILE);
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 }
}


//========================================================
// Write a Hierarchy to a file in the specified format
//========================================================
int E3d_HierarchyWriteToFile(char* LFileName, char* LNameReturn, int LNameMaxLen, E3dModel* LModel, E3dIOModule* LFileFormatIn, E3dIOModule** LFileFormatRet)
{
 char			LFName[MAXPATHLEN+1];
 char*			LExt;
 E3dModelWriteProc	LWriteProc;
 E3dIOModule*		LFileFormat=NULL;
 unsigned int		LEL;


 if(LFileFormatIn==NULL) LFileFormat=E3d_GetIOFilterFromFileNameExtension(EStr_GetFileNameExtension(LFileName), E3d_3DHierarchyFileFormats, E3d_NumOf3DHierarchyFileFormats);
 else LFileFormat=LFileFormatIn;

 if(LFileFormatRet) *LFileFormatRet=LFileFormat;

 if(LFileFormat)
 {
  if((LWriteProc=(E3dModelWriteProc)(LFileFormat->WriteProc))!=NULL)
  {
   if(LFileFormat->FileNameExtensions)
   {
    unsigned int	LC, LLen=EStr_GetFileNameLength(LFileName);
    EBool		LFound=FALSE;


// See if the file name already has any of the extensions specified for this format
//
    for(LC=0;LFileFormat->FileNameExtensions[LC]!=NULL;LC++)
    {
     LExt=LFileFormat->FileNameExtensions[LC];

     if((LEL=strlen(LExt))>0)
     {
      if((LLen>LEL)&&EStr_StringsNEqual(LExt, LFileName+strlen(LFileName)-LEL, LEL))
      {
       strcpy(LFName, LFileName);
       LFound=TRUE;
       break;
      }
     }
    }
    if(!LFound) sprintf(LFName, "%s%s", LFileName, LFileFormat->FileNameExtensions[0]);
   }
   else strcpy(LFName, LFileName);

   if(strlen(LFName)<=LNameMaxLen) strcpy(LNameReturn, LFName);
   return(LWriteProc(LFName, LModel, E3dMDL_BINARY));
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 } else return(EIO_NO_IOFILTER_FOUND);
}


//========================================
// Read Scene from a file
//========================================
int E3d_SceneReadFromFile(char* LFileName, E3dIOModule* LFileFormatIn, E3dIOModule** LFileFormatRet, E3dScene** LSceneRet)
{
 E3dIOModule*		LFormatIORec;
 FILE*			LInFile;
 E3dCheckProc		LCheckProc;
 E3dSceneReadProc	LReadProc;
 int			LResult;
 unsigned int		LC, LN;


 LN=E3d_NumOf3DSceneFileFormats;
 if(LFileFormatIn==NULL)	 	// Check all supported file formats
 {
  if(LFileFormatRet) *LFileFormatRet=NULL;
  if((LInFile=fopen(LFileName, "r"))!=NULL)
  {
   for(LC=0;LC<LN;LC++)
   {
    LFormatIORec=E3d_3DSceneFileFormats[LC];
    if((LCheckProc=LFormatIORec->CheckProc)!=NULL)
    {
     if(LCheckProc(LFileName, LInFile))
     {
      if((LReadProc=(E3dSceneReadProc)(LFormatIORec->ReadProc))!=NULL)
      {
       if(LFileFormatRet) *LFileFormatRet=LFormatIORec;
       LResult=LReadProc(LFileName, LInFile, LSceneRet);

       fclose(LInFile);
       return(LResult);
      }
     }
    }
   }
   fclose(LInFile);
   return(EIO_UNSUPPORTED_FORMAT);
  }
  return(EIO_COULD_NOT_OPEN_FILE);
 }
 else
 {
  if((LReadProc=(E3dSceneReadProc)(LFileFormatIn->ReadProc))!=NULL)
  {
   if((LInFile=fopen(LFileName, "r"))!=NULL)
   {
    if(LFileFormatRet) *LFileFormatRet=LFileFormatIn;
    LResult=LReadProc(LFileName, LInFile, LSceneRet);
    fclose(LInFile);
    return(LResult);
   }
   else return(EIO_COULD_NOT_OPEN_FILE);
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 }
}


//========================================================
// Write a Scene to a file in the specified format
//========================================================
int E3d_SceneWriteToFile(char* LFileName, char* LNameReturn, int LNameMaxLen, E3dScene* LScene, E3dIOModule* LFileFormatIn, E3dIOModule** LFileFormatRet)
{
 char			LFName[MAXPATHLEN+1];
 char*			LExt;
 E3dSceneWriteProc	LWriteProc;
 E3dIOModule*		LFileFormat=NULL;
 unsigned int		LEL;


 if(LFileFormatIn==NULL) LFileFormat=E3d_GetIOFilterFromFileNameExtension(EStr_GetFileNameExtension(LFileName), E3d_3DSceneFileFormats, E3d_NumOf3DSceneFileFormats);
 else LFileFormat=LFileFormatIn;

 if(LFileFormatRet) *LFileFormatRet=LFileFormat;

 if(LFileFormat)
 {
  if((LWriteProc=(E3dSceneWriteProc)(LFileFormat->WriteProc))!=NULL)
  {
   if(LFileFormat->FileNameExtensions)
   {
    unsigned int	LC, LLen=EStr_GetFileNameLength(LFileName);
    EBool		LFound=FALSE;


// See if the file name already has any of the extensions specified for this format
//
    for(LC=0;LFileFormat->FileNameExtensions[LC];LC++)
    {
     LExt=LFileFormat->FileNameExtensions[LC];

     if((LEL=strlen(LExt))>0)
     {
      if((LLen>LEL)&&EStr_StringsNEqual(LExt, LFileName+strlen(LFileName)-LEL, LEL))
      {
       strcpy(LFName, LFileName);
       LFound=TRUE;
       break;
      }
     }
    }
    if(!LFound) sprintf(LFName, "%s%s", LFileName, LFileFormat->FileNameExtensions[0]);
   }
   else strcpy(LFName, LFileName);

   if(strlen(LFName)<=LNameMaxLen) strcpy(LNameReturn, LFName);
   return(LWriteProc(LFName, LScene, E3dMDL_BINARY));
  }
  else return(EIO_UNSUPPORTED_OPERATION);
 } else return(EIO_NO_IOFILTER_FOUND);
}
