/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program 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 3 of the License, or
  (at your option) any later version.

  This program 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, see <http://www.gnu.org/licenses/>.
*/
#include "../pch.h"
#include "../quakedef.h"
#include "file.h"

#include "physfs.h"


File::File() : fp(NULL) {}


bool File::Exists(const std::string &path)
{
    return PHYSFS_exists(path.c_str()) != 0;
}


bool File::OpenRead(const std::string &path)
{
    // Don't open a new file without closing
    if (fp)
        return false;
    
    fp = PHYSFS_openRead(path.c_str());
    return fp != NULL;
}

bool File::OpenWrite(const std::string &path)
{
    // Don't open a new file without closing
    if (fp)
        return false;
    
    fp = PHYSFS_openWrite(path.c_str());
    return fp != NULL;
}

void File::Close()
{
    if (!fp)
        return;
    
    PHYSFS_close(fp);
    fp = NULL;
}


uint64_t File::Size()
{
    if (!fp)
        return UINT64_MAX;
    
    PHYSFS_sint64 ret = PHYSFS_fileLength(fp);
    if (ret == -1)
        return UINT64_MAX;
    
    return (uint64_t)ret;
}

bool File::Seek(uint64_t position)
{
    if (!fp)
        return false;
    
    return PHYSFS_seek(fp, position) != 0;
}

uint64_t File::Tell()
{
    if (!fp)
        return UINT64_MAX;
    
    PHYSFS_sint64 ret = PHYSFS_tell(fp);
    if (ret == -1)
        return UINT64_MAX;
    
    return (uint64_t)ret;
}

bool File::eof()
{
    if (!fp)
        return true;
    return PHYSFS_eof(fp) != 0;
}


uint8_t *File::Read()
{
    if (!Seek(0))
        return NULL;
    return Read(Size());
}

uint8_t *File::Read(uint64_t len)
{
    // Note that on some platforms this may truncate, but that's the best
    // we can do for memory allocation.
    uint8_t *buf = new uint8_t[(size_t)len + 1];
    if (!buf)
        return NULL;
    
    if (!Read(buf, len))
    {
        delete[] buf;
        return NULL;
    }
    
    buf[len] = 0;
    return buf;
}

bool File::Read(void *buf, uint64_t len)
{
    if (!fp)
        return false;
    
    // Calculate count if we need to
    uint32_t count = 1;
    if (len > UINT32_MAX)
    {
        while (len > UINT32_MAX)
        {
            len >>= 1;
            count <<= 1;
        }
    }

    // Do the read
    return PHYSFS_read(fp, buf, len, count) == count;
}


std::string File::GetLine()
{
    if (!fp || PHYSFS_eof(fp))
        return std::string();
    
    std::string ret;
    do
    {
        char c;
        if (!Read(&c, 1))
            break;
        if (c == '\n')
            break;
        
        ret.push_back(c);
    } while (!PHYSFS_eof(fp));
    
    return ret;
}

bool File::Printf(const char *str, ...)
{
    // Thanks to the vsnprintf() manpage for this snippet
    int n;
    size_t size = 100;
    char *p, *np;
    va_list ap;
    
    if ((p = (char *)malloc(size)) == NULL)
        return false;
    
    while (1)
    {
        // Try to print in the available space
        va_start(ap, str);
        n = vsnprintf(p, size, str, ap);
        va_end(ap);
        
        // If that worked, we're done
        if (n > -1 && n < (int)size)
            break;
        
        // Try again with more space
        if (n > -1)
            size = n + 1;
        else
            size *= 2;
        
        if ((np = (char *)realloc(p, size)) == NULL)
        {
            free(p);
            return NULL;
        }
        else
            p = np;
    }
    
    // Okay, we have it, write it out
    bool ret = Write(p, strlen(p));
    free(p);
    
    return ret;
}


bool File::Write(const void *buffer, uint64_t len)
{
    if (!fp)
        return false;
    
    // Calculate count if we need to
    uint32_t count = 1;
    if (len > UINT32_MAX)
    {
        while (len > UINT32_MAX)
        {
            len >>= 1;
            count <<= 1;
        }
    }
    
    PHYSFS_sint64 ret = PHYSFS_write(fp, buffer, len, count);
    if (ret != count)
        return false;
    
    return true;
}

bool File::Flush()
{
    return PHYSFS_flush(fp) != 0;
}
