/*---------------------------------------------------------------------------*
 *  PANSIFileSystemImpl.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 "LCHAR.h"
#include "PFileSystemImpl.h"
#include "PANSIFileSystemImpl.h"
#include "PANSIFileImpl.h"
#include "plog.h"
#include "pmemory.h"

//extern PFileSystem* PANSIFileSystemSingleton;
PFileSystem* PANSIFileSystemSingleton = (PFileSystem*)NULL;

#define MTAG NULL


#ifdef USE_THREAD
/* Prototype of private function */
PORTABLE_API ESR_ReturnCode PtrdFlush();
#endif

/**
 * [file path, PFileSystem*] mapping.
 */
extern PHashTable* PFileSystemPathMap;


ESR_ReturnCode PANSIFileSystemCreate(void)
{
  PANSIFileSystemImpl* impl;
  ESR_ReturnCode rc;
  
  if (PANSIFileSystemSingleton != NULL)
    return ESR_SUCCESS;
  impl = NEW(PANSIFileSystemImpl, MTAG);
  if (impl == NULL)
    return ESR_OUT_OF_MEMORY;
  impl->super.super.destroy = &PANSIFileSystemDestroyImpl;
  impl->super.super.createPFile = &PANSIFileSystemCreatePFileImpl;
  impl->super.addPath = &PANSIFileSystemAddPathImpl;
  impl->super.removePath = &PANSIFileSystemRemovePathImpl;
  impl->super.getcwd = &PANSIFileSystemGetcwdImpl;
  impl->super.super.mkdir = &PANSIFileSystemMkdirImpl;
  impl->super.super.chdir = &PANSIFileSystemChdirImpl;
  
  CHKLOG(rc, PHashTableCreate(NULL, MTAG, &impl->directoryMap));
  PANSIFileSystemSingleton = &impl->super.super;
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}

ESR_ReturnCode PANSIFileSystemDestroyImpl(PFileSystem* self)
{
  PANSIFileSystemImpl* impl = (PANSIFileSystemImpl*) self;
  PHashTableEntry* entry;
  PHashTableEntry* oldEntry;
  LCHAR* key;
  LCHAR* value;
  ESR_ReturnCode rc;
  
  if (impl->directoryMap != NULL)
  {
    CHKLOG(rc, PHashTableEntryGetFirst(impl->directoryMap, &entry));
    while (entry != NULL)
    {
      CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value));
      oldEntry = entry;
      CHKLOG(rc, PHashTableEntryAdvance(&entry));
      CHKLOG(rc, PHashTableEntryRemove(oldEntry));
      CHKLOG(rc, PHashTableRemoveValue(PFileSystemPathMap, key, NULL));
      FREE(key);
      FREE(value);
    }
    CHKLOG(rc, PHashTableDestroy(impl->directoryMap));
    impl->directoryMap = NULL;
  }
  FREE(self);
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}

ESR_ReturnCode PANSIFileSystemAddPathImpl(PFileSystem* self, const LCHAR* virtualPath, const LCHAR* realPath)
{
  PANSIFileSystemImpl* impl = (PANSIFileSystemImpl*) self;
  ESR_BOOL exists;
  LCHAR* key = NULL;
  LCHAR* value = NULL;
  ESR_ReturnCode rc;
  size_t len;
  
  if (virtualPath == NULL || realPath == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  len = LSTRLEN(virtualPath) + 1;
  if (virtualPath[LSTRLEN(virtualPath)-1] != L('/'))
    ++len;
  key = MALLOC(sizeof(LCHAR) * len, MTAG);
  if (key == NULL)
  {
    rc = ESR_OUT_OF_MEMORY;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  LSTRCPY(key, virtualPath);
  /* Make sure paths end with '/' */
  CHKLOG(rc, PFileSystemCanonicalSlashes(key));
  if (key[LSTRLEN(key)-1] != L('/'))
    LSTRCAT(key, L("/"));
  value = MALLOC(sizeof(LCHAR) * (LSTRLEN(realPath) + 1), MTAG);
  if (value == NULL)
  {
    rc = ESR_OUT_OF_MEMORY;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  LSTRCPY(value, realPath);
  
  /* Make sure realPath is not an empty string */
  lstrtrim(value);
  if (LSTRLEN(value) == 0)
  {
    FREE(value);
    value = NULL;
    rc = ESR_INVALID_ARGUMENT;
    PLogError(L("%s: realPath cannot be empty"), ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  /* Make sure paths end with '/' */
  CHKLOG(rc, PFileSystemCanonicalSlashes(value));
  if (value[LSTRLEN(value)-1] != L('/'))
    LSTRCAT(value, L("/"));
    
  CHKLOG(rc, PHashTableContainsKey(impl->directoryMap, key, &exists));
  if (exists)
  {
    LCHAR* oldValue;
    
    CHKLOG(rc, PHashTableGetValue(impl->directoryMap, key, (void **)&oldValue));
    if (LSTRCMP(oldValue, value) != 0)
    {
      rc = ESR_IDENTIFIER_COLLISION;
      PLogError(ESR_rc2str(rc));
      goto CLEANUP;
    }
  }
  CHKLOG(rc, PHashTablePutValue(impl->directoryMap, key, value, NULL));
  CHKLOG(rc, PHashTablePutValue(PFileSystemPathMap, key, self, NULL));
  return ESR_SUCCESS;
CLEANUP:
  FREE(key);
  FREE(value);
  return rc;
}

ESR_ReturnCode PANSIFileSystemRemovePathImpl(PFileSystem* self, const LCHAR* virtualPath)
{
  PANSIFileSystemImpl* impl = (PANSIFileSystemImpl*) self;
  LCHAR path[P_PATH_MAX];
  LCHAR* key;
  LCHAR* value;
  PHashTableEntry* entry;
  ESR_ReturnCode rc;
  
  if (virtualPath == NULL)
  {
    rc = ESR_INVALID_ARGUMENT;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  /* Make sure paths end with '/' */
  LSTRCPY(path, virtualPath);
  CHKLOG(rc, PFileSystemCanonicalSlashes(path));
  if (path[LSTRLEN(path)-1] != L('/'))
    LSTRCAT(path, L("/"));
  CHKLOG(rc, PHashTableGetEntry(impl->directoryMap, path, &entry));
  CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value));
  CHKLOG(rc, PHashTableEntryRemove(entry));
  CHKLOG(rc, PHashTableRemoveValue(PFileSystemPathMap, key, NULL));
  FREE(key);
  FREE(value);
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}

ESR_ReturnCode PANSIFileSystemGetRealPathImpl(PFileSystem* self, LCHAR* path, size_t* len)
{
  PANSIFileSystemImpl* impl = (PANSIFileSystemImpl*) self;
  PHashTableEntry* entry;
  LCHAR* key;
  LCHAR* value;
  LCHAR* bestKey = NULL;
  LCHAR* bestValue = NULL;
  ESR_BOOL isAbsolute;
  ESR_ReturnCode rc;
  
  CHKLOG(rc, PFileSystemGetAbsolutePath(path, len));
  CHKLOG(rc, PHashTableEntryGetFirst(impl->directoryMap, &entry));
  while (entry != NULL)
  {
    CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void**)&key, (void**)&value));
    if (LSTRNCMP(path, key, LSTRLEN(key)) == 0)
    {
      /* File-system handles file path */
      if (bestKey == NULL || LSTRLEN(key) > LSTRLEN(bestKey))
      {
        /* Found a better match -- the new key is a subdirectory of the previous bestKey */
        bestKey = key;
        bestValue = value;
      }
    }
    CHKLOG(rc, PHashTableEntryAdvance(&entry));
  }
  if (bestKey == NULL)
  {
    rc = ESR_INVALID_STATE;
    PLogError(L("PANSIFileSystem does not handle the specified path: %s"), path);
    goto CLEANUP;
  }
  
  if (LSTRLEN(bestValue) + 1 > *len)
  {
    *len = LSTRLEN(bestValue) + 1;
    rc = ESR_BUFFER_OVERFLOW;
    PLogError(ESR_rc2str(rc));
    goto CLEANUP;
  }
  /* Delete the virtual-path */
  LSTRCPY(path, path + LSTRLEN(bestKey));
  
  CHKLOG(rc, PFileSystemIsAbsolutePath(path, &isAbsolute));
  if (LSTRCMP(bestValue, L("/")) == 0 && isAbsolute)
  {
    /* do nothing */
  }
  else
  {
    /* Insert the key-path */
    CHKLOG(rc, lstrinsert(bestValue, path, 0, len));
  }
  return ESR_SUCCESS;
CLEANUP:
  return rc;
}

ESR_ReturnCode PANSIFileSystemCreatePFileImpl(PFileSystem* self, const LCHAR* path, ESR_BOOL littleEndian, PFile** file)
{
  LCHAR realPath[P_PATH_MAX];
  size_t len;
  ESR_ReturnCode rc;
  
  LSTRCPY(realPath, path);
  len = P_PATH_MAX;
  CHKLOG(rc, PANSIFileSystemGetRealPathImpl(self, realPath, &len));
  return PANSIFileCreateImpl(realPath, littleEndian, file);
CLEANUP:
  return rc;
}

ESR_ReturnCode PANSIFileSystemSetDefault(ESR_BOOL isDefault)
{
  PANSIFileSystemImpl* impl = (PANSIFileSystemImpl*) PANSIFileSystemSingleton;
  ESR_BOOL exists;
  LCHAR* key = NULL;
  LCHAR* value = NULL;
  PHashTableEntry* entry;
  ESR_ReturnCode rc;
  
  if (isDefault)
  {

		key = MALLOC(sizeof(LCHAR), MTAG);
    if (key == NULL)
    {
      rc = ESR_OUT_OF_MEMORY;
      PLogError(ESR_rc2str(rc));
      goto CLEANUP;
    }
    LSTRCPY(key, L(""));
    value = MALLOC(sizeof(LCHAR), MTAG);
    if (value == NULL)
    {
      rc = ESR_OUT_OF_MEMORY;
      PLogError(ESR_rc2str(rc));
      goto CLEANUP;
    }
    LSTRCPY(value, L(""));
    
    CHKLOG(rc, PHashTableContainsKey(impl->directoryMap, key, &exists));
    if (exists)
    {
      LCHAR* key;
      LCHAR* value;
      
      CHKLOG(rc, PHashTableGetEntry(impl->directoryMap, L(""), &entry));
      CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value));
      CHKLOG(rc, PHashTableEntryRemove(entry));
      CHKLOG(rc, PHashTableRemoveValue(PFileSystemPathMap, key, NULL));
      FREE(key);
      FREE(value);
    }
    CHKLOG(rc, PHashTablePutValue(impl->directoryMap, key, value, NULL));
    CHKLOG(rc, PHashTablePutValue(PFileSystemPathMap, key, PANSIFileSystemSingleton, NULL));

		/* Set virtual current working directory to native current working directory */
  }
  else
  {
    CHKLOG(rc, PHashTableContainsKey(impl->directoryMap, L(""), &exists));
    if (exists)
    {
      LCHAR* key;
      LCHAR* value;
      
      CHKLOG(rc, PHashTableGetEntry(impl->directoryMap, L(""), &entry));
      CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value));

      CHKLOG(rc, PHashTableContainsKey(PFileSystemPathMap, L(""), &exists));
      if (exists)
      {
        LCHAR* key;
        PFileSystem* value;
        PHashTableEntry* entry;
        
        CHKLOG(rc, PHashTableGetEntry(PFileSystemPathMap, L(""), &entry));
        CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value));
        if (value == PANSIFileSystemSingleton)
          CHKLOG(rc, PHashTableEntryRemove(entry));
      }

      CHKLOG(rc, PHashTableEntryRemove(entry));
      FREE(key);
      FREE(value);
    }
  }
  return ESR_SUCCESS;
CLEANUP:
  FREE(key);
  FREE(value);
  return rc;
}