/**********************************************************************
 *
 * File.cpp
 * Copyright (C) 2003  UNESCO
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

////////////////////////////////////////////////////////////
// File.cpp   File object methods
////////////////////////////////////////////////////////////
#include "stdafx.h"

 
#include <assert.h>
#include "CRC32.h"
#include "File.h"




static TCHAR closed_file_name[] = _T("closed-file");

static const int MaxExceptionMessages = 34;
static const TCHAR *fileExceptionMsg[MaxExceptionMessages] =
{
  _T("File exception: No exception reported"),               // FILE_NO_ERROR
  _T("File exception: Checksum error"),                      // FILE_CHECKSUM_ERROR
  _T("File exception: Error closing file"),                  // FILE_CLOSE_ERROR
  _T("File exception: File is corrupted"),
  _T("File exception: Error creating file"),                 // FILE_CREATION_ERROR
  _T("File exception: File already exists"),                 // FILE_EXISTS
  _T("File exception: Trying to use a closed file"),         // FILE_NOT_OPEN_ERROR
  _T("File exception: File not ready for reading/writing"),  // FILE_NOT_READY
  _T("File exception: Could not write to file"),             // FILE_NOT_WRITEABLE
  _T("File exception: Error opening file"),                  // FILE_OPEN_ERROR
  _T("File exception: Cannot obtain a file position"),       // FILE_POSITION_ERROR
  _T("File exception: Error reading from file"),             // FILE_READ_ERROR
  _T("File exception: Error seeking in file"),               // FILE_SEEK_ERROR
  _T("File exception: Error writing to file"),               // FILE_WRITE_ERROR

  _T("Fmgr exception: No database open"),                    // FMGR_NO_DATABASE_OPEN
  _T("Fmgr exception: No such file exists"),                 // FMGR_NO_FILE_EXISTS
  _T("Fmgr exception: No objects exist"),                    // FMGR_NO_OBJECTS_EXIST
  _T("Fmgr exception: Accessing a null pointer"),            // FMGR_NULL_PTR
  _T("Fmgr exception: Object already exists"),               // FMGR_OBJECT_EXISTS
  _T("Fmgr exception: Another object is referencing this file"), // FMGR_OBJECT_EXISTS
  _T("Fmgr exception: Math overflow error"),                 // FMGR_OVERFLOW
  _T("Fmgr exception: Parse error"),                         // FMGR_PARSE_ERROR
  _T("Fmgr exception: Invalid path"),                        // FMGR_PATH_ERROR
  _T("Fmgr exception: Trying to write to a read-only file"), // FMGR_READ_ONLY_FILE
  _T("Fmgr exception: Stack empty"),                         // FMGR_STACK_EMPTY
  _T("Fmgr exception: Stack full"),                          // FMGR_STACK_FULL
  _T("Fmgr exception: Synchronization Error"),               // FMGR_SYNC_ERROR    
  _T("Fmgr exception: Math under-flow error"),               // FMGR_UNDERFLOW
  _T("Fmgr exception: Wrong file type"),                     // FMGR_WRONG_FILE_TYPE
  
  // Persistent lock error messages
  _T("Fmgr exception: Invalid lock type specified"),          // FMGR_INVALID_LOCK_TYPE
  _T("Fmgr exception: Cannot access the file lock header"),   // FMGR_FILELOCK_ACCESS_ERROR
  _T("Fmgr exception: Cannot lock the file"),                 // FMGR_FILELOCK_ERROR
  _T("Fmgr exception: Cannot access the record lock header"), // FMGR_RECORDLOCK_ACCESS_ERROR
  _T("Fmgr exception: Cannot lock the specified record")      // FMGR_RECORDLOCK_ERROR
};
 
//---------------------------------------------------------------------------
// CFileBase::CFileBase()
//
// Default constructor, makes a File object that isn't attached to a file
//--------------------------------------------------------------------------- 
CFileBase::CFileBase()
{
    _tcscpy(file_name, closed_file_name); // Set the initial file name
    fp = 0;

    // Reset the last reported error and the file status members
    file_error        = FILE_NO_ERROR;
    is_open           = 0;
    is_ok             = 0;
    ready_for_reading = 0;
    ready_for_writing = 0;
}
  
//---------------------------------------------------------------------------
// CFileBase::~CFileBase()
//
// Destructor for CFileBase file object
//--------------------------------------------------------------------------- 
CFileBase::~CFileBase()
{
	Close();
}

//---------------------------------------------------------------------------
// const char* CFileBase::FileExceptionMessage()
//
// Returns a null terminated string that can / be used to log or print a
// file exception.
//---------------------------------------------------------------------------
const TCHAR *CFileBase::FileExceptionMessage()
{
	assert(file_error<MaxExceptionMessages);
    return fileExceptionMsg[file_error];
}
 
//--------------------------------------------------------------------------------
// int Create(const char* fname)
//
// Creates a new file, truncate the file if it already exists. The file open for
// Read/Write access. 
// Returns a non-zero value to indicate an error condition or zero if successful.
//--------------------------------------------------------------------------------
FileError CFileBase::Create(const TCHAR *fname)
{
    // Close any open files 
    if (Close() != FILE_NO_ERROR) 
		return file_error;
	
    // Create and truncate existing files
    fp = FileSystem::Create(fname);
	
    if (fp == 0)
	{
		is_ok             = 0;
		is_open           = 0;
		ready_for_writing = 0;
		ready_for_reading = 0;
		file_error        = FILE_CREATION_ERROR;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(file_error);
#else
		return file_error;
#endif
	}
	else
	{
		is_open           = 1;
		is_ok             = 1;
		ready_for_writing = 1;
		ready_for_reading = 1;
		last_operation    = IO_READ;
		_tcscpy(file_name, fname);
	}
	
	// Returns 0 if the file was successfully created and opened.
	return file_error = FILE_NO_ERROR; 
}
 
//----------------------------------------------------------------------------------------
// FileError CFileBase::Open(const TCHAR *fname, FileSystem::AccessMode mode
//                                               /* = FileSystem::FILE_READWRITE */ )
//
// Open an existing file. The "mode" variable determines if the file is opened for
// read only or read/write access.  Returns a non-zero value to indicate an error
// condition or zero if successful.
// NOTE: This version of the open functions will only accept:
// FILE_READONLY and FILE_READWRITE access modes.
//---------------------------------------------------------------------------------------
FileError CFileBase::Open(const TCHAR *fname, FileSystem::AccessMode mode /* = FileSystem::FILE_READWRITE */)
{
	// Close any open files
	if (Close() != FILE_NO_ERROR) 
		return file_error;
	
	if (mode == FileSystem::FILE_READONLY)
	{   // Open with read only access
		ready_for_reading = 1;
		ready_for_writing = 0;
		fp = FileSystem::Open(fname, FileSystem::FILE_READONLY);
	}
	else
	{   // Open with read/write access
		ready_for_reading = 1;
		ready_for_writing = 1;
		fp = FileSystem::Open(fname, FileSystem::FILE_READWRITE);
	}
    
	if (fp == 0)
	{
		ready_for_reading = 0;
		ready_for_writing = 0;
		file_error        = FILE_OPEN_ERROR;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(file_error);
#else
		return file_error;
#endif
	}
	else
	{
		is_open         = 1;
		is_ok           = 1;
		last_operation  = IO_WRITE;
		_tcscpy(file_name, fname);
	}
	return file_error = FILE_NO_ERROR;
}
 
//--------------------------------------------------------------------------------
// void CFileBase::Close()
//
// Close the open database file. Returns a non-zero value
// to indicate an error condition or zero if successful.
//--------------------------------------------------------------------------------
FileError CFileBase::Close()
{
   if (IsOpen()) 
   {
       if (FileSystem::Close(fp) != 0) 
	   {
		   file_error = FILE_CLOSE_ERROR;
#ifdef __CPP_EXCEPTIONS__
		   throw CFileBaseException(file_error);
#else
		   return file_error;
#endif
	   }
       _tcscpy(file_name, closed_file_name); // Set the initial file name
   }

   is_open           = 0;
   is_ok             = 0;
   ready_for_reading = 0;
   ready_for_writing = 0;
   fp                = 0; // Reset the file pointer to zero after the file is closed

   return file_error = FILE_NO_ERROR; 
}

//----------------------------------------------------------------------------------
// FileError CFileBase::Seek(FAU offset, FileSystem::SeekMode mode)
//
// Seek to the specified offset starting at the beginning (SEEK_SET), end (SEEK_END)
// or current offset (SEEK_CUR). Returns a non-zero value to indicate an error
// condition or zero if successful.
//----------------------------------------------------------------------------------
FileError CFileBase::Seek(FileSystem::FAU_t offset, FileSystem::SeekMode mode)
{
	if (IsOK()) 
	{
		if (FileSystem::Seek(fp, offset, mode) == (isis::int32_t) - 1)
		{
			file_error = FILE_SEEK_ERROR;
#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(file_error);
#else
			return file_error;
#endif
		}
		last_operation = IO_SEEK;
	}
	else 
	{
		file_error = FILE_NOT_READY;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(file_error);
#else
		return file_error;
#endif
	}
	return file_error = FILE_NO_ERROR;
}

//----------------------------------------------------------------------------------
// FAU CFileBase::SeekTo(FileSystem::FAU_t file_address)
//
// Seek to the specified address, optimizing the seek operation by moving the 
// file position indicator based on the current stream position. Returns the
// current file position after performing the seek operation.
//----------------------------------------------------------------------------------
FileSystem::FAU_t CFileBase::SeekTo(FileSystem::FAU_t file_address)
{
	// Get the current stream position
	FileSystem::StreamPos pos = FilePosition();
	
	if (file_address == CURRADDR) 
	{   // Do not perform a seek operation
		return pos;
	}
	else if (file_address > pos)
	{   // Seek forward to the specified address
		FileSystem::StreamPos offset = file_address - pos;
		Seek(offset, FileSystem::FILE_SEEK_CUR);
	}
	else if (file_address < pos) 
	{   // Seek backward to the specified address
		Seek(file_address, FileSystem::FILE_SEEK_BEG);
	}
	else 
	{  // Current file position equals the specified address
		// Find current the position
		Seek((FileSystem::FAU_t)0, FileSystem::FILE_SEEK_CUR);
	}
	
	return FilePosition(); // Return current file position after seeking
}

//-------------------------------------------------------------------------------
// FileError Store(const void* buf, isis::uint32_t nBytes,
//                             FAU file_addres = CFileBase::CURRADDR,
//			                   int flush = 1, int bit_test = 1);
//
// Write a specific number of bytes from a memory buffer to a specified file
// offset. If the "flush" variable is true, the file buffers will be flushed to
// disk with each write operation. If the bit_test variable if true, the CRC of 
// the buffer will be compared to the CRC of the actual bytes written to disk.
// Returns a non-zero value to indicate an error condition or zero if successful.
//
// void* buf         - Pointer to the user-supplied buffer that is to receive
//                     the data read from the file.
// isis::uint32_t nBytes - The maximum number of bytes to be read from the 
//                     file.  Note that an CFileBase is always a binary file,
//                     so a CR/LF pair will always be read or written as two bytes.
// FAU file_address  - The file byte address. The address is always interpreted to 
//                     be from the beginning of the file, unless dwAddr is equal 
//                     to CURRADDR, which means from the current position.
//------------------------------------------------------------------------------- 
FileError CFileBase::Store(const void* buf, isis::uint32_t nBytes,
						   FileSystem::FAU_t file_address /*= CURRADDR */, 
						   int flush_flag /* = 1 */,
						   int bit_test /* = 1 */)
{
    FileSystem::FAU_t buf_address;
	
    if (ReadyForWriting())
	{
        if (file_address == CURRADDR)
		{
			if (last_operation == IO_READ)
			{
				if (Seek(0, FileSystem::FILE_SEEK_CUR) != FILE_NO_ERROR)
					return file_error;
			}
		}
        else
		{
			if (Seek(file_address, FileSystem::FILE_SEEK_BEG) != FILE_NO_ERROR)
				return file_error;
		}
		
        buf_address = FilePosition(); 
		
        if (file_error != FILE_NO_ERROR)
			return file_error;
		
        if (FileSystem::Write(fp, buf, nBytes) != nBytes)
		{
			file_error = FILE_WRITE_ERROR;
#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(file_error);
#else
			return file_error;
#endif
		}
        last_operation = IO_WRITE;
	}
    else
	{
        file_error = FILE_NOT_WRITEABLE;
#ifdef __CPP_EXCEPTIONS__
        throw CFileBaseException(file_error);
#else
        return file_error;
#endif
	} 
	
    // Allow application to flush disk buffers after each write
    // operation to ensure the file data stays in sync during multiple
    // file access.
    if (flush_flag)
	{  
		if (FileSystem::Flush(fp) != FILE_NO_ERROR)
		{
			file_error = FILE_WRITE_ERROR;
#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(file_error);
#else
			return file_error;
#endif
		}
        if (Seek(0, FileSystem::FILE_SEEK_CUR) != FILE_NO_ERROR)
			return file_error;
	}
	
    if (bit_test) 
	{
        isis::uint32_t w_csum = CalcCRC32((char* ) buf, nBytes);
        isis::uint32_t r_csum = CalcChecksum(nBytes, buf_address);
		
		// Check for file errors
		
		if (file_error != FILE_NO_ERROR)
			return file_error;
		
		if (w_csum ^ r_csum)
		{
			file_error = FILE_CHECKSUM_ERROR;
#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(file_error);
#else
			return file_error;
#endif
		}
	}
	return file_error = FILE_NO_ERROR;	
}
 
//-------------------------------------------------------------------------------
// FileError CFileBase::Fetch(void* buf, isis::uint32_t nBytes, FAU file_addres
//                                                  /* = CFileBase::CURRADDR */)
//
// Read a specified number of bytes from the specified file offset into a memory
// buffer. Returns a non-zero value to indicate an error condition or zero if
// successful.
// void* buf         - Pointer to the user-supplied buffer that is to receive
//                     the data read from the file.
// isis::uint32_t nBytes - The maximum number of bytes to be read from the file. 
//                     Note that an CFileBase is always a binary file, so a 
//                     CR/LF pair will always be read or written as two bytes.
// FAU file_addres   - The file byte address. The address is always interpreted 
//                     to be from the beginning of the file, unless dwAddr is 
//                     equal to CURRADDR, which means from the current position.
//------------------------------------------------------------------------------- 
FileError CFileBase::Fetch(void* buf, isis::uint32_t nBytes, 
						   FileSystem::FAU_t file_address /* = CFileBase::CURRADDR */)
{
    if (IsOK())
	{
        if (file_address == CURRADDR)
		{
            if (last_operation == IO_WRITE)
			{
	           if (Seek(0, FileSystem::FILE_SEEK_CUR) != FILE_NO_ERROR) 
				   return file_error;
			}
		}
        else
		{
            if (Seek(file_address, FileSystem::FILE_SEEK_BEG) != FILE_NO_ERROR)
              	return file_error;
		}

		//TRACE("\nFetchfile_address=%ld nBytes=%u",file_address, nBytes);
        if (FileSystem::Read(fp, buf, nBytes) != nBytes)
		{
            file_error = FILE_READ_ERROR;
#ifdef __CPP_EXCEPTIONS__
            throw CFileBaseException(file_error);
#else
            return file_error;
#endif
		}
        last_operation = IO_READ;
	}
    else 
	{
        file_error = FILE_NOT_READY;
#ifdef __CPP_EXCEPTIONS__
        throw CFileBaseException(file_error);
#else
        return file_error;
#endif
	}

    return file_error = FILE_NO_ERROR;
}
//------------------------------------------------------------------------------- 
// FileError Flush()
//
// Flush any open disk buffers. Returns a non-zero value to indicate an error
// condition or zero if successful.
//------------------------------------------------------------------------------- 
FileError CFileBase::Flush()
{
	PRECONDITION(ReadyForWriting());
	if (FileSystem::Flush(fp) != FILE_NO_ERROR)
	{
		file_error = FILE_WRITE_ERROR;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(file_error);
#else
		return file_error;
#endif
	}
	Seek((FileSystem::FAU_t)0, FileSystem::FILE_SEEK_CUR);
	return file_error = FILE_NO_ERROR;
}


//------------------------------------------------------------------------------- 
// StreamPos CFileBase::FilePosition()
//
// Returns the current file position or -1 to indicate an error condition.
//------------------------------------------------------------------------------- 
FileSystem::StreamPos CFileBase::FilePosition()
{
   if (!IsOK())
   {
      file_error = FILE_NOT_READY;
#ifdef __CPP_EXCEPTIONS__
      throw CFileBase();
#else
      return (StreamPos) - 1;
#endif
   }

   FileSystem::StreamPos pos = FileSystem::Tell(fp);
   if (pos < 0)
   {
      file_error = FILE_POSITION_ERROR;
#ifdef __CPP_EXCEPTIONS__
      throw CFileBaseException(file_error);
#else
      return (StreamPos) - 1;
#endif
   }
   return pos;
}

//------------------------------------------------------------------------------- 
// isis::uint32_t CFileBase::CalcChecksum(isis::uint32_t bytes, FAU file_address, 
//                                                         int mem_alloc)
//
// Calculate a 32-bit CRC checksum for a given number of bytes starting at the
// specified address. Returns a 32-bit CRC value. If the mem_alloc variable is
// true, a buffer equal to the specified number of bytes will be created in
// memory. If the mem_alloc variable is false or memory allocation fails, the CRC
// will be calculated byte by byte starting at the specified address. 
// NOTE: the calling function must check for disk file errors.
// Returns the CRC value.
//------------------------------------------------------------------------------- 

isis::uint32_t CFileBase::CalcChecksum(isis::uint32_t bytes, FileSystem::FAU_t file_address, int mem_alloc)
{
   isis::uint32_t CRC;
   isis::uint32_t len = bytes;
   unsigned char data;
   char *buf = 0;  

   // Create a buffer equal to the object length
   if (mem_alloc) 
	   buf = new char[bytes]; 

   if (buf)
   {
      if (IsOK())
	  {
         if (Fetch(buf, bytes, file_address) != FILE_NO_ERROR) return 0;
         CRC = CalcCRC32(buf, bytes);
         delete[] buf;
	  }
   }
   else
   {
      if (IsOK())
	  {
          // Seek to the specified file address
          SeekTo(file_address);
          if (file_error != FILE_NO_ERROR) return 0; 
          CRC = 0xffffffffL;
          while (len--)
		  {
	         if (Fetch(&data, sizeof(data)) != FILE_NO_ERROR) return 0;
	         CRC = CalcCRC32(data, CRC);
		  }
          CRC ^= 0xffffffffL;
	  }
   }

   return CRC; 
}
///////////////////////////////////////////////////////////////////////////
// General purpose file utilites 
//
int CFileBase::Exists(const TCHAR *fname)
// Returns true if the file exists
{
	return FileSystem::Exists(fname);
}

FileSystem::FAU_t CFileBase::FileSize(const TCHAR *fname)
// Returns the file size. Use after file has been closed
// and re-opened to ensure that all the buffers are flushed
// to disk. Returns -1 to indicate an error condition.
{
	return (FileSystem::FAU_t) FileSystem::FileSize(fname);
}

int CFileBase::CanOpenForWriting(const TCHAR *fname)
// Returns true if the file is opened
{
	return 1;
	//return FileSystem::CanOpenForWriting(fname);
}

int CFileBase::CanOpenReadOnly(const TCHAR *fname)
// Returns true if the file is opened
{
	return 1;
	//return FileSystem::CanOpenReadOnly(fname);
}
