#include "shm.h"
#include "utils.h"
#include <string.h>
#include <stdio.h>

#ifdef __linux
#define _strdup strdup
#endif

typedef struct __shm_item__ {

    ShmPlace next;
    ShmPlace prev;
    int      size;
    int      alloc_size;
} ShmItem;


class ShmApiBase
{
protected:
    char    *_mem;

    int      *_used;
    ShmPlace *_slots;
    ShmPlace *_first;
    ShmPlace *_free_list;

    char *_mem_name;
    char *_sem_name;

    int    _in_critical;
    size_t  _size;
    bool    _owner;

public:
    virtual void enterCritical() = 0;
    virtual void leaveCritical() = 0;

public:
    int slot(int s) {
        return _slots[s];
    }

    void setSlot(int s, ShmPlace place) {
        _slots[s] = place;
    }

public:
    const char *name() {
        return _mem_name;
    }

public:
    template <class T> void ref(int place, T** p) {
        if (place == SHM_NULL) {
            *p = nullptr;
        } else {
            *p = reinterpret_cast<T *>(_mem + place);
        }
    }

    inline void *ref(int place)
    {
        if (place == SHM_NULL) {
            return nullptr;
        }

        return _mem + place;
    }

public:
    void info(int &usage, int &free_depth, int &free_size, int &item_depth, int &item_size, double &usage_factor) {
        enterCritical();

        usage = *_used;

        auto count = [this](ShmPlace *l, int &depth, int &size, double &factor) {
            depth = 0;
            size = 0;
            int alloc_size = 0;
            ShmPlace p = *l;
            while (p != SHM_NULL) {
                //p -= sizeof(ShmItem);
                ShmItem *i;
                ref(p, &i);
                size += i->size;
                alloc_size += i->alloc_size;
                depth += 1;
                p = i->next;
            }
            if (size > 0) {
                factor = (static_cast<double>(alloc_size) / static_cast<double>(size));
            } else {
                factor = 1.0;
            }
        };

        double x;
        count(_free_list, free_depth, free_size, x);
        count(_first, item_depth, item_size, usage_factor);

        leaveCritical();
    }

public:
    int alloc(size_t bytes) {
        enterCritical();

        ShmPlace p_i = *_free_list;
        ShmItem *i;
        ref(p_i, &i);

        while (i != nullptr && i->size < bytes) {
            p_i = i->next;
            ref(p_i, &i);
        }

        int place;
        if (i != nullptr) {
            if (i->prev != SHM_NULL) {
                ShmItem *prev_i;
                ref(i->prev, &prev_i);
                prev_i->next = i->next;
            }
            if (i->next != SHM_NULL) {
                ShmItem *next_i;
                ref(i->next, &next_i);
                next_i->prev = i->prev;
            }
            if (i->prev == SHM_NULL) {
                *_free_list = i->next;
            }
            i->next = *_first;
            if (*_first != SHM_NULL) {
                ShmItem *first_i;
                ref(*_first, &first_i);
                first_i->prev = p_i;
            }
            i->prev = SHM_NULL;
            i->alloc_size = static_cast<int>(bytes);

            *_first = p_i;
            place = p_i + sizeof(ShmItem);
        } else {
            ShmPlace p_i = *_used;
            i = reinterpret_cast<ShmItem *>(reinterpret_cast<char *>(_mem) + p_i);
            int u = static_cast<int>(*_used + sizeof(ShmItem) + bytes);

            if (u >= _size) {
                place = SHM_NULL;
            } else {
                *_used = u;
                i->prev = SHM_NULL;
                i->size = static_cast<int>(bytes);
                i->alloc_size = static_cast<int>(bytes);
                i->next = *_first;
                if (*_first != SHM_NULL) {
                    ShmItem *first_i;
                    ref(*_first, &first_i);
                    first_i->prev = p_i;
                }

                *_first = p_i;
                place = p_i + sizeof(ShmItem);
            }
        }

        leaveCritical();
        return place;
    }

    void free(ShmPlace place) {
        if (place == SHM_NULL) {
            return;
        }

        enterCritical();

        ShmPlace p_i = place - sizeof(ShmItem);
        ShmItem *i;
        ref(p_i, &i);
        if (i == nullptr) return;

        if (i->prev != SHM_NULL) {
            ShmItem *prev_i;
            ref(i->prev, &prev_i);
            prev_i->next = i->next;
        }
        if (i->next != SHM_NULL) {
            ShmItem *next_i;
            ref(i->next, &next_i);
            next_i->prev = i->prev;
        }
        if (i->prev == SHM_NULL) {
            *_first = i->next;
        }
        i->next = *_free_list;
        if (*_free_list != SHM_NULL) {
            ShmItem *fl;
            ref(*_free_list, &fl);
            fl->prev = p_i;
        }
        i->prev = SHM_NULL;
        *_free_list = p_i;

        leaveCritical();
    }

public:
    ShmSem *makeSem(const char *name, bool owner) {
        return new ShmSem(name, owner);
    }

public:
    void takeOwnership() {
        _owner = true;
    }

public:
    ShmApiBase(const char *name, size_t size, bool owner) {
        size_t buf_size = strlen(name) + 50;
        char *buf = reinterpret_cast<char *>(malloc(buf_size));
        snprintf(buf, buf_size, "sem_%s", name);
        _sem_name = _strdup(buf);
        _mem_name = _strdup(name);
        ::free(buf);

        _owner = owner;

        _size = size;

        _used = nullptr;
        _slots = nullptr;
        _first = nullptr;
        _free_list = nullptr;
        _mem = nullptr;

        _in_critical = 0;
    }

    virtual ~ShmApiBase()
    {
        ::free(_sem_name);
        ::free(_mem_name);
    }
};

#ifdef __linux

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#include <time.h>
#include <assert.h>


class ShmApi : public ShmApiBase
{
private:
    int     _shm_fd;
    sem_t  *_sem;

private:
    void cleanup() {
        // We don't need to do anything here.

    }

public:
    void enterCritical() {
        if (_in_critical > 0) {
            _in_critical++;
        } else {
            sem_wait(_sem);
            _in_critical = 1;
        }
    }

    void leaveCritical() {
        if (_in_critical > 1) {
            _in_critical--;
        } else {
            _in_critical = 0;
            sem_post(_sem);
        }
    }

public:
    bool isValid() {
        return true;
    }

public:
    ShmApi(const char *name, size_t size, bool owner) : ShmApiBase(name, size, owner)
    {
        size_t slots_size = sizeof(ShmPlace) * SHM_MAX_SLOTS;
        size += slots_size;

        if (_owner) {
            _sem = sem_open(_sem_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR, 0);
            _shm_fd = shm_open(name, O_CREAT | O_RDWR, 0600);
            ftruncate(_shm_fd, size);
        } else {
            _sem = sem_open(_sem_name, O_RDWR, S_IRUSR | S_IWUSR, 0);
            _shm_fd = shm_open(name, O_RDWR, 0600);
        }
        _mem = reinterpret_cast<char *>(mmap(nullptr, _size, PROT_READ|PROT_WRITE, MAP_SHARED, _shm_fd, 0));

        _slots = reinterpret_cast <ShmPlace *>(_mem + sizeof(int) + sizeof(ShmPlace) + sizeof(ShmPlace));
        _used = reinterpret_cast<int *>(_mem);

        _first = reinterpret_cast<ShmPlace *>(_mem + sizeof(int));
        _free_list = reinterpret_cast<ShmPlace *>(_mem + sizeof(int) + sizeof(ShmPlace));

        if (_owner) {
            *_first = SHM_NULL;
            *_free_list = SHM_NULL;
            *_used = sizeof(int) + sizeof(ShmItem *) + sizeof(ShmItem *) + slots_size;
            sem_post(_sem);
        }

        _in_critical = 0;
    }

    ~ShmApi() {
        cleanup();
        munmap(_mem, _size);
        close(_shm_fd);
        sem_close(_sem);
        if (_owner) {
            shm_unlink(_mem_name);
            sem_unlink(_sem_name);
        }
    }
};

#define TIME_NS_IN_MSEC  1000000ULL

class ShmSemApi {
    private:
        sem_t *_sem;
        char  *_name;
        bool   _owner;

    private:
        void makeSemTimeoutTime(struct timespec *ts, int ms) {
            clock_gettime(CLOCK_REALTIME, ts);
            ts->tv_sec += ms / 1000;
            ts->tv_nsec += (ms % 1000) * TIME_NS_IN_MSEC;
            if (ts->tv_nsec >= 1000000000L) {
                ts->tv_sec++;
                ts->tv_nsec = ts->tv_nsec - 1000000000L;
            }
        }

    public:
        void post() {
            sem_post(_sem);
        }

        bool wait(int ms) {
            struct timespec ts;
            makeSemTimeoutTime(&ts, ms);
            int r = sem_timedwait(_sem, &ts);
            if (r != 0) {
                if (errno != ETIMEDOUT) {
                    ERROR2("sem_wait error: %d, %s\n", errno, strerror(errno));
                }
                return false;
            }
            return true;
        }

        bool trywait() {
            int r = sem_trywait(reinterpret_cast<sem_t *>(_sem));
            if (r != 0 && r != EAGAIN) {
                ERROR2("sem_wait error: %d, %s\n", errno, strerror(errno));
            }

            return (r == 0);
        }

    public:
        void takeOwnership() {
            _owner = true;
        }

    public:
        ShmSemApi(const char *n, bool owner) {
            _name = strdup(n);

            sem_t *s;
            if (owner) {
                s = sem_open(n, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR, 0);
            } else {
                s = sem_open(n, O_RDWR, S_IRUSR | S_IWUSR, 0);
            }
            _sem = s;
            _owner = owner;
        }

        virtual ~ShmSemApi() {
            sem_close(_sem);
            if (_owner) {
                sem_unlink(_name);
            }
            free(_name);
        }
};
#endif

#ifdef _WIN32
#include <windows.h>

class ShmApi : public ShmApiBase
{
private:
    HANDLE      _shm_handle;
    HANDLE      _sem_handle;
    bool        _valid;

public:
    ShmApi(const char *name, size_t size, bool owner) : ShmApiBase(name, size, owner)
    {
        size_t slots_size = sizeof(ShmPlace) * SHM_MAX_SLOTS;
        size += slots_size;

        _valid = true;
        if (_owner) {
            _shm_handle = CreateFileMappingA(INVALID_HANDLE_VALUE,
                                            NULL,
                                            PAGE_READWRITE,
                                            0,
                                            static_cast<DWORD>(size),                // size must be smaller < 2GB
                                            name);

            if (_shm_handle == NULL) {
                ERROR2("Cannot create shared memory with name %s and size %lld\n", name, size);
                _valid = false;
            }
        } else {
            _shm_handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS,
                                          FALSE,
                                          name);
            if (_shm_handle == NULL) {
                ERROR1("Cannot open shared memory with name %s\n", name);
                _valid = false;
            }
        }

        if (_valid) {
            _mem = static_cast<LPSTR>(MapViewOfFile(_shm_handle,
                                                    FILE_MAP_ALL_ACCESS,
                                                    0,
                                                    0,
                                                    size));
            if (_mem == NULL) {
                ERROR1("Cannot map shared memory, errorcode = %ld\n", GetLastError());
                _valid = false;
                CloseHandle(_shm_handle);
            }
        }

        if (_valid) {
            if (_owner) {
                _sem_handle = CreateSemaphoreA(NULL,
                                              1,
                                              10000,
                                              _sem_name
                                              );
            } else {
                _sem_handle = OpenSemaphoreA(SYNCHRONIZE|SEMAPHORE_MODIFY_STATE, FALSE, _sem_name);
            }
            if (_sem_handle == NULL) {
                ERROR2("Cannot create or open semaphore with name '%s' (%ld)\n", _sem_name, GetLastError());
                UnmapViewOfFile(_mem);
                CloseHandle(_shm_handle);
                _valid = false;
            }
        }

        if (_valid) {
            _slots = reinterpret_cast <ShmPlace *>(_mem + sizeof(int) + sizeof(ShmPlace) + sizeof(ShmPlace));
            _used = reinterpret_cast<int *>(_mem);

            _first = reinterpret_cast<ShmPlace *>(_mem + sizeof(int));
            _free_list = reinterpret_cast<ShmPlace *>(_mem + sizeof(int) + sizeof(ShmPlace));

            if (_owner) {
                *_first = SHM_NULL;
                *_free_list = SHM_NULL;
                *_used = static_cast<int>(sizeof(int) + sizeof(ShmItem *) + sizeof(ShmItem *) + slots_size);
            }
        }
    }

    ~ShmApi()
    {
        if (_valid) {
            UnmapViewOfFile(_mem);
            CloseHandle(_shm_handle);
            CloseHandle(_sem_handle);
        }
    }

    bool isValid() {
        return _valid;
    }

    void enterCritical()
    {
        if (_in_critical > 0) {
            _in_critical++;
        } else {
            WaitForSingleObject(_sem_handle, INFINITE);
            _in_critical = 1;
        }
    }

    void leaveCritical()
    {
        if (_in_critical > 1) {
            _in_critical--;
        } else {
            _in_critical = 0;
            ReleaseSemaphore(_sem_handle, 1, NULL);
        }
    }

    ShmSem *sem(const char *name, bool owner)
    {
        return nullptr;
    }
};


class ShmSemApi {
private:
    HANDLE _sem;
    char  *_name;
    bool   _owner;

public:
    void post() {
        ReleaseSemaphore(_sem, 1, NULL);
    }

    bool wait(int ms) {
        DWORD r = WaitForSingleObject(_sem, ms);
        if (r != WAIT_OBJECT_0) {
            if (r != WAIT_TIMEOUT) {
                ERROR1("sem_wait error: %ld\n", r);
            }
            return false;
        }
        return true;
    }

    bool trywait() {
        DWORD r = WaitForSingleObject(_sem , 0);
        if (r != WAIT_OBJECT_0 && r != WAIT_TIMEOUT) {
            ERROR1("sem_wait error: %ld\n", r);
        }
        return r == WAIT_OBJECT_0;
    }

    void takeOwnership() {
        _owner = true;
    }

public:
    ShmSemApi(const char *n, bool owner) {
        _name = _strdup(n);

        if (owner) {
            _sem = CreateSemaphoreA(NULL,
                                   0,
                                   10000,
                                   _name);
        } else {
            _sem = OpenSemaphoreA(SYNCHRONIZE|SEMAPHORE_MODIFY_STATE, FALSE, _name);
        }

        if (_sem == NULL) {
            ERROR1("Cannot create or open semaphore with name '%s'\n", _name);
        }
        _owner = owner;
    }

    virtual ~ShmSemApi() {
        if (_sem != NULL) {
            CloseHandle(_sem);
        }
    }
};

#endif



ShmPlace Shm::alloc(size_t mem)
{
    return _shm_api->alloc(mem);
}

void Shm::free(ShmPlace place)
{
    _shm_api->free(place);
}

void Shm::unlock()
{
    _shm_api->leaveCritical();
}

ShmPlace Shm::slot(ShmSlot s)
{
    return _shm_api->slot(s);
}

void Shm::setSlot(ShmSlot s, ShmPlace pl)
{
    _shm_api->setSlot(s, pl);
}

ShmSem *Shm::sem(const char *name, bool owner)
{
    return _shm_api->makeSem(name, owner);
}

bool Shm::isValid()
{
    return _shm_api->isValid();
}

const char *Shm::name()
{
    return _shm_api->name();
}

void *Shm::ref(int place)
{
    return _shm_api->ref(place);
}

void Shm::takeOwnership()
{
    _shm_api->takeOwnership();
}

void Shm::info(int &usage, int &free_depth, int &free_size, int &item_depth, int &item_size, double &usage_factor)
{
    _shm_api->info(usage, free_depth, free_size, item_depth, item_size, usage_factor);
}

void Shm::lock()
{
    _shm_api->enterCritical();
}

Shm::Shm(const char *name, size_t bytes, bool owner)
{
    _shm_api = new ShmApi(name, bytes, owner);
}

Shm::~Shm()
{
    delete _shm_api;
}


void ShmSem::post() {
    _api->post();
}

bool ShmSem::wait(int ms) {
    return _api->wait(ms);
}

bool ShmSem::trywait() {
    return _api->trywait();
}

void ShmSem::takeOwnership()
{
    _api->takeOwnership();
}

ShmSem::ShmSem(const char *n, bool owner) {
    _api = new ShmSemApi(n, owner);
}

ShmSem::~ShmSem() {
    delete _api;
}
