/**************************************************************************

Filename    :   OVR_File.cpp
Content     :   File wrapper class implementation (Win32)

Created     :   April 5, 1999
Authors     :   Michael Antonov

Copyright   :   Copyright 2014 Oculus VR, LLC All Rights reserved.

Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); 
you may not use the Oculus VR Rift SDK except in compliance with the License, 
which is provided at the time of installation or download, or which 
otherwise accompanies this software in either electronic or hard copy form.

You may obtain a copy of the License at

http://www.oculusvr.com/licenses/LICENSE-3.2 

Unless required by applicable law or agreed to in writing, the Oculus VR SDK 
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.

**************************************************************************/

#define  GFILE_CXX

// Standard C library (Captain Obvious guarantees!)
#include <stdio.h>

#include "OVR_File.h"

namespace OVR {

// Buffered file adds buffering to an existing file
// FILEBUFFER_SIZE defines the size of internal buffer, while
// FILEBUFFER_TOLERANCE controls the amount of data we'll effectively try to buffer
#define FILEBUFFER_SIZE         (8192-8)
#define FILEBUFFER_TOLERANCE    4096

// ** Constructor/Destructor

// Hidden constructor
// Not supposed to be used
BufferedFile::BufferedFile() : DelegatedFile(0)
{
    pBuffer     = (uint8_t*)OVR_ALLOC(FILEBUFFER_SIZE);
    BufferMode  = NoBuffer;
    FilePos     = 0;
    Pos         = 0;
    DataSize    = 0;
}

// Takes another file as source
BufferedFile::BufferedFile(File *pfile) : DelegatedFile(pfile)
{
    pBuffer     = (uint8_t*)OVR_ALLOC(FILEBUFFER_SIZE);
    BufferMode  = NoBuffer;
    FilePos     = pfile->LTell();
    Pos         = 0;
    DataSize    = 0;
}


// Destructor
BufferedFile::~BufferedFile()
{
    // Flush in case there's data
    if (pFile)
        FlushBuffer();
    // Get rid of buffer
    if (pBuffer)
        OVR_FREE(pBuffer);
}

/*
bool    BufferedFile::VCopy(const Object &source)
{
    if (!DelegatedFile::VCopy(source))
        return 0;

    // Data members
    BufferedFile *psource = (BufferedFile*)&source;

    // Buffer & the mode it's in
    pBuffer         = psource->pBuffer;
    BufferMode      = psource->BufferMode;
    Pos             = psource->Pos;
    DataSize        = psource->DataSize;
    return 1;
}
*/

// Initializes buffering to a certain mode
bool    BufferedFile::SetBufferMode(BufferModeType mode)
{
    if (!pBuffer)
        return false;
    if (mode == BufferMode)
        return true;
     
    FlushBuffer();

    // Can't set write mode if we can't write
    if ((mode==WriteBuffer) && (!pFile || !pFile->IsWritable()) )
        return 0;

    // And SetMode
    BufferMode = mode;
    Pos        = 0;
    DataSize   = 0;
    return 1;
}

// Flushes buffer
void    BufferedFile::FlushBuffer()
{
    switch(BufferMode)
    {
        case WriteBuffer:
            // Write data in buffer
            FilePos += pFile->Write(pBuffer,Pos);
            Pos = 0;
            break;

        case ReadBuffer:
            // Seek back & reset buffer data
            if ((DataSize-Pos)>0)
                FilePos = pFile->LSeek(-(int)(DataSize-Pos), Seek_Cur);
            DataSize = 0;
            Pos      = 0;
            break;
        default:
            // not handled!
            break;
    }
}

// Reloads data for ReadBuffer
void    BufferedFile::LoadBuffer()
{
    if (BufferMode == ReadBuffer)
    {
        // We should only reload once all of pre-loaded buffer is consumed.
        OVR_ASSERT(Pos == DataSize);

        // WARNING: Right now LoadBuffer() assumes the buffer's empty
        int sz   = pFile->Read(pBuffer,FILEBUFFER_SIZE);
        DataSize = sz<0 ? 0 : (unsigned)sz;
        Pos      = 0;
        FilePos  += DataSize;
    }
}


// ** Overridden functions

// We override all the functions that can possibly
// require buffer mode switch, flush, or extra calculations

// Tell() requires buffer adjustment
int     BufferedFile::Tell()
{
    if (BufferMode == ReadBuffer)
        return int (FilePos - DataSize + Pos);

    int pos = pFile->Tell();
    // Adjust position based on buffer mode & data
    if (pos!=-1)
    {
        OVR_ASSERT(BufferMode != ReadBuffer);
        if (BufferMode == WriteBuffer)
            pos += Pos;
    }
    return pos;
}

int64_t BufferedFile::LTell()
{
    if (BufferMode == ReadBuffer)
        return FilePos - DataSize + Pos;

    int64_t pos = pFile->LTell();
    if (pos!=-1)
    {
        OVR_ASSERT(BufferMode != ReadBuffer);
        if (BufferMode == WriteBuffer)
            pos += Pos;
    }
    return pos;
}

int     BufferedFile::GetLength()
{
    int len = pFile->GetLength();
    // If writing through buffer, file length may actually be bigger
    if ((len!=-1) && (BufferMode==WriteBuffer))
    {
        int currPos = pFile->Tell() + Pos;
        if (currPos>len)
            len = currPos;
    }
    return len;
}
int64_t BufferedFile::LGetLength()
{
    int64_t len = pFile->LGetLength();
    // If writing through buffer, file length may actually be bigger
    if ((len!=-1) && (BufferMode==WriteBuffer))
    {
        int64_t currPos = pFile->LTell() + Pos;
        if (currPos>len)
            len = currPos;
    }
    return len;
}

/*
bool    BufferedFile::Stat(FileStats *pfs)
{
    // Have to fix up length is stat
    if (pFile->Stat(pfs))
    {
        if (BufferMode==WriteBuffer)
        {
            int64_t currPos = pFile->LTell() + Pos;
            if (currPos > pfs->Size)
            {
                pfs->Size   = currPos;
                // ??
                pfs->Blocks = (pfs->Size+511) >> 9;
            }
        }
        return 1;
    }
    return 0;
}
*/

int     BufferedFile::Write(const uint8_t *psourceBuffer, int numBytes)
{
    if ( (BufferMode==WriteBuffer) || SetBufferMode(WriteBuffer))
    {
        // If not data space in buffer, flush
        if ((FILEBUFFER_SIZE-(int)Pos)<numBytes)
        {
            FlushBuffer();
            // If bigger then tolerance, just write directly
            if (numBytes>FILEBUFFER_TOLERANCE)
            {
                int sz = pFile->Write(psourceBuffer,numBytes);
                if (sz > 0)
                    FilePos += sz;
                return sz;
            }
        }

        // Enough space in buffer.. so copy to it
        memcpy(pBuffer+Pos, psourceBuffer, numBytes);
        Pos += numBytes;
        return numBytes;
    }
    int sz = pFile->Write(psourceBuffer,numBytes);
    if (sz > 0)
        FilePos += sz;
    return sz;
}

int     BufferedFile::Read(uint8_t *pdestBuffer, int numBytes)
{
    if ( (BufferMode==ReadBuffer) || SetBufferMode(ReadBuffer))
    {
        // Data in buffer... copy it
        if ((int)(DataSize-Pos) >= numBytes)
        {
            memcpy(pdestBuffer, pBuffer+Pos, numBytes);
            Pos += numBytes;
            return numBytes;
        }

        // Not enough data in buffer, copy buffer
        int     readBytes = DataSize-Pos;
        memcpy(pdestBuffer, pBuffer+Pos, readBytes);
        numBytes    -= readBytes;
        pdestBuffer += readBytes;
        Pos = DataSize;

        // Don't reload buffer if more then tolerance
        // (No major advantage, and we don't want to write a loop)
        if (numBytes>FILEBUFFER_TOLERANCE)
        {
            numBytes = pFile->Read(pdestBuffer,numBytes);
            if (numBytes > 0)
            {
                FilePos += numBytes;
                Pos = DataSize = 0;
            }
            return readBytes + ((numBytes==-1) ? 0 : numBytes);
        }

        // Reload the buffer
        // WARNING: Right now LoadBuffer() assumes the buffer's empty
        LoadBuffer();
        if ((int)(DataSize-Pos) < numBytes)
            numBytes = (int)DataSize-Pos;

        memcpy(pdestBuffer, pBuffer+Pos, numBytes);
        Pos += numBytes;
        return numBytes + readBytes;
        
        /*
        // Alternative Read implementation. The one above is probably better
        // due to FILEBUFFER_TOLERANCE.
        int     total = 0;

        do {
            int     bufferBytes = (int)(DataSize-Pos);
            int     copyBytes = (bufferBytes > numBytes) ? numBytes : bufferBytes;

            memcpy(pdestBuffer, pBuffer+Pos, copyBytes);
            numBytes    -= copyBytes;
            pdestBuffer += copyBytes;
            Pos         += copyBytes;
            total       += copyBytes;

            if (numBytes == 0)
                break;
            LoadBuffer();

        } while (DataSize > 0);

        return total;
        */     
    }
    int sz = pFile->Read(pdestBuffer,numBytes);
    if (sz > 0)
        FilePos += sz;
    return sz;
}


int     BufferedFile::SkipBytes(int numBytes)
{
    int skippedBytes = 0;

    // Special case for skipping a little data in read buffer
    if (BufferMode==ReadBuffer)
    {
        skippedBytes = (((int)DataSize-(int)Pos) >= numBytes) ? numBytes : (DataSize-Pos);
        Pos          += skippedBytes;
        numBytes     -= skippedBytes;
    }

    if (numBytes)
    {
        numBytes = pFile->SkipBytes(numBytes);
        // Make sure we return the actual number skipped, or error
        if (numBytes!=-1)
        {
            skippedBytes += numBytes;
            FilePos += numBytes;
            Pos = DataSize = 0;
        }
        else if (skippedBytes <= 0)
            skippedBytes = -1;
    }
    return skippedBytes;
}

int     BufferedFile::BytesAvailable()
{
    int available = pFile->BytesAvailable();
    // Adjust available size based on buffers
    switch(BufferMode)
    {
        case ReadBuffer:
            available += DataSize-Pos;
            break;
        case WriteBuffer:
            available -= Pos;
            if (available<0)
                available= 0;
            break;
        default:
            break;
    }
    return available;
}

bool    BufferedFile::Flush()
{
    FlushBuffer();
    return pFile->Flush();
}

// Seeking could be optimized better..
int     BufferedFile::Seek(int offset, int origin)
{    
    if (BufferMode == ReadBuffer)
    {
        if (origin == Seek_Cur)
        {
            // Seek can fall either before or after Pos in the buffer,
            // but it must be within bounds.
            if (((unsigned(offset) + Pos)) <= DataSize)
            {
                Pos += offset;
                return int (FilePos - DataSize + Pos);
            }

            // Lightweight buffer "Flush". We do this to avoid an extra seek
            // back operation which would take place if we called FlushBuffer directly.
            origin = Seek_Set;
            OVR_ASSERT(((FilePos - DataSize + Pos) + (uint64_t)offset) < ~(uint64_t)0);
            offset = (int)(FilePos - DataSize + Pos) + offset;
            Pos = DataSize = 0;
        }
        else if (origin == Seek_Set)
        {
            if (((unsigned)offset - (FilePos-DataSize)) <= DataSize)
            {
                OVR_ASSERT((FilePos-DataSize) < ~(uint64_t)0);
                Pos = (unsigned)offset - (unsigned)(FilePos-DataSize);
                return offset;
            }
            Pos = DataSize = 0;
        }
        else
        {
            FlushBuffer();
        }
    }
    else
    {
        FlushBuffer();
    }    

    /*
    // Old Seek Logic
    if (origin == Seek_Cur && offset + Pos < DataSize)
    {
        //OVR_ASSERT((FilePos - DataSize) >= (FilePos - DataSize + Pos + offset));
        Pos += offset;
        OVR_ASSERT(int (Pos) >= 0);
        return int (FilePos - DataSize + Pos);
    }
    else if (origin == Seek_Set && unsigned(offset) >= FilePos - DataSize && unsigned(offset) < FilePos)
    {
        Pos = unsigned(offset - FilePos + DataSize);
        OVR_ASSERT(int (Pos) >= 0);
        return int (FilePos - DataSize + Pos);
    }   
    
    FlushBuffer();
    */


    FilePos = pFile->Seek(offset,origin);
    return int (FilePos);
}

int64_t BufferedFile::LSeek(int64_t offset, int origin)
{
    if (BufferMode == ReadBuffer)
    {
        if (origin == Seek_Cur)
        {
            // Seek can fall either before or after Pos in the buffer,
            // but it must be within bounds.
            if (((unsigned(offset) + Pos)) <= DataSize)
            {
                Pos += (unsigned)offset;
                return int64_t(FilePos - DataSize + Pos);
            }

            // Lightweight buffer "Flush". We do this to avoid an extra seek
            // back operation which would take place if we called FlushBuffer directly.
            origin = Seek_Set;            
            offset = (int64_t)(FilePos - DataSize + Pos) + offset;
            Pos = DataSize = 0;
        }
        else if (origin == Seek_Set)
        {
            if (((uint64_t)offset - (FilePos-DataSize)) <= DataSize)
            {                
                Pos = (unsigned)((uint64_t)offset - (FilePos-DataSize));
                return offset;
            }
            Pos = DataSize = 0;
        }
        else
        {
            FlushBuffer();
        }
    }
    else
    {
        FlushBuffer();
    }

/*
    OVR_ASSERT(BufferMode != NoBuffer);

    if (origin == Seek_Cur && offset + Pos < DataSize)
    {
        Pos += int (offset);
        return FilePos - DataSize + Pos;
    }
    else if (origin == Seek_Set && offset >= int64_t(FilePos - DataSize) && offset < int64_t(FilePos))
    {
        Pos = unsigned(offset - FilePos + DataSize);
        return FilePos - DataSize + Pos;
    }

    FlushBuffer();
    */

    FilePos = pFile->LSeek(offset,origin);
    return FilePos;
}

int     BufferedFile::CopyFromStream(File *pstream, int byteSize)
{
    // We can't rely on overridden Write()
    // because delegation doesn't override virtual pointers
    // So, just re-implement
    uint8_t*  buff = new uint8_t[0x4000];
    int     count = 0;
    int     szRequest, szRead, szWritten;

    while(byteSize)
    {
        szRequest = (byteSize > int(sizeof(buff))) ? int(sizeof(buff)) : byteSize;

        szRead    = pstream->Read(buff,szRequest);
        szWritten = 0;
        if (szRead > 0)
            szWritten = Write(buff,szRead);

        count   +=szWritten;
        byteSize-=szWritten;
        if (szWritten < szRequest)
            break;
    }

	delete[] buff;

    return count;
}

// Closing files
bool    BufferedFile::Close()
{
    switch(BufferMode)
    {
        case WriteBuffer:
            FlushBuffer();
            break;
        case ReadBuffer:
            // No need to seek back on close
            BufferMode = NoBuffer;
            break;
        default:
            break;
    }
    return pFile->Close();
}


// ***** Global path helpers

// Find trailing short filename in a path.
const char* OVR_CDECL GetShortFilename(const char* purl)
{    
    size_t len = OVR_strlen(purl);
    for (size_t i=len; i>0; i--) 
        if (purl[i]=='\\' || purl[i]=='/')
            return purl+i+1;
    return purl;
}

} // OVR