/*
 * dmlib
 * -- Demo engine / editor common code and definitions
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2013 Tecnic Software productions (TNSP)
 */
#include "dmengine.h"
#include "dmimage.h"


#ifdef DM_USE_TREMOR
#include <tremor/ivorbiscodec.h>
#include <tremor/ivorbisfile.h>
#endif


DMEngineData engine;
DMEffect *engineEffects = NULL;
int nengineEffects = 0, nengineEffectsAlloc = 0;


int engineRegisterEffect(const DMEffect *ef)
{
    if (ef == NULL)
        return DMERR_NULLPTR;

    // Allocate more space for effects
    if (nengineEffects + 1 >= nengineEffectsAlloc)
    {
        nengineEffectsAlloc += 16;
        engineEffects = dmRealloc(engineEffects, sizeof(DMEffect) * nengineEffectsAlloc);
        if (engineEffects == NULL)
        {
            dmError("Could not expand effects structure.\n"); 
            return DMERR_INIT_FAIL;
        }
    }

    // Copy effects structure
    memcpy(engineEffects + nengineEffects, ef, sizeof(DMEffect));
    nengineEffects++;
    
    return DMERR_OK;
}


int engineInitializeEffects(DMEngineData *engine)
{
    int i, res;

    dmFree(engine->effectData);
    engine->effectData = dmCalloc(nengineEffectsAlloc, sizeof(void *));
    if (engine->effectData == NULL)
    {
        dmError("Could not expand effects data structure.\n"); 
        return DMERR_INIT_FAIL;
    }

    for (i = 0; i < nengineEffects; i++)
    {
        if (engineEffects[i].init != NULL &&
            (res = engineEffects[i].init(engine, &(engine->effectData[i]))) != DMERR_OK)
            return res;
    }

    return DMERR_OK;
}


void engineShutdownEffects(DMEngineData *engine)
{
    if (engine != NULL && engine->effectData != NULL)
    {
        int i;
        for (i = 0; i < nengineEffects; i++)
        {
            if (engineEffects[i].shutdown != NULL)
                engineEffects[i].shutdown(engine, engine->effectData[i]);
        }
        dmFree(engine->effectData);
        engine->effectData = NULL;
    }
}


DMEffect *engineFindEffect(const char *name, const int nparams)
{
    int i;
    for (i = 0; i < nengineEffects; i++)
    {
        if (strcmp(engineEffects[i].name, name) == 0 &&
            engineEffects[i].nparams == nparams)
            return &engineEffects[i];
    }
    return NULL;
}


DMEffect *engineFindEffectByName(const char *name)
{
    int i;
    for (i = 0; i < nengineEffects; i++)
    {
        if (strcmp(engineEffects[i].name, name) == 0)
            return &engineEffects[i];
    }
    return NULL;
}


static int engineResImageLoad(DMResource *res)
{
    SDL_Surface *img = dmLoadImage(res);
    if (res != NULL)
    {
        res->resData = img;
        return DMERR_OK;
    }
    else
        return dmferror(res);
}


static void engineResImageFree(DMResource *res)
{
    SDL_FreeSurface((SDL_Surface *)res->resData);
}

static BOOL engineResImageProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && (strcasecmp(fext, ".jpg") == 0 || strcasecmp(fext, ".png") == 0);
}


#ifdef JSS_SUP_XM
static int engineResXMModuleLoad(DMResource *res)
{
    return jssLoadXM(res, (JSSModule **) &(res->resData));
}

static void engineResXMModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->resData);
}

static BOOL engineResXMModuleProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && strcasecmp(fext, ".xm") == 0;
}
#endif


#ifdef JSS_SUP_JSSMOD
static int engineResJSSModuleLoad(DMResource *res)
{
    return jssLoadJSSMOD(res, (JSSModule **) &(res->resData));
}

static void engineResJSSModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->resData);
}

static BOOL engineResJSSModuleProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL &&
        (strcasecmp(fext, ".jss") == 0 || strcasecmp(fext, ".jmod") == 0);
}
#endif


#ifdef DM_USE_TREMOR
static size_t vorbisFileRead(void *ptr, size_t size, size_t nmemb, void *datasource)
{
    return dmfread(ptr, size, nmemb, (DMResource *) datasource);
}

static int vorbisFileSeek(void *datasource, ogg_int64_t offset, int whence)
{
    return dmfseek((DMResource *) datasource, offset, whence);
}

static int vorbisFileClose(void *datasource)
{
    (void) datasource;
    return 0;
}

static long vorbisFileTell(void *datasource)
{
    return dmftell((DMResource *) datasource);
}
      

static ov_callbacks vorbisFileCBS =
{
    vorbisFileRead,
    vorbisFileSeek,
    vorbisFileClose,
    vorbisFileTell
};

static int engineResVorbisLoad(DMResource *res)
{
    OggVorbis_File vf;

    dmMsg(1, "vorbisfile '%s', %d bytes resource loading\n",
        res->filename, res->rawSize);

    if (ov_open_callbacks(res, &vf, NULL, 0, vorbisFileCBS) < 0)
        return DMERR_FOPEN;

    res->resSize = ov_pcm_total(&vf, -1) * 2 * 2;
    if ((res->resData = dmMalloc(res->resSize + 16)) == NULL)
    {
        ov_clear(&vf);
        return DMERR_MALLOC;
    }

    dmMsg(1, "rdataSize=%d bytes?\n", res->resSize);

    BOOL eof = FALSE;
    int left = res->resSize;
    char *ptr = res->resData;
    int current_section;
    while (!eof && left > 0)
    {
        int ret = ov_read(&vf, ptr, left > 4096 ? 4096 : left, &current_section);
        if (ret == 0)
            eof = TRUE;
        else
        if (ret < 0)
        {
            ov_clear(&vf);
            return DMERR_INVALID_DATA;
        }
        else
        {
            left -= ret;
            ptr += ret;
        }
    }

    ov_clear(&vf);
    return DMERR_OK;
}

static void engineResVorbisFree(DMResource *res)
{
    dmFree(res->resData);
}

static BOOL engineResVorbisProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && (strcasecmp(fext, ".ogg") == 0);
}
#endif


static DMResourceDataOps engineResOps[] =
{
    {
        engineResImageProbe,
        engineResImageLoad,
        engineResImageFree
    },

#ifdef JSS_SUP_JSSMOD
    {
        engineResJSSModuleProbe,
        engineResJSSModuleLoad,
        engineResJSSModuleFree
    },
#endif

#ifdef JSS_SUP_XM
    {
        engineResXMModuleProbe,
        engineResXMModuleLoad,
        engineResXMModuleFree
    },
#endif

#ifdef DM_USE_TREMOR
    {
        engineResVorbisProbe,
        engineResVorbisLoad,
        engineResVorbisFree
    },
#endif

};

static const int nengineResOps = sizeof(engineResOps) / sizeof(engineResOps[0]);


int engineClassifier(DMResource *res)
{
    int i;
    char *fext;

    if (res == NULL)
        return DMERR_NULLPTR;
    
    fext = strrchr(res->filename, '.');
    for (i = 0; i < nengineResOps; i++)
    {
        DMResourceDataOps *rops = &engineResOps[i];
        if (rops->probe != NULL && rops->probe(res, fext))
        {
            res->rops = rops;
            return DMERR_OK;
        }
    }
    
    return DMERR_OK;
}


void *engineGetResource(DMEngineData *eng, const char *name)
{
    DMResource *res;
    if (eng != NULL &&
        (res = dmResourceFind(eng->resources, name)) != NULL &&
        res->resData != NULL)
        return res->resData;
    else
    {
        dmError("Could not find resource '%s'.\n", name);
        return NULL;
    }
}


#ifdef DM_USE_JSS
void engineGetJSSInfo(DMEngineData *eng, BOOL *playing, int *order, JSSPattern **pat, int *npattern, int *row)
{
    JSS_LOCK(eng->jssPlr);

    *playing = eng->jssPlr->isPlaying;
    *row = eng->jssPlr->row;
    *pat = eng->jssPlr->pattern;
    *npattern = eng->jssPlr->npattern;
    *order = eng->jssPlr->order;

    JSS_UNLOCK(eng->jssPlr);
}

void engineGetJSSChannelInfo(DMEngineData *eng, const int channel, int *ninst, int *nextInst, int *freq, int *note)
{
    JSS_LOCK(eng->jssPlr);
    JSSPlayerChannel *chn = &(eng->jssPlr->channels[channel]);
    *ninst = chn->ninstrument;
    *nextInst = chn->nextInstrument;
    *freq = chn->freq;
    *note = chn->note;
    JSS_UNLOCK(eng->jssPlr);
}
#endif


int engineGetTick(DMEngineData *engine)
{
    return engine->frameTime - engine->startTime;
}


float engineGetTimeDT(DMEngineData *engine)
{
    return (float) engineGetTick(engine) / 1000.0f;
}


int engineGetTimeDTi(DMEngineData *engine)
{
    return (float) engineGetTick(engine) / 1000;
}


int engineGetTime(DMEngineData *engine, int t)
{
    return engineGetTick(engine) - (1000 * t);
}


int engineGetDT(DMEngineData *engine, int t)
{
    return engineGetTime(engine, t) / 1000;
}
