/*
 * mod2wav - Render XM/JSSMOD module to WAV waveform file
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2007 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "jss.h"
#include "jssmod.h"
#include "jssmix.h"
#include "jssplr.h"
#include "dmlib.h"
#include "dmargs.h"
#include "dmwav.h"
#include "dmmutex.h"


char    *optInFilename = NULL, *optOutFilename = NULL;
int     optOutFormat = JSS_AUDIO_S16,
        optOutChannels = 2,
        optOutFreq = 44100,
        optMuteOChannels = -1,
        optStartOrder = -1;
BOOL    optUsePlayTime = FALSE;
size_t  optPlayTime;


DMOptArg optList[] =
{
    {  0, '?', "help",     "Show this help", OPT_NONE },
    {  2, 'v', "verbose",  "Be more verbose", OPT_NONE },
    {  3, '1', "16bit",    "16-bit output", OPT_NONE },
    {  4, '8', "8bit",     "8-bit output", OPT_NONE },
    {  5, 'm', "mono",     "Mono output", OPT_NONE },
    {  6, 's', "stereo",   "Stereo output", OPT_NONE },
    {  7, 'f', "freq",     "Output frequency", OPT_ARGREQ },
    {  8, 'M', "mute",     "Mute other channels than #", OPT_ARGREQ },
    {  9, 'o', "order",    "Start from order #", OPT_ARGREQ },
    { 10, 't', "time",     "Play for # seconds", OPT_ARGREQ },
//    {10, 'l', "loop",    "Loop for # times", OPT_ARGREQ },
};

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


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    (void) optArg;
    
    switch (optN)
    {
        case 0:
            dmPrintBanner(stdout, dmProgName,
                "[options] [sourcefile] [destfile]");
                
            dmArgsPrintHelp(stdout, optList, optListN);
            exit(0);
            break;

        case 2:
            dmVerbosity++;
            break;
        
        case 3:
            optOutFormat = JSS_AUDIO_S16;
            break;

        case 4:
            optOutFormat = JSS_AUDIO_U8;
            break;

        case 5:
            optOutChannels = JSS_AUDIO_MONO;
            break;

        case 6:
            optOutChannels = JSS_AUDIO_STEREO;
            break;

        case 7:
            optOutFreq = atoi(optArg);
            break;

        case 8:
            optMuteOChannels = atoi(optArg);
            break;

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

        case 10:
            optPlayTime = atoi(optArg);
            optUsePlayTime = TRUE;
            break;

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


BOOL argHandleFile(char *currArg)
{
    if (!optInFilename)
        optInFilename = currArg;
    else
    if (!optOutFilename)
        optOutFilename = currArg;
    else
    {
        dmError("Too many filename arguments (only source and dest needed) '%s'\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


int main(int argc, char *argv[])
{
    DMResource *inFile = NULL;
    FILE *outFile = NULL;
    JSSModule *mod = NULL;
    JSSMixer *dev = NULL;
    JSSPlayer *plr = NULL;
    int result = -1;
    size_t bufLen = 1024*4, dataTotal, dataWritten, sampSize;
    Uint8 *mb = NULL;

    dmInitProg("mod2wav", "XM/JSSMOD to WAV renderer", "0.2", NULL, NULL);
    dmVerbosity = 1;

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

    // Check arguments
    if (optInFilename == NULL || optOutFilename == NULL)
    {
        dmError("Input or output file not specified. Try --help.\n");
        return 1;
    }
    
    // Initialize miniJSS
    jssInit();
    
    // Open the source file
    if ((result = dmf_create_stdio(optInFilename, "rb", &inFile)) != DMERR_OK)
    {
        dmError("Error opening input file '%s', %d: %s\n",
            optInFilename, result, dmErrorStr(result));
        return 1;
    }

    // Read module file
    fprintf(stderr, "Reading file: %s\n", optInFilename);
#ifdef JSS_SUP_XM
    fprintf(stderr, "* Trying XM...\n");
    result = jssLoadXM(inFile, &mod);
#endif
#ifdef JSS_SUP_JSSMOD
    if (result != 0)
    {
        dmfseek(inFile, 0L, SEEK_SET);
        dmMsg(1, "* Trying JSSMOD ...\n");
        result = jssLoadJSSMOD(inFile, &mod);
    }
#endif
    dmf_close(inFile);
    if (result != DMERR_OK)
    {
        dmError("Error loading module file, %d: %s\n",
            result, dmErrorStr(result));
        return 3;
    }

    // Try to convert it
    if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK)
    {
        dmError("Could not convert module for playing, %d: %s\n",
            result, dmErrorStr(result));
        return 3;
    }

    // Open mixer
    dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO);
    if (dev == NULL)
    {
        dmError("jvmInit() returned NULL\n");
        return 4;
    }

    sampSize = jvmGetSampleSize(dev);
    if ((mb = dmMalloc(bufLen * sampSize)) == NULL)
    {
        dmError("Could not allocate mixing buffer\n");
        return 5;
    }
    
    dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n",
        optOutFormat, jvmGetSampleRes(dev), optOutChannels, optOutFreq,
        sampSize);
    
    // Initialize player
    if ((plr = jmpInit(dev)) == NULL)
    {
        dmError("jmpInit() returned NULL.\n");
        return 6;
    }
    
    // Set callback
    jvmSetCallback(dev, jmpExec, plr);
    
    // Initialize playing
    jmpSetModule(plr, mod);
    if (optStartOrder >= 0)
    { 
        dmMsg(1, "Starting from song order #%d\n", optStartOrder);
    } else
        optStartOrder = 0;

    jmpPlayOrder(plr, optStartOrder);
    jvmSetGlobalVol(dev, 150);
    
    if (optMuteOChannels > 0 && optMuteOChannels <= mod->nchannels)
    {
        int i;
        for (i = 0; i < mod->nchannels; i++)
            jvmMute(dev, i, TRUE);
        jvmMute(dev, optMuteOChannels - 1, FALSE);
    }
    
    // Open output file
    if ((outFile = fopen(optOutFilename, "wb")) == NULL)
    {
        dmError("Error opening output file '%s'. (%s)\n",
            optInFilename, strerror(errno));
        return 7;
    }

    // Write initial header
    dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, 1024);

    // Render audio data and output to file
    if (optUsePlayTime)
        dmMsg(1, "Rendering module (%d seconds) ...\n", optPlayTime);
    else
        dmMsg(1, "Rendering module ...\n");
    
    optPlayTime *= optOutFreq;
    dataTotal = 0;
    dataWritten = 1;
    while (plr->isPlaying && dataWritten > 0)
    {
        size_t writeLen = bufLen;
        if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime)
            writeLen = optPlayTime - dataTotal;
        
        if (writeLen > 0)
        {
            jvmRenderAudio(dev, mb, writeLen);
#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
            jssEncodeSample16((Uint16 *)mb, writeLen * optOutChannels, jsampSwapEndianess);
#endif
            dataWritten = fwrite(mb, sampSize, writeLen, outFile);
            if (dataWritten < writeLen)
            {
                dmError("Error writing data!\n");
                fclose(outFile);
                return 8;
            }
            dataTotal += dataWritten;
        }
        
        if (optUsePlayTime && dataTotal >= optPlayTime)
            break;
    }
    
    // Write the correct header
    if (fseek(outFile, 0L, SEEK_SET) != 0)
    {
        dmError("Error rewinding to header position!\n");
        return 9;
    }
    
    dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, dataTotal);
    
    // Done!
    fclose(outFile);

    jmpClose(plr);
    jvmClose(dev);
    jssFreeModule(mod);
    jssClose();

    dmMsg(1, "OK.\n");
    return 0;
}
