/*
 * miniJSS - JSSMOD module loader
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2007-2009 Tecnic Software productions (TNSP)
 */
#include "jssmod.h"
#include <string.h>


#ifdef JM_SUP_PATMODE_ALL
#    define JM_SUP_PATMODE_1 1
#    define JM_SUP_PATMODE_2 1
#    define JM_SUP_PATMODE_3 1
#    define JM_SUP_PATMODE_4 1
#    define JM_SUP_PATMODE_5 1
#endif


#define JSGETBUF(XV, XT) if (!dmf_read_str(inFile, XV, sizeof(XT))) return DMERR_OUT_OF_DATA
#define JSGETBYTE(XV) if (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA


#if defined(JM_SUP_PATMODE_1) || defined(JM_SUP_PATMODE_3)
static int jssGetConvertedNote(DMResource *inFile, JSSNote *note)
{
    Uint8 tmp;

    JSGETBYTE(&tmp);

    if (tmp == 127)
        note->note = jsetNoteOff;
    else if (tmp == 0)
        note->note = jsetNotSet;
    else
        note->note = tmp - 1;

    JSGETBYTE(&tmp);
    note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;

    return DMERR_OK;
}
#endif


#if defined(JM_SUP_PATMODE_2) || defined(JM_SUP_PATMODE_4)
static int jssGetCompressedNote(DMResource *inFile, JSSNote *note)
{
    Uint8 packb, tmp;

    JSGETBYTE(&packb);
    if (packb & 0x80)
    {
        if (packb & COMP_NOTE)
        {
            JSGETBYTE(&tmp);
            if (tmp == 127)
                note->note = jsetNoteOff;
            else
                note->note = tmp;
        }

        if (packb & COMP_INSTRUMENT)
        {
            JSGETBYTE(&tmp);
            note->instrument = tmp;
        }

        if (packb & COMP_VOLUME)
        {
            JSGETBYTE(&tmp);
            note->volume = tmp;
        }

        if (packb & COMP_EFFECT)
        {
            JSGETBYTE(&tmp);
            note->effect = tmp;
            note->param = 0;
        }

        if (packb & COMP_PARAM)
        {
            JSGETBYTE(&tmp);
            note->param = tmp;
        }
    }
    else
    {
        tmp = packb;

        if (tmp == 127)
            note->note = jsetNoteOff;
        else if (tmp == 0)
            note->note = jsetNotSet;
        else
            note->note = tmp - 1;

        JSGETBYTE(&tmp);
        note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_2
static int jssGetPatternCompHoriz(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (row = 0; row < pattern->nrows; row++)
    for (channel = 0; channel < pattern->nchannels; channel++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(inFile, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_4
static int jssGetPatternCompVert(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (channel = 0; channel < pattern->nchannels; channel++)
    for (row = 0; row < pattern->nrows; row++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(inFile, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_1
static int jssGetPatternRawHoriz(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (row = 0; row < pattern->nrows; row++)
    for (channel = 0; channel < pattern->nchannels; channel++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(inFile, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_3
static int jssGetPatternRawVert(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (channel = 0; channel < pattern->nchannels; channel++)
    for (row = 0; row < pattern->nrows; row++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(inFile, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_5

#undef JSGETBYTE
#define JSGETBYTE(XV) if (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA

#define JSFOREACHNOTE1                                                              \
  for (channel = 0; channel < pattern->nchannels; channel++)                        \
  for (row = 0; row < pattern->nrows; row++) {                                      \
      JSSNote *note = pattern->data + (pattern->nchannels * row) + channel;

#define JSFOREACHNOTE2 }

static int jssGetPatternRawVertElem(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;
    Uint8 tmp;

    assert(buf != NULL);
    assert(pattern != NULL);

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    if (tmp == 0)
        note->note = jsetNotSet;
    else if (tmp == 127)
        note->note = jsetNoteOff;
    else
        note->note = tmp - 1;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;
    JSFOREACHNOTE2

    return DMERR_OK;
}
#endif


#undef JSGETBUF
#undef JSGETBYTE
#define JSGETBUF(XV, XT) do { \
    if (!dmf_read_str(inFile, XV, sizeof(XT)))  \
        JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA,    \
        "Out of data at getting " # XT " (%d bytes)\n", sizeof(XT)); \
} while (0)
#define JSGETBYTE(XV) if (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA


#ifdef JM_SUP_EXT_INSTR
static void jssCopyEnvelope(JSSEnvelope *e, JSSMODEnvelope *je)
{
    int i;

    e->flags   = je->flags;
    e->npoints = je->npoints;
    e->sustain = je->sustain;
    e->loopS   = je->loopS;
    e->loopE   = je->loopE;

    for (i = 0; i < je->npoints; i++)
    {
        e->points[i].frame = je->points[i].frame;
        e->points[i].value = je->points[i].value;
    }
}
#endif


int jssLoadJSSMOD(DMResource *inFile, JSSModule **ppModule)
{
    JSSModule *module;
    JSSMODHeader jssH;
    int index;

    *ppModule = NULL;

    // Check the JSSMOD header
    memset(&jssH, 0, sizeof(jssH));
    JSGETBUF(&jssH, JSSMODHeader);

    if (memcmp(jssH.idMagic, "JM", 2) != 0)
    {
        JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Not a valid JSSMOD file, header signature missing!\n");
    }

    if (jssH.idVersion != JSSMOD_VERSION)
    {
        JSSERROR(DMERR_VERSION, DMERR_VERSION,
        "Unsupported version of JSSMOD 0x%4x, this version only supports 0x%4x!\n",
        jssH.idVersion, JSSMOD_VERSION);
    }

    // Allocate the module
    module = jssAllocateModule();
    if (module == NULL)
    {
        JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
        "Could not allocate memory for module structure.\n");
    }
    *ppModule = module;

    // Copy header information
    module->norders         = jssH.norders;
    module->npatterns       = jssH.npatterns;
    module->nchannels       = jssH.nchannels;
    module->nextInstruments = jssH.nextInstruments;
    module->ninstruments    = jssH.ninstruments;
    module->defFlags        = jssH.defFlags;
    module->intVersion      = jssH.intVersion;
    module->defRestartPos   = jssH.defRestartPos;
    module->defSpeed        = jssH.defSpeed;
    module->defTempo        = jssH.defTempo;

    // Get the orders list
    for (index = 0; index < module->norders; index++)
    {
        Sint16 order;
        JSGETBUF(&order, Sint16);
        module->orderList[index] = order;
    }

    // Parse the patterns
    for (index = 0; index < module->npatterns; index++)
    {
        JSSMODPattern jssP;
        int result = DMERR_INVALID_DATA;

        // Get header and check size
        memset(&jssP, 0, sizeof(jssP));
        JSGETBUF(&jssP, JSSMODPattern);

        // Allocate pattern
        module->patterns[index] = jssAllocatePattern(jssP.nrows, module->nchannels);
        if (module->patterns[index] == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate memory for pattern #%d.\n", index);
        }

        // Get pattern data
        switch (jssH.patMode)
        {
#ifdef JM_SUP_PATMODE_1
            case PATMODE_RAW_HORIZ:
                result = jssGetPatternRawHoriz(inFile, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_2
            case PATMODE_COMP_HORIZ:
                result = jssGetPatternCompHoriz(inFile, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_3
            case PATMODE_RAW_VERT:
                result = jssGetPatternRawVert(inFile, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_4
            case PATMODE_COMP_VERT:
                result = jssGetPatternCompVert(inFile, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_5
            case PATMODE_RAW_ELEM:
                result = jssGetPatternRawVertElem(inFile, module->patterns[index]);
                break;
#endif
            default:
                JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Unsupported pattern mode %d. Check compilation options.", jssH.patMode);
                break;
        }

        if (result != DMERR_OK)
        {
            JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index);
        }
    }

#ifdef JM_SUP_EXT_INSTR
    // Read extended instruments
    for (index = 0; index < module->nextInstruments; index++)
    {
        JSSMODExtInstrument jssE;
        JSSExtInstrument *einst;
        int i;

        memset(&jssE, 0, sizeof(jssE));
        JSGETBUF(&jssE, JSSMODExtInstrument);

        if ((einst = jssAllocateExtInstrument()) == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate extended instrument structure #%i\n", index);
        }

        module->extInstruments[index] = einst;

        einst->nsamples     = jssE.nsamples;
        einst->vibratoType  = jssE.vibratoType;
        einst->vibratoSweep = jssE.vibratoSweep;
        einst->vibratoDepth = jssE.vibratoDepth;
        einst->vibratoRate  = jssE.vibratoRate;
        einst->fadeOut      = jssE.fadeOut;

        for (i = 0; i < jsetNNotes; i++)
        {
            int snum = jssE.sNumForNotes[i];
            einst->sNumForNotes[i] = (snum > 0) ? snum : jsetNotSet;
        }

        jssCopyEnvelope(&(einst->volumeEnv), &jssE.volumeEnv);
        jssCopyEnvelope(&(einst->panningEnv), &jssE.panningEnv);
    }

#ifdef JM_SUP_INSTR
    // Read sample instrument headers
    for (index = 0; index < module->ninstruments; index++)
    {
        JSSMODInstrument jssI;
        JSSInstrument *inst;

        memset(&jssI, 0, sizeof(jssI));
        JSGETBUF(&jssI, JSSMODInstrument);

        if ((inst = jssAllocateInstrument()) == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate instrument structure #%i\n", index);
        }

        module->instruments[index] = inst;

        inst->size          = jssI.size;
        inst->loopS         = jssI.loopS;
        inst->loopE         = jssI.loopE;
        inst->volume        = jssI.volume;
        inst->flags         = jssI.flags;
        inst->C4BaseSpeed   = jssI.C4BaseSpeed;
        inst->ERelNote      = jssI.ERelNote;
        inst->EFineTune     = jssI.EFineTune;
        inst->EPanning      = jssI.EPanning;
        inst->hasData       = jssI.hasData;
        inst->convFlags     = jssI.convFlags;
    }

#ifdef JM_SUP_SAMPLES
    // Read sample data
    for (index = 0; index < module->ninstruments; index++)
    {
        JSSInstrument *inst = module->instruments[index];

        if (inst && inst->hasData)
        {
            size_t sz;

            // Calculate data size
            if (inst->flags & jsf16bit)
                sz = inst->size * sizeof(Uint16);
            else
                sz = inst->size * sizeof(Uint8);

            // Allocate
            if ((inst->data = dmMalloc(sz)) == NULL)
            {
                JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
                "Could not allocate sample data #%d\n", index);
            }

            // Copy data
            if (!dmf_read_str(inFile, inst->data, sz))
            {
                JSSERROR(DMERR_FREAD, DMERR_FREAD,
                "Could not read sample data for #%d\n", index);
            }

            // Convert, if needed
            if (inst->flags & jsf16bit)
                jssDecodeSample16(inst->data, inst->size, inst->convFlags);
            else
                jssDecodeSample8(inst->data, inst->size, inst->convFlags);
        }
    }
#else
#    warning Not including JSSMOD sample loading!
#endif  // JM_SUP_SAMPLES
#else
#    warning Not including JSSMOD instrument loading!
#endif  // JM_SUP_INSTR
#else
#    warning Not including JSSMOD ext.instrument loading!
#endif  // JM_SUP_EXT_INSTR

    return DMERR_OK;
}
