#include <stdio.h>
#include <stdlib.h>

/**
 * Some typedefs and enums for neater look.
 */
enum eStopGo {
    STOP        = 0,
    CONTINUE
};
typedef enum eStopGo eStopGo;
typedef char *StrPtr;
typedef void *VPtr;

/**
 * These are the various status that the parser
 * returns.
 */
enum eParseResult {
    PR_PARSE_OK    =   0,

    PR_MODULE_PATH,
    PR_ADDED_FILE,
    PR_UPDATED_FILE,
    PR_DELETED_FILE,
    PR_MERGED_FILE,
    PR_CONFLICTED_FILE,
    PR_AT_REVISION_NUMBER,
    PR_UPDT_REVISION_NUMBER,

    PR_BAD_HANDLE,
    PR_READ_FAILED,
    PR_LINE_TOO_LONG,
    PR_USER_STOPPED,
};
typedef enum eParseResult eParseResult;


/**
 * This callback is passed each line of the input
 * file + a parsed version (if recognized).
 */
typedef
eStopGo (*parse_wup_callback) (StrPtr pNextLine,
                               eParseResult pToken,
                               StrPtr pParsedLine,
                               VPtr pObj);


/**
 * Parses a single line of input, recognizing certain patterns.
 */
eStopGo parse_wup_line (StrPtr pRaw, parse_wup_callback pCbk, VPtr pObj)
{
        char pathtoken[]  = "Update Path:\t";
        char atrev[]      = "At revision ";
        char updrev[]     = "Updated to revision ";
        char added[]      = "A    ";
        char updated[]    = "U    ";
        char conflicted[] = "C    ";
        char merged[]     = "G    ";
        char deleted[]    = "D    ";

/* helper macro for recognizing lines */
#define wup_startswith(x,y)   if (!strncmp(pRaw,x, sizeof (x)-1)) \
                                return pCbk (pRaw,y,pRaw+(sizeof(x)-1),pObj)


    wup_startswith (pathtoken,  PR_MODULE_PATH);
    wup_startswith (atrev,      PR_AT_REVISION_NUMBER);
    wup_startswith (updrev,     PR_UPDT_REVISION_NUMBER);
    wup_startswith (added,      PR_ADDED_FILE);
    wup_startswith (updated,    PR_UPDATED_FILE);
    wup_startswith (conflicted, PR_CONFLICTED_FILE);
    wup_startswith (merged,     PR_MERGED_FILE);
    wup_startswith (deleted,    PR_DELETED_FILE);

#undef wup_startswith

    return pCbk (pRaw, 0, NULL, pObj);
}

/**
 * This is the main function that parses a given WSCM Update Log (.wup) file.
 */
int parse_wup (StrPtr pFName, parse_wup_callback pCbk, VPtr pObj)
{
        FILE    *wup;
        long    sz, cz;
        char    buf [256];
        char    *p, *o;

    /* Open the file for reading in text mode */
    wup =   fopen (pFName, "rt");

    /* check that we have a valid file */
    if (!wup) {
        /* nope! return */
        return PR_BAD_HANDLE;
    }

/* helper macro for cleanup+return */
#define wup_err(x)  fclose(wup);return (x);

    /* read all file data in one go (it should be small enough, being
     * some simple text */
    o   =   NULL;               /* Read into this */
    sz  =   0;                  /* Total size read */
    while (cz = fread (buf, 1, sizeof (buf), wup)) {
        /* allocate space to save read data */
        p   =   malloc (sz + cz);
        if (o) {
            /* copy old data into new buffer */
            memcpy (p, o, sz);
            free (o);
        }
        /* copy data into new buffer */
        memcpy (p + sz, buf, cz);
        o = p;
        sz  +=  cz;
    }
    o [sz] = 0;             /* Terminating null */
    /* managed to read everthing correctly? */
    if (!feof (wup) || !o) {
        /* Nope! Fail */
        wup_err (PR_READ_FAILED);
    }

    /* parse the entire file */
    for (;1;) {
            int i;
        /* a single line at a time */
        i = 0;
        while ((*p) && (*p != '\r') && (*p != '\n')) {
            buf [i++] = *p;
            if (i == sizeof(buf)) {
                wup_err (PR_LINE_TOO_LONG);
            }
            ++p;
        }
        /* got some data on line */
        if (i) {
            buf[i] = 0;
            /* parse it! */
            if (parse_wup_line (buf, pCbk, pObj) != CONTINUE) {
                /* user stopped us */
                wup_err (PR_USER_STOPPED);
            }
        }
        /* finished parsing entire input */
        if (!*p) {
            break;
        }
        /* continue with next line */
        ++p;
    }

#undef wup_err

    /* All ok! */
    fclose (wup);
    return PR_PARSE_OK;
}


/*****************************************************************************/
/* Sample code for testing                                                   */

eStopGo just_print (StrPtr pNextLine,
                    eParseResult pToken,
                    StrPtr pParsedLine,
                    VPtr pObj)
{
    if (pParsedLine) {
        switch (pToken) {
            case PR_MODULE_PATH:
                printf ("%s ==> %s [MODULE]\n", pNextLine, pParsedLine);
                break;
            case PR_ADDED_FILE:
                printf ("%s ==> %s [ADDED]\n", pNextLine, pParsedLine);
                break;
            case PR_UPDATED_FILE:
                printf ("%s ==> %s [UPDATED]\n", pNextLine, pParsedLine);
                break;
            case PR_DELETED_FILE:
                printf ("%s ==> %s [DELETED]\n", pNextLine, pParsedLine);
                break;
            case PR_MERGED_FILE:
                printf ("%s ==> %s [MERGED]\n", pNextLine, pParsedLine);
                break;
            case PR_CONFLICTED_FILE:
                printf ("%s ==> %s [CONFLICTED]\n", pNextLine, pParsedLine);
                break;
            case PR_AT_REVISION_NUMBER:
                printf ("%s ==> %s [AT REVISION NUMBER]\n", pNextLine, pParsedLine);
                break;
            case PR_UPDT_REVISION_NUMBER:
                printf ("%s ==> %s [UPDATED REVISION NUMBER]\n", pNextLine, pParsedLine);
                break;
            default:
                printf ("ERROR!!!!!!!!\n");
        }
    } else {
        /* print starting with comments */
        printf ("//%s\n", pNextLine);
    }
    return CONTINUE;
}

void
main (int argc, char *argv[])
{
        int     r;

    if (argc != 2) {
        printf ("Usage: %s <.wup file to parse>\n", argv [0]);
        return;
    }

    r   =   parse_wup (argv [1], just_print, NULL);
    if (r) {
        printf ("Error: Failed parsing %s! [Reason: %d]\n", argv [1], r);
    }
}


