#include "jss.h"
#include "jssmod.h"
#include "jssmix.h"
#include "jssplr.h"
#include <string.h>
#include <unistd.h>
#include <SDL.h>


static const char patNoteTable[12][3] =
{
    "C-", "C#", "D-",
    "D#", "E-", "F-",
    "F#", "G-", "G#",
    "A-", "A#", "B-"
};


#define jmpNMODEffectTable (36)
static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


void printRow(FILE * f, JSSPattern * p, int row)
{
    int j, k;
    char c;
    JSSNote *n;

    if (!p)
        return;

    n = &(p->data[p->nchannels * row]);

    fprintf(f, "%.2x: ", row);
    
    k = p->nchannels < 5 ? p->nchannels : 5;

    for (j = 0; j < k; j++)
    {
        switch (n->note)
        {
            case jsetNotSet:
                fprintf(f, "... ");
                break;
            case jsetNoteOff:
                fprintf(f, "=== ");
                break;
            default:
                fprintf(f, "%s%i ", patNoteTable[n->note % 12], n->note / 12);
                break;
        }

        if (n->instrument != jsetNotSet)
            fprintf(f, "%.2x ", n->instrument + 1);
        else
            fprintf(f, ".. ");

        if (n->volume == jsetNotSet)
            fprintf(f, ".. ");
        else if (n->volume >= 0x00 && n->volume <= 0x40)
            fprintf(f, "%.2x ", n->volume);
        else
        {
            switch (n->volume & 0xf0)
            {
                case 0x50: c = '-'; break;
                case 0x60: c = '+'; break;
                case 0x70: c = '/'; break;
                case 0x80: c = '\\'; break;
                case 0x90: c = 'S'; break;
                case 0xa0: c = 'V'; break;
                case 0xb0: c = 'P'; break;
                case 0xc0: c = '<'; break;
                case 0xd0: c = '>'; break;
                case 0xe0: c = 'M'; break;
                default:   c = '?'; break;
            }
            fprintf(f, "%c%x ", c, (n->volume & 0x0f));
        }

        if (n->effect >= 0 && n->effect < jmpNMODEffectTable)
            fprintf(f, "%c", jmpMODEffectTable[n->effect]);
        else if (n->effect == jsetNotSet)
            fprintf(f, ".");
        else
            fprintf(f, "?");

        if (n->param != jsetNotSet)
            fprintf(f, "%.2x|", n->param);
        else
            fprintf(f, "..|");

        n++;
    }
}


void audioCallback(void *userdata, Uint8 *stream, int len)
{
    JSSMixer *d = (JSSMixer *) userdata;

    if (d != NULL)
    {
        jvmRenderAudio(d, stream, len / jvmGetSampleSize(d));
    }
}


int main(int argc, char *argv[])
{
    SDL_AudioSpec afmt;
    DMResource *file = NULL;
    char *inFilename = NULL;
    int result = -1;
    JSSModule *mod = NULL;
    JSSMixer *dev = NULL;
    JSSPlayer *plr = NULL;

    if (argc > 1)
        inFilename = argv[1];

    // Open the files
    if (inFilename == NULL)
        result = dmf_create_stdio_stream(stdin, &file);
    else
        result = dmf_create_stdio(inFilename, "rb", &file);
    
    if (result != DMERR_OK)
    {
        fprintf(stderr, "Error opening input file '%s', #%d: %s\n",
            inFilename, result, dmErrorStr(result));
        return 1;
    }
        
    // Initialize miniJSS
    fprintf(stderr, "Initializing miniJSS\n");
    jssInit();

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

    if (result != DMERR_OK)
    {
        fprintf(stderr, "Error loading module file, %d: %s\n",
            result, dmErrorStr(result));
        return 3;
    }

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

    // Initialize SDL audio
    afmt.freq     = 48000;
    afmt.format   = AUDIO_S16SYS;
    afmt.channels = 2;

    // Initialize mixing device
    fprintf(stderr, "Initializing miniJSS mixer with: %d, %d, %d\n",
        JSS_AUDIO_S16, afmt.channels, afmt.freq);

    dev = jvmInit(JSS_AUDIO_S16, afmt.channels, afmt.freq, JMIX_AUTO);
    if (dev == NULL)
    {
        fprintf(stderr, "jvmInit() returned NULL\n");
        return 3;
    }
    
    afmt.samples  = afmt.freq / 4;
    afmt.callback = audioCallback;
    afmt.userdata = (void *) dev;

    // Open the audio device
    fprintf(stderr, "Trying to init SDL with: %d, %d, %d\n",
        afmt.format, afmt.channels, afmt.freq);
    
    if (SDL_OpenAudio(&afmt, NULL) < 0)
    {
        fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
        return 4;
    }
    
    // Initialize player
    if ((plr = jmpInit(dev)) == NULL)
    {
        fprintf(stderr, "jmpInit() returned NULL\n");
        return 4;
    }
    
    // Initialize playing
    jvmSetCallback(dev, jmpExec, plr);
    jmpSetModule(plr, mod);
    jmpPlayOrder(plr, 0);
    jvmSetGlobalVol(dev, 60);

    // okay, main loop here ... "play" module and print out info
    printf("----------------------------------------------------\n");
    SDL_LockAudio();
    SDL_PauseAudio(0);
    SDL_UnlockAudio();
    BOOL playing = TRUE;
    while (playing)
    {
        JSSPattern *pattern;
        int currRow, prevRow;

        JSS_LOCK(plr);
        currRow = prevRow = plr->row;
        JSS_UNLOCK(plr);

        while (currRow == prevRow && playing)
        {
            JSS_LOCK(plr);
            currRow = plr->row;
            playing = plr->isPlaying;
            pattern = plr->pattern;
            JSS_UNLOCK(plr);
            SDL_Delay(50);
        }

        if (playing)
        {
            printRow(stdout, pattern, currRow);
            printf("\n");
        }
    }
    
    printf("----------------------------------------------------\n");

    SDL_LockAudio();
    SDL_PauseAudio(1);
    jmpClose(plr);
    jvmClose(dev);
    jssFreeModule(mod);
    SDL_UnlockAudio();

    SDL_Quit();

    jssClose();

    return 0;
}
