/** 
 * @file    kFile.cpp
 *
 * @internal
 * Copyright (C) 2004-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#define K_PLATFORM
#include <kApi/Io/kFile.h>
#include <kApi/Io/kDirectory.h>
#include <kApi/Io/kPath.h>
#include <kApi/Data/kArray1.h>
#include <kApi/Threads/kThread.h>

kBeginFullClassEx(k, kFile)
    kAddPrivateVMethod(kFile, kObject, VRelease)
    kAddPrivateVMethod(kFile, kStream, VReadSomeImpl)
    kAddPrivateVMethod(kFile, kStream, VWriteImpl)
    kAddPrivateVMethod(kFile, kStream, VSeek)
    kAddPrivateVMethod(kFile, kStream, VFlush)
    kAddPrivateVMethod(kFile, kStream, VFill)
kEndFullClassEx()

kFx(kStatus) xkFile_InitStatic()
{
    kApiFileFx handlers = { kNULL }; 

    handlers.open = xkFile_OpenImpl; 
    handlers.close = xkFile_CloseImpl; 
    handlers.read = xkFile_ReadImpl; 
    handlers.write = xkFile_WriteImpl; 
    handlers.flush = xkFile_FlushImpl; 
    handlers.seek = xkFile_SeekImpl; 
    handlers.size = xkFile_SizeImpl; 
    handlers.exists = xkFile_ExistsImpl; 
    handlers.copy = xkFile_CopyImpl; 
    handlers.move = xkFile_MoveImpl; 
    handlers.del = xkFile_DeleteImpl; 
    handlers.tempName = xkFile_TempNameImpl; 

    //respect handlers that have already been installed    
    if (kApiLib_HasFileHandlers())
    {
        kCheck(xkOverrideFunctions(&handlers, sizeof(handlers), kApiLib_FileHandlers())); 
    }
        
    kCheck(kApiLib_SetFileHandlers(&handlers)); 
 
    return kOK; 
}

kFx(kStatus) xkFile_ReleaseStatic()
{
    return kOK; 
}

kFx(kStatus) kFile_Load(const kChar* path, void* data, kSize* size, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kFile file = kNULL; 
    kByte* buffer = kNULL; 
    k64u fileSize; 
    kStatus exception; 

    kTry
    {
        kTest(kFile_Construct(&file, path, kFILE_MODE_READ, kNULL)); 

        if ((fileSize = kFile_Length(file)) >= kSIZE_MAX)
        {
            kThrow(kERROR_INCOMPLETE); 
        }

        kTest(kAlloc_Get(alloc, (kSize) fileSize, &buffer)); 
        kTest(kStream_Read(file, buffer, (kSize) fileSize)); 

        *(void**)data = buffer; 
        *size = (kSize) fileSize; 

        kTest(kFile_Close(file)); 
    }
    kCatchEx(&exception)     
    {
        kAlloc_Free(alloc, buffer); 
        kEndCatchEx(exception); 
    }
    kFinallyEx
    {
        kCheck(kDestroyRef(&file)); 
        kEndFinallyEx(); 
    }

    return kOK; 
}

kFx(kStatus) kFile_LoadTo(const kChar* path, void* data, kSize capacity)
{
    kFile file = kNULL; 

    kTry
    {
        kTest(kFile_Construct(&file, path, kFILE_MODE_READ, kNULL)); 
        kTestTrue(kFile_Length(file) == capacity, kERROR_INCOMPLETE); 

        kTest(kStream_Read(file, data, capacity)); 
        kTest(kFile_Close(file)); 
    }
    kFinally
    {
        kDestroyRef(&file); 
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kFile_Save(const kChar* path, const void* data, kSize size)
{
    kFile file = kNULL; 

    kTry
    {
        kTest(kFile_Construct(&file, path, kFILE_MODE_WRITE, kNULL)); 
        kTest(kStream_Write(file, data, size)); 
        kTest(kFile_Close(file)); 
    }
    kFinally
    {
        kDestroyRef(&file); 
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kFile_Construct(kFile* file, const kChar* path, kFileMode mode, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kType type = kTypeOf(kFile); 
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, type, file)); 

    if (!kSuccess(status = xkFile_Init(*file, type, path, mode, alloc)))
    {
        kAlloc_FreeRef(alloc, file); 
    }

    return status; 
} 

kFx(kStatus) xkFile_Init(kFile file, kType type, const kChar* path, kFileMode mode, kAlloc allocator)
{
    kObjR(kFile, file); 
    kStatus status = kOK;  
    k64u zero; 
    
    kCheck(kStream_Init(file, type, allocator)); 

    obj->streamPosition = 0; 
    obj->streamLength = 0; 
    obj->lastMode = xkFILE_MODE_NULL; 

    xkFile_InitPlatformFields(file); 

    kTry
    {         
        kTest(kApiLib_FileHandlers()->open(file, path, mode)); 

        if (kIsError(kApiLib_FileHandlers()->seek(file, 0, kSEEK_ORIGIN_END, &obj->streamLength)) || 
            kIsError(kApiLib_FileHandlers()->seek(file, 0, kSEEK_ORIGIN_BEGIN, &zero)))
        {
            //file may not support seek; set the reported length to max, but allow the file to be used 
            obj->streamLength = k64U_MAX; 
        }
    }
    kCatch(&status) 
    {
        xkFile_VRelease(file); 
        kEndCatch(status); 
    }
       
    return kOK;
}

kFx(kStatus) xkFile_VRelease(kFile file)
{
    kObj(kFile, file); 

    //ignore errors
    kFile_Close(file); 

    kCheck(kObject_FreeMemRef(file, &obj->base.readBuffer)); 
    kCheck(kObject_FreeMemRef(file, &obj->base.writeBuffer));

    kCheck(kStream_VRelease(file)); 

    return kOK; 
}

kFx(kStatus) kFile_Close(kFile file)
{
    kStatus flushStatus = kOK; 
    kStatus closeStatus = kOK; 

    if (xkFile_IsOpen(file))
    {
        flushStatus = kStream_Flush(file); 
        closeStatus = kApiLib_FileHandlers()->close(file); 
    }

    return kIsError(flushStatus) ? flushStatus : (kIsError(closeStatus) ? closeStatus : kOK); 
}

kFx(kStatus) kFile_SetWriteBuffer(kFile file, kSize size)
{
    kObj(kFile, file); 

    kCheckState(xkFile_IsOpen(file)); 

    kCheck(kStream_Flush(file)); 

    kCheck(kObject_FreeMemRef(file, &obj->base.writeBuffer)); 

    obj->base.writeCapacity = 0; 
    obj->base.writeBegin = 0; 
    obj->base.writeEnd = 0; 

    if (size > 0)
    {
        kCheck(kObject_GetMem(file, size, &obj->base.writeBuffer)); 
        obj->base.writeCapacity = size; 
    }

    return kOK;
}

kFx(kStatus) kFile_SetReadBuffer(kFile file, kSize size)
{
    kObj(kFile, file); 

    kCheckState(xkFile_IsOpen(file)); 

    kCheck(kStream_Flush(file)); 

    kCheck(kObject_FreeMemRef(file, &obj->base.readBuffer)); 

    obj->base.readCapacity = 0; 
    obj->base.readBegin = 0; 
    obj->base.readEnd = 0; 

    if (size > 0)
    {
        kCheck(kObject_GetMem(file, size, &obj->base.readBuffer));                 
        obj->base.readCapacity = size; 
    }
    
    return kOK;
}

kFx(kStatus) xkFile_VReadSomeImpl(kFile file, void* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
{
    kObj(kFile, file); 
    kByte* dest = (kByte*) buffer;  
    kSize readCount = 0; 
    kSize copyCount, mediumCount; 

    kCheckState(xkFile_IsOpen(file)); 
    kCheckState((kFile_Position(file) + minCount) <= kFile_Length(file)); 

    //configure the stream for reading, if necessary
    if (obj->lastMode != kFILE_MODE_READ)
    {
        kCheck(kStream_Flush(file)); 

        obj->lastMode = kFILE_MODE_READ; 
    }
    
    //consume any bytes in the read buffer first
    if ((obj->base.readEnd - obj->base.readBegin) > 0)
    {
        copyCount = kMin_(maxCount, obj->base.readEnd - obj->base.readBegin);

        kMemCopy(dest, &obj->base.readBuffer[obj->base.readBegin], copyCount);     
        
        obj->base.readBegin += copyCount; 
        readCount += copyCount; 
    }

    //if the request is not yet satisfied
    if (readCount < minCount)
    {
        //if the request is larger than the internal read buffer, read directly into the caller's buffer; else read into the internal buffer
        if ((maxCount - readCount) >= obj->base.readCapacity)
        {            
            kCheck(xkFile_ReadAtLeast(file, &dest[readCount], minCount-readCount, maxCount-readCount, &mediumCount));

            readCount += mediumCount; 
        }
        else
        {
            kCheck(xkFile_ReadAtLeast(file, &obj->base.readBuffer[0], minCount - readCount, obj->base.readCapacity, &mediumCount));    

            copyCount = kMin_(mediumCount, maxCount - readCount); 

            kCheck(kMemCopy(&dest[readCount], &obj->base.readBuffer[0], copyCount)); 

            obj->base.readBegin = copyCount; 
            obj->base.readEnd = mediumCount; 
            readCount += copyCount; 
        }
    }

    if (!kIsNull(bytesRead))
    {
        *bytesRead = readCount; 
    }

    return kOK; 
}

kFx(kStatus) xkFile_VWriteImpl(kFile file, const void* buffer, kSize size)
{
    kObj(kFile, file); 

    kCheckState(xkFile_IsOpen(file)); 

    //configure the stream for writing, if necessary
    if (obj->lastMode != kFILE_MODE_WRITE)
    {
        kCheck(kStream_Flush(file)); 

        obj->lastMode = kFILE_MODE_WRITE; 
        obj->base.writeEnd = obj->base.writeCapacity; 
    }

    //if the internal write buffer already has content, but not enough free space, flush
    if ((obj->base.writeBegin > 0) && ((obj->base.writeEnd - obj->base.writeBegin) < size))
    {
        kCheck(kStream_Flush(file)); 
    }

    //if the write is larger than the internal write buffer, write directly to the medium; else write to the internal buffer
    if ((obj->base.writeEnd - obj->base.writeBegin) < size)
    {
        kCheck(xkFile_WriteAll(file, (const kByte*)buffer, size)); 
    }
    else
    {
        kCheck(kMemCopy(&obj->base.writeBuffer[obj->base.writeBegin], buffer, size)); 
        obj->base.writeBegin += size; 
    }

    return kOK; 
}

kFx(kStatus) xkFile_VFlush(kFile file)
{
    kObj(kFile, file); 
        
    kCheckState(xkFile_IsOpen(file)); 

    if ((obj->lastMode == kFILE_MODE_WRITE) && (obj->base.writeBegin > 0))
    {
        kCheck(xkFile_WriteAll(file, obj->base.writeBuffer, obj->base.writeBegin));
    }
    else if ((obj->lastMode == kFILE_MODE_READ) && ((obj->base.readEnd - obj->base.readBegin) > 0))
    {
        obj->base.bytesRead -= (obj->base.readEnd - obj->base.readBegin); 
    }

    obj->base.readBegin = obj->base.readEnd = 0; 
    obj->base.writeBegin = obj->base.writeEnd = 0; 
            
    return kOK;
}

kFx(kStatus) xkFile_VFill(kFile file)
{
    kObj(kFile, file); 
    kSize mediumCount; 

    kCheckState(xkFile_IsOpen(file)); 
    kCheckState(kFile_Position(file) < kFile_Length(file)); 

    //configure the stream for reading, if necessary
    if (obj->lastMode != kFILE_MODE_READ)
    {
        kCheck(kStream_Flush(file)); 

        obj->lastMode = kFILE_MODE_READ; 
    }
    
    kCheck(xkFile_ReadAtLeast(file, &obj->base.readBuffer[0], 1, obj->base.readCapacity, &mediumCount));    

    obj->base.readBegin = 0; 
    obj->base.readEnd = mediumCount; 

    return kOK; 
}


kFx(kStatus) xkFile_VSeek(kFile file, k64s offset, kSeekOrigin origin)
{
    kObj(kFile, file); 
      
    kCheckState(xkFile_IsOpen(file)); 

    kCheck(kStream_Flush(file)); 
    
    kCheck(kApiLib_FileHandlers()->seek(file, offset, origin, &obj->streamPosition)); 

    return kOK; 
}

kFx(k64u) kFile_Length(kFile file)
{
    kObj(kFile, file); 

    kAssert(xkFile_IsOpen(file)); 

    if (obj->lastMode != kFILE_MODE_WRITE)
    {
        return obj->streamLength; 
    }
    else
    {
        return kMax_(obj->streamLength, obj->streamPosition + obj->base.writeBegin); 
    }
}

kFx(k64u) kFile_Position(kFile file)
{
    kObj(kFile, file); 

    kAssert(xkFile_IsOpen(file)); 

    if (obj->lastMode == kFILE_MODE_READ)
    {
        return obj->streamPosition - (obj->base.readEnd - obj->base.readBegin); 
    }
    else if (obj->lastMode == kFILE_MODE_WRITE)
    {
        return obj->streamPosition + obj->base.writeBegin; 
    }
    else
    {
        return obj->streamPosition; 
    }
}

kFx(kBool) kFile_Exists(const kChar* fileName)
{
    return kApiLib_FileHandlers()->exists(fileName); 
}

kFx(k64u) kFile_Size(const kChar* fileName)
{
    return kApiLib_FileHandlers()->size(fileName); 
}

kFx(kStatus) kFile_Copy(const kChar* source, const kChar* destination)
{
    return kApiLib_FileHandlers()->copy(source, destination, kNULL, kNULL); 
} 

kFx(kStatus) kFile_CopyEx(const kChar* source, const kChar* destination, kCallbackFx progress, kPointer context)
{    
    return kApiLib_FileHandlers()->copy(source, destination, progress, context); 
}

kFx(kStatus) kFile_Move(const kChar* source, const kChar* destination)
{
    return kApiLib_FileHandlers()->move(source, destination, kNULL, kNULL); 
}

kFx(kStatus) kFile_MoveEx(const kChar* source, const kChar* destination, kCallbackFx progress, kPointer context)
{    
    return kApiLib_FileHandlers()->move(source, destination, progress, context); 
}

kFx(kStatus) kFile_Delete(const kChar* path)
{
    return kApiLib_FileHandlers()->del(path); 
}

kFx(kStatus) kFile_TempName(kChar* name, kSize capacity)
{
    return kApiLib_FileHandlers()->tempName(name, capacity); 
}

kFx(kStatus) xkFile_ReadAtLeast(kFile file, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
{
    kObj(kFile, file); 

    kCheck(kApiLib_FileHandlers()->read(file, buffer, minCount, maxCount, bytesRead)); 

    obj->streamPosition += *bytesRead;  
    obj->base.bytesRead += *bytesRead; 

    return kOK; 
}

kFx(kStatus) xkFile_WriteAll(kFile file, const kByte* buffer, kSize count)
{
    kObj(kFile, file); 

    kCheck(kApiLib_FileHandlers()->write(file, buffer, count)); 

    obj->streamPosition += count; 
    obj->streamLength = kMax_(obj->streamLength, obj->streamPosition); 
    obj->base.bytesWritten += count; 
    
    return kOK;    
}

kFx(k64u) xkFile_SizeImpl(const kChar* path)
{
    kFile file = kNULL; 
    k64u size = 0; 

    if (kSuccess(kFile_Construct(&file, path, kFILE_MODE_READ, kNULL)))
    {
        size = kFile_Length(file); 
        kObject_Destroy(file); 
    }

    return size; 
}

kFx(kStatus) xkFile_CopyImpl(const kChar* source, const kChar* destination, kCallbackFx progress, kPointer context)
{    
    kFile from = kNULL;
    kFile to = kNULL;
    kArray1 buffer = kNULL; 
    k64u size = 0; 
    k64u remaining = 0;
    kStatus status = kOK; 
    k64u updateTime = k64U_NULL; 
    
    if (kStrEquals(source, destination))
    {
        xkUpdateProgress(progress, context, kNULL, kNULL, 100); 
        return kOK; 
    }

    kTry
    {
        kTest(kFile_Construct(&from, source, kFILE_MODE_READ, kNULL)); 
        kTest(kFile_Construct(&to, destination, kFILE_MODE_WRITE, kNULL));
        kTest(kArray1_Construct(&buffer, kTypeOf(kByte), xkFILE_COPY_BUFFER_SIZE, kNULL)); 

        remaining = size = kFile_Length(from);

        while (remaining > 0)
        {
            kSize segment = (kSize) kMin_(remaining, xkFILE_COPY_BUFFER_SIZE); 

            kTest(kStream_Read(from, kArray1_DataT(buffer, kByte), segment));
            kTest(kStream_Write(to, kArray1_DataT(buffer, kByte), segment));

            remaining -= segment;

            xkUpdateProgress(progress, context, kNULL, &updateTime, (k32u)(100*(size-remaining)/size)); 
        }           

        kTest(kFile_Close(from)); 
        kTest(kFile_Close(to)); 

        xkUpdateProgress(progress, context, kNULL, kNULL, 100); 
    } 
    kCatchEx(&status)
    {
        //eliminate any partially-copied remnant (not strictly required, but convenient)
        if (!kIsNull(to))
        {
            kDestroyRef(&to);
            kFile_Delete(destination); 
        }

        kEndCatchEx(status); 
    }
    kFinallyEx
    {
        kCheck(kDestroyRef(&from));
        kCheck(kDestroyRef(&to));
        kCheck(kObject_Destroy(buffer));

        kEndFinallyEx(); 
    }

    return kOK;
}

kFx(kStatus) xkFile_MoveImpl(const kChar* source, const kChar* destination, kCallbackFx progress, kPointer context)
{
    kCheck(kApiLib_FileHandlers()->copy(source, destination, progress, context)); 
    kCheck(kApiLib_FileHandlers()->del(source)); 

    return kOK;
}

kFx(kStatus) xkFile_TempNameImpl(kChar* name, kSize capacity)
{
    return kStrPrintf(name, capacity, "%08X%08X%08X%08X", kRandom32u(),kRandom32u(), kRandom32u(), kRandom32u()); 
}

#if defined(K_WINDOWS)

kFx(void) xkFile_InitPlatformFields(kFile file)
{
    kObj(kFile, file); 

    obj->handle = INVALID_HANDLE_VALUE; 
}

kFx(kBool) xkFile_IsOpen(kFile file)
{
    kObj(kFile, file); 

    return obj->handle != INVALID_HANDLE_VALUE; 
}

kFx(kStatus) xkFile_OpenImpl(kFile file, const kChar* path, kFileMode mode)
{
    kObj(kFile, file); 
    DWORD desiredAccess = 0; 
    DWORD shareMode = 0; 
    DWORD creationDisposition = 0; 
    DWORD flags = FILE_ATTRIBUTE_NORMAL;  
    kChar nativePath[kPATH_MAX]; 
    WCHAR wpath[MAX_PATH]; 

    kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath))); 
    kCheck(kPath_ToNative(nativePath, nativePath, kCountOf(nativePath))); 

    if (MultiByteToWideChar(CP_UTF8, 0, nativePath, -1, wpath, kCountOf(wpath)) == 0)
    {
        return kERROR_PARAMETER; 
    }

    if (mode == kFILE_MODE_READ)
    {
        desiredAccess = GENERIC_READ;        
        shareMode = FILE_SHARE_READ; 
        creationDisposition = OPEN_EXISTING;         
    }
    else if (mode == kFILE_MODE_WRITE)
    {
        desiredAccess = GENERIC_WRITE;        
        creationDisposition = CREATE_ALWAYS;         
    }
    else if (mode == (kFILE_MODE_READ | kFILE_MODE_WRITE))
    {
        desiredAccess = GENERIC_READ | GENERIC_WRITE;        
        creationDisposition = CREATE_ALWAYS;         
    }
    else if (mode == (kFILE_MODE_WRITE | kFILE_MODE_UPDATE))
    {
        desiredAccess = GENERIC_WRITE;        
        creationDisposition = OPEN_ALWAYS;         
    }
    else if (mode == (kFILE_MODE_READ | kFILE_MODE_WRITE | kFILE_MODE_UPDATE))
    {
        desiredAccess = GENERIC_READ | GENERIC_WRITE;        
        creationDisposition = OPEN_ALWAYS;         
    }
    else
    {
        return kERROR_PARAMETER; 
    }
  
    if ((obj->handle = CreateFile(wpath, desiredAccess, shareMode, kNULL, creationDisposition, flags, kNULL)) == INVALID_HANDLE_VALUE)
    {
        switch (GetLastError())
        {
        case ERROR_FILE_NOT_FOUND:
        case ERROR_PATH_NOT_FOUND:
            return kERROR_NOT_FOUND;
        default:
            return kERROR_STATE; 
        }
    }

    return kOK; 
}

kFx(kStatus) xkFile_CloseImpl(kFile file)
{
    kObj(kFile, file); 
    BOOL result = TRUE; 
    
    if (xkFile_IsOpen(file))
    {
        result = CloseHandle(obj->handle); 
        obj->handle = INVALID_HANDLE_VALUE; 
    }
    
    return (result) ? kOK : kERROR_STREAM; 
}

kFx(kStatus) xkFile_ReadImpl(kFile file, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
{
    kObj(kFile, file); 
    DWORD read; 

    kCheckArgs(maxCount <= xkFILE_MAX_IO_SIZE); 

    kCheck(ReadFile(obj->handle, buffer, (DWORD)maxCount, &read, kNULL)); 

    kCheck(read >= minCount); 

    *bytesRead = read;    

    return kOK; 
}

kFx(kStatus) xkFile_WriteImpl(kFile file, const kByte* buffer, kSize count)
{
    kObj(kFile, file); 
    DWORD written; 

    kCheckArgs(count <= xkFILE_MAX_IO_SIZE); 

    kCheck(WriteFile(obj->handle, buffer, (DWORD)count, &written, kNULL));

    kCheck(written == count); 

    return kOK; 
}

kFx(kStatus) xkFile_FlushImpl(kFile file)
{
    kObj(kFile, file); 

    if (obj->lastMode == kFILE_MODE_WRITE)
    {
        kCheck(FlushFileBuffers(obj->handle)); 
    }

    return kOK; 
}

kFx(kStatus) xkFile_SeekImpl(kFile file, k64s offset, kSeekOrigin origin, k64u* position)
{
    kObj(kFile, file); 
    DWORD moveMethod; 
    LARGE_INTEGER offsetLarge; 
    LARGE_INTEGER positionLarge; 

    switch(origin)
    {
    case kSEEK_ORIGIN_BEGIN:        moveMethod = FILE_BEGIN;    break;
    case kSEEK_ORIGIN_CURRENT:      moveMethod = FILE_CURRENT;  break;
    case kSEEK_ORIGIN_END:          moveMethod = FILE_END;      break;
    default:                                                    return 0; 
    }

    offsetLarge.QuadPart = offset; 

    kCheck((SetFilePointerEx(obj->handle, offsetLarge, &positionLarge, moveMethod) == TRUE) ? kOK : kERROR_STREAM); 

    *position = (k64u) positionLarge.QuadPart; 

    return kOK; 
}

kFx(kBool) xkFile_ExistsImpl(const kChar* path)
{
    kFile file = kNULL; 
    kBool exists = kFALSE; 

    //works on Windows (not Linux) -- should we do something else?
    if (kSuccess(kFile_Construct(&file, path, kFILE_MODE_READ, kNULL)))
    {
        exists = kTRUE; 
        kObject_Destroy(file); 
    }

    return exists; 
}

kFx(kStatus) xkFile_DeleteImpl(const kChar* path)
{
    kChar absPath[kPATH_MAX];
    kChar nativePath[kPATH_MAX];
    kChar directory[kPATH_MAX];
    WCHAR wdirectory[MAX_PATH];
    WCHAR wpath[MAX_PATH];
    WCHAR wtempFile[MAX_PATH];
    kSize i = 0;
    kBool moved = kFALSE;
    
    //confirm that the file doesn't already exist, to prevent needlessly delaying on rename failure (below)
    if (!kFile_Exists(path))
    {
        return kERROR_NOT_FOUND;
    }

    //we want to get the parent directory (below), but the given path may be relative;
    //if so, we convert to an absolute path that is relative to the current working directory
    kCheck(xkPath_FromVirtual(path, absPath, kCountOf(absPath)));
    kCheck(kPath_ToAbsolute(kNULL, absPath, absPath, sizeof(absPath)));
    kCheck(kPath_ToNative(absPath, nativePath, kCountOf(nativePath)));
    
    if (MultiByteToWideChar(CP_UTF8, 0, nativePath, -1, wpath, kCountOf(wpath)) == 0)
    {
        return kERROR_PARAMETER;
    }

    kCheck(kPath_Directory(absPath, directory, kCountOf(directory)));
    kCheck(kPath_ToNative(directory, directory, kCountOf(directory)));

    if (MultiByteToWideChar(CP_UTF8, 0, directory, -1, wdirectory, kCountOf(wdirectory)) == 0)
    {
        return kERROR_PARAMETER;
    }

    if (GetTempFileName(wdirectory, L"kFile", 0, wtempFile) == 0)
    {
        return kERROR_OS;
    }

    for (i = 0; i < xkFILE_WINDOWS_IO_ATTEMPTS && !moved; i++)
    {
        if (MoveFileEx(wpath, wtempFile, MOVEFILE_REPLACE_EXISTING) != 0)
        {
            moved = kTRUE;
        }
        else
        {
            kCheck(kThread_Sleep(100000));
        }
    }

    if (!moved)
    {
        return kERROR_OS;
    }

    for (i = 0; i < xkFILE_WINDOWS_IO_ATTEMPTS; i++)
    {
        if (DeleteFile(wtempFile) != 0)
        {
            return kOK;
        }
        kCheck(kThread_Sleep(100000));
    }

    return kERROR_OS;
}

kFx(kStatus) xkFile_TempFileImpl(const kChar* path, kChar* name, kSize capacity)
{
    kChar nativePath[kPATH_MAX];
    WCHAR wpath[MAX_PATH];
    WCHAR tempFile[MAX_PATH];
        
    kCheck(kPath_ToNative(path, nativePath, kCountOf(nativePath))); 
    
    if (MultiByteToWideChar(CP_UTF8, 0, nativePath, -1, wpath, kCountOf(wpath)) == 0)
    {
        return kERROR_PARAMETER; 
    }

    if (GetTempFileName(wpath, L"kFile", 0, tempFile) == 0)
    {
        return kERROR_OS;
    }

    return xkPath_NativeWideToNormalizedWin(tempFile, name, capacity);
}

#elif defined(K_DARWIN) || defined (K_LINUX) || defined (K_QNX)

kFx(void) xkFile_InitPlatformFields(kFile file)
{
    kObj(kFile, file); 
    obj->handle = -1; 
}

kFx(kBool) xkFile_IsOpen(kFile file)
{
    kObj(kFile, file); 
    return obj->handle != -1; 
}

kFx(kStatus) xkFile_OpenImpl(kFile file, const kChar* path, kFileMode mode)
{
    kObj(kFile, file); 
    kChar nativePath[kPATH_MAX]; 
    int oflag = 0; 

    kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath))); 
    kCheck(kPath_ToNative(nativePath, nativePath, kCountOf(nativePath))); 

    if (mode == kFILE_MODE_READ)
    {
        oflag = O_RDONLY;        
    }
    else if (mode == kFILE_MODE_WRITE)
    {
        oflag = O_WRONLY | O_CREAT | O_TRUNC;     
    }
    else if (mode == (kFILE_MODE_READ | kFILE_MODE_WRITE))
    {
        oflag = O_RDWR | O_CREAT | O_TRUNC; 
    }
    else if (mode == (kFILE_MODE_WRITE | kFILE_MODE_UPDATE))
    {
        oflag = O_WRONLY | O_CREAT;     
    }
    else if (mode == (kFILE_MODE_READ | kFILE_MODE_WRITE | kFILE_MODE_UPDATE))
    {
        oflag = O_RDWR | O_CREAT; 
    }
    else
    {
        return kERROR_PARAMETER; 
    }

    if ((oflag & O_CREAT) != 0)
    {
        obj->handle = open(nativePath, oflag, S_IRWXU); 
    }
    else
    {
        obj->handle = open(nativePath, oflag); 
    }

    if (obj->handle == -1)
    {
        switch (errno)
        {
        case ENOENT:
        case ENOTDIR:
            return kERROR_NOT_FOUND;
        default:
            return kERROR_STATE; 
        }
    }
  
    return kOK; 
}

kFx(kStatus) xkFile_CloseImpl(kFile file)
{
    kObj(kFile, file); 
    int result = 0; 
    
    if (xkFile_IsOpen(file))
    {
        result = close(obj->handle); 
        obj->handle = -1; 
    }

    return (result != -1) ? kOK : kERROR_STREAM; 
}

kFx(kStatus) xkFile_ReadImpl(kFile file, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
{
    kObj(kFile, file); 
    ssize_t readSize; 

    if ((readSize = read(obj->handle, buffer, maxCount)) < 0)
    {
        return kERROR_STREAM; 
    } 

    if ((kSize)readSize < minCount)
    {
        return kERROR_STREAM; 
    }

    *bytesRead = (kSize) readSize; 

    return kOK; 
}

kFx(kStatus) xkFile_WriteImpl(kFile file, const kByte* buffer, kSize count)
{
    kObj(kFile, file); 
    ssize_t writeSize; 

    if ((writeSize = write(obj->handle, buffer, count)) < 0)
    {
        return kERROR_STREAM; 
    } 

    if ((kSize)writeSize != count)
    {
        return kERROR_STREAM; 
    }

    return kOK; 
}

kFx(kStatus) xkFile_FlushImpl(kFile file)
{
    return kOK; 
}

kFx(kStatus) xkFile_SeekImpl(kFile file, k64s offset, kSeekOrigin origin, k64u* position)
{
    kObj(kFile, file); 
    off_t updatedPosition; 
    int whence; 

    switch(origin)
    {
    case kSEEK_ORIGIN_BEGIN:        whence = SEEK_SET;    break;
    case kSEEK_ORIGIN_CURRENT:      whence = SEEK_CUR;    break;
    case kSEEK_ORIGIN_END:          whence = SEEK_END;    break;
    default:                                              return 0; 
    }

    if ((updatedPosition = lseek(obj->handle, (off_t)offset, whence)) == -1)
    {
        return kERROR_STREAM; 
    }

    *position = updatedPosition; 

    return kOK; 
}

kFx(kBool) xkFile_ExistsImpl(const kChar* path)
{
    struct stat st; 
    kChar nativePath[kPATH_MAX];

    kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath)));

    return (stat(nativePath, &st) != -1) && S_ISREG(st.st_mode);
}

kFx(kStatus) xkFile_DeleteImpl(const kChar* path)
{
    kChar nativePath[kPATH_MAX]; 

    kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath))); 
    kCheck(kPath_ToNative(nativePath, nativePath, kCountOf(nativePath))); 

    return (unlink(nativePath) == 0) ? kOK : kERROR_OS; 
}

#else 

kFx(void) xkFile_InitPlatformFields(kFile file)
{
    kObj(kFile, file); 
    obj->handle = kNULL; 
}

kFx(kBool) xkFile_IsOpen(kFile file)
{
    kObj(kFile, file); 

    return !kIsNull(obj->handle); 
}

kFx(kStatus) xkFile_OpenImpl(kFile file, const kChar* path, kFileMode mode)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkFile_CloseImpl(kFile file)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkFile_ReadImpl(kFile file, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkFile_WriteImpl(kFile file, const kByte* buffer, kSize count)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkFile_FlushImpl(kFile file)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkFile_SeekImpl(kFile file, k64s offset, kSeekOrigin origin, k64u* position)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

kFx(kBool) xkFile_ExistsImpl(const kChar* path)
{
    kAssert(kFALSE); 
    return kFALSE;
}

kFx(kStatus) xkFile_DeleteImpl(const kChar* path)
{
    kAssert(kFALSE); 
    return kERROR_UNIMPLEMENTED;
}

#endif
