/*
 * data2inc - Convert binary data to "C"-source or XA-compatible include file
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2003,2009-2012 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include <errno.h>
#include "dmlib.h"
#include "dmargs.h"
#include "dmmutex.h"

#define RA_LINEBUF    (16)

enum
{
    FMT_AUTO = 0,
    FMT_C,
    FMT_ASM
};

char    *optInFilename = NULL,
        *optOutFilename = NULL,
        *optObjName = "default_object",
        *optDataType = NULL,
        *optAddLine = NULL;

int     optIndentation = -1;
int     optFormat = FMT_AUTO;
BOOL    optHexMode = FALSE,
        optQuiet = FALSE,
        optExtraData = FALSE,
        optFormatting = TRUE;


void (*writeHeader) (FILE *, char *) = NULL;
void (*writeDecl) (FILE *, size_t, char *) = NULL;
void (*writeData) (FILE *, Uint8 *, size_t) = NULL;
void (*writeFooter) (FILE *, size_t, char *) = NULL;


static DMOptArg optList[] =
{
    {  0, '?', "help",           "Show this help", OPT_NONE },
    {  4, 'A', "format-asm",     "Output in XA-compatible asm", OPT_NONE },
    {  5, 'C', "format-c",       "Output in ANSI C", OPT_NONE },
    {  1, 'n', "name",           "Set object name", OPT_ARGREQ },
    {  2, 't', "type",           "Set datatype (unsigned char/byte)", OPT_ARGREQ },
    {  3, 'a', "add-line",       "Add this line to start of file", OPT_ARGREQ },
    {  6, 'x', "hexadecimal",    "Use hexadecimal output", OPT_NONE },
    {  7, 'q', "quiet",          "Do not add comments", OPT_NONE },
    {  8, 'f', "no-formatting",  "Disable additional output formatting", OPT_NONE },
    {  9, 'i', "indentation",    "Set indentation (negative value = tab)", OPT_ARGREQ },
    { 10, 'e', "extra-data",     "Add object end labels and size in asm output", OPT_NONE },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName,
        "[options] [sourcefile] [destfile]");

    dmArgsPrintHelp(stdout, optList, optListN);
    
    printf(
    "\n"
    "To convert a data file to a C structure using 'Uint8' as type:\n"
    "$ data2inc -C -n variable_name -t Uint8 input.bin output.h\n"
    "\n"
    );
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            optObjName = optArg;
            break;

        case 2:
            optDataType = optArg;
            break;

        case 3:
            optAddLine = optArg;
            break;

        case 4:
            optFormat = FMT_ASM;
            break;
        case 5:
            optFormat = FMT_C;
            break;
        case 6:
            optHexMode = TRUE;
            break;
        case 7:
            optQuiet = TRUE;
            break;
        case 8:
            optFormatting = FALSE;
            break;

        case 9:
            optIndentation = atoi(optArg);
            break;

        case 10:
            optExtraData = TRUE;
            break;

        default:
            dmError("Unknown option '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char * currArg)
{
    if (!optInFilename)
        optInFilename = currArg;
    else
    if (!optOutFilename)
        optOutFilename = currArg;
    else
        dmError("Source and destination filenames already specified, extraneous argument '%s'.\n", currArg);

    return TRUE;
}


/* Assembler include data output functions
 */
void writeHeader_ASM(FILE * f, char *name)
{
    if (name)
        fprintf(f, "; '%s'", name);
    else
        fprintf(f, "; Generated");
    fprintf(f, " by %s v%s\n",
        dmProgName, dmProgVersion);
}

void writeDecl_ASM(FILE * f, size_t len, char *name)
{
    if (optExtraData)
        fprintf(f, "%s_size = %u\n", name, len);
    fprintf(f, "%s:\n", name);
}

void writeData_ASM(FILE * f, Uint8 * buf, size_t len)
{
    size_t i;

    fprintf(f, "%s ", optDataType);
    for (i = 0; i < len; i++)
    {
        if (optFormatting)
        {
            if (optHexMode)
                fprintf(f, "$%.2x", buf[i]);
            else
                fprintf(f, "%3d", buf[i]);
        }
        else
        {
            if (optHexMode)
                fprintf(f, "$%x", buf[i]);
            else
                fprintf(f, "%d", buf[i]);
        }

        if (i < (len - 1))
            fprintf(f, ",");
    }
}

void writeFooter_ASM(FILE * f, size_t len, char *name)
{
    (void) len;
    if (optExtraData)
        fprintf(f, "%s_end: \n", name);
    else
        fprintf(f, "\n");
}


/* ANSI-C include data output functions
 */
void writeHeader_C(FILE * f, char *name)
{
    if (name)
        fprintf(f, "/* '%s' generated", name);
    else
        fprintf(f, "/* Generated");

    fprintf(f, " by %s v%s\n" " */\n",
        dmProgName, dmProgVersion);
}

void writeDecl_C(FILE * f, size_t len, char *name)
{
    fprintf(f, "%s %s[%u] = {\n",
        optDataType, name, len);

    printf("extern %s %s[%u];\n",
        optDataType, name, len);
}

void writeData_C(FILE * f, Uint8 * buf, size_t len)
{
    size_t i;

    for (i = 0; i < len; i++)
    {
        if (optFormatting)
        {
            if (optHexMode)
                fprintf(f, "0x%.2x", buf[i]);
            else
                fprintf(f, "%3d", buf[i]);
        }
        else
        {
            if (optHexMode)
                fprintf(f, "0x%x", buf[i]);
            else
                fprintf(f, "%d", buf[i]);
        }

        fprintf(f, ",");
    }
}

void writeFooter_C(FILE * f, size_t len, char *name)
{
    (void) len;
    (void) name;
    fprintf(f, "};\n");
}


off_t dmGetFileSize(FILE *f)
{
    off_t len, pos = ftell(f);
    fseeko(f, 0, SEEK_END);
    len = ftell(f);
    fseek(f, pos, SEEK_SET);
    return len;
}


int main(int argc, char *argv[])
{
    FILE *sfile = NULL, *dfile = NULL;
    off_t inSize;
    Uint8 inBuf[RA_LINEBUF];
    int tmpRes;

    /* Initialize */
    dmInitProg("data2inc", "Data to include converter", "0.6", NULL, NULL);
    dmVerbosity = 0;

    /* Parse arguments */
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, TRUE))
        exit(1);

    /* Determine output type, if not specified */
    if (optFormat == FMT_AUTO)
    {
        char *dext;

        if (optOutFilename == NULL)
        {
            dmError("Output format not specified and no output filename given (try --help)\n");
            exit(1);
        }

        /* Check filename extension */
        dext = strrchr(optOutFilename, '.');
        if (dext)
        {
            dext++;
            if (!strcasecmp(dext, "c") || !strcasecmp(dext, "h") ||
                !strcasecmp(dext, "cc") || !strcasecmp(dext, "cpp") ||
                !strcasecmp(dext, "hpp") || !strcasecmp(dext, "c++"))
                optFormat = FMT_C;
            else
                optFormat = FMT_ASM;
        } else
            optFormat = FMT_ASM;
    }

    /* Set functions */
    switch (optFormat)
    {
        case FMT_ASM:
            if (!optDataType)
                optDataType = ".byte";

            writeHeader = writeHeader_ASM;
            writeDecl = writeDecl_ASM;
            writeData = writeData_ASM;
            writeFooter = writeFooter_ASM;
            break;

        case FMT_C:
            if (!optDataType)
                optDataType = "unsigned char";

            writeHeader = writeHeader_C;
            writeDecl = writeDecl_C;
            writeData = writeData_C;
            writeFooter = writeFooter_C;
            break;

        case FMT_AUTO:
        default:
            dmError("Internal error, FMT_AUTO at output function init.\n");
            exit(2);
    }

    /* Open the files */
    if (optInFilename == NULL)
        sfile = stdin;
    else
    if ((sfile = fopen(optInFilename, "rb")) == NULL)
    {
        tmpRes = errno;
        dmError("Error opening input file '%s'. (%s)\n",
            optInFilename, strerror(tmpRes));
        exit(3);
    }

    if (optOutFilename == NULL)
        dfile = stdout;
    else
    if ((dfile = fopen(optOutFilename, "wa")) == NULL)
    {
        tmpRes = errno;
        dmError("Error creating output file '%s'. (%s)\n",
            optOutFilename, strerror(tmpRes));
        exit(4);
    }

    /* Get sourcefile size */
    inSize = dmGetFileSize(sfile);

    /* Output header */
    if (!optQuiet)
        writeHeader(dfile, optOutFilename);

    if (optAddLine)
        fprintf(dfile, "%s\n", optAddLine);

    /* Output declaration */
    writeDecl(dfile, inSize, optObjName);

    /* Output data */
    while (!feof(sfile))
    {
        tmpRes = fread(inBuf, sizeof(Uint8), RA_LINEBUF, sfile);
        if (tmpRes > 0)
        {
            if (optIndentation < 0)
                fprintf(dfile, "\t");
            else
            if (optIndentation > 0)
            {
                int i;
                for (i = 0; i < optIndentation; i++)
                    fputs(" ", dfile);
            }

            writeData(dfile, inBuf, tmpRes);

            fprintf(dfile, "\n");
        }
    }


    /* Output footer */
    writeFooter(dfile, inSize, optObjName);

    /* Exit */
    fclose(sfile);
    fclose(dfile);

    exit(0);
    return 0;
}
