/* A simple program to benchmark an isam file system
   -------------------------------------------------
Author: G.D. van Albada
University of Amsterdam
Faculty of Science
Informatics Institute
dick at science.uva.nl
Version: 0.0
Date: January 2002 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "isam.h"

typedef struct KLANT
{
    char    naam[64];
    char    voorvoegsel[16];
    char    initialen[16];
    char    titel[16];
    unsigned long klantSinds;       /* Dag na 1/1/1900 */
    unsigned long laatsteMailing;   /* Dag na 1/1/1900 */
    unsigned long laatsteBestelling;
    unsigned long nummerBestelling;
} klant;

static char initialen[500][16];
static int Ninit = 0;

typedef struct NAAM
{
    char naam[64];
    char voorvoegsel[16];
} naam;

static naam namen[500];
static int Nnamen = 0;
static char titels[64][16];
static int Ntitels = 0;
static char sleutels[9000][20];
static int Nsleutels = 0;
static int report = 0;

/*
 * Bereken dagnummer met 1/1/1900 == 1
 * Routine faalt op en na 1/3/2100
 */
    static long
berekenDag(int dag, int maand, int jaar)
{
    long dagen;

    dagen = dag;
    dagen += 365 * (jaar - 1900) + (jaar - 1901) / 4;

    switch (maand) {
        case 12:
            dagen += 30;
        case 11:
            dagen += 31;
        case 10:
            dagen += 30;
        case 9:
            dagen += 31;
        case 8:
            dagen += 31;
        case 7:
            dagen += 30;
        case 6:
            dagen += 31;
        case 5:
            dagen += 30;
        case 4:
            dagen += 31;
        case 3:
            dagen += ((0 == jaar % 4) && jaar > 1900) ? 29 : 28;
        case 2:
            dagen += 31;
        case 1:
            break;
        default:
            return -1;
    }

    return dagen;
}

/*
 * Print klant-informatie
 */
    static void
printKlant(FILE * log, char *kop, char *sleutel, klant * Klant)
{
    if (kop) {
        fprintf(log, "%s\n", kop);
    }
    fprintf(log, "%s \t||%s %s %s %s\t%lu\t%lu\t%lu\t%lu\n",
            sleutel, Klant->titel, Klant->initialen,
            Klant->voorvoegsel, Klant->naam,
            Klant->klantSinds, Klant->laatsteMailing,
            Klant->laatsteBestelling, Klant->nummerBestelling);
}

/*
 * Maak een random klant
 */
    static void
maakKlant(klant *nieuweKlant)
{
    int i, dag, maand, jaar;

    if (Nnamen) {
        i = lrand48() % Nnamen;
        strncpy(nieuweKlant->naam, namen[i].naam, 64);
        strncpy(nieuweKlant->voorvoegsel, namen[i].voorvoegsel, 16);
    }

    if (Ninit) {
        i = lrand48() % Ninit;
        strncpy(nieuweKlant->initialen, initialen[i], 16);
    }

    if (Ntitels) {
        i = lrand48() % Ntitels;
        strncpy(nieuweKlant->titel, titels[i], 16);
    }

    jaar = 1950 + lrand48() % 52;
    dag = lrand48() % 29;
    maand = 1 + lrand48() % 12;
    nieuweKlant->klantSinds = berekenDag(dag, maand, jaar);
    jaar = 2001;
    nieuweKlant->laatsteMailing = berekenDag(dag, maand, jaar);
    jaar = 1998 + lrand48() % 4;
    nieuweKlant->laatsteBestelling = berekenDag(dag, maand, jaar);
    nieuweKlant->nummerBestelling = 100 * nieuweKlant->laatsteBestelling
        + lrand48() % 95;
    return;
}

static int code = 1000;

/*
 * Maak een random sleutel, dichtbij de postcode in "code". Verhoog code.
 * Sleutel is een postcode plus huisnummer.
 */
    static int
maakSleutel(char *sleutel)
{
    int huisnummer;

    code += lrand48() % 8;

    /*
     * maak een huisnummer tot 53, met een voorkeur voor lage nummers.
     */
    huisnummer = lrand48() % 30 + lrand48() % 25;
    huisnummer -= lrand48() % 30 + lrand48() % 25;
    huisnummer = (huisnummer <= 0) ? (1 - huisnummer) : huisnummer;

    sprintf(sleutel, "%4d %c%c - %4d", code, (char) ('A' + lrand48 () % 26),
            (char) ('A' + lrand48 () % 26), huisnummer);
    return (code < 9999);
}

/*
 * Schrijf een nieuw record op een willekeurige plek in het bestand
 */
    static int
randomNieuwRecord(isamPtr ip)
{
    char  sleutel[20];
    klant nieuweKlant;
    int   rv;

    memset(&nieuweKlant, 0, sizeof(nieuweKlant));
    memset(sleutel, 0, sizeof(sleutel));

    do {
        code = 1000 + lrand48() % 8990;
        maakSleutel(sleutel);
    }
    while (0 == isam_readByKey(ip, sleutel, &nieuweKlant));

    maakKlant(&nieuweKlant);
    rv = isam_writeNew(ip, sleutel, &nieuweKlant);
    if (rv && report) {
        isam_perror("write new client record");
    }
    else {
        strncpy(sleutels[Nsleutels], sleutel, 20);
    }
    Nsleutels++;
    return Nsleutels;
}

/*
 * Lees sequentieel alle records in bepaald sleutelbereik, en pleeg een
 * bewerking
 */
    static int
leesBereik(isamPtr ip, char *minSleutel, char *maxSleutel, int datum)
{
    char  sleutel[20];
    char  vorigeSleutel[20] = "";
    int   i = 0;
    int   j = 0;
    int   rv;
    klant klantRecordOud;
    klant klantRecordNieuw;

    /*
     * Net als bij het plegen van een mailing
     */
    isam_setKey(ip, minSleutel);

    do {
        rv = isam_readNext(ip, sleutel, &klantRecordOud);

        if (rv) {
            isam_perror("reading next record");
            break;
        }

        if (strncmp(sleutel, vorigeSleutel, 20) <= 0) {
            printf("Sleutels staan verkeerd gesorteerd: %s komt voor %s\n",
                    vorigeSleutel, sleutel);
        }

        if (strncmp(sleutel, maxSleutel, 20) > 0)
            break;

        i++;

        if (report) {
            printKlant(stderr, NULL, sleutel, &klantRecordOud);
        }

        if ((int) klantRecordOud.laatsteMailing < datum - 200) {
            klantRecordNieuw = klantRecordOud;
            klantRecordNieuw.laatsteMailing = datum;

            /*
             * Nog te controleren: Lees ik na een update wel het direct
             * volgende record?
             */
            rv = isam_update(ip, sleutel, &klantRecordOud,
                    &klantRecordNieuw);

            if (rv) {
                isam_perror("updating record");
                break;
            }

            j++;
        }
    }
    while (strncmp(sleutel, maxSleutel, 20) <= 0);
    printf("Er zijn %d records gelezen; mailings zijn verzonden aan %d\n",
            i, j);
    return i;
}


    int
leesBestaandRecord(isamPtr ip, int sleutelNr)
{
    klant klantRecord;
    int   rv;

    /*
     * Als klantgegevens uitvragen
     */
    rv = isam_readByKey(ip, sleutels[sleutelNr], &klantRecord);

    if (rv && report) {
        isam_perror("reading a record by key");
    }

    return rv;
}

    int
poetsBestaandRecord(isamPtr ip, int sleutelNr)
{
    klant klantRecord;
    int   rv;

    /*
     * Als klantgegevens wissen
     */
    rv = isam_readByKey(ip, sleutels[sleutelNr], &klantRecord);

    if (rv) {
        if (report)
            isam_perror("reading a record by key");

        return rv;
    }

    rv = isam_delete(ip, sleutels[sleutelNr], &klantRecord);

    if (rv) {
        isam_perror("deleting a record by key");
    }

    return rv;
}

    int
main(int argc, char *argv[])
{
    isamPtr ip;
    FILE   *inp;
    char    sleutel[20] = { 0 };
    klant   nieuweKlant;
    klant   oudeKlant;
    char    str[512];
    int     i, j;

    struct ISAM_CACHE_STATS initStats, stats, bsStats;

    memset(&nieuweKlant, 0, sizeof(nieuweKlant));

    /*
     * Lees het bestand met namen (en voorvoegsels)
     */
    srand48(171717);

    if (argc < 4) {
        printf("Gebruik: %s namen initialen titels [optional-debug]\n",
                argv[0]);
        return -1;
    }

    if (argc == 5) {
        report = 1;
        printf("Produceer meer debug uitvoer\n");
    }

    inp = fopen(argv[1], "r");

    if (inp) {
        i = 0;
        printf("Lees namen van %s\n", argv[1]);

        while (fgets(str, 512, inp)) {
            strncpy(namen[i].naam, strtok(str, ";\t\n"), 64);
            strncpy(namen[i].voorvoegsel, strtok(NULL, ";\t\n"), 16);
            i++;
        }

        fclose(inp);
        Nnamen = i;
    }
    else {
        perror("Failed to open names file");
        exit(-1);
    }

    /*
     * Lees het bestand met initialen
     */
    inp = fopen(argv[2], "r");

    if (inp) {
        i = 0;
        printf("Lees initialen van %s\n", argv[2]);

        while (fgets(str, 512, inp)) {
            strncpy(initialen[i], strtok(str, "\n"), 16);
            i++;
        }

        fclose(inp);
        Ninit = i;
    }
    else {
        perror("Failed to open initials file");
        exit(-1);
    }

    /*
     * Lees het bestand met titels
     */

    inp = fopen(argv[3], "r");

    if (inp) {
        i = 0;
        printf("Lees titels van %s\n", argv[3]);

        while (fgets(str, 512, inp)) {
            strncpy(titels[i], strtok(str, "\n"), 16);
            i++;
        }

        fclose(inp);
        Ntitels = i;
    }
    else {
        perror("Failed to open titles file");
        exit(-1);
    }

    /*
     * Probeer een isam bestand aan te maken. Als dat mislukt, bestaat het
     * mogelijk al - probeer het dan te lezen
     */
    ip = isam_create("klant.isam", 20, sizeof(klant), 8, 360);

    if (!ip) {
        /*
         * Mislukt ... bestaat het al ?
         */

        isam_perror("Failed to create file");
        ip = isam_open("klant.isam", 1);

        if (!ip) {
            isam_perror("Failed to open file");
            exit(-1);
        }

        /*
         * Lees alle sleutels
         */
        for(i = 0; 0 == isam_readNext(ip, sleutels[i], &nieuweKlant); i++);
        Nsleutels = i;
        printf("Bestaand bestand met %d records geopend\n", Nsleutels);
    }
    else {
        /*
         * Vul het bestand, min of meer sequentieel
         */
        i = 0;
        while (maakSleutel(sleutel)) {
            maakKlant(&nieuweKlant);

            if (isam_writeNew(ip, sleutel, &nieuweKlant)) {
                isam_perror("Failed to create customer record");
            }
            else {
                if (report) {
                    printKlant(stdout, NULL, sleutel, &nieuweKlant);
                }

                strncpy(sleutels[i], sleutel, 20);
                i++;
            }
        }

        printf("Isam bestand bevat %d records\n", i);
        Nsleutels = i;

        /*
         * Dit hoeft niet, maar test open en close
         */
        isam_close(ip);
        ip = isam_open("klant.isam", 1);
    }

    isam_cacheStats(&initStats);

    printf("Stats after opening isam file...\n");
    printf("calls: %d\nreads: %d\nwrites: %d\nhwrites: %d\n\n", initStats.calls,
            initStats.reads, initStats.writes, initStats.hwrites);

    /*
     * Doe een aantal bewerkingen op het bestand -
     * lees sequentieel plus update
     * lees op sleutel
     * poets
     * schrijf nieuw
     */
    leesBereik(ip, "2300", "4500", berekenDag (25, 1, 2002));
    for (i = 0, j = 0; i < 500; i++) {
        if (leesBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
    }
    printf("%d records gelezen, %d mislukt\n", i, j);
    for (i = 0, j = 0; i < 200; i++) {
        if (poetsBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
        randomNieuwRecord(ip);
    }

    printf("%d nieuwe klanten, %d vertrokken\n", i, i - j);
    leesBereik(ip, "2300", "4500", berekenDag (25, 1, 2002));
    for (i = 0, j = 0; i < 500; i++) {
        if (leesBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
    }

    printf("%d records gelezen, %d mislukt\n", i, j);
    for (i = 0, j = 0; i < 200; i++) {
        if (poetsBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
        randomNieuwRecord(ip);
    }

    printf("%d nieuwe klanten, %d vertrokken\n", i, i - j);
    leesBereik(ip, "2300", "4500", berekenDag (25, 1, 2002));
    for (i = 0, j = 0; i < 500; i++) {
        if (leesBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
    }
    printf ("%d records gelezen, %d mislukt\n", i, j);
    for (i = 0, j = 0; i < 200; i++) {
        if (poetsBestaandRecord(ip, lrand48 () % Nsleutels))
            j++;
        randomNieuwRecord(ip);
    }

    printf ("%d nieuwe klanten, %d vertrokken\n", i, i - j);
    leesBereik(ip, "1000", "9999", berekenDag(25, 1, 2002));
    leesBereik(ip, "1000", "9999", berekenDag(25, 1, 2002));

    isam_cacheStats(&bsStats);

    printf("Stats after Dick Albada's benchmark...\n");
    printf("calls: %d\nreads: %d\nwrites: %d\nhwrites: %d\n\n",
            bsStats.calls - initStats.calls,
            bsStats.reads - initStats.reads,
            bsStats.writes - initStats.writes,
            bsStats.hwrites - initStats.hwrites);

    /*
     * Sequentieel lezen
     */
    isam_setKey(ip, sleutels[0]);
    i = 0;
    while (0 == isam_readNext(ip, sleutels[i], &nieuweKlant))
        i++;

    /*
     * Veel readBykey()
     */
    for(i = 0; i < 1000; i++) {
        maakSleutel(sleutel);
        isam_readByKey(ip, sleutel, &nieuweKlant);
    }

    /*
     * flink aantal update
     */
    for(i = 0; i < 400; i++) {
        j = lrand48() % Nsleutels;
        isam_readByKey(ip, sleutels[j], &oudeKlant);
        maakKlant(&nieuweKlant);
        isam_update(ip, sleutels[j], &oudeKlant, &nieuweKlant);
    }

    /*
     * Write 100 new records
     */
    for(i = 0; i < 100; i++)
        randomNieuwRecord(ip);

    /*
     * Delete keys (at the end)
     */
    for(i = 0; i < 100; i++) {
        j = Nsleutels - 1;
        isam_readByKey(ip, sleutels[j], &nieuweKlant);
        isam_delete(ip, sleutels[j], &nieuweKlant);
        Nsleutels--;
    }

    printf("Stats after benchmarking:\n1000 readByKey\n400 update\n");
    printf("100 writes\n100 delete\n\n");
    isam_cacheStats(&stats);
    printf("calls: %d\nreads: %d\nwrites: %d\nhwrites: %d\n",
            stats.calls - bsStats.calls - initStats.calls,
            stats.reads - bsStats.reads - initStats.reads,
            stats.writes - bsStats.writes - initStats.writes,
            stats.hwrites - bsStats.hwrites - initStats.hwrites);

    isam_close(ip);

    return 0;
}

