#include "fs_private.h"

int32_t FS_Seek(int32_t handle, long offset, int32_t origin)
{
    unsigned char buffer[0x10000];
    long chunkSize;
    int32_t bytesRead;
    long position;
    FILE *file;
    int32_t whence;

    chunkSize = (long)sizeof(buffer);
    FS_CheckFileSystemStarted();

    if (fs_handleFiles[handle].seekCallbackGuard != 0) {
        fs_handleFiles[handle].seekCallbackGuard = 0;
        Sys_StreamSeek(handle, offset, origin);
        fs_handleFiles[handle].seekCallbackGuard = 1;
    }

    if (fs_handleFiles[handle].zipArchive != NULL) {
        if (offset == 0 && origin == 2) {
            Unzip_GoToFilePosition(
                (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject,
                fs_handleFiles[handle].zipRewindOffset);
            return Unzip_OpenCurrentFile(
                (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject);
        }

        if (offset == 0 && origin == 0) {
            return 0;
        }

        position = Unzip_TellCurrentFile(
            (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject);
        if (origin == 0) {
            if (offset < 0) {
                Unzip_GoToFilePosition(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject,
                    fs_handleFiles[handle].zipRewindOffset);
                Unzip_OpenCurrentFile(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject);
                offset += position;
            }
        } else if (origin == 1) {
            if (FS_filelength(handle) + offset < position) {
                Unzip_GoToFilePosition(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject,
                    fs_handleFiles[handle].zipRewindOffset);
                Unzip_OpenCurrentFile(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject);
                offset += FS_filelength(handle);
            } else {
                offset = FS_filelength(handle) + offset - position;
            }
        } else if (origin == 2) {
            if (offset < position) {
                Unzip_GoToFilePosition(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject,
                    fs_handleFiles[handle].zipRewindOffset);
                Unzip_OpenCurrentFile(
                    (coduo_fs_zip_io_file_t *)fs_handleFiles[handle].ioObject);
            } else {
                offset -= position;
            }
        } else {
            return -1;
        }

        /* ORIGINAL_BINARY_BUG: a before-start ZIP target can remain negative
         * and is narrowed to the unsigned-dword FS_Read sink below; see
         * original-bugs/coduomp-vm-filesystem-negative-byte-count.md. */
        while (offset != 0) {
            if (offset < chunkSize) {
                bytesRead = FS_Read(buffer, (int32_t)offset, handle);
                offset = 0;
            } else {
                bytesRead = FS_Read(buffer, (int32_t)chunkSize, handle);
                offset -= chunkSize;
            }

            if (bytesRead == 0) {
                return -1;
            }
        }

        return 0;
    }

    file = FS_LoadIOFile(FS_FileForHandle(handle));
    switch (origin) {
    case 0:
        whence = SEEK_CUR;
        break;
    case 1:
        whence = SEEK_END;
        break;
    case 2:
        whence = SEEK_SET;
        break;
    default:
        return 0;
    }

    return fseek(file, offset, whence);
}
