#include "dmlib.h"
#include "dmargs.h"
#include <math.h>

enum
{
    MT_SIN,
    MT_COS,
    MT_SMOOTH1,
    MT_SCURVE,
    MT_SMOOTH1_CLAMP,
    MT_SCURVE_CLAMP,
    MT_SIN_SCURVE,

    MT_LAST
};


typedef struct
{
    char *name;
    char *desc;
} DMTransType;

static DMTransType dmTransTypes[MT_LAST] =
{
    { "sin", "Sine" },
    { "cos", "Cosine" },
    { "smooth1", "Smoothstep1 LERP" },
    { "scurve", "S-curve LERP" },
    { "smooth1-clamp", "Clamped smoothstep1 LERP" },
    { "scurve-clamp", "Clamped S-curve LERP" },
    { "sin-scurve", "Sine S-curve" },
};


DMFloat
    optSOffset     = 0.0f,
    optSAmplitude  = 1.0f,
    optSOmega      = 1.0f,
    optStartValue  = 0.0f,
    optEndValue    = 1.0f;

int optNSteps      = 64,
    optPerLine     = 16,
    optTransType   = -1;

char
    *optObjectName = NULL,
    *optOutFilename = NULL;


static DMOptArg optList[] =
{
    {  0, '?', "help",        "Show this help", OPT_NONE },
    {  1, 'v', "verbose",     "Increase verbosity", OPT_NONE },
    {  2, 'o', "output",      "Set output file (default stdout)", OPT_ARGREQ },
    {  3, 'n', "name",        "Set output object name", OPT_ARGREQ },

    {  4, 's', "steps",       "Number of steps/values in output table", OPT_ARGREQ },
    {  5, 't', "type",        "Curve/interpolation type (see list)", OPT_ARGREQ },

    {  6, 'O', "offset",      "Output data offset", OPT_ARGREQ },
    {  7, 'A', "amplitude",   "Output amplitude scale", OPT_ARGREQ },
    {  8, 'W', "omega",       "Omega (w) multiplier", OPT_ARGREQ },

    {  9, 'S', "start",       "Start value (only smooth/scurve)", OPT_ARGREQ },
    { 10, 'E', "end",         "End value (only smooth/scurve)", OPT_ARGREQ },
};

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


void argShowHelp()
{
    int index;
    dmPrintBanner(stdout, dmProgName, "[options]");
    dmArgsPrintHelp(stdout, optList, optListN);

    printf("\nAvailable types:\n");
    for (index = 0; index < MT_LAST; index++)
    {
        DMTransType *tm = &dmTransTypes[index];
        printf("%-15s | %s\n", tm->name, tm->desc);
    }
    printf("\n");
}


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

        case 1:
            dmVerbosity++;
            break;
        
        case 2:
            optOutFilename = optArg;
            break;
        
        case 3:
            optObjectName = optArg;
            break;
        
        case 4:
            {
                int tmp;
                if (sscanf(optArg, "%d", &tmp) != 1)
                {
                    dmError("Invalid number of steps argument '%s'.\n", optArg);
                    return FALSE;
                }
                optNSteps = tmp;
            }
            break;

        case 5:
            {
                int index;
                for (index = 0; index < MT_LAST; index++)
                {
                    DMTransType *tm = &dmTransTypes[index];
                    if (strcasecmp(tm->name, optArg) == 0)
                    {
                        optTransType = index;
                        return TRUE;
                    }
                }
                dmError("Invalid transformation type option '%s'.\n",
                    optArg);
                return FALSE;
            }
            break;

        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
            {
                DMFloat tmp;
                if (sscanf(optArg, "%f", &tmp) != 1)
                {
                    dmError("Invalid %s option argument '%s', expected a floating point value.\n",
                        currArg, optArg);
                    return FALSE;
                }
                switch (optN)
                {
                    case  6: optSOffset = tmp; break;
                    case  7: optSAmplitude = tmp; break;
                    case  8: optSOmega = tmp; break;
                    case  9: optStartValue = tmp; break;
                    case 10: optEndValue = tmp; break;
                }
            }
            break;

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

    return TRUE;
}


int main(int argc, char *argv[])
{
    FILE *outFile;
    DMLerpContext ctx;
    int step, n;

    dmInitProg("gentab", "Sine, etc. table generator", "0.1", NULL, NULL);
    dmVerbosity = 1;

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

    // Check settings
    if (optTransType < 0)
    {
        dmError("No transformation type set, perhaps try --help\n");
        return -1;
    }
    
    if (optObjectName == NULL)
    {
        dmError("Object name not specified, try --help\n");
        return -2;
    }

    if (optOutFilename == NULL)
        outFile = stdout;
    else
    if ((outFile = fopen(optOutFilename, "w")) == NULL)
    {
        int err = dmGetErrno();
        dmError("Could not open output file '%s', %d: %s\n",
            optOutFilename, err, dmErrorStr(err));
        return -2;
    }
    

    // Generate table
    dmLerpInit(&ctx, optStartValue, optEndValue, optNSteps);

    fprintf(outFile,
        "cnt_%s = %d\n"
        "vtab_%s: ",
        optObjectName,
        optNSteps,
        optObjectName
        );

    for (n = 0, step = 0; step < optNSteps; step++)
    {
        DMFloat t = ((DMFloat) step * optSOmega) / (DMFloat) optNSteps, delta, value;
        
        switch (optTransType)
        {
            case MT_SIN:           delta = sin(t * 2 * DM_PI); break;
            case MT_COS:           delta = cos(t * 2 * DM_PI); break;

            case MT_SMOOTH1:       delta = dmLerp1(&ctx, step); break;
            case MT_SCURVE:        delta = dmLerpSCurve(&ctx, step); break;
            case MT_SMOOTH1_CLAMP: delta = dmLerp1Clamp(&ctx, step); break;
            case MT_SCURVE_CLAMP:  delta = dmLerpSCurveClamp(&ctx, step); break;
            case MT_SIN_SCURVE:    delta = dmLerpSCurveClamp(&ctx, step); break;
            
            default: delta = 0;
        }
        
        value = optSOffset + delta * optSAmplitude;
        
        // Print the value
        if (n == 0)
            fprintf(outFile, "\t.byte ");

        fprintf(outFile, "%ld%s",
            lrint(value),
            (n < optPerLine - 1) ? "," : "");

        if (++n >= optPerLine)
        {
            fprintf(outFile, "\n");
            n = 0;
        }
    }
    if (n > 0)
        fprintf(outFile, "\n");
    
    fprintf(outFile, "\n");

    return 0;
}
