/*---------------------------------------------------------------------------*
 *  PANSIFileImpl.c  *
 *                                                                           *
 *  Copyright 2007, 2008 Nuance Communciations, Inc.                               *
 *                                                                           *
 *  Licensed under the Apache License, Version 2.0 (the 'License');          *
 *  you may not use this file except in compliance with the License.         *
 *                                                                           *
 *  You may obtain a copy of the License at                                  *
 *      http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                           *
 *  Unless required by applicable law or agreed to in writing, software      *
 *  distributed under the License is distributed on an 'AS IS' BASIS,        *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 
 *  See the License for the specific language governing permissions and      *
 *  limitations under the License.                                           *
 *                                                                           *
 *---------------------------------------------------------------------------*/

#include "errno.h"
#include "passert.h"
#include "pendian.h"
#include "PFileImpl.h"
#include "PANSIFileImpl.h"
#include "PFileSystem.h"
#include "ESR_ReturnCode.h"
#include "plog.h"
#include "pmemory.h"
#include "pstdio.h"
#include "ptypes.h"

#define MTAG NULL

ESR_ReturnCode PANSIFileCreateImpl(const LCHAR* filename, ESR_BOOL isLittleEndian, PFile** self)
{
  PANSIFileImpl* impl = NULL;
  ESR_ReturnCode rc;
  
  impl = NEW(PANSIFileImpl, MTAG);
  if (impl == NULL)
  {
    rc = ESR_OUT_OF_MEMORY;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  PFileCreateImpl(&impl->Interface.Interface, filename, isLittleEndian);
  impl->Interface.Interface.close = &PANSIFileCloseImpl;
  impl->Interface.Interface.clearError = &PANSIFileClearErrorImpl;
  impl->Interface.Interface.destroy = &PANSIFileDestroyImpl;
  impl->Interface.Interface.fgetc = &PANSIFileFgetcImpl;
  impl->Interface.Interface.fgets = &PANSIFileFgetsImpl;
  impl->Interface.Interface.getPosition = &PANSIFileGetPositionImpl;
  impl->Interface.Interface.hideMemoryAllocation = &PANSIFileHideMemoryAllocation;
  impl->Interface.Interface.isEOF = &PANSIFileIsEOFImpl;
  impl->Interface.Interface.isErrorSet = &PANSIFileIsErrorSetImpl;
  impl->Interface.Interface.isOpen = &PANSIFileIsOpenImpl;
  impl->Interface.Interface.open = &PANSIFileOpenImpl;
  impl->Interface.Interface.read = &PANSIFileReadImpl;
  impl->Interface.Interface.seek = &PANSIFileSeekImpl;
  impl->Interface.Interface.flush = &PANSIFileFlushImpl;
  impl->Interface.Interface.write = &PANSIFileWriteImpl;
  
  impl->Interface.filename[0] = 0;
  impl->value = NULL;
  
  LSTRCAT(impl->Interface.filename, filename);
  *self = &impl->Interface.Interface;
  return ESR_SUCCESS;
CLEANUP:
  if (impl != NULL)
    impl->Interface.Interface.destroy(&impl->Interface.Interface);
  return rc;
}

ESR_ReturnCode PANSIFileDestroyImpl(PFile* self)
{
  ESR_ReturnCode rc;
  
  CHK(rc, PFileDestroyImpl(self));
  FREE(self);
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}


#ifdef USE_THREAD
#define LOCK_MUTEX(rc, impl) \
  if (impl->Interface.lock != NULL) \
    CHKLOG(rc, PtrdMonitorLock(impl->Interface.lock));
#else
#define LOCK_MUTEX(rc, impl)
#endif


#ifdef USE_THREAD
#define CLEANUP_AND_RETURN(rc, impl) \
  if (impl->Interface.lock!=NULL) \
    CHKLOG(rc, PtrdMonitorUnlock(impl->Interface.lock)); \
  return ESR_SUCCESS; \
  CLEANUP: \
  if (impl->Interface.lock!=NULL) \
    PtrdMonitorUnlock(impl->Interface.lock); \
  return rc;
#else
#define CLEANUP_AND_RETURN(rc, impl) \
  return ESR_SUCCESS; \
  CLEANUP: \
  return rc;
#endif


ESR_ReturnCode PANSIFileOpenImpl(PFile* self, const LCHAR* mode)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (impl->value != NULL)
  {
    rc = ESR_ALREADY_OPEN;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  impl->value = fopen(impl->Interface.filename, mode);
  
  if (impl->value == NULL)
  {
    LCHAR path[P_PATH_MAX];
    size_t len;
    
    len = P_PATH_MAX;
    CHKLOG(rc, PFileSystemGetcwd(path, &len));
    rc = ESR_OPEN_ERROR;
    /* PLOG_DBG_TRACE((L("%s: filename=%s, cwd=%s"), ESR_rc2str(rc), impl->Interface.filename, path)); */
    PLogError(L("%s: filename=%s, cwd=%s"), ESR_rc2str(rc), impl->Interface.filename, path);
    goto CLEANUP;
  }
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileCloseImpl(PFile* self)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (fclose(impl->value) != 0)
  {
    rc = ESR_CLOSE_ERROR;
    PLogMessage(L("%s: file %s, handle"), ESR_rc2str(rc), impl->Interface.filename, impl->value);
    goto CLEANUP;
  }
  impl->value = NULL;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileReadImpl(PFile* self, void* buffer, size_t size, size_t* count)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (count == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  if (size != 0 && *count != 0)
  {
    ESR_BOOL needToSwap;
    
    *count = fread(buffer, size, *count, impl->value);
    if (*count == 0 && ferror(impl->value))
    {
      rc = ESR_READ_ERROR;
      PLogMessage(ESR_rc2str(rc));
      goto CLEANUP;
    }
    
#ifdef __LITTLE_ENDIAN
    needToSwap = !impl->Interface.littleEndian;
#else
    needToSwap = impl->Interface.littleEndian;
#endif
    
    if (needToSwap)
      swap_byte_order(buffer, *count, size);
  }
  else
    *count = 0;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileWriteImpl(PFile* self, void* buffer, size_t size, size_t* count)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  size_t requested = *count;
  
  LOCK_MUTEX(rc, impl);
  if (count == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  if (size != 0 && *count != 0)
  {
    ESR_BOOL needToSwap;
    void* temp;
    
#ifdef __LITTLE_ENDIAN
    needToSwap = !impl->Interface.littleEndian;
#else
    needToSwap = impl->Interface.littleEndian;
#endif
    if (needToSwap)
    {
      temp = MALLOC(*count * size, MTAG);
      if (temp == NULL)
      {
        rc = ESR_OUT_OF_MEMORY;
        PLogError(ESR_rc2str(rc));
        goto CLEANUP;
      }
      memcpy(temp, buffer, *count * size);
      
      swap_byte_order(temp, *count, size);
    }
    else
      temp = buffer;
      
    *count = fwrite(temp, size, *count, impl->value);
    if (needToSwap)
    {
      FREE(temp);
      temp = NULL;
    }
    
    if (*count < requested)
    {
      rc = ESR_WRITE_ERROR;
      PLogMessage(ESR_rc2str(rc));
      goto CLEANUP;
    }
  }
  else
    *count = 0;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileFlushImpl(PFile* self)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (fflush(impl->value) != 0)
  {
    rc = ESR_FLUSH_ERROR;
    PLogMessage(ESR_rc2str(rc));
    goto CLEANUP;
  }
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileSeekImpl(PFile* self, long offset, int origin)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (fseek(impl->value, offset, origin) != 0)
  {
    rc = ESR_SEEK_ERROR;
    PLogMessage(ESR_rc2str(rc));
    goto CLEANUP;
  }
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileGetPositionImpl(PFile* self, size_t* position)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  long pos;
  
  LOCK_MUTEX(rc, impl);
  pos = ftell(impl->value);
  if (pos == -1)
  {
    switch (errno)
    {
      case EBADF:
        rc = ESR_INVALID_STATE;
        PLogError(L("%s: Got EBADF"), rc);
        goto CLEANUP;
      case EINVAL:
        rc = ESR_INVALID_STATE;
        PLogError(L("%s: Got EINVAL"), rc);
        goto CLEANUP;
      default:
        rc = ESR_INVALID_STATE;
        PLogError(ESR_rc2str(rc));
        goto CLEANUP;
    }
  }
  *position = pos;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileIsOpenImpl(PFile* self, ESR_BOOL* isOpen)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (isOpen == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  *isOpen = impl->value != NULL;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileIsEOFImpl(PFile* self, ESR_BOOL* isEof)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (isEof == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
#ifdef NO_FEOF
  {
    long posCur;	/* remember current file position */
    long posEnd;	/* end of file position */
		
    posCur = ftell(impl->value);
    fseek(impl->value, 0, SEEK_END);
    posEnd = ftell(impl->value);
    *isEof = (posCur == posEnd);
    fseek(impl->value, posCur, SEEK_SET);  /* restore position in file */
  }
#else	
  *isEof = feof(impl->value) != 0;
#endif
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileIsErrorSetImpl(PFile* self, ESR_BOOL* isError)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  if (isError == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  *isError = ferror(impl->value) != 0;
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileClearErrorImpl(PFile* self)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  clearerr(impl->value);
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileFgetsImpl(PFile* self, LCHAR* string, int n, LCHAR** result)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  LCHAR* temp;
  
  LOCK_MUTEX(rc, impl);
  temp = fgets(string, n, impl->value);
  if (result != NULL)
    *result = temp;
  if (temp == NULL && ferror(impl->value))
  {
    rc = ESR_INVALID_STATE;
    PLogMessage(ESR_rc2str(rc));
    goto CLEANUP;
  }
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileFgetcImpl(PFile* self, LINT* result)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  *result = fgetc(impl->value);
  if (*result == PEOF && ferror(impl->value))
  {
    rc = ESR_INVALID_STATE;
    PLogMessage(ESR_rc2str(rc));
    goto CLEANUP;
  }
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PANSIFileHideMemoryAllocation(PFile* self)
{
  PANSIFileImpl* impl = (PANSIFileImpl*) self;
  ESR_ReturnCode rc;
  
  LOCK_MUTEX(rc, impl);
  rc = PMemLogFree(self);
  if (rc != ESR_SUCCESS)
  {
    pfprintf(PSTDERR, L("%s: PMemDumpLogFile() at %s:%d"), ESR_rc2str(rc), __FILE__, __LINE__);
    goto CLEANUP;
  }
  rc = PMemLogFree(impl->Interface.filename);
  if (rc != ESR_SUCCESS)
  {
    pfprintf(PSTDERR, L("%s: PMemDumpLogFile() at %s:%d"), ESR_rc2str(rc), __FILE__, __LINE__);
    goto CLEANUP;
  }
#ifdef USE_THREAD
  rc = PMemLogFree(impl->Interface.lock);
  if (rc != ESR_SUCCESS)
  {
    pfprintf(PSTDERR, L("%s: PMemDumpLogFile() at %s:%d"), ESR_rc2str(rc), __FILE__, __LINE__);
    goto CLEANUP;
  }
#endif
  CLEANUP_AND_RETURN(rc, impl);
}