#define SQL_COMMAND "COPY Shadoks (name, value) FROM STDIN;"
#define NUM_PARAMS 0
#define PREPARED_STMT "copy-data"
#define MAX_INTEGER_LENGTH 30
#define MAX_NAME_LENGTH 18
#define DEFAULT_DATABASE "dbname=essais"
#define DEFAULT_NUM_ITERATIONS 1000
#define INCREMENT 1000
#define BUFSIZE ((INCREMENT * MAX_NAME_LENGTH * MAX_INTEGER_LENGTH) + (INCREMENT * 2))

#include <postgresql/libpq-fe.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

static void
fatal(char *msg, ...)
{
    va_list         args;
    va_start(args, msg);
    fprintf(stderr, "Fatal error: ");
    vfprintf(stderr, msg, args);
    va_end(args);
    exit(1);
}

/* Randomization routines stolen from pcaputils */
void
rng_seed(bool secure)
{
    char           *dev;
    int             fd;
    unsigned        seed;

    if (secure)
        dev = "/dev/random";
    else
        dev = "/dev/urandom";
    if ((fd = open(dev, O_RDONLY)) != -1) {
        if (read(fd, &seed, sizeof(seed)) != sizeof(seed))
            fatal("unable to read %u bytes from %s", (unsigned) sizeof(seed), dev);
    } else {
        fatal("unable to open %s for reading: %s", dev, strerror(errno));
    }
    srandom(seed);
    close(fd);
}

long int
rng_randint(int min, int max)
{
    return (long int) (min + ((double) (max - min + 1)) *
                       (rand() / (RAND_MAX + ((double) min))));
}

char           *
random_name()
{
    char           *result = malloc(MAX_NAME_LENGTH);
    unsigned int    i;
    const unsigned int max = rng_randint(2, MAX_NAME_LENGTH);
    if (result == NULL) {
        fatal("Cannot malloc a name");
    }
    for (i = 0; i < max; i++) {
        result[i] = rng_randint(97, 122);
    }
    result[max] = '\0';
    return result;
}

int
main(int argc, char **argv)
{

    PGconn         *conn = NULL;
    ConnStatusType  status;
    PGresult       *result;
    unsigned long int i, j, num, steps, rest, nbytes, copied, limit;
    char           *db;
    char           *name, *val;
    char           *buffer, *ptr;

    if (argc > 3) {
        fatal("Usage: %s [database] [num-tuples]\n", argv[0]);
    }
    if (argc >= 2) {
        db = argv[1];
    } else {
        db = DEFAULT_DATABASE;
    }
    if (argc >= 3) {
        num = atoi(argv[2]);
        if (num <= 0) {
            fatal("Invalid integer value %s\n", argv[2]);
        }
    } else {
        num = DEFAULT_NUM_ITERATIONS;
    }
    conn = PQconnectdb(db);
    if (conn == NULL) {
        fatal("Cannot connect to the database (unknown reason)");
    }
    status = PQstatus(conn);
    if (status != CONNECTION_OK) {
        fatal(PQerrorMessage(conn));
    }
    result = PQexec(conn, "BEGIN;");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot start transaction");
    }
    result = PQprepare(conn, PREPARED_STMT, SQL_COMMAND, 1, NULL);
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot prepare statement: %s", PQresultErrorMessage(result));
    }
    buffer = malloc(BUFSIZE);
    if (buffer == NULL) {
        fatal("Cannot malloc the big buffer");
    }
    result = PQexecPrepared(conn, PREPARED_STMT, NUM_PARAMS, NULL, NULL, NULL, 0);
    if (PQresultStatus(result) != PGRES_COPY_IN) {
        fatal("Cannot start data sending with '%s'\n", SQL_COMMAND);
    }
    steps = (unsigned long int) num / INCREMENT;
    rest = num - (steps * INCREMENT);
    val = malloc(MAX_INTEGER_LENGTH);
    for (i = 0; (i < steps) || (rest > 0); i++) {
        /* printf("Step %li\n", i); */
        nbytes = 0;
        ptr = buffer;
        if ((i == steps) && (rest > 0)) {
            limit = rest;
            rest = 0;
        } else {
            limit = INCREMENT;
        }
        for (j = 0; j < limit; j++) {
            name = random_name();
            strcpy(ptr, name);
            ptr += strlen(name);
            nbytes += strlen(name);
            *ptr = '\t';
            ptr++;
            nbytes++;
            sprintf(val, "%li", rng_randint(0, num));
            strcpy(ptr, val);
            ptr += strlen(val);
            nbytes += strlen(val);
            *ptr = '\r';
            ptr++;
            nbytes++;
            free(name);
            if ((nbytes >= BUFSIZE) || ((buffer - ptr) >= BUFSIZE)) {
                fatal("Buffer too small (maximum is %li)", BUFSIZE);
            }
        }
        /* buffer[nbytes] = '\0'; */
        /* printf("%s\n", buffer); */
        copied = PQputCopyData(conn, buffer, nbytes);
        if (copied != 1) {
            fatal("Cannot put data: %s", PQerrorMessage(conn));
        }
    }
    free(val);
    copied = PQputCopyEnd(conn, NULL);
    if (copied != 1) {
        fatal("Cannot end the data stream: %s", PQerrorMessage(conn));
    }
    result = PQexec(conn, "COMMIT;");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot commit transaction");
    }
    PQfinish(conn);
    free(buffer);
    return 0;
}

