#include "Main.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Defines.h"
#include "Error.h"
#include "HpFile.h"
#include "Utilities.h"

#if !defined(atof)
double atof PROTO((const char *));
#endif

/* own stuff already included */

#define N_MARKS 50              /* start size of the mark table */
#define N_SAMPLES 500           /* start size of the sample table */

char *theident;
static char *thestring;
int theinteger;
floatish thefloatish;
int ch;                                         /* last character read  */
token thetok;                                   /* last token           */
int linenum;                                    /* current line number  */
int endfile;                                    /* true at end of file  */

static boolish gotjob = 0;                      /* "JOB" read           */
static boolish gotdate = 0;                     /* "DATE" read          */
static boolish gotvalueunit = 0;                /* "VALUE_UNIT" read    */
static boolish gotsampleunit = 0;               /* "SAMPLE_UNIT" read   */
static boolish insample = 0;                    /* true when in sample  */

static floatish lastsample;                     /* the last sample time */

static void GetHpLine PROTO((FILE *));          /* forward */
static void GetHpTok  PROTO((FILE *, int));     /* forward */

static struct entry *GetEntry PROTO((char *));  /* forward */

static void GetString PROTO((FILE *));          /* forward */

static void MakeIdentTable PROTO((void));       /* forward */

char *jobstring;
char *datestring;

char *sampleunitstring;
char *valueunitstring;

floatish *samplemap;            /* sample intervals     */
floatish *markmap;              /* sample marks         */

/*
 *      An extremely simple parser. The input is organised into lines of
 *      the form
 *
 *      JOB s              -- job identifier string
 *      DATE s             -- date string
 *      SAMPLE_UNIT s      -- sample unit eg "seconds"
 *      VALUE_UNIT s       -- value unit eg "bytes"
 *      MARK i             -- sample mark
 *      BEGIN_SAMPLE i     -- start of ith sample
 *      identifier i       -- there are i identifiers in this sample
 *      END_SAMPLE i       -- end of ith sample
 *
 */

void
GetHpFile(FILE *infp)
{
    nsamples = 0;
    nmarks   = 0;
    nidents  = 0;

    ch = ' ';
    endfile = 0;
    linenum = 1;
    lastsample = 0.0;

    GetHpTok(infp, 1);

    while (endfile == 0) {
        GetHpLine(infp);
    }

    if (!gotjob) {
        Error("%s: JOB missing", hpfile);
    }

    if (!gotdate) {
        Error("%s: DATE missing", hpfile);
    }

    if (!gotvalueunit) {
        Error("%s: VALUE_UNIT missing", hpfile);
    }

    if (!gotsampleunit) {
        Error("%s: SAMPLE_UNIT missing", hpfile);
    }

    if (nsamples == 0) {
        Error("%s: contains no samples", hpfile);
    }


    MakeIdentTable();

    fclose(hpfp);
}


/*
 *      Read the next line from the input, check the syntax, and perform
 *      the appropriate action.
 */

static void
GetHpLine(FILE *infp)
{
    static intish nmarkmax = 0, nsamplemax = 0;

    switch (thetok) {
    case JOB_TOK:
        GetHpTok(infp, 0);
        if (thetok != STRING_TOK) {
            Error("%s, line %d: string must follow JOB", hpfile, linenum);
        }
        jobstring = thestring;
        gotjob = 1;
        GetHpTok(infp, 1);
        break;

    case DATE_TOK:
        GetHpTok(infp, 0);
        if (thetok != STRING_TOK) {
            Error("%s, line %d: string must follow DATE", hpfile, linenum);
        }
        datestring = thestring;
        gotdate = 1;
        GetHpTok(infp, 1);
        break;

    case SAMPLE_UNIT_TOK:
        GetHpTok(infp, 0);
        if (thetok != STRING_TOK) {
            Error("%s, line %d: string must follow SAMPLE_UNIT", hpfile,
                  linenum);
        }
        sampleunitstring = thestring;
        gotsampleunit = 1;
        GetHpTok(infp, 1);
        break;

    case VALUE_UNIT_TOK:
        GetHpTok(infp, 0);
        if (thetok != STRING_TOK) {
            Error("%s, line %d: string must follow VALUE_UNIT", hpfile,
                  linenum);
        }
        valueunitstring = thestring;
        gotvalueunit = 1;
        GetHpTok(infp, 1);
        break;

    case MARK_TOK:
        GetHpTok(infp, 0);
        if (thetok != FLOAT_TOK) {
            Error("%s, line %d, floating point number must follow MARK",
                  hpfile, linenum);
        }
        if (insample) {
            Error("%s, line %d, MARK occurs within sample", hpfile, linenum);
        }
        if (nmarks >= nmarkmax) {
            if (!markmap) {
                nmarkmax = N_MARKS;
                markmap = (floatish*) xmalloc(nmarkmax * sizeof(floatish));
            } else {
                nmarkmax *= 2;
                markmap = (floatish*) xrealloc(markmap, nmarkmax * sizeof(floatish));
            }
        }
        markmap[ nmarks++ ] = thefloatish;
        GetHpTok(infp, 1);
        break;

    case BEGIN_SAMPLE_TOK:
        insample = 1;
        GetHpTok(infp, 0);
        if (thetok != FLOAT_TOK) {
            Error("%s, line %d, floating point number must follow BEGIN_SAMPLE",                  hpfile, linenum);
        }
        if (thefloatish < lastsample) {
            Error("%s, line %d, samples out of sequence", hpfile, linenum);
        } else {
            lastsample = thefloatish;
        }
        if (nsamples >= nsamplemax) {
            if (!samplemap) {
                nsamplemax = N_SAMPLES;
                samplemap = (floatish*) xmalloc(nsamplemax * sizeof(floatish));
            } else {
                nsamplemax *= 2;
                samplemap = (floatish*) xrealloc(samplemap,
                                              nsamplemax * sizeof(floatish));
            }
        }
        samplemap[ nsamples ] = thefloatish;
        GetHpTok(infp, 1);
        break;

    case END_SAMPLE_TOK:
        insample = 0;
        GetHpTok(infp, 0);
        if (thetok != FLOAT_TOK) {
            Error("%s, line %d: floating point number must follow END_SAMPLE",
                  hpfile, linenum);
        }
        nsamples++;
        GetHpTok(infp, 1);
        break;

    case IDENTIFIER_TOK:
        GetHpTok(infp, 0);
        if (thetok != INTEGER_TOK) {
            Error("%s, line %d: integer must follow identifier", hpfile,
                  linenum);
        }
        StoreSample(GetEntry(theident), nsamples, thefloatish);
        GetHpTok(infp, 1);
        break;

    case EOF_TOK:
        endfile = 1;
        break;

    default:
        Error("%s, line %d: %s unexpected", hpfile, linenum,
              TokenToString(thetok));
        break;
    }
}


char *
TokenToString(token t)
{
   switch (t) {
        case EOF_TOK:           return "EOF";
        case INTEGER_TOK:       return "integer";
        case FLOAT_TOK:         return "floating point number";
        case IDENTIFIER_TOK:    return "identifier";
        case STRING_TOK:        return "string";
        case BEGIN_SAMPLE_TOK:  return "BEGIN_SAMPLE";
        case END_SAMPLE_TOK:    return "END_SAMPLE";
        case JOB_TOK:           return "JOB";
        case DATE_TOK:          return "DATE";
        case SAMPLE_UNIT_TOK:   return "SAMPLE_UNIT";
        case VALUE_UNIT_TOK:    return "VALUE_UNIT";
        case MARK_TOK:          return "MARK";

        case X_RANGE_TOK:       return "X_RANGE";
        case Y_RANGE_TOK:       return "Y_RANGE";
        case ORDER_TOK:         return "ORDER";
        case SHADE_TOK:         return "SHADE";
        default:                return "(strange token)";
    }
}

/*
 *      Read the next token from the input and assign its value
 *      to the global variable "thetok". In the case of numbers,
 *      the corresponding value is also assigned to "theinteger"
 *      or "thefloatish" as appropriate; in the case of identifiers
 *      it is assigned to "theident".
 *
 *      startline argument should be true for the first token on a line
 */

static void
GetHpTok(FILE *infp, int startline)
{

    while (isspace(ch)) {               /* skip whitespace */
        if (ch == '\n') linenum++;
        ch = getc(infp);
    }

    if (ch == EOF) {
        thetok = EOF_TOK;
        return;
    }

    if (isdigit(ch) && !startline) {
        /* there should not be numbers at start of line */
        thetok = GetNumber(infp);
        return;
    } else if (ch == '\"') {
        GetString(infp);
        thetok = STRING_TOK;
        return;
    } else if (IsIdChar(ch)) {
        GetIdent(infp);
        if (!isupper((int)theident[0])) {
            thetok = IDENTIFIER_TOK;
        } else if (strcmp(theident, "BEGIN_SAMPLE") == 0) {
            thetok = BEGIN_SAMPLE_TOK;
        } else if (strcmp(theident, "END_SAMPLE") == 0) {
            thetok = END_SAMPLE_TOK;
        } else if (strcmp(theident, "JOB") == 0) {
            thetok = JOB_TOK;
        } else if (strcmp(theident, "DATE") == 0) {
            thetok = DATE_TOK;
        } else if (strcmp(theident, "SAMPLE_UNIT") == 0) {
            thetok = SAMPLE_UNIT_TOK;
        } else if (strcmp(theident, "VALUE_UNIT") == 0) {
            thetok = VALUE_UNIT_TOK;
        } else if (strcmp(theident, "MARK") == 0) {
            thetok = MARK_TOK;
        } else {
            thetok = IDENTIFIER_TOK;
        }
        return;
    } else {
        Error("%s, line %d: strange character (%c)", hpfile, linenum, ch);
    }
}


/*
 *      Read a sequence of digits and convert the result to an integer
 *      or floating point value (assigned to the "theinteger" or
 *      "thefloatish").
 */

static char numberstring[ NUMBER_LENGTH + 1 ];

token
GetNumber(FILE *infp)
{
    int i;
    int containsdot;

    ASSERT(isdigit(ch)); /* we must have a digit to start with */

    containsdot = 0;

    for (i = 0; i < NUMBER_LENGTH && (isdigit(ch) || ch == '.'); i++) {
        numberstring[ i ] = ch;
        containsdot |= (ch == '.');
        ch = getc(infp);
    }

    ASSERT(i <= NUMBER_LENGTH); /* did not overflow */

    numberstring[ i ] = '\0';

    if (containsdot) {
        thefloatish = (floatish) atof(numberstring);
        return FLOAT_TOK;
    } else {
        theinteger = atoi(numberstring);
        /* Set thefloatish too.
           If this is an identifier line, the value might exceed
           the size of 'int', and we are going to convert it to
           a floatish anyways. */
        thefloatish = (floatish) atof(numberstring);
        return INTEGER_TOK;
    }
}

/*
 *      Read a sequence of identifier characters and assign the result
 *      to the string "theident".
 */

void
GetIdent(FILE *infp)
{
    unsigned int i;
    char idbuffer[5000];

    for (i = 0; i < (sizeof idbuffer)-1 && IsIdChar(ch); i++) {
        idbuffer[ i ] = ch;
        ch = getc(infp);
    }

    idbuffer[ i ] = '\0';

    if (theident)
        free(theident);

    theident = copystring(idbuffer);
}


/*
 * Read a sequence of characters that make up a string and assign the result to
 * "thestring". A string is surrounded by double quotes, with a double quote
 * itself escaped as two contiguous double quotes.
 */

void
GetString(FILE *infp)
{
    ASSERT(ch == '\"');

    size_t stringbuffersize = 5000;
    char *stringbuffer = xmalloc(stringbuffersize);

    ch = getc(infp);    /* skip the '\"' that begins the string */

    for (size_t i = 0; ; ++i) {
        if (ch == EOF) {
            Error("%s, line %d: EOF when expecting \"", hpfile, linenum, ch);
        }
        if (i == stringbuffersize) {
            stringbuffersize *= 2;
            stringbuffer = xrealloc(stringbuffer, stringbuffersize);
        }
        if (ch == '\"') {
            ch = getc(infp);
            if (ch != '\"') {
                stringbuffer[i] = '\0';
                break;
            }
        }
        stringbuffer[i] = ch;
        ch = getc(infp);
    }

    thestring = copystring(stringbuffer);
    free(stringbuffer);
}

boolish
IsIdChar(int ch)
{
    return (!isspace(ch));
}


/*
 *      The information associated with each identifier is stored
 *      in a linked list of chunks. The table below allows the list
 *      of chunks to be retrieved given an identifier name.
 */

#define N_HASH          513

static struct entry* hashtable[ N_HASH ];

static intish
Hash(char *s)
{
    int r;

    for (r = 0; *s; s++) {
        r = r + r + r + *s;
    }

    if (r < 0) r = -r;

    return r % N_HASH;
}

/*
 *      Get space for a new chunk. Initialise it, and return a pointer
 *      to the new chunk.
 */

static struct chunk*
MakeChunk(void)
{
    struct chunk* ch;
    struct datapoint* d;

    ch = (struct chunk*) xmalloc( sizeof(struct chunk) );

    d = (struct datapoint*) xmalloc (sizeof(struct datapoint) * N_CHUNK);

    ch->nd = 0;
    ch->d = d;
    ch->next = 0;
    return ch;
}


/*
 *      Get space for a new entry. Initialise it, and return a pointer
 *      to the new entry.
 */

struct entry *
MakeEntry(char *name)
{
    struct entry* e;

    e = (struct entry *) xmalloc(sizeof(struct entry));
    e->chk = MakeChunk();
    e->name = copystring(name);
    return e;
}

/*
 *      Get the entry associated with "name", creating a new entry if
 *      necessary.
 */

static struct entry *
GetEntry(char *name)
{
    intish h;
    struct entry* e;

    h = Hash(name);

    for (e = hashtable[ h ]; e; e = e->next) {
        if (strcmp(e->name, name) == 0) {
            break;
        }
    }

    if (e) {
        return (e);
    } else {
        nidents++;
        e = MakeEntry(name);
        e->next = hashtable[ h ];
        hashtable[ h ] = e;
        return (e);
    }
}


/*
 *      Store information from a sample.
 */

void
StoreSample(struct entry *en, intish bucket, floatish value)
{
    struct chunk* chk;

    for (chk = en->chk; chk->next != 0; chk = chk->next)
        ;

    if (chk->nd < N_CHUNK) {
        chk->d[ chk->nd ].bucket = bucket;
        chk->d[ chk->nd ].value  = value;
        chk->nd += 1;
    } else {
        struct chunk* t;
        t = chk->next = MakeChunk();
        t->d[ 0 ].bucket = bucket;
        t->d[ 0 ].value  = value;
        t->nd += 1;
    }
}


struct entry** identtable;

/*
 *      The hash table is useful while reading the input, but it
 *      becomes a liability thereafter. The code below converts
 *      it to a more easily processed table.
 */

static void
MakeIdentTable(void)
{
    intish i;
    intish j;
    struct entry* e;

    nidents = 0;
    for (i = 0; i < N_HASH; i++) {
        for (e = hashtable[ i ]; e; e = e->next) {
            nidents++;
        }
    }

    identtable = (struct entry**) xmalloc(nidents * sizeof(struct entry*));
    j = 0;

    for (i = 0; i < N_HASH; i++) {
        for (e = hashtable[ i ]; e; e = e->next, j++) {
            identtable[ j ] = e;
        }
    }
}
