#include "shmat.h"

#include <util/generic/guid.h>

#if defined(_win_)
    #include <stdio.h>
    #include "winint.h"
#elif defined(_bionic_)
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/syscall.h>
#elif defined(_unix_)
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
#endif

#if defined(_cygwin_)
    #define WINAPI __stdcall
    #define FILE_MAP_ALL_ACCESS ((long)983071)
    #define PAGE_READWRITE 4
    #define FALSE 0

extern "C" {
    using HANDLE = OS_HANDLE;
    using BOOL = int;
    using DWORD = ui32;
    using LPCTSTR = const char*;
    using LPVOID = void*;
    using LPCVOID = void const*;
    using SIZE_T = size_t;

    BOOL WINAPI CloseHandle(HANDLE hObject);
    HANDLE WINAPI OpenFileMappingA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
    LPVOID WINAPI MapViewOfFile(HANDLE hFileMappingObject, DWORD DesiredAccess, DWORD FileOffsetHigh, DWORD FileOffsetLow, SIZE_T NumberOfBytesToMap);
    HANDLE WINAPI CreateFileMappingA(HANDLE hFile, LPVOID lpAttributes, DWORD flProtect, DWORD MaximumSizeHigh, DWORD MaximumSizeLow, LPCTSTR lpName);
    BOOL WINAPI UnmapViewOfFile(LPCVOID lpBaseAddress);
    DWORD WINAPI GetLastError(void);
}
#endif

#if defined(_bionic_)
namespace {
    #if !defined(__i386__)
    static int shmget(key_t key, size_t size, int flag) {
        if (size > PTRDIFF_MAX) {
            size = SIZE_MAX;
        }

        return syscall(__NR_shmget, key, size, flag);
    }

    static void* shmat(int id, const void* addr, int flag) {
        return (void*)syscall(__NR_shmat, id, addr, flag);
    }

    static int shmctl(int id, int cmd, void* buf) {
        return syscall(__NR_shmctl, id, cmd | IPC_64, buf);
    }

    static int shmdt(const void* addr) {
        return syscall(__NR_shmdt, addr);
    }

    #else
        #define IPCOP_shmat 21
        #define IPCOP_shmdt 22
        #define IPCOP_shmget 23
        #define IPCOP_shmctl 24

    static int shmget(key_t key, size_t size, int flag) {
        return syscall(__NR_ipc, IPCOP_shmget, key, size, flag, 0);
    }

    static void* shmat(int id, const void* addr, int flag) {
        void* retval;
        long res = syscall(__NR_ipc, IPCOP_shmat, id, flag, (long)&retval, addr);
        return (res >= 0) ? retval : (void*)-1;
    }

    static int shmctl(int id, int cmd, void* buf) {
        return syscall(__NR_ipc, IPCOP_shmctl, id, cmd | IPC_64, 0, buf);
    }

    static int shmdt(const void* addr) {
        return syscall(__NR_ipc, IPCOP_shmdt, 0, 0, 0, addr);
    }
    #endif
}
#endif

TSharedMemory::TSharedMemory()
    : Handle(INVALID_FHANDLE)
    , Data(nullptr)
    , Size(0)
{
}

#if defined(_win_)
static void FormatName(char* buf, const TGUID& id) {
    sprintf(buf, "Global\\shmat-%s", GetGuidAsString(id).c_str());
}

bool TSharedMemory::Open(const TGUID& id, int size) {
    //Y_ASSERT(Data == 0);
    Id = id;
    Size = size;

    char name[100];
    FormatName(name, Id);
    Handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name);

    if (Handle == 0) {
        return false;
    }

    Data = MapViewOfFile(Handle, FILE_MAP_ALL_ACCESS, 0, 0, size);

    if (Data == 0) {
        //Y_ASSERT(0);
        CloseHandle(Handle);
        Handle = INVALID_OS_HANDLE;

        return false;
    }

    return true;
}

bool TSharedMemory::Create(int size) {
    //Y_ASSERT(Data == 0);
    Size = size;

    CreateGuid(&Id);

    char name[100];
    FormatName(name, Id);
    Handle = CreateFileMappingA(INVALID_OS_HANDLE, nullptr, PAGE_READWRITE, 0, size, name);

    if (Handle == 0) {
        //Y_ASSERT(0);
        return false;
    }

    Data = MapViewOfFile(Handle, FILE_MAP_ALL_ACCESS, 0, 0, size);

    if (Data == 0) {
        //Y_ASSERT(0);
        CloseHandle(Handle);
        Handle = INVALID_OS_HANDLE;

        return false;
    }

    return true;
}

TSharedMemory::~TSharedMemory() {
    if (Data) {
        UnmapViewOfFile(Handle);
    }

    CloseHandle(Handle);
}
#else
static key_t GetKey(const TGUID& id) {
    i64 id64 = (ui64)(((ui64)id.dw[0] + (ui64)id.dw[2]) << 32) + (ui64)id.dw[1] + (ui64)id.dw[3];

    return id64;
}

bool TSharedMemory::Open(const TGUID& id, int size) {
    Y_VERIFY(id, "invalid shared memory guid: %s", GetGuidAsString(id).data());

    //Y_ASSERT(Data == 0);
    Size = size;

    key_t k = GetKey(id);
    int shmId = shmget(k, Size, 0777); // do not fill Handle, since IPC_RMID should be called by owner

    if (shmId < 0) {
        return false;
    }

    Data = shmat(shmId, nullptr, 0);

    if (Data == nullptr) {
        //Y_ASSERT(0);
        return false;
    }

    return true;
}

bool TSharedMemory::Create(int size) {
    //Y_ASSERT(Data == 0);
    Size = size;

    CreateGuid(&Id);

    key_t k = GetKey(Id);
    Handle = shmget(k, Size, IPC_CREAT | IPC_EXCL | 0777);

    if (Handle < 0) {
        //Y_ASSERT(0);
        return false;
    }

    Data = shmat(Handle, nullptr, 0);

    if (Data == (void*)-1) {
        //Y_ASSERT(0);
        shmctl(Handle, IPC_RMID, nullptr);
        Handle = -1;

        return false;
    }

    return true;
}

TSharedMemory::~TSharedMemory() {
    if (Data) {
        shmdt(Data);
    }

    if (Handle >= 0) {
        shmctl(Handle, IPC_RMID, nullptr);
    }
}
#endif