/*---------------------------------------------------------------------------*
 *  PFileImpl.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 "passert.h"
#include "pendian.h"
#include "PFileImpl.h"
#include "PFileSystem.h"
#include "plog.h"
#include "pmemory.h"
#include "pstdio.h"
#include "ptypes.h"

#define MTAG NULL


/**
 * Initializes variables declared in the superinterface.
 */
ESR_ReturnCode PFileCreateImpl(PFile* self, const LCHAR* filename, ESR_BOOL isLittleEndian)
{
  PFileImpl* impl = (PFileImpl*) self;
  ESR_ReturnCode rc;
#ifdef USE_THREAD
  ESR_BOOL threadingEnabled;
#endif
  
#ifdef USE_THREAD
  impl->lock = NULL;
#endif
  impl->littleEndian = isLittleEndian;
  
  impl->Interface.destroy = &PFileDestroyImpl;
  impl->Interface.getFilename = &PFileGetFilenameImpl;
  impl->Interface.vfprintf = &PFileVfprintfImpl;
  impl->filename = MALLOC(sizeof(LCHAR) * (LSTRLEN(filename) + 1), MTAG);
  
  if (impl->filename == NULL)
  {
    rc = ESR_OUT_OF_MEMORY;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  LSTRCPY(impl->filename, filename);
  
#ifdef USE_THREAD
  rc = PtrdIsEnabled(&threadingEnabled);
  if (rc != ESR_SUCCESS)
  {
    pfprintf(PSTDERR, L("[%s:%d] PtrdIsEnabled failed with %s\n"), __FILE__, __LINE__, ESR_rc2str(rc));
    goto CLEANUP;
  }
  if (threadingEnabled)
  {
    rc = PtrdMonitorCreate(&impl->lock);
    if (rc != ESR_SUCCESS)
      goto CLEANUP;
  }
#endif
  return ESR_SUCCESS;
CLEANUP:
  self->destroy(self);
  return rc;
}


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


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


ESR_ReturnCode PFileDestroyImpl(PFile* self)
{
  PFileImpl* impl = (PFileImpl*) self;
  ESR_ReturnCode rc;
  ESR_BOOL isOpen;
  
  LOCK_MUTEX(rc, impl);
  CHKLOG(rc, self->isOpen(self, &isOpen));
  if (isOpen)
    CHKLOG(rc, self->close(self));
  if (impl->filename)
  {
    FREE(impl->filename);
    impl->filename = NULL;
  }
#ifdef USE_THREAD
  if (impl->lock != NULL)
  {
    PtrdMonitorUnlock(impl->lock);
    rc = PtrdMonitorDestroy(impl->lock);
    if (rc != ESR_SUCCESS)
      goto CLEANUP;
  }
#endif
  return ESR_SUCCESS;
CLEANUP:
#ifdef USE_THREAD
  if (impl->lock != NULL)
    PtrdMonitorUnlock(impl->lock);
#endif
  return rc;
}

ESR_ReturnCode PFileGetFilenameImpl(PFile* self, LCHAR* filename, size_t* len)
{
  PFileImpl* impl = (PFileImpl*) self;
  ESR_ReturnCode rc;
  
  if (self == NULL || len == NULL)
  {
    PLogError(L("ESR_INVALID_ARGUMENT"));
    return ESR_INVALID_ARGUMENT;
  }
  LOCK_MUTEX(rc, impl);
  if (LSTRLEN(impl->filename) + 1 > *len)
  {
    *len = LSTRLEN(impl->filename) + 1;
    rc = ESR_BUFFER_OVERFLOW;
    goto CLEANUP;
  }
  LSTRCPY(filename, impl->filename);
  CLEANUP_AND_RETURN(rc, impl);
}

ESR_ReturnCode PFileVfprintfImpl(PFile* self, int* result, const LCHAR* format, va_list args)
{
  ESR_ReturnCode rc;
  ESR_BOOL isOpen;
#define BUFFER_SIZE 5120
  static LCHAR buffer[BUFFER_SIZE];
  size_t len;
  
  if (self == NULL)
  {
    PLogError(L("ESR_INVALID_ARGUMENT"));
    return ESR_INVALID_ARGUMENT;
  }
  
  CHKLOG(rc, self->isOpen(self, &isOpen));
  if (!isOpen)
  {
    rc = ESR_OPEN_ERROR;
    PLogError(L("%s: cannot operate on closed file"), ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  /*
   * fprintf() is computationally expensive, so we compute its output without grabbing a lock
   * and only lock while actually writing the results into the file.
   */
  if (result != NULL)
    *result = vsprintf(buffer, format, args);
  else
    vsprintf(buffer, format, args);
  len = LSTRLEN(buffer);
  passert(len < BUFFER_SIZE);
  
  CHKLOG(rc, self->write(self, buffer, sizeof(LCHAR), &len));
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}