/*
 * mpo_fileio.cpp
 *
 * Copyright (C) 2005 Matthew P. Ownby
 *
 * This file is part of MPOLIB, a multi-purpose library
 *
 * MPOLIB is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPOLIB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// MPO's NOTE:
//  I may wish to use MPOLIB in a proprietary product some day.  Therefore,
//   the only way I can accept other people's changes to my code is if they
//   give me full ownership of those changes.

// mpo_fileio.cpp
// by Matt Ownby

// Replacement functions for the traditional fopen/fread/fclose/etc functions
// Main purpose: to support files larger than 4 gigs
// Secondary purpose: to not rely on MSCVRT dll in windows

#include "config.h"

#include "mpo_fileio.h"

#ifndef WIN32
#include <stdlib.h> // for malloc
#endif

// if this doesn't crash, you're good to go!
// if anyone knows how to do this check at compile time, let me know!!!
void mpo_test()
{
    if (sizeof(uint64_t) != 8) // make sure this is really 64-bit
    {
        int i = 0;
        int b;

        b = 5 / i; // force crash
        fprintf(stderr, "%d", b);
    }
}

bool mpo_file_exists(const char *filename)
{
    bool result = false;
    mpo_io *io  = NULL;

    io = mpo_open(filename, MPO_OPEN_READONLY);
    if (io) {
        mpo_close(io);
        result = true;
    }
    return result;
}

mpo_io *mpo_open(const char *filename, int flags)
{
    mpo_io *io   = NULL;
    bool success = false;

    // dynamically allocate, will be freed by mpo_close
    io = (mpo_io *)malloc(sizeof(mpo_io));

#ifdef WIN32
    ZeroMemory(io, sizeof(mpo_io));
    io->handle = INVALID_HANDLE_VALUE;

    if (flags == MPO_OPEN_READONLY) {
        io->handle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
                                OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
    } else if (flags == MPO_OPEN_READWRITE) {
        io->handle = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                                OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    } else if (flags == MPO_OPEN_CREATE) {
        io->handle = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL, NULL);
    } else if (flags == MPO_OPEN_APPEND) // append
    {
        // will this seek to the end of the file automatically?
        io->handle = CreateFile(filename, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL, NULL);
        if (io->handle != INVALID_HANDLE_VALUE) {
            mpo_seek(0, MPO_SEEK_END, io); // seek to the end of the file for
                                           // appending
        }
    } else // unknown value, error
    {
    }

    // if opening the file succeeded
    if (io->handle != INVALID_HANDLE_VALUE) {
        /*
        // GetFileSizeEx apparently only works for Win2k and WinXP so I reverted
        to GetFileSize for the older windows
        LARGE_INTEGER size;
        GetFileSizeEx(io->handle, &size);	// get the file size, we always
        want it..
        io->size = size.QuadPart;
        io->eof = false;
        result = true;
        */

        DWORD hSize = 0;
        DWORD lSize = 0;
        lSize       = GetFileSize(io->handle, &hSize);
        // if getting the file size succeeded
        if (lSize != INVALID_FILE_SIZE) {
            io->size = hSize;  // assign higher 32-bits initially
            io->size <<= 32;   // shift up 32-bits
            io->size |= lSize; // and merge in lower 32-bit bits
            io->eof = false;

            // now try to get the file time
            FILETIME last_modified;
            if (GetFileTime(io->handle, NULL, NULL, &last_modified)) {
                io->time_last_modified = last_modified.dwHighDateTime; // high
                                                                       // part
                io->time_last_modified <<= 32; // shift up 32
                io->time_last_modified |= last_modified.dwLowDateTime; // low
                                                                       // part
                success = true;
            }
            // else it failed for unknown reasons
        }
        // else getfilesize failed
    }
#else
    const char *mode = "rb"; // assume reading
    if (flags == MPO_OPEN_CREATE) {
        mode = "wb";
    } else if (flags == MPO_OPEN_READWRITE) {
        if (mpo_file_exists(filename))
            mode = "rb+"; // read/write existing file
        else
            mode = "wb+"; // create file, open in read/write mode
    } else if (flags == MPO_OPEN_APPEND) {
        mode = "ab";
    }
    // else unknown... eror

    io->handle = MPO_FOPEN(filename, mode);
    if (io->handle) {
        struct stat teh_stats;              // to get last modified
        MPO_FSEEK(io->handle, 0, SEEK_END); // go to end of file
        io->size = MPO_FTELL(io->handle);   // get position (total file size)
        MPO_FSEEK(io->handle, 0, SEEK_SET); // go to beginning
        io->eof = false;
        if (fstat(fileno(io->handle), &teh_stats) == 0) {
            io->time_last_modified = teh_stats.st_mtime;
            success                = true;
        }
        // else something went wrong (probably won't ever happen so we won't add
        // error handling)
    }
#endif

    // if something went wrong
    if (!success) {
        free(io);
        io = NULL;
    }

    return io;
}

// returns true on success
// EOF is when bytes_read is 0, but true is returned
bool mpo_read(void *buf, size_t bytes_to_read, MPO_BYTES_READ *bytes_read, mpo_io *io)
{
    bool result                      = false;
    MPO_BYTES_READ backup_bytes_read = 0; // in case their 'bytes_read' is NULL

    // if they don't care what bytes_read is, then we'll just squelch it
    if (bytes_read == NULL) {
        bytes_read = &backup_bytes_read;
    }

#ifdef WIN32
    LPDWORD ptr = bytes_read;
    if (ReadFile(io->handle, buf, (DWORD)bytes_to_read, ptr, NULL) != 0) {
        result = true;
    }

/*
else
{
        char s[160];
        DWORD i = GetLastError();
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, i, 0, s, sizeof(s), NULL);
        printline(s);
        tmp = "Error message # is " + g_numstr.ToStr((unsigned int) i);
        printline(tmp.c_str());
}
*/

#else
    *bytes_read = fread(buf, 1, bytes_to_read, io->handle);

    // if we read some bytes, or if we properly got to EOF
    if ((*bytes_read > 0) || ((*bytes_read == 0) || feof(io->handle) != 0)) {
        result = true;
    }
#endif

    // if we hit EOF, set a flag for convenience
    if ((*bytes_read) != bytes_to_read) {
        io->eof = true;
    }

    return result;
}

// returns true on success
bool mpo_write(const void *buf, size_t bytes_to_write, unsigned int *bytes_written, mpo_io *io)
{
    bool result = false;

    unsigned int backup_bytes_written = 0; // in case their 'bytes_written' is
                                           // NULL

    // if they don't care what bytes_written is, then we'll just squelch it
    if (bytes_written == NULL) {
        bytes_written = &backup_bytes_written;
    }

#ifdef WIN32
    LPDWORD ptr = (LPDWORD)bytes_written;
    if (WriteFile(io->handle, buf, (DWORD)bytes_to_write, ptr, NULL) != 0) {
        result = true;
    }
#else
    *bytes_written = fwrite(buf, 1, bytes_to_write, io->handle);
    if (*bytes_written == bytes_to_write) {
        result = true;
    }
#endif
    return result;
}

// fseek replacement
// returns true if seek was successful
bool mpo_seek(int64_t offset, seek_type type, mpo_io *io)
{
    bool result = false;

#ifdef WIN32
    /*
    // This code (SetFilePointerEx) works but only for Win2k and WinXP so I have
    commented it out
    LARGE_INTEGER l;
    l.QuadPart = offset;
    LARGE_INTEGER pre_result;
    pre_result.QuadPart = -1;

    // if seek is successful
    if (SetFilePointerEx(io->handle, l, &pre_result, type) != 0)
    {
        // result should now contain the current position
    }
    else
    {
        pre_result.QuadPart = -1;
    }

    result = true;	// no decent error checking here
    */

    LONG loffset     = (LONG)offset; // NOTE : assumes long is 32-bits
    LONG hoffset     = (LONG)(offset >> 32);
    DWORD pre_result = 0;

    pre_result = SetFilePointer(io->handle, loffset, &hoffset, type);

    // if we potentially got an error ...
    if (pre_result == INVALID_SET_FILE_POINTER)
    {
        result     = false;
        pre_result = GetLastError(); // check to see if we really got an error
        if (pre_result == NO_ERROR) {
            result = true;
        }
    }

    // no error
    else {
        result = true;
    }

#else
    int pre_result = MPO_FSEEK(io->handle, offset, type);
    if (pre_result == 0) result = true;
#endif

    return result;
}

void mpo_close(mpo_io *io)
{
    if (io != NULL) {
#ifdef WIN32
        CloseHandle(io->handle);
#else
        fclose(io->handle);
#endif
        io->handle             = 0;
        io->eof                = false;
        io->size               = 0;
        io->time_last_modified = 0;
        free(io); // de-allocate
    }
    // else we cannot reference a NULL pointer
}

bool mpo_mkdir(const char *dirname)
{
    bool result = false;
#ifdef WIN32
    if (CreateDirectory(dirname, NULL) != 0) result = true;
#else
    // create directory with minimal permissions
    if (mkdir(dirname, 0700) == 0) {
        result = true;
    }
#endif
    return result;
}

bool safe_dir(const char *s, int len)
{
    for (int i = 0; i < len && s[i] != '\0'; ++i) {
        unsigned char c = (unsigned char)s[i];
        if (!isalnum(c)
            && c != '/'
            && c != '.'
            && c != '-'
            && c != '_'
#ifdef _WIN32
            && c != ' '
            && c != ':'
            && c != '\\'
#endif
        ) return false;
    }
    return true;
}
