/*
 *  The ManaPlus Client
 *  Copyright (C) 2013-2017  The ManaPlus Developers
 *
 *  This file is part of The ManaPlus Client.
 *
 *  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 2 of the License, or
 *  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 "fs/virtfs/fsdirrwops.h"

#include "fs/virtfs/file.h"

#include "utils/cast.h"
#include "utils/checkutils.h"

PRAGMA48(GCC diagnostic push)
PRAGMA48(GCC diagnostic ignored "-Wshadow")
#include <SDL_rwops.h>
PRAGMA48(GCC diagnostic pop)

#include "debug.h"

namespace VirtFs
{

namespace FsDir
{
    RWOPSINT rwops_seek(SDL_RWops *const rw,
                        const RWOPSINT offset,
                        const int whence)
    {
        if (rw == nullptr)
            return -1;
        File *const handle = static_cast<File *>(
            rw->hidden.unknown.data1);
        FILEHTYPE fd = handle->mFd;
        RWOPSINT pos = 0;

        if (whence == SEEK_SET)
        {
            pos = offset;
        }
        else if (whence == SEEK_CUR)
        {
#ifdef USE_FILE_FOPEN
            const int64_t current = ftell(fd);
#else  // USE_FILE_FOPEN
            const int64_t current = lseek(fd, 0, SEEK_CUR);
#endif  // USE_FILE_FOPEN

            if (current == -1)
            {
                logger->assertLog(
                    "VirtFs::rwops_seek: Can't find position in file.");
                return -1;
            }

            pos = CAST_S32(current);
            if (static_cast<int64_t>(pos) != current)
            {
                logger->assertLog("VirtFs::rwops_seek: "
                    "Can't fit current file position in an int!");
                return -1;
            }

            if (offset == 0)  /* this is a "tell" call. We're done. */
                return pos;

            pos += offset;
        }
        else if (whence == SEEK_END)
        {
            int64_t len = 0;
#ifdef USE_FILE_FOPEN
            const long curpos = ftell(fd);
            if (curpos < 0)
            {
                reportAlways("FsDir::fileLength ftell error.");
                return -1;
            }
            fseek(fd, 0, SEEK_END);
            len = ftell(fd);
//            fseek(fd, curpos, SEEK_SET);
#else  // USE_FILE_FOPEN
            struct stat statbuf;
            if (fstat(fd, &statbuf) == -1)
            {
                reportAlways("FsDir::fileLength error.");
                len = -1;
            }
            else
            {
                len = static_cast<int64_t>(statbuf.st_size);
            }
#endif  // USE_FILE_FOPEN

            if (len == -1)
            {
#ifdef USE_FILE_FOPEN
                if (fseek(fd, curpos, SEEK_SET) < 0)
                {
                    reportAlways("FsDir::fileLength fseek error.");
                }
#endif  // USE_FILE_FOPEN
                logger->assertLog(
                    "VirtFs::rwops_seek:Can't find end of file.");
                return -1;
            }

            pos = static_cast<RWOPSINT>(len);
            if (static_cast<int64_t>(pos) != len)
            {
#ifdef USE_FILE_FOPEN
                fseek(fd, curpos, SEEK_SET);
#endif  // USE_FILE_FOPEN
                logger->assertLog("VirtFs::rwops_seek: "
                    "Can't fit end-of-file position in an int!");
                return -1;
            }

            pos += offset;
        }
        else
        {
            logger->assertLog(
                "VirtFs::rwops_seek: Invalid 'whence' parameter.");
            return -1;
        }

        if (pos < 0)
        {
            logger->assertLog("VirtFs::rwops_seek: "
                "Attempt to seek past start of file.");
            return -1;
        }

        const int64_t res = FILESEEK(fd, pos, SEEK_SET);
        if (res == -1)
        {
            logger->assertLog("VirtFs::rwops_seek: seek error.");
            return -1;
        }

        return pos;
    }

    RWOPSSIZE rwops_read(SDL_RWops *const rw,
                         void *const ptr,
                         const RWOPSSIZE size,
                         const RWOPSSIZE maxnum)
    {
        if (rw == nullptr)
            return 0;
        File *const handle = static_cast<File *>(
            rw->hidden.unknown.data1);
        FILEHTYPE fd = handle->mFd;

#ifdef USE_FILE_FOPEN
        const int64_t rc = fread(ptr, size, maxnum, fd);
#else  // USE_FILE_FOPEN
        int max = size * maxnum;
        int cnt = ::read(fd, ptr, max);
        if (cnt <= 0)
            return cnt;
        const int64_t rc = cnt / size;
#endif  // USE_FILE_FOPEN

#ifndef USE_FILE_FOPEN
        if (rc != static_cast<int64_t>(maxnum))
        {
            const int64_t pos = lseek(fd, 0, SEEK_CUR);
            struct stat statbuf;
            if (fstat(fd, &statbuf) == -1)
            {
                reportAlways("FsDir::fileLength error.");
                return CAST_S32(rc);
            }
        }
#endif  // USE_FILE_FOPEN
        return CAST_S32(rc);
    }

    RWOPSSIZE rwops_write(SDL_RWops *const rw,
                          const void *const ptr,
                          const RWOPSSIZE size,
                          const RWOPSSIZE maxnum)
    {
        if (rw == nullptr)
            return 0;
        File *const handle = static_cast<File *>(
            rw->hidden.unknown.data1);
        FILEHTYPE fd = handle->mFd;

#ifdef USE_FILE_FOPEN
        const int64_t rc = fwrite(ptr, size, maxnum, fd);
#else  // USE_FILE_FOPEN
        int max = size * maxnum;
        int cnt = ::write(fd, ptr, max);
        if (cnt <= 0)
            return cnt;
        const int64_t rc = cnt / size;
#endif  // USE_FILE_FOPEN

#ifndef USE_FILE_FOPEN
        if (rc != static_cast<int64_t>(maxnum))
        {
            const int64_t pos = lseek(fd, 0, SEEK_CUR);
            struct stat statbuf;
            if (fstat(fd, &statbuf) == -1)
            {
                reportAlways("FsDir::fileLength error.");
                return CAST_S32(rc);
            }
        }
#endif  // USE_FILE_FOPEN

        return CAST_S32(rc);
    }

    int rwops_close(SDL_RWops *const rw)
    {
        if (rw == nullptr)
            return 0;
        File *const handle = static_cast<File*>(
            rw->hidden.unknown.data1);
        delete handle;
        SDL_FreeRW(rw);
        return 0;
    }

#ifdef USE_SDL2
    RWOPSINT rwops_size(SDL_RWops *const rw)
    {
        File *const handle = static_cast<File *>(
            rw->hidden.unknown.data1);
        FILEHTYPE fd = handle->mFd;
#ifdef USE_FILE_FOPEN
        const long pos = ftell(fd);
        fseek(fd, 0, SEEK_END);
        const long sz = ftell(fd);
        fseek(fd, pos, SEEK_SET);
        return sz;
#else  // USE_FILE_FOPEN
        struct stat statbuf;
        if (fstat(fd, &statbuf) == -1)
        {
            reportAlways("FsDir::fileLength error.");
            return -1;
        }
        return static_cast<int64_t>(statbuf.st_size);
#endif  // USE_FILE_FOPEN
    }
#endif  // USE_SDL2

}  // namespace FsDir

}  // namespace VirtFs
