/******************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   File functions.
 * @remarks Some Windows functions will recognize UNIX forward slash, v.v.
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 ******************************************************************************/

#define BASE_FILE_CC 1
#include <utility>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#if COMPILE_ZLIB
// C++ streams for zlib are at base/stream_zlib.hh
#include <zlib.h>
#endif
#include "base/module.hh"
#include "base/misc.hh"
#include "base/file.hh"
#if OS_WINDOWS
#include <cstdlib>
#include <cstdio>
#include <direct.h>
#include "base/file_dirent.hh"  // Windows
#endif

namespace base {

/*****************************************************************************
 * @return True if file exists.
 *****************************************************************************/
bool
IfFileExists( const string& fname )
{
    FILE* file = fopen( fname.c_str(), "rb" );
    if ( file != NULL )
    {
        fclose( file );
        return true;
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * @return Size of an opened file else -1.
 *****************************************************************************/
long
FileSize( FILE* file )
{
    int fd = fileno( file );  // fileno() shouldn't ever fail
    struct stat s;
    if ( fstat( fd, &s ) == 0 )
        return s.st_size;
    else
        return -1;
}

/*****************************************************************************
 * @return Size of a file else -1.
 *****************************************************************************/
long
FileSize( const string& fname )
{
    FILE* file = fopen( fname.c_str(), "rb" );
    if ( file != NULL )
    {
        const long size = FileSize( file );
        fclose( file );
        return size;
    }
    else
    {
        // Error.
        return -1;
    }
}

/*****************************************************************************
 * Read a file into a C++ string.
 *****************************************************************************/
bool
ReadFile( const string& fname, string& obuf )
{
    obuf.erase();                                               // clear buffer
    FILE* file = fopen( fname.c_str(), "rb" );
    if ( file )
    {
        long fileSize = FileSize( file );                       // returns -1 if error
        if ( fileSize > 0 )
        {
            char* buf = new char[ fileSize + 10 ];
            uint bytesRead = fread( buf, 1, fileSize, file );   // should return 0..fileSize
            obuf.assign( buf, bytesRead );                      // assign bytes actually read
            delete[] buf;
        }
        fclose( file );
    }
    return obuf.size() > 0;
}

/*****************************************************************************
 * Read a file into a StringBuf object.
 * A StringBuf can be used with shptr.
 *****************************************************************************/
bool
ReadFile( const string& fname, StringBuf& strbuf )
{
    return ReadFile( fname, strbuf.mBuf );
}

/*****************************************************************************
 * Decompress a gzip-compressed file (.gz) using zlib functions into a C++ string.
 * @param   fname
 * @param   obuf
 *          Output buffer.
 * @param   chunkLen
 *          Default arg.  For speed, pass expected size of decompressed buffer.
 * @remarks gzread() is smart enough to function like fread() without returning
 * an error in case a file isn't really gzip-compressed.
 *****************************************************************************/
#if COMPILE_ZLIB
bool
ReadFileGzipIntoString( const string& fname, string& obuf, const uint chunkLen )
{
    obuf.erase();
    gzFile file = gzopen( fname.c_str(), "rb" );
    if ( file )
    {
        // Decompress gzip file, iteratively appending
        // a decompressed chunk to output string.
        char* chunk = new char[chunkLen+10];
        int bytes;
        while ( (bytes = gzread( file, chunk, chunkLen )) > 0 )  // returns 0=EOF -1=error
        {
            obuf.append( chunk, bytes );    // append chunk
        }
        gzclose( file );
        delete[] chunk;
    }
    return obuf.size() > 0;
}

/*****************************************************************************
 * Decompress a gzip-compressed file (.gz) using zlib functions into a raw memory buffer.
 *****************************************************************************/
std::pair<void*,long>
ReadFileGzipIntoMemory( const string& fname )
{
    // zlib provides no way to determine uncompressed size
    // in order to pre-allocate a raw memory buffer.
    // Reuse the C++ string variant of ReadFileGzip() and copy memory (inefficient).

    std::pair<void*,long> buf_bufSize( NULL, 0 );
    string bufString;
    if ( ReadFileGzipIntoString( fname, bufString ) )
    {
        // Copy string into raw memory.
        buf_bufSize.first  = malloc( bufString.size() + 0x100 );  // extra
        buf_bufSize.second = bufString.size();
        memcpy( buf_bufSize.first,   // dest
                bufString.c_str(),   // src
                bufString.size() );  // len
                
    }
    return buf_bufSize;
}
#endif // COMPILE_ZLIB

/*****************************************************************************
 * Delete a file.
 * Will normally return false if file doesn't exist (not really an error)
 * since remove() is based on unlink().
 *****************************************************************************/
bool
RemoveFile( const string& fname )
{
    return remove( fname.c_str() ) == 0;
}

/*****************************************************************************
 * Remove suffix from a pathname.
 * Pass ".txt" rather than "txt".
 *****************************************************************************/
void
RemoveSuffix( string& fname/*IN/OUT*/, const string& suffix )
{
    // string::size_type should be used when comparing against string::npos.
    string::size_type idx = fname.find( suffix, 0 );
    if ( idx != string::npos )
        fname.erase( idx, suffix.size() );
}

/*****************************************************************************
 * @return True if a pathname is absolute.
 *****************************************************************************/
bool
IfPathIsAbsolute( const string& path )
{
    // Some Windows functions will recognize UNIX forward slash, v.v.
    if ( path.size() >= 1 )
    {
        if ( (path[0] == '/') or (path[0] == '\\') )  // leading slash?
        {
            return true;
        }
        else if ( path.size() >= 2 )
        {
            if ( isalpha(path[0]) and path[1] == ':' )  // Windows drive letter?
                return true;
        }
    }

    // None of the above.
    return false;
}

/*****************************************************************************
 * @return Base part of filename without directories.
 *****************************************************************************/
string
FileBasename( const string& path )
{
    // Find last occurrence of forward-slash or backward-slash.
    string::size_type idx = path.rfind( '/' );
    if ( idx == string::npos )
        idx = path.rfind( '\\' );

    // If found, return remainder after last slash.
    if ( (idx != string::npos) and (idx + 1 < path.size()) )
        return path.substr( idx + 1 );
    else
        return path;
}

/*****************************************************************************
 * @return True if a pathname is purely a filename (no directory slashes nor drive names).
 *****************************************************************************/
bool
IfPathIsFilename( const string& path )
{
    // Some Windows functions will recognize UNIX forward slash, v.v.
    if ( path.size() >= 1 )
    {
        // True if no slashes/colon.
        return (path.find('/') == string::npos)
           and (path.find('\\') == string::npos)
           and (path.find(':') == string::npos);
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * Make a directory.
 *****************************************************************************/
bool
MakeDir( const string& path )  // mkdir
{
#if DOSFS
    return (_mkdir( path.c_str() ) == 0);
#else
    return (mkdir( path.c_str(), 0750 ) == 0);  // u=rwx,g=rx,o=0  //@lint
#endif
}

/*****************************************************************************
 * @return True if directory exists.
 *****************************************************************************/
bool
IfDirExists( const string& dirname )
{
    // Open directory.
    DIR* dir = opendir( dirname.c_str() );
    if ( dir == NULL )
    {
        return false;
    }
    else
    {
        closedir( dir );
        return true;
    }
}

/*****************************************************************************
 * Fills a STL vector with filenames from a directory.
 * @verbatim
 * Example of usage:
 *  vector<string> files;
 *  if ( FilesInDir( "/tmp", files ) )
 *  {
 *      vector<string>::iterator iter;
 *      for ( iter = files.begin(); iter != files.end(); ++iter )
 *          cout << *iter << "\n";
 *  }
 * @endverbatim
 *****************************************************************************/
bool
FilesInDir( const string& dirname, std::vector<string>& vec /*OUT*/ )
{
    vec.clear();

    // Open directory.
    DIR* dir = opendir( dirname.c_str() );
    if ( dir == NULL )
        return false;

    // For each file in directory.
    for (;;)
    {
        // Read next filename.
        dirent* file = readdir( dir );
        const char* name = file->d_name;
        if ( file == NULL )
            break;

        // Exclude parent and current directories.
        if ( STRNE( name, "."  )
         and STRNE( name, ".." ) )
        {
            vec.push_back( name );
        }
    }

    closedir( dir );
    return true;
}

/*****************************************************************************
 * @return CWD as a C++ string.
 *****************************************************************************/
string
GetCwd( void )
{
    PERSISTENT char cwd_[BASE_MAX_PATH];
    if ( GETCWD( cwd_, sizeof(cwd_) ) == NULL )
        throw Exception( "GetCwd" );  // shutup gcc 4.3
    return string(cwd_);
}

} // namespace base
