// Some standalone unit testing for EncodeBuffer/DecodeBuffer.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>

#include "EncodeBuffer.H"
#include "DecodeBuffer.H"

#define NUM_ENTRIES 10000

static void usage(void)
{
    puts("Grow clue.");
    exit(1);
}

typedef enum { write, read } TestMode;

static int readDump(const char   *fileName,
                    unsigned     **values,
                    unsigned     **bitSize,
                    unsigned     **blockSize,
                    DecodeBuffer **decoder)
{
    FILE *fp = fopen(fileName, "rb");
    unsigned tmp, i;
    unsigned *v, *b, *b2;
    unsigned char *data;

    if (!fp)
    {
        printf("Cannot open %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    if (fread(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
    {
        fclose(fp);
        printf("Cannot read %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    if (ntohl(tmp) != NUM_ENTRIES)
    {
        fclose(fp);
        printf("NUM_ENTRIES mismatch!\n");
        return 1;
    }

    v = new unsigned[NUM_ENTRIES];
    b = new unsigned[NUM_ENTRIES];
    b2 = new unsigned[NUM_ENTRIES];

    for (i = 0; i < NUM_ENTRIES; i++)
    {
        if (fread(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot read %s: %s\n", fileName, strerror(errno));
            return 1;
        }
        v[i] = ntohl(tmp);
        if (fread(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot read %s: %s\n", fileName, strerror(errno));
            return 1;
        }
        b[i] = ntohl(tmp);
        if (fread(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot read %s: %s\n", fileName, strerror(errno));
            return 1;
        }
        b2[i] = ntohl(tmp);
    }

    if (fread(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
    {
        fclose(fp);
        printf("Cannot read %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    tmp = ntohl(tmp);
    data = new unsigned char[tmp];

    if (fread(data, 1, tmp, fp) != tmp)
    {
        fclose(fp);
        printf("Cannot read %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    *values = v;
    *bitSize = b;
    *blockSize = b2;
    *decoder = new DecodeBuffer(data, tmp);

    return 0;
}

static int writeDump(const char   *fileName,
                     unsigned     *values,
                     unsigned     *bitSize,
                     unsigned     *blockSize,
                     EncodeBuffer *encoder)
{
    FILE *fp = fopen(fileName, "wb");
    unsigned tmp, i;

    if (!fp)
    {
        printf("Cannot open %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    tmp = htonl(NUM_ENTRIES);
    if (fwrite(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
    {
        fclose(fp);
        printf("Cannot write %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    for (i = 0; i < NUM_ENTRIES; i++)
    {
        tmp = htonl(values[i]);
        if (fwrite(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot write %s: %s\n", fileName, strerror(errno));
            return 1;
        }
        tmp = htonl(bitSize[i]);
        if (fwrite(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot write %s: %s\n", fileName, strerror(errno));
            return 1;
        }
        tmp = htonl(blockSize[i]);
        if (fwrite(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
        {
            fclose(fp);
            printf("Cannot write %s: %s\n", fileName, strerror(errno));
            return 1;
        }
    }

    tmp = htonl(encoder->getDataLength());
    if (fwrite(&tmp, 1, sizeof(unsigned), fp) != sizeof(unsigned))
    {
        fclose(fp);
        printf("Cannot write %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    if (fwrite(encoder->getData(), 1, encoder->getDataLength(), fp) != encoder->getDataLength())
    {
        fclose(fp);
        printf("Cannot write %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    if (fclose(fp))
    {
        printf("Cannot close %s: %s\n", fileName, strerror(errno));
        return 1;
    }

    return 0;
}

int main(int argc, const char *argv[])
{
    TestMode     mode;
    unsigned     *values, *bitSize, *blockSize;
    EncodeBuffer *encoder = 0;
    DecodeBuffer *decoder;
    unsigned     i;

    if (argc < 3)
    {
        usage();
    }

    if (!strcmp(argv[1], "write"))
    {
        mode = write;
    }
    else if (!strcmp(argv[1], "read"))
    {
        mode = read;
    }
    else if (!strcmp(argv[1], "hack"))
    {
        unsigned value;
        int      rc = 0;

        encoder = new EncodeBuffer();
        encoder->encodeValue(0x16, 6);
        encoder->encodeDirect(1,3);
        decoder = new DecodeBuffer(encoder->getData(), encoder->getDataLength());
        decoder->decodeValue(value,6);
        if (value != 0x16)
        {
            puts("Crap 1.");
            rc = 1;
        }
        decoder->decodeDirect(value, 3);
        if (value != 1)
        {
            puts("Crap 2.");
            rc = 1;
        }
        delete decoder;
        delete encoder;

        if (!rc)
            puts("Happy day!");
        return rc;
    }

    else
    {
        usage();
    }

    if (mode == read)
    {
        printf("Reading data from %s...\n", argv[2]);

        if (readDump(argv[2], &values, &bitSize, &blockSize, &decoder))
        {
            return 1;
        }
    }
    else
    {
        puts("Generating random data...");

        values = new unsigned[NUM_ENTRIES];
        bitSize = new unsigned[NUM_ENTRIES];
        blockSize = new unsigned[NUM_ENTRIES];

        for (i = 0; i < NUM_ENTRIES; i++)
        {
            bitSize[i] = (rand() % 32) + 1;
            blockSize[i] = (rand() % bitSize[i]) + 1;
            values[i] = rand();
        }

        puts("Encoding...");

        // Now encode 'em.
        encoder = new EncodeBuffer();
        for (i = 0; i < NUM_ENTRIES; i++)
        {
            encoder->encodeValue(values[i], bitSize[i], blockSize[i]);
        }

        decoder = new DecodeBuffer(encoder->getData(), encoder->getDataLength());
    }

    // Now decode 'em.

    puts("Decoding...");

    for (i = 0; i < NUM_ENTRIES; i++)
    {
        unsigned value, valueMask;

        valueMask = ~0U >> (32 - bitSize[i]);
        decoder->decodeValue(value, bitSize[i], blockSize[i], 0);
        if (value != (values[i] & valueMask))
        {
            printf("Bogus decoding data @ %d(%d): expected 0x%08x, got 0x%08x\n",
                   i, bitSize[i], values[i], value);
            return 1;
        }
    }

    delete decoder;

    if (mode == write)
    {
        printf("Dumping data to %s...\n", argv[2]);
        if (writeDump(argv[2], values, bitSize, blockSize, encoder))
        {
            return 1;
        }
    }

    delete values;
    delete bitSize;
    if (encoder)
    {
        delete encoder;
    }

    puts("Success.");

    return 0;
}
