/*=========================================================================
 *
 * DFBSA.CPP - Dave Humphrey (uesp@m0use.net), 29 October 2000
 *
 *=======================================================================*/

	/* Include Files */
#include "dfbsa.h"
#include "dfcommon.h"


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::CDFBSAFile()"
/*=========================================================================
 *
 * Class CDFBSAFile Constructor
 *
 *=======================================================================*/
CDFBSAFile::CDFBSAFile() {
  DirectoryType = DFBSA_FILENAME_DIRECTORY;
  NumRecords = 0;
  pFileHandle = NULL;
  pBSAFilename = NULL;
  HeaderRead = FALSE;
  DirectoryRead = FALSE;
 }
/*=========================================================================
 *		End of Class CDFBSAFile Constructor
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::Destroy()"
/*=========================================================================
 *
 * Class CDFBSAFile Destructor
 *
 *=======================================================================*/
void CDFBSAFile::Destroy (void) {

	/* Close the file if its open */
  Close();

  	/* Delete the record data */
  ClearRecords();

	/* Delete the directory data */
  ClearDirectory();
  
  DirectoryType = DFBSA_FILENAME_DIRECTORY;
  HeaderRead = FALSE;
  DirectoryRead = FALSE;
 }
/*=========================================================================
 *		End of Class CDFBSAFile Destructor
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::ClearDirectory"
/*=========================================================================
 *
 * Class CDFBSAFile Method - void ClearDirectory (void);
 *
 * Deletes all the directory entries. Protected class method.
 *
 *=======================================================================*/
void CDFBSAFile::ClearDirectory (void) {
  int LoopCounter;

	/* Delete all record directory entries */
  for (LoopCounter = 0; LoopCounter < NumRecords; LoopCounter++) {
    DestroyPointer(pDirectory[LoopCounter]);
   }

 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::ClearDirectory();
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::Dump()"
/*===========================================================================
 *
 * Class CDFBSAFile Method - void Dump (pFileHandle);
 *
 * Outputs debug information about the object to the given file stream.
 *
 *=========================================================================*/
void CDFBSAFile::Dump (FILE* pFileHandle) {
  int LoopCounter;
  int MaxCount;
  int FirstCount;
  
	/* Ignore any invalid handles */
  if (pFileHandle == NULL) return;

  fprintf (pFileHandle, "CDFBSAFile Object (0x%08p)\n", this);
  fprintf (pFileHandle, "\tNumRecords = %d\n", NumRecords);
  fprintf (pFileHandle, "\tDirectoryType = 0x%04X\n", DirectoryType);

	/* Output first five directory records */
  if (NumRecords >= 5) 
    MaxCount = 5;
  else 
    MaxCount = NumRecords;

  fprintf (pFileHandle, "\tDirectory Entries...\n");

  for (LoopCounter = 0; LoopCounter < MaxCount; LoopCounter++) 
    DumpDirectoryEntry(pFileHandle, LoopCounter);

	/* Output last five directory records */
  if (NumRecords >= 10)
    FirstCount = NumRecords - 5;
  else
    FirstCount = MaxCount;

  fprintf (pFileHandle, "\t\t....\n");

  for (LoopCounter = FirstCount; LoopCounter < NumRecords; LoopCounter++) 
    DumpDirectoryEntry(pFileHandle, LoopCounter);

 }
/*===========================================================================
 *		End of Class Method CDFBSAFile::Dump()
 *=========================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::DumpDirectoryEntry()"
/*===========================================================================
 *
 * Class CDFBSAFile Method - void DumpDirectoryEntry (pFileHandle, DirIndex);
 *
 * Dumps the specified directory entry to the given file stream.  Protected
 * class method.
 *
 *=========================================================================*/
void CDFBSAFile::DumpDirectoryEntry (FILE* pFileHandle, const int DirIndex) {

	/* Ensure a valid index */
  if (!IsValidRecordIndex(DirIndex)) return;

	/* Output based on the directory type */
  if (DirectoryType == DFBSA_FILENAME_DIRECTORY) 
    fprintf (pFileHandle, "\t\t%d) '%s' = 0x%08lX bytes at 0x%08lX\n", DirIndex, pDirectory[DirIndex]->Filename, pDirectory[DirIndex]->RecordSize, pDirectory[DirIndex]->RecordOffset);
  else 
    fprintf (pFileHandle, "\t\t%d) 0x%08lX = 0x%08lX bytes at 0x%08lX\n", DirIndex, pDirectory[DirIndex]->Value, pDirectory[DirIndex]->RecordSize, pDirectory[DirIndex]->RecordOffset);
   
 }
/*===========================================================================
 *		End of Class Method CDFBSAFile::DumpDirectoryEntry()
 *=========================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::ExtractRecords()"
/*===========================================================================
 *
 * Class CDFBSAFile Method - boolean ExtractRecords (pCallBack);
 *
 * Saves all BSA record information to seperate files in the current
 * directory.  Returns FALSE on any error.  The callback function, if
 * not NULL, is called before each record is extracted.
 *
 *=========================================================================*/
boolean CDFBSAFile::ExtractRecords (PDFBSA_EXTRACT_PROC pCallBack) {
  boolean HadToOpenFile = FALSE;
  int     LoopCounter;
  boolean Result = TRUE;

	/* Ensure file is current open */
  if (!IsOpen()) {
    if (!Open()) return (FALSE);
    HadToOpenFile = TRUE;
   }
	
	/* Output all records */
  for (LoopCounter = 0; LoopCounter < NumRecords; LoopCounter++) {
    if (pCallBack != NULL) pCallBack(LoopCounter, NumRecords, *pDirectory[LoopCounter], DirectoryType);
    Result = WriteRawRecord(LoopCounter);
    if (!Result) break;
   }

  if (HadToOpenFile) Close();
  return (Result);
 }
/*===========================================================================
 *		End of Class Method CDFBSAFile::ExtractRecords()
 *=========================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::FindRecordIndex()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - int FindRecordIndex (pFilename);
 *
 * Searches for the given filename in the current directory and returns
 * the record index for it.  If the current directory is value type,
 * the file is not found, or on other errors, the function returns
 * DFBSA_ERROR.
 *
 *=======================================================================*/
int CDFBSAFile::FindRecordIndex (const char* pFilename) {
  int LoopCounter;
  int Result;
  int MinResult = 0;
  int MinIndex;

	/* Ignore any invalid input */
  if (pFilename == NULL) {
    SET_EXT_ERROR2(ERR_NULL, "Invalid NULL filename received!");
    return (DFBSA_ERROR);
   }

	/* Ensure correct directory type */
  if (DirectoryType != DFBSA_FILENAME_DIRECTORY) {
    SET_EXT_ERROR3(DFERR_BSA_DIRTYPE, "BSA filename directory type not in use!", pFilename);
    return (DFBSA_ERROR);
   }

	/* Search through all directory records */
  for (LoopCounter = 0; LoopCounter < NumRecords; LoopCounter++) {

		/* Ignore any NULL array records */
    if (pDirectory[LoopCounter] == NULL) continue;

		/* Compare record filename values */
    Result = strnicmp(pFilename, pDirectory[LoopCounter]->Filename, 13);
    if (Result == 0) return (LoopCounter);

		/* For debugging purposes mainly */
    if (LoopCounter == 0) {
      MinResult = Result;
      MinIndex = 0;
     }
    else if (MinResult > Result) {
      MinResult = Result;
      MinIndex = LoopCounter;
     }
   }

	/* No matching record found */
  if (NumRecords == 0) {
    SET_EXT_ERROR3(DFERR_BSA_NOTFOUND, "No matching record found for '%s', no directory records defined!", pFilename);
   }
  else {
    SET_EXT_ERROR5(DFERR_BSA_NOTFOUND, "No matching record found for '%s'. Closest match is %d[%s]", pFilename, MinIndex, pDirectory[MinIndex]->Filename);
   }

  return (DFBSA_ERROR);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::FindRecordIndex();
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::FindRecordIndex()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - int FindRecordIndex (Value);
 *
 * Searches for the given value in the current directory and returns
 * the record index for it.  If the current directory is filenametype,
 * the file is not found, or on other errors, the function returns
 * DFBSA_ERROR.
 *
 *=======================================================================*/
int CDFBSAFile::FindRecordIndex (const long Value) {
  int  LoopCounter;
  long Result;
  long MinResult;
  int  MinIndex;

	/* Ensure correct directory type */
  if (DirectoryType != DFBSA_VALUE_DIRECTORY) {
    SET_EXT_ERROR3(DFERR_BSA_DIRTYPE, "BSA value directory type not in use!", Value);
    return (DFBSA_ERROR);
   }

	/* Search through all directory records */
  for (LoopCounter = 0; LoopCounter < NumRecords; LoopCounter++) {

		/* Ignore any NULL array records */
    if (pDirectory[LoopCounter] == NULL) continue;

		/* Compare record values */
    Result = labs(Value - pDirectory[LoopCounter]->Value);
    if (Result == 0) return (LoopCounter);

		/* For debugging purposes mainly */
    if (LoopCounter == 0) {
      MinResult = Result;
      MinIndex = 0;
     }
    else if (MinResult > Result) {
      MinResult = Result;
      MinIndex = LoopCounter;
     }
   }

	/* No matching record found */
  SET_EXT_ERROR5(DFERR_BSA_NOTFOUND, "No matching record found for %ld. Closest match is %d[%s]", Value, MinIndex, pDirectory[MinIndex]->Filename);
  return (DFBSA_ERROR);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::FindRecordIndex();
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::GetRecordSize()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - long GetRecordSize (pFilename);
 *
 * Returns the record size of the given filename record, or DFBSA_ERROR
 * on any error.
 *
 *=======================================================================*/
long CDFBSAFile::GetRecordSize (const char* pFilename) {
  int Result;

	/* Get the matching record index, if any */
  Result = FindRecordIndex(pFilename);
  if (Result == DFBSA_ERROR) return(DFBSA_ERROR);

	/* Return the record size */
  return (pDirectory[Result]->RecordSize);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::GetRecordSize()
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::GetRecordSize()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - long GetRecordSize (Value);
 *
 * Returns the record size of the given value record, or DFBSA_ERROR
 * on any error.
 *
 *=======================================================================*/
long CDFBSAFile::GetRecordSize (const long Value) {
  int Result;

	/* Get the matching record index, if any */
  Result = FindRecordIndex(Value);
  if (Result == DFBSA_ERROR) return(DFBSA_ERROR);

	/* Return the record size */
  return (pDirectory[Result]->RecordSize);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::GetRecordSize()
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::Open()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - FILE* Open (pFilename, ReadMode);
 *
 * Attempt to open the given BSA file for reading/writing.  Attempts to
 * read/write the BSA header and directory.  Returns TRUE on success or
 * FALSE on any error. Use Close() to close the file after opening.
 *
 *=======================================================================*/
boolean CDFBSAFile::Open (const char* pFilename, const boolean ReadMode) {
  boolean Result;

	/* Write mode not yet supported */
  if (!ReadMode) {
    SET_EXT_ERROR3(ERR_FILE, "Write mode not yet supported!", pFilename);
    return (NULL);
   }

	/* Ignore if file is already open */
  if (IsOpen()) return (TRUE);

	/* Attempt to open file, use previous filename if none specified */
  if (pFilename == NULL) {
    pFileHandle = openfile(pBSAFilename, (ReadMode ? "rb" : "wb"));
   }
  else {
    pFileHandle = openfile(pFilename, (ReadMode ? "rb" : "wb"));
    if (pFileHandle != NULL) SetFilename(pFilename);
   }

	/* Ensure the open command was successfull */
  if (pFileHandle == NULL) {
    SET_EXT_ERROR3(ERR_FILE, "Failed to open file %s!", (pFilename == NULL) ? pBSAFilename : pFilename);
    return (FALSE);
   }

  if (DirectoryRead) return (TRUE);

	/* Read in file header and directory */
  if (ReadMode) {
    Result  = ReadHeader();
    if (Result) Result = ReadDirectory();
   }
  else
    Result = TRUE;

	/* Check for input/output errors */
  if (!Result) {
    Close();
    return (FALSE);
   }

  return (TRUE);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::Open()
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::ReadDirectory()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - boolean ReadDirectory (void);
 *
 * Attempt to read the BSA directory from the current file stream.
 * Returns FALSE on any error, TRUE on success. Protected class method.
 *
 *=======================================================================*/
boolean CDFBSAFile::ReadDirectory (void) {
  int  Result;
  int  LoopCounter;
  long LastRecordOffset = 4;	/* Skip the 4 bytes of BSA header */

	/* Move to start of directory at end of file */
  Result = fseek (pFileHandle, -GetDirectorySize(), SEEK_END);

  if (Result != 0) {
    SET_EXT_ERROR2(ERR_READ, "Error moving to start of BSA directory!");
    return (FALSE);
   }

	/* Read in all directory records */
  for (LoopCounter = 0; LoopCounter < NumRecords; LoopCounter++) {
    CreatePointer(pDirectory[LoopCounter], dfbsa_dir_record_t);
    errno = 0;

		/* Read in the filename 0x0100 type */
    if (DirectoryType == DFBSA_FILENAME_DIRECTORY) {
      Result = fread (pDirectory[LoopCounter]->Filename, 1, 14, pFileHandle);
      if (Result != 14) errno = EFAULT;
     }
		/* Read in the value 0x0200 type */
    else if (DirectoryType == DFBSA_VALUE_DIRECTORY) {
      pDirectory[LoopCounter]->Value = read_long(pFileHandle);
     }

		/* Input the record size */
    pDirectory[LoopCounter]->RecordSize = read_long(pFileHandle);

		/* Check for errors in any of the above reads */
    if (errno != 0 || feof(pFileHandle)) {
      SET_EXT_ERROR3(ERR_READ, "Failed to read directory record %d", LoopCounter);
      return (FALSE);
     }

		/* Update the record offset */
    pDirectory[LoopCounter]->RecordOffset = LastRecordOffset;
    LastRecordOffset += pDirectory[LoopCounter]->RecordSize;
   }

	/* Return success */
  DirectoryRead = TRUE;
  return (TRUE);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::ReadDirectory()
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::ReadHeader()"
/*=========================================================================
 *
 * Class CDFBSAFile Method - boolean ReadHeader (void);
 *
 * Attempt to read the BSA header from the given open file stream.
 * Returns FALSE on any error, TRUE on success. Protected class method.
 *
 *=======================================================================*/
boolean CDFBSAFile::ReadHeader (void) {
  int  Result;

	/* Move to start of file */
  Result = fseek (pFileHandle, 0, SEEK_SET);

  if (Result != 0) {
    SET_EXT_ERROR2(ERR_READ, "Error moving to start of BSA file!");
    return (FALSE);
   }

	/* Read in the number of records. Assumes that all
	 * previous data has been cleared by this point. */
  NumRecords = read_short(pFileHandle);

  if (NumRecords <= 0 || NumRecords >= MAX_DFBSA_RECORDS) {
    SET_EXT_ERROR3(ERR_READ, "Invalid number of BSA records received (%d)!", NumRecords);
    return (FALSE);
   }

	/* Read in the directory type */
  DirectoryType = (dfbsa_dir_t) read_short(pFileHandle);

  if (DirectoryType <= 0) {
    SET_EXT_ERROR3(ERR_READ, "Invalid BSA directory type received (%d)!", DirectoryType);
    return (FALSE);
   }

	/* Return success */
  HeaderRead = TRUE;
  return (TRUE);
 }
/*=========================================================================
 *		End of Class Method CDFBSAFile::ReadHeader()
 *=======================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::WriteRawRecord()"
/*===========================================================================
 *
 * Class CDFBSAFile Method - boolean WriteRawRecord (Index);
 *
 * Attempts to output the given BSA record data to a filename derived from
 * the items directory entry. Returns FALSE on any error.
 *
 *=========================================================================*/
boolean CDFBSAFile::WriteRawRecord (const int Index) {
  FILE*   pOutputHandle;
  boolean HadToOpenFile;
  char    Buffer[20];
  boolean Result;

	/* Ensure valid record index */
  if (!IsValidIndex(Index)) {
    SET_EXT_ERROR3(ERR_NULL, "Invalid record index %d specified!", Index);
    return (FALSE);
   }

	/* Open file if required */
  if (!IsOpen()) {
    if (!Open()) return (FALSE);
    HadToOpenFile = TRUE;
   }

	/* Generate the filename from the directory entry */
  if (DirectoryType == DFBSA_FILENAME_DIRECTORY)
    strcpy (Buffer, pDirectory[Index]->Filename);
  else 
    sprintf (Buffer, "%08lX.dat", pDirectory[Index]->Value);

	/* Attempt to open file for output */
  pOutputHandle = openfile (Buffer, "wb");

  if (pOutputHandle == NULL) {
    SET_EXT_ERROR3(ERR_NULL, "Failed to open file '%s' for output!", Buffer);
    if (HadToOpenFile) Close();
    return (FALSE);
   }

	/* Output the record */
  Result = WriteRawRecord(pOutputHandle, Index);

	/* Close files if we had to open it */
  fclose (pOutputHandle);
  if (HadToOpenFile) Close();
  return (Result);
 }
/*===========================================================================
 *		End of Class Method CDFBSAFile::WriteRawRecord()
 *=========================================================================*/


#undef  __FUNC__
#define __FUNC__ "CDFBSAFile::WriteRawRecord()"
/*===========================================================================
 *
 * Class CDFBSAFile Method - boolean WriteRawRecord (pOutputHandle, Index);
 *
 * Attempts to output the given BSA record data to the specified file.
 * Returns FALSE on any error.
 *
 *=========================================================================*/
boolean CDFBSAFile::WriteRawRecord (FILE* pOutputHandle, const int Index) {
  byte*   pData;
  boolean HadToOpenFile = FALSE;
  int	  Result;

	/* Ensure valid input */
  if (pOutputHandle == NULL) {
    SET_EXT_ERROR2(ERR_NULL, "Invalid NULL file handle received!");
    return (FALSE);
   }

	/* Ensure valid record index */
  if (!IsValidIndex(Index)) {
    SET_EXT_ERROR3(ERR_NULL, "Invalid record index %d specified!", Index);
    return (FALSE);
   }

	/* Open file if required */
  if (!IsOpen()) {
    if (!Open()) return (FALSE);
    HadToOpenFile = TRUE;
   }

	/* Allocate a temporary pointer for input */
  CreatePointerArray(pData, byte, pDirectory[Index]->RecordSize);

	/* Jump to record and read it */
  fseek (pFileHandle, pDirectory[Index]->RecordOffset, SEEK_SET);
  Result = fread (pData, 1, pDirectory[Index]->RecordSize, pFileHandle);

  if (Result != pDirectory[Index]->RecordSize) {
    SET_EXT_ERROR3(ERR_READ, "Failed to read BSA record index %d from file!", Index);
    if (HadToOpenFile) Close();
    DestroyPointerArray(pData);
    return (FALSE);
   }

	/* Output data record to new file */
  Result = fwrite (pData, 1, pDirectory[Index]->RecordSize, pOutputHandle);
  if (HadToOpenFile) Close();
  DestroyPointerArray(pData);

  if (Result != pDirectory[Index]->RecordSize) {
    SET_EXT_ERROR3(ERR_READ, "Failed to write raw BSA record index %d to file!", Index);
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDFBSAFile::WriteRawRecord()
 *=========================================================================*/