#include "status.hpp"
#ifdef NO_SHM


bool status_init(size_t) { return true; }
void status_deinit() {}
bool status_attach() { return true; }
void status_detach() {}
void status_add(unsigned, bool) {}
bool status_get(unsigned) { return true; }
unsigned status_rating(unsigned) { return 100; }


#else
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <assert.h>


typedef struct {
    unsigned tries;
    unsigned fails;
} status_t;

static const char* shm_fn = "/proxypool";
static volatile status_t* status = NULL;
static size_t nstatus = 0;


static void* status_attach(int fd, size_t size) {
    void* p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        LOG_ERRNO("mmap(%s)", shm_fn);
        return NULL;
    }
    close(fd); // After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping
    return p;
}


bool status_init(size_t maxid) {
    ++maxid;

    int fd = shm_open(shm_fn, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
    if (fd == -1) {
        LOG_ERRNO("shm_open(%s)", shm_fn);
        return false;
    }

    if (ftruncate(fd, maxid * sizeof(status_t)) == -1) {
        LOG_ERRNO("ftruncate(%s)", shm_fn);
        shm_unlink(shm_fn);
        close(fd);
        return false;
    }

    status = (volatile status_t*)status_attach(fd, maxid * sizeof(status_t));
    if (!status) {
        shm_unlink(shm_fn);
        close(fd);
        return false;
    }
    nstatus = maxid;

    memset((void*)status, 0, nstatus * sizeof(status_t));
    return true;
}


void status_deinit() {
    if (!nstatus) return;

    if (shm_unlink(shm_fn) == -1) {
        LOG_ERRNO("shm_unlink(%s)", shm_fn);
    }
    status_detach();
}


bool status_attach() {
    if (!nstatus) {
        return false;
    }

    int fd = shm_open(shm_fn, O_RDWR, 0);
    if (fd == -1) {
        LOG_ERRNO("shm_open(%s)", shm_fn);
        return false;
    }

    status = (volatile status_t*)status_attach(fd, nstatus * sizeof(status_t));
    if (!status) {
        close(fd);
        return false;
    }

    return true;
}


void status_detach() {
    if (status && munmap((void*)status, nstatus * sizeof(status_t)) == -1) {
        LOG_ERRNO("munmap(%s)", shm_fn);
    }

    status = NULL;
    nstatus = 0;
}


void status_add(unsigned id, bool ok) {
    if (!nstatus) return;
    assert(id < nstatus);
    ++status[id].tries;
    if (!ok) ++status[id].fails;
}


bool status_get(unsigned id) {
    if (!nstatus) return true;
    assert(id < nstatus);

    for (unsigned i=0; i<nstatus; ++i) {
        //LOG("%u: %u/%u", i, status[i].fails, status[i].tries);
    }

    if (status[id].fails <= status[id].tries/2) {
        return true;
    } else if (rand() % nstatus == 0) { // only one or retry from time to time
        return true;
    } else {
        return false;
    }
}


int status_rating(unsigned id) {
    if (!nstatus) return -1;
    assert(id < nstatus);

    if (!status[id].tries) {
        return -1;
    } else {
        return ((status[id].tries - status[id].fails) * 100) / status[id].tries;
    }
}


#endif /* !NO_SHM */