#define SQL_COMMAND "INSERT INTO Shadoks (name, value) VALUES ($1, $2);"
#define NUM_PARAMS 2
#define PREPARED_STMT "insert-data"
#define MAX_INTEGER_LENGTH 30
#define MAX_NAME_LENGTH 18
#define DEFAULT_DATABASE "dbname=essais"
#define DEFAULT_NUM_ITERATIONS 1000

#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, num;
    const char     *params[NUM_PARAMS];
    char           *db;
    char           *name;

    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));
    }
    params[1] = malloc(MAX_INTEGER_LENGTH);
    if (params[1] == NULL) {
        fatal("Cannot malloc");
    }
    for (i = 0; i < num; i++) {
        name = random_name();
        params[0] = name;
        sprintf((char *) params[1], "%li", rng_randint(0, num));
        result =
            PQexecPrepared(conn, PREPARED_STMT, NUM_PARAMS, params, NULL, NULL, 0);
        if (PQresultStatus(result) == PGRES_COMMAND_OK) {
            /* OK for an INSERT */
        } else if (PQresultStatus(result) == PGRES_TUPLES_OK) {
            fatal("Data pending for '%s'\n", SQL_COMMAND);
        } else {
            fatal("Result for '%s' with \"%s\" is %s", SQL_COMMAND, params[0],
                  PQresultErrorMessage(result));
        }
        free(name);
    }
    result = PQexec(conn, "COMMIT;");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot commit transaction");
    }
    PQfinish(conn);
    return 0;
}

