//
// Demo Editor -- Main program
// (C) Copyright 2012 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
//
#include <SDL.h>
#include "dmengine.h"
#include "edmain.h"
#include <QSettings>
#include <QGLWidget>


int main(int argc, char *argv[])
{
    dmVerbosity = 5;

    QApplication app(argc, argv);

    app.setOrganizationName("TNSP");
    app.setOrganizationDomain("tnsp.org");
    app.setApplicationName(PROGRAM_NAME);
    app.setApplicationVersion(PROGRAM_VERSION);

    DemoEditor mainWin;

    mainWin.show();
          
    return app.exec();
}


void engineAudioCallback(void *userdata, Uint8 * stream, int len)
{
    DMEngineData *engine = (DMEngineData *) userdata;

    if (engine->paused)
    {
        memset(stream, 0, len);
    }
    else
#ifdef DM_USE_JSS
    {
        if (engine->dev != NULL)
            jvmRenderAudio(engine->dev, stream,
                           len / jvmGetSampleSize(engine->dev));
    }
#endif
#ifdef DM_USE_TREMOR
    if (engine->audioPos + len >= engine->audioRes->resSize)
    {
        engine->exitFlag = true;
    }
    else
    {
        memcpy(stream, (Uint8 *) engine->audioRes->resData + engine->audioPos, len);
        engine->audioPos += len;
    }
#endif
}


int DemoEditor::reopenResources()
{
    int err;

    if ((err = dmResourcesInit(
        &engine.resources, engine.optPackFilename, engine.optDataPath,
        engine.optResFlags, engineClassifier)) != DMERR_OK)
    {
        dmError("Could not initialize resource manager: %d, %s.\n",
            err, dmErrorStr(err));
    }
    return err;
}


int DemoEditor::loadResources()
{
    int err, loaded = 0, total = 0;
    BOOL first = TRUE;

    do
    {
/*
        // Show a nice progress bar while loading
        if ((err = engineShowProgress(loaded, total)) != DMERR_OK)
            return err;
*/
        err = dmResourcesPreload(engine.resources, first, &loaded, &total);
        first = FALSE;
    }
    while (err == DMERR_PROGRESS);
    return DMERR_OK;
}


DemoEditor::DemoEditor()
{
    int err;

    resize(1024, 768);
    setWindowTitle(QCoreApplication::applicationName());

    memset(&engine, 0, sizeof(engine));
    initSDL = FALSE;
    currTimeline = NULL;

    // Pre-initialization
    if ((err = demoPreInit(&engine)) != DMERR_OK)
        goto error_exit;

    // Initialize SDL components
    dmPrint(1, "Initializing libSDL.\n");
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = true;

    // Initialize audio parts
    if (engine.optAfmt.freq == 0 && engine.optAfmt.channels == 0)
    {
        // Defaults, if none seem to be set
        engine.optAfmt.freq = 44100;
        engine.optAfmt.format = AUDIO_S16SYS;
        engine.optAfmt.channels = 2;
        engine.optAfmt.samples = engine.optAfmt.freq / 16;
    }

#ifdef DM_USE_JSS
    jssInit();

    switch (engine.optAfmt.format)
    {
        case AUDIO_S16SYS:
            engine.jss_format = JSS_AUDIO_S16;
            break;
        case AUDIO_U16SYS:
            engine.jss_format = JSS_AUDIO_U16;
            break;
        case AUDIO_S8:
            engine.jss_format = JSS_AUDIO_S8;
            break;
        case AUDIO_U8:
            engine.jss_format = JSS_AUDIO_U8;
            break;
    }

    dmPrint(1, "Initializing miniJSS mixer with fmt=%d, chn=%d, freq=%d\n",
            engine.jss_format, engine.optAfmt.channels, engine.optAfmt.freq);

    if ((engine.dev =
         jvmInit(engine.jss_format, engine.optAfmt.channels,
                 engine.optAfmt.freq, JMIX_AUTO)) == NULL)
    {
        dmError("jvmInit() returned NULL, voi perkele.\n");
        goto error_exit;
    }

    if ((engine.plr = jmpInit(engine.dev)) == NULL)
    {
        dmError("jmpInit() returned NULL\n");
        goto error_exit;
    }
#endif

    // Initialize SDL audio
    dmPrint(1, "Trying to init SDL audio with: fmt=%d, chn=%d, freq=%d\n",
            engine.optAfmt.format, engine.optAfmt.channels,
            engine.optAfmt.freq);

    engine.optAfmt.callback = engineAudioCallback;

    if (SDL_OpenAudio(&engine.optAfmt, NULL) < 0)
    {
        dmError("Couldn't open SDL audio: %s\n", SDL_GetError());
        goto error_exit;
    }

    // Initialize SDL video
    if (engine.demoInitPreVideo != NULL &&
        (err = engine.demoInitPreVideo(&engine)) != DMERR_OK)
    {
        dmError("demoInitPreVideo() failed, %d: %s\n", err, dmErrorStr(err));
        goto error_exit;
    }

    dmPrint(1, "Initializing SDL video %d x %d x %dbpp, flags=0x%08x\n",
        engine.optVidWidth, engine.optVidHeight, engine.optVidDepth, engine.optVFlags);

    engine.screen = SDL_CreateRGBSurface(SDL_SWSURFACE, engine.optVidWidth, engine.optVidHeight, engine.optVidDepth, 0, 0, 0, 0);
    if (engine.screen == NULL)
    {
        dmError("Could not allocate video backbuffer surface.\n");
        goto error_exit;
    }

    if (engine.demoInitPostVideo != NULL &&
        (err = engine.demoInitPostVideo(&engine)) != DMERR_OK)
    {
        dmError("demoInitPostVideo() failed, %d: %s\n", err, dmErrorStr(err));
        goto error_exit;
    }


error_exit:

    // Setup GUI elements
    createMainGUI();
    createNewFile();
    timelineView->setTimeline(currTimeline);
    settingsRestore();
    initEffectsAndResources();
    statusMsg("Application started.");
}


DemoEditor::~DemoEditor()
{
    statusMsg("Shutting down.");

    settingsSave();
    delete demoView;
    historyReset();

    if (engine.screen)
        SDL_FreeSurface(engine.screen);

    SDL_LockAudio();
    SDL_PauseAudio(1);
#ifdef DM_USE_JSS
    jmpClose(engine.plr);
    jvmClose(engine.dev);
    jssClose();
#endif
    SDL_UnlockAudio();

    shutdownEffectsAndResources();

    if (initSDL)
        SDL_Quit();
}


int DemoEditor::getTimelineDuration()
{
    return timelineAudioTrack->getDuration();
}


void DemoEditor::updateResourceView()
{
}


void DemoEditor::updateTimelineView()
{
    demoView->setEngineData(&engine);    

    if (engine.audioRes != NULL)
    {
        timelineAudioTrack->setWaveform(
            engine.audioRes->resData, engine.audioRes->resSize,
            engine.optAfmt.format, engine.optAfmt.channels,
            engine.optAfmt.freq);
    }

    timelineAudioTrack->setOffset(currViewOffset);
    timelineAudioTrack->setScale(currViewScale);
    

    timelineView->setTime(currFrameTime);
    timelineView->setOffset(currViewOffset);
    timelineView->setScale(currViewScale);

    timelineScrollBar->setRange(0, getTimelineDuration());
    timelineScrollBar->setValue(currViewOffset);
}


void DemoEditor::actionTimelineScrollChanged(int value)
{   
    currViewOffset = value;
    updateTimelineView();
}


void DemoEditor::actionOffsetChanged(float value)
{   
    currViewOffset = value;
    updateTimelineView();
}


void DemoEditor::actionTimeChanged(float value)
{   
    currFrameTime = value;
    updateTimelineView();
}


void DemoEditor::actionTimelineChanged()
{
    updateMenuStates();
    update();
}


int DemoEditor::initEffectsAndResources()
{
    int err;

    currViewOffset = 0;
    currFrameTime = 0;
    currViewScale = 1.0f;
    
    // Initialize resource subsystem
    statusMsg("Initializing resources subsystem.");
    if ((err = reopenResources()) != DMERR_OK)
        return err;

    // Load resources
    statusMsg("Loading resources, please wait...");
    if ((err = loadResources()) != DMERR_OK)
    {
        dmError("Error loading resources, %d: %s.\n", err, dmErrorStr(err));
        return err;
    }


    // Final initializations
    statusMsg("Initializing custom demo data.");
    if ((err = engine.demoInit(&engine)) != DMERR_OK)
    {
        dmError("Failure in demoInit(), %d: %s\n",
            err, dmErrorStr(err));
        return err;
    }

    // Initialize effects
    statusMsg("Initializing effects ...");
    if ((err = engineInitializeEffects(&engine)) != DMERR_OK)
    {
        dmError("Effects initialization failed, %d: %s\n",
            err, dmErrorStr(err));
        return err;
    }

    // Etc.
    rehash();
    return DMERR_OK;
}


void DemoEditor::shutdownEffectsAndResources()
{
    delete currTimeline;
    dmFreePreparedTimelineData(engine.ptl);
    engineShutdownEffects(&engine);
    dmResourcesClose(engine.resources);

    if (engine.demoShutdown != NULL)
        engine.demoShutdown(&engine);
}


void DemoEditor::rehash()
{
    timelineView->setTimeline(currTimeline);
    updateResourceView();
    updateTimelineView();
    updateMenuStates();
    update();
}


void DemoEditor::createNewFile()
{
    delete currTimeline;
    currTimeline = new EDTimelineObject();

    DMTimelineTrack *tr;
    dmTimelineAddTrack(currTimeline->tl, &tr, "Penis");
    
    DMTimelineEvent *ev;
    dmTimelineTrackAddEvent(tr, 500, 100, &ev);
    
    dmTimelineEventSetEffectByIndex(ev, 0);

    historyReset();
    updateMenuStates();
    update();
}


void DemoEditor::readFromFile(QString filename)
{
    EDTimelineObject *tmp = new EDTimelineObject();
    int ret = tmp->load(filename);
    if (ret != DMERR_OK)
    {
        showFileErrorDialog("Loading demo blob file "+ filename, ret);
        delete tmp;
    }
    else
    {
        delete currTimeline;
        currTimeline = tmp;
        rehash();
    }
}


void DemoEditor::saveToFile(QString filename)
{
    int ret = currTimeline->save(filename);
    if (ret != DMERR_OK)
    {
        showFileErrorDialog("Saving demo blob file "+ filename, ret);
    }

    updateMenuStates();
}


void DemoEditor::settingsRestore()
{
    QSettings s;

    restoreGeometry(s.value("windowGeometry").toByteArray());
    restoreState(s.value("windowState").toByteArray());
}


void DemoEditor::settingsSave()
{
    QSettings s;

    s.setValue("windowGeometry", saveGeometry());
    s.setValue("windowState", saveState());
}


//
// Edit history functionality
//
void DemoEditor::historyReset()
{
    if (currTimeline != NULL)
        currTimeline->scrub();

    undoHistoryPos = -1;
    undoHistoryMax = DOC_UNDO_MAX;
    undoHistory.clear();
}


void DemoEditor::historyPush(QString description)
{
    if (currTimeline == NULL)
        return;

    if (!undoHistory.isEmpty() && undoHistory.last()->state() == "-")
    {
        delete undoHistory.takeLast();
    }
    
    while (undoHistory.size() >= undoHistoryMax)
    {
        delete undoHistory.takeFirst();
    }
    
    EDTimelineObject *copy = new EDTimelineObject(currTimeline);
    copy->setState(description);
    undoHistory.append(copy);
}


void DemoEditor::historyTop()
{
    if (currTimeline == NULL)
        return;

    EDTimelineObject *copy = new EDTimelineObject(currTimeline);
    copy->setState("-");
    undoHistory.append(copy);

    undoHistoryPos = undoHistory.size() - 1;
    currTimeline->touch();
    updateTimelineView();
    updateMenuStates();
    update();
}


void DemoEditor::historyPop()
{
    if (!undoHistory.isEmpty())
    {
        delete undoHistory.takeLast();
    }
}


void DemoEditor::performRedo()
{
    if (undoHistoryPos >= 0 && undoHistoryPos < undoHistory.size() - 1)
    {
        undoHistoryPos++;
        delete currTimeline;
        currTimeline = new EDTimelineObject(undoHistory.at(undoHistoryPos));
        currTimeline->touch();
        updateTimelineView();
        updateMenuStates();
        update();
    }
}


void DemoEditor::performUndo()
{
    if (undoHistoryPos > 0 && undoHistory.size() > 1)
    {
        undoHistoryPos--;
        delete currTimeline;
        currTimeline = new EDTimelineObject(undoHistory.at(undoHistoryPos));
        currTimeline->touch();
        updateTimelineView();
        updateMenuStates();
        update();
    }
}
