////////////////////////////////////////////////////////////////////////////////
// FileCache.cpp
// Copyright Bjoern Ganster 2005-2012
////////////////////////////////////////////////////////////////////////////////

#include "BGBase.h"
#include "BGCPString.h"
#include "FileCache.h"
#include "FileList.h"
#include "MemoryBlock.h"

#include <algorithm>
#include <iostream>
#include <limits>
#include <assert.h>
using namespace std;

#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#else
#define LINUX
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

////////////////////////////////////////////////////////////////////////////////
// Choose between the functions below to enable or disable logging

inline void debugOut (const char*)
{
}

inline void reportRange (FileOffset , FileOffset)
{
}

////////////////////////////////////////////////////////////////////////////////
// Elementary file operations

#ifndef WIN32
inline FileHandle fileOpen (const TCHAR* FileName, bool readOnly)
{
   FileHandle newHandle;
   #ifdef UNICODE
      newHandle = _wfopen (FileName, L"r+b");
      if (newHandle == 0) {
         newHandle = _wfopen (FileName, L"w+b");
      }
   #else
   try {
      if (readOnly) {
         //newHandle = fopen (FileName, "rb");
         newHandle = fopen (FileName, "rb");
      } else {
         newHandle = fopen (FileName, "r+b");
         if (newHandle == 0) {
            newHandle = fopen (FileName, "w+b");
         }
      }
   } catch (...) {
      return 0;
   }
   #endif
   return newHandle; 
}

inline void fileClose (FileHandle& file)
{ fclose (file); }

inline size_t fileRead (FileHandle& file, void* buf, size_t BytesToRead)
{ return fread (buf, 1, BytesToRead, file); }

inline size_t fileWrite (FileHandle& file, const void* buf, size_t BytesToWrite)
{ return fwrite (buf, 1, BytesToWrite, file); }

inline bool fileSeek (FileHandle& file, size_t newPos, int)
{ 
   int res = fseek (file, (long) newPos, SEEK_SET);
   return (res == 0);
}
#endif

#ifdef _WIN32
void logError (const wchar_t* msg)
{
   BGCPString str(msg, CP_UTF16_LE);
   str += ": ";
   str += BGCPString::getOSError();
   str.printLn();
}
#endif

void logError (const char* msg)
{
   COUT << msg;
}

////////////////////////////////////////////////////////////////////////////////
// Constructor, destructor

FileCache::FileCache (CacheOffset NewCacheSize)
: VirtualClass("FileCache", FileCacheID),
  m_CacheStartPos (0),
  m_CacheSize (0),
  m_FileSize (0),
  cache (NULL)
{
   #ifdef WIN32
   m_FileHandle = INVALID_HANDLE_VALUE;
   #else
   m_FileHandle = 0;
   #endif

   m_isReadOnly = true;
   invalidateCache();
   setCacheSize (NewCacheSize);
}

FileCache::~FileCache ()
{
   setCacheSize (0);
   close();
}

////////////////////////////////////////////////////////////////////////////////
// Property setters

bool FileCache::setCacheStartPos (FileOffset newPos)
{
   if (!writeBackCache())
      return false;
   m_CacheStartPos = newPos;
   return fillCache();
}

bool FileCache::setCacheSize (CacheOffset newSize)
{
   writeBackCache();
   if (newSize != 0) {
      char* NewMem = (char*) traceAlloc(newSize, "FileCacheBuf", FileCacheBufID);
      if (NewMem != NULL) {
         if (cache  != NULL)
            delete cache;
         cache = NewMem;
         m_CacheSize = newSize;
         UsedCacheSize = 0;
         fillCache();
         return true;
      } else {
         cache = NULL;
         m_CacheSize = 0;
         return false;
      }
   } else {
      if (cache  != NULL)
         delete cache;
      cache = NULL;
      return true;
   }
}

/*bool FileCache::setFileHandle (FileHandle NewFileHandle)
{
   writeBackCache();
   if (CloseFileOnDestroy && m_FileHandle != 0)
      fileClose (m_FileHandle);
   m_FileHandle = NewFileHandle;
   if (NewFileHandle != 0) {
      m_FileSize = ::getFileSize (m_FileHandle);
   } else
      m_FileSize = 0;
   CloseFileOnDestroy = false;
   MustWriteCacheBack = false;
   return fillCache();
}*/

////////////////////////////////////////////////////////////////////////////////
// Invalidate Cache

void FileCache::invalidateCache()
{
   m_CacheStartPos = 0;
   UsedCacheSize = 0;
   MustWriteCacheBack = false;
}

////////////////////////////////////////////////////////////////////////////////
// Fill cache

bool FileCache::fillCache()
{
   if (m_CacheSize != 0
   &&  cache != NULL
   &&  isHandleValid())
   {
      writeBackCache();
      #ifdef _WIN32
      if (SetFilePointer (m_FileHandle, m_CacheStartPos, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
         logError ("Could not set file pointer: ");
      #else
      if (!fileSeek (m_FileHandle, m_CacheStartPos, 0)) {
         printf ("File seek failed\n");
         return false;
      }
      #endif

      CacheOffset toRead = min (m_CacheSize, CacheOffset (m_FileSize - m_CacheStartPos));
      #ifdef _WIN32
      DWORD read;
      if (ReadFile (m_FileHandle, cache, toRead, &read, NULL) == 0)
         logError ("Read error: ");
      UsedCacheSize = read;
      #else
      UsedCacheSize = fileRead (m_FileHandle, cache, toRead);
      #endif
      if (UsedCacheSize == toRead) {
         MustWriteCacheBack = false;
      } else {
         COUT << "Read failure" << std::endl;
         return false;
      }
      return true;
   } else {
      invalidateCache();
      return false;
   }
}

////////////////////////////////////////////////////////////////////////////////
// Write cache back to file

bool FileCache::writeBackCache ()
{
   if (m_CacheSize != 0
   &&  cache != NULL
   &&  isHandleValid()
   &&  MustWriteCacheBack
   &&  UsedCacheSize > 0)
   {
      #ifdef _WIN32
      if (SetFilePointer (m_FileHandle, m_CacheStartPos, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
         return false;    
      #else
      if (!fileSeek (m_FileHandle, m_CacheStartPos, 0))
         return false;
      #endif

      #ifdef _WIN32
      DWORD BytesWritten;
      WriteFile(m_FileHandle, cache, UsedCacheSize, &BytesWritten, NULL);
      #else
      CacheOffset BytesWritten = fileWrite (m_FileHandle, cache, UsedCacheSize);
      #endif
      MustWriteCacheBack = false;
      m_FileSize = std::max<DWORD> (m_FileSize,m_CacheStartPos + UsedCacheSize);
      return (BytesWritten == UsedCacheSize);
   } else
      return true; // need not write back, so operation was successful
}

////////////////////////////////////////////////////////////////////////////////
// Open, close file

bool FileCache::create (const PathChar* FileName)
{
   m_isReadOnly = false;
   bool result = false;
   #ifdef _WIN32
   m_FileHandle = CreateFileW (FileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, 
                               CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (m_FileHandle == INVALID_HANDLE_VALUE) {
      BGCPString str = "Could not open ";
      str += BGCPString(FileName, CP_SYSTEM);
      str += ": ";
      str.printLn();
   } else {
      m_FileSize = GetFileSize (m_FileHandle, NULL);
      CloseFileOnDestroy = true;
      result = true;
   }
   #else
   if (fileExists (FileName))
      remove (FileName);
   m_FileHandle = fileOpen (FileName, m_isReadOnly);
   if (m_FileHandle != NULL) {
      m_FileSize = getFileSize();
      result = true;
      CloseFileOnDestroy = true;
   }
   #endif
   return result;
}

bool FileCache::openReadOnly (const PathChar* FileName)
{
   m_isReadOnly = true;
   bool result = false;
   #ifdef _WIN32
   m_FileHandle = CreateFileW (FileName, GENERIC_READ, FILE_SHARE_READ, NULL, 
                               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   if (m_FileHandle == INVALID_HANDLE_VALUE) {
      BGCPString str = "Could not open ";
      str += BGCPString(FileName, CP_SYSTEM);
      str.printLn();
   } else {
      m_FileSize = GetFileSize (m_FileHandle, NULL);
      CloseFileOnDestroy = true;
      result = true;
   }
   #else
   m_FileHandle = fopen (FileName, "r");
   if (m_FileHandle != NULL) {
      m_FileSize = ::getFileSize(m_FileHandle);
      result = true;
      CloseFileOnDestroy = true;
   }
   #endif
   return result;
}

bool FileCache::openReadWrite (const PathChar* FileName)
{
   m_isReadOnly = false;
   #ifdef _WIN32
   m_FileHandle = CreateFileW (FileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, 
                               OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (m_FileHandle == INVALID_HANDLE_VALUE) {
      BGCPString str = "Could not open ";
      str += BGCPString(FileName, CP_SYSTEM);
      str.printLn();
   } else {
      m_FileSize = GetFileSize (m_FileHandle, NULL);
   }
   #else
   m_FileHandle = fileOpen (FileName, m_isReadOnly);
   m_FileSize = getFileSize();
   #endif
   CloseFileOnDestroy = true;
   return isHandleValid();
}

void FileCache::close()
{
   writeBackCache();
   if (CloseFileOnDestroy && isHandleValid()) {
      #ifdef _WIN32
      CloseHandle(m_FileHandle);
      m_FileHandle = INVALID_HANDLE_VALUE;
      #else
      fileClose (m_FileHandle);
      m_FileHandle = 0;
      #endif
   }

   // Reset FileCache in case it is reused
   m_CacheStartPos = 0;
   m_CacheSize = 0;
   m_FileSize = 0;
   UsedCacheSize = 0;
}

////////////////////////////////////////////////////////////////////////////////

bool memCopy (const char* source, char* target, FileOffset sourceOffs, 
              FileOffset targetOffs, FileOffset count)
{
   try {
	   memcpy(target+targetOffs, source+sourceOffs, count);
      //for (DWORD i = 0; i < count; i++)
      //   target[i+targetOffs] = source [i+sourceOffs];
      return true;
   } catch (...) {
      debugOut ("memCopy failed\n");
      return false;
   }
}

////////////////////////////////////////////////////////////////////////////////
// Read bytes from file

bool FileCache::read (FileOffset offset, char* buf, FileOffset count)
{
   if (m_FileHandle != 0) {
      if (count <= m_CacheSize) {
         if (offset < m_CacheStartPos
         ||  offset + count > m_CacheStartPos + UsedCacheSize) 
         {
            // Read miss in cache
            if (!setCacheStartPos (offset))
               return false;
            if (UsedCacheSize >= count) {
               return memCopy (cache, buf, offset - m_CacheStartPos, 0, count);
            } else
               return false;
         } else {
            // Read hit in cache
            return memCopy (cache, buf, offset - m_CacheStartPos, 0, count);
         }
      } else {
         // Amount to read is larger than cache
         writeBackCache();
         #ifdef _WIN32
         if (SetFilePointer (m_FileHandle, offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
            return false;    
         #else
         if (!fileSeek (m_FileHandle, offset, 0))
            return false;
         #endif
         #ifdef _WIN32
         DWORD BytesRead;
         ReadFile (m_FileHandle, buf, count, &BytesRead, NULL);
         #else
         FileOffset BytesRead = fileRead (m_FileHandle, buf, count);
         #endif
         UsedCacheSize = 0;
         return (BytesRead == count);
      }
   } else
      return false;
}

////////////////////////////////////////////////////////////////////////////////
// Read a line from a file - will skip empty lines
// Returns false at end of file

/*bool FileCache::readLine (FileOffset& offset, BGString& line)
{
   line.clear();
   char c;
   bool done = false;
   bool skipEOLs = false;

   // Read chars until an EOL is encountered
   while (!done) {
      bool success = read (offset, &c, 1);
      if (success) {
         if (c == 0x0a || c == 0x0d) {
            done = true;
            skipEOLs = true;
         } else {
            line += c;
            offset++;
         }
      } else
         done = true;
   }

   // Skip EOL chars
   char lastEOLChar = ' ';
   size_t EOLcharsRead = 0;
   while (skipEOLs) {
      bool success = read (offset, &c, 1);
      if (success) {
         if (c == 0x0a || c == 0x0d) {
            EOLcharsRead++;
            if (c == lastEOLChar || EOLcharsRead > 1)
               skipEOLs = false;
            offset++;
            lastEOLChar = c;
         } else
            skipEOLs = false;
      } else
         skipEOLs = false;
   }

   return (line.getLength() > 0 || EOLcharsRead > 0);
}*/

////////////////////////////////////////////////////////////////////////////////
// Write bytes to file

bool FileCache::enlargeFile (FileOffset NewSize)
{
   writeBackCache();
   #ifdef _WIN32
   if (SetFilePointer (m_FileHandle, m_FileSize, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
      return false;    
   #else
   if (!fileSeek (m_FileHandle, m_FileSize, 0))
      return false;
   #endif

   for (unsigned int i = 0; i < m_CacheSize; i++) 
      cache[i] = 0;
   while (m_FileSize < NewSize) {
      size_t toWrite = NewSize - m_FileSize;
      if (toWrite > m_CacheSize)
         toWrite = m_CacheSize;
      #ifdef _WIN32
      DWORD written;
      WriteFile (m_FileHandle, cache, toWrite, &written, NULL);
      #else
      FileOffset written;
      written = fileWrite (m_FileHandle, cache, toWrite);
      #endif
      m_FileSize += written;
      if (written != toWrite)
         return false;
   }
   return true;
}

bool FileCache::write (FileOffset offset, const char* buf, 
                       FileOffset count)
{
   assert (buf != NULL);
   if (!m_isReadOnly) {
      // Test if we need to enlarge the file
      if (offset > m_FileSize) {
         if (!enlargeFile (offset))
            return false;
      }

      if (count < m_CacheSize) {
         if (offset == m_FileSize)
         {
            append (buf, count);
         } else
         // Treat write hits & misses
         if (offset < m_CacheStartPos
         ||  offset + count >= m_CacheStartPos + UsedCacheSize) 
         {
            // Cache write miss
            setCacheStartPos (offset);
         }
         // Now we eiter have a write hit or this case can now be treated
         // like one
         MustWriteCacheBack = true;
         return memCopy (buf, cache, 0, offset - m_CacheStartPos, count);
      } else {
         // todo: Workaround for linux. Should not be necessary but fixes
         // problems when writing to newly created files
         if (offset + count > m_FileSize)
            enlargeFile (offset + count);

         // New data does not fit into cache, but has to be written directly
         #ifdef _WIN32
         SetFilePointer (m_FileHandle, offset, NULL, FILE_BEGIN);
         DWORD BytesWritten;
         WriteFile (m_FileHandle, buf, count, &BytesWritten, NULL);
         #else
         if (!fileSeek (m_FileHandle, offset, 0))
            return false;
         size_t BytesWritten = fileWrite (m_FileHandle, buf, count);
         #endif
         
         // If write range overlaps with cached range, copy part of the cache
         // from written bytes
//         WriteBackCache();
//         UsedCacheSize = 0;
         copyBytesFromBuf (buf, offset, count);
         return (BytesWritten == count);
      }
   } else
      return false;
}

bool FileCache::append (const char* buf, size_t count)
{
   if (m_isReadOnly)
      return false;

   if (count > m_CacheSize) {
      // Append directly without caching
      writeBackCache();
      m_FileSize += count;

      // New data does not fit into cache, but has to be written directly
      #ifdef _WIN32
      LARGE_INTEGER li;
      li.QuadPart = 0;   
      SetFilePointerEx (m_FileHandle, li, NULL, FILE_END);
      DWORD BytesWritten;
      WriteFile (m_FileHandle, buf, count, &BytesWritten, NULL);
      #else
      if (!fileSeek (m_FileHandle, m_FileSize, 0))
         return false;
      size_t BytesWritten = fileWrite (m_FileHandle, buf, count);
      #endif
      return BytesWritten == count;
   } else {
      // Append to file using the cache
      if (m_FileSize + count > m_CacheStartPos + UsedCacheSize)
         if (!setCacheStartPos (m_FileSize))
            return false;
      MustWriteCacheBack = true;
      UsedCacheSize = (CacheOffset) (m_FileSize + count - m_CacheStartPos);

      if (memCopy (buf, cache, 0, m_FileSize - m_CacheStartPos, count)) {
         m_FileSize = m_CacheStartPos + UsedCacheSize;
         return true;
      } else
         return false;
   }
}

////////////////////////////////////////////////////////////////////////////////
// Flush file

void FileCache::flush ()
{
   writeBackCache();
   #ifndef _WIN32
   fflush (m_FileHandle);
   #endif
}

////////////////////////////////////////////////////////////////////////////////
// Copy bytes from buf, used when reads or writes request more data than fits
// into the cache

bool FileCache::copyBytesFromBuf (
   const char* buf, FileOffset offset, FileOffset count)
{
   if (offset > m_CacheStartPos
   &&  offset + count < m_CacheStartPos + UsedCacheSize)
   { 
      // buf lies entirely inside the cache
      debugOut ("a\n");
      MustWriteCacheBack = true;
      return memCopy (buf, cache, 0, offset - m_CacheStartPos, count);
   } else
   if (offset > m_CacheStartPos
   &&  offset < m_CacheStartPos + UsedCacheSize)
   {
      // Start of buf lies in cache
      debugOut ("b\n");
      MustWriteCacheBack = true;
      return memCopy (buf, cache, 0, offset - m_CacheStartPos, 
                      m_CacheStartPos + UsedCacheSize - offset);
   } else 
   if (offset + count > m_CacheStartPos
   &&  offset + count < m_CacheStartPos + UsedCacheSize)
   {
      // End of buf affects cache or cache lies entirely in buf
      debugOut ("c\n");
      MustWriteCacheBack = true;
      return memCopy (buf, cache, 0, 0,
                         offset + count - m_CacheStartPos);
   } else
      // Cache and written range do not overlap, trivial success
      return true;
}

////////////////////////////////////////////////////////////////////////////////
// Is given path absolute?

bool FileCache::isPathAbsolute (const char* path) 
{
   #ifdef WIN32
   if (strlen (path) > 3) {
      if (path[0] == '\"'
      &&  path[2] == ':'
      &&  path[3] == OSPathSep)
      {
         return true;
      }
   }

   if (strlen (path) > 2) {
      if (path[1] == ':'
      &&  path[2] == OSPathSep)
      {
         return true;
      }
   }
   return false;
   #else
      if (strlen(path) > 0) {
         return (path[0] == OSPathSep);
      } else
         return false;
   #endif
}

////////////////////////////////////////////////////////////////////////////////
// Path parsing

#ifdef WIN32

bool convertLetterToWindowsDriveNumber (char drvLetter, int& driveNum)
{
   if (drvLetter >= 'a'
   &&  drvLetter <= 'z')
   {
         driveNum = drvLetter - 'a' + 1;
         return true;
   } else
   if (drvLetter >= 'A'
   &&  drvLetter <= 'Z')
   {
         driveNum = drvLetter - 'A' + 1;
         return true;
   } else {
      return false; // Error: illegal drive character
   }
}

PathChar getCurrWinDriveLetter()
{
   return _getdrive () + 'A' -1;
}
#endif

///////////////////////////////////////////////////////////////////////////////
// Patch targetFN using a previously produced diffFile

bool patch(const PathChar* diffFile, const PathChar* targetFN)
{
   // Open <diff>, <file>
   MemoryBlock diff, mb;
   if (!diff.loadFromFile(diffFile)) {
      COUT << "Unable to open " << diffFile << std::endl;
      return false;
   }
   if (!mb.loadFromFile(targetFN)) {
       COUT << "Unable to open" << targetFN << std::endl;
      return false;
   }

   // Check if <diff> applies
   DiffHeader* header = (DiffHeader*) diff.getPtr<unsigned char>(0);
   if (strcmp(header->signature, "PRJPP diff") != 0) {
       COUT << diffFile << ": not a diff file" << std::endl;
      return false;
   }

   if (header->size1 != mb.getSize()) {
      COUT << diffFile << " does not apply to " << targetFN << " (wrong size)" << std::endl;
      return false;
   }

   size_t offset = sizeof(*header);
   for (size_t i = 0; i < header->diffs; i++) {
      size_t outOffset = diff.get<DWORD>(offset);
      char oldVal1 = diff.get<uint8_t>(offset+4);
      char oldVal2 = mb.get<uint8_t>(outOffset);
      if (oldVal1 != oldVal2) {
         COUT << diffFile << " does not apply to " << targetFN 
              << " (difference at offset " << outOffset << ": " 
              << oldVal1 << "-" << oldVal2 << std::endl;
         return false;
      }
      offset += 6;
   }

   // Apply diffs
   offset = sizeof(*header);
   for (size_t i = 0; i < header->diffs; i++) {
      size_t outOffset = diff.get<DWORD>(offset);
      char newVal = diff.get<uint8_t>(offset+5);
      mb.set<BYTE>(outOffset, newVal);
      offset += 6;
   }

   mb.append(diff.getPtr<unsigned char>(offset), diff.getSize()-offset);

   // Write back results
   if (mb.saveToFile(targetFN)) {
      COUT << "Patch operation successful for " << targetFN << std::endl;
      return true;
   }else {
       COUT << "Patch: writing " << targetFN << " failed" << std::endl;
      return false;
   }
}

////////////////////////////////////////////////////////////////////////////////
// Delete all files in dirName

void deleteTree (const PathChar* dirName)
{
	FileList list;
	BGCPString searchMask(dirName, CP_SYSTEM);
	searchMask.addPathSep("*");
	if (list.start(searchMask.getAsPath())) {
		do {
			if (bgstrcmp (list.getFileName(), ".") != 0
			&&  bgstrcmp (list.getFileName(), "..") != 0) 
			{
				BGCPString next(dirName, CP_SYSTEM);
				next.addPathSep(BGCPString(list.getFileName(), CP_SYSTEM));
				if (list.isDirectory()) {
					deleteTree(next.getAsPath());
					bgrmdir(next.getAsPath());
				} else {
					bgDeleteFile(next.getAsPath());
				}
			}
		} while (++list);
	}
}

#ifdef _WIN32
#include <windows.h>
#include <direct.h>
void bgmkdir (const char* newDir)
{
	auto result = _mkdir(newDir);
	if (result != 0 && errno != EEXIST) {
		COUT << "make dir result=" << errno << std::endl;
	}
}
void bgmkdir (const wchar_t* newDir)
{
	auto result = _wmkdir(newDir);
	if (result != 0 && errno != EEXIST) {
		COUT << "make dir result=" << errno << std::endl;
	}
}
#else
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
typedef int64_t INT64;
typedef u_int32_t DWORD;
typedef u_int16_t WORD;
void bgmkdir (const char* newDir)
{
	mkdir(newDir, 0777);
}
#endif
