/* 
vst~ - VST plugin object for PD 
based on the work of Jarno Sepp�nen and Mark Williamson

Copyright (c)2003-2005 Thomas Grill (gr@grrrr.org)
For information on usage and redistribution, and for a DISCLAIMER OF ALL
WARRANTIES, see the file, "license.txt," in this distribution.  
*/

#include "vsthost.h"
#include "editor.h"
#include <exception>
#include "flcontainers.h"

const t_symbol 
    *VSTPlugin::sym_param,
    *VSTPlugin::sym_event,
    *VSTPlugin::sym_evmidi,
    *VSTPlugin::sym_evaudio,
    *VSTPlugin::sym_evvideo,
    *VSTPlugin::sym_evparam,
    *VSTPlugin::sym_evtrigger,
    *VSTPlugin::sym_evsysex,
    *VSTPlugin::sym_ev_,
    *VSTPlugin::sym_midi[8];


class DelPlugin
    : public Fifo::Cell
{
public:
    DelPlugin(VSTPlugin *p): plug(p) {}
    VSTPlugin *plug;
};

static TypedLifo<DelPlugin> todel;
flext::ThrCond VSTPlugin::thrcond;

void VSTPlugin::Setup()
{
    LaunchThread(worker);

    sym_param = flext::MakeSymbol("param");
    sym_event = flext::MakeSymbol("event");
    sym_evmidi = flext::MakeSymbol("midi");
    sym_evaudio = flext::MakeSymbol("audio");
    sym_evvideo = flext::MakeSymbol("video");
    sym_evparam = flext::MakeSymbol("param");
    sym_evtrigger = flext::MakeSymbol("trigger");
    sym_evsysex = flext::MakeSymbol("sysex");
    sym_ev_ = flext::MakeSymbol("???");

    sym_midi[0] = flext::MakeSymbol("noteoff");
    sym_midi[1] = flext::MakeSymbol("note");
    sym_midi[2] = flext::MakeSymbol("atouch");
    sym_midi[3] = flext::MakeSymbol("ctlchg");
    sym_midi[4] = flext::MakeSymbol("progchg");
    sym_midi[5] = flext::MakeSymbol("atouch");
    sym_midi[6] = flext::MakeSymbol("pbend");
    sym_midi[7] = flext::MakeSymbol("sysex");
}

VSTPlugin::VSTPlugin(Responder *resp)
    : effect(NULL),pluginmain(NULL),audiomaster(NULL)
#if FLEXT_OS == FLEXT_OS_WIN
    , hdll(NULL)
#endif
    , hwnd(NULL)
    , responder(resp)
    , posx(0),posy(0),sizex(0),sizey(0)
    , visible(true),caption(true),handle(false)
    , midichannel(0),eventqusz(0),dumpevents(false)
    , paramnamecnt(0)
    , transchg(true)
    , playing(false),looping(false),feedback(false)
    , samplerate(0)
    , samplepos(0),ppqpos(0)
    , tempo(120)
    , timesignom(4),timesigden(4)
    , barstartpos(0)
    , cyclestartpos(0),cycleendpos(0)
    , smpteoffset(0),smpterate(0)
{}

VSTPlugin::~VSTPlugin()
{
	Free();
}

VSTPlugin *VSTPlugin::New(Responder *resp)
{
    FLEXT_ASSERT(resp);
    return new VSTPlugin(resp);
}

void VSTPlugin::Delete(VSTPlugin *p)
{
    FLEXT_ASSERT(p);

    // tell plugin to close editor!
    StopEditor(p);
    // transfer to deletion thread
    todel.Push(new DelPlugin(p));
    thrcond.Signal();
}

void VSTPlugin::worker(thr_params *)
{
    TypedLifo<DelPlugin> tmp;
    bool again = false;
    for(;;) {
        // wait for signal
        if(again) {
            thrcond.TimedWait(0.01);
            again = false;
        }
        else
            thrcond.Wait();

        DelPlugin *p;
        while((p = todel.Pop()) != NULL) {
            // see if editing has stopped
            if(p && p->plug->hwnd == NULL) {
                // yes, it is now safe to delete the plug
                delete p->plug;
                delete p;
            }
            else {
                tmp.Push(p);
                again = true;
            }
        }

        // put back remaining entries
        while((p = tmp.Pop()) != NULL) todel.Push(p);
    }
}
    
#if FLEXT_OS == FLEXT_OS_MAC
OSStatus FSPathMakeFSSpec(const UInt8 *path,FSSpec *spec,Boolean *isDirectory)  /* can be NULL */
{
    OSStatus  result;
    FSRef    ref;

    /* check parameters */
    require_action(NULL != spec, BadParameter, result = paramErr);

    /* convert the POSIX path to an FSRef */
    result = FSPathMakeRef(path, &ref, isDirectory);
    require_noerr(result, FSPathMakeRef);

    /* and then convert the FSRef to an FSSpec */
    result = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL);
    require_noerr(result, FSGetCatalogInfo);
  
FSGetCatalogInfo:
FSPathMakeRef:
BadParameter:
    return result;
}
#endif

// hdll, pluginmain and audiomaster are set here
// must be NULL beforehand!
bool VSTPlugin::NewPlugin(const char *plugname)
{
    FLEXT_ASSERT(!pluginmain && !audiomaster);

    dllname = plugname;

#if FLEXT_OS == FLEXT_OS_WIN
    hdll = LoadLibraryEx(dllname.c_str(),NULL,0 /*DONT_RESOLVE_DLL_REFERENCES*/);
    if(hdll) pluginmain = (PVSTMAIN)GetProcAddress(hdll,"main");
    audiomaster = Master;  

#elif FLEXT_OS == FLEXT_OS_MAC
    short   resFileID;
    FSSpec  spec;
    OSErr err;

    err = FSPathMakeFSSpec(dllname.c_str(),&spec,NULL);
    resFileID = FSpOpenResFile(&spec, fsRdPerm);
    short cResCB = Count1Resources('aEff');

    for(int i = 0; i < cResCB; i++) {
        Handle             codeH;
        CFragConnectionID  connID;
        Ptr                mainAddr;
        Str255             errName;
        Str255             fragName;
        char               fragNameCStr[256];
        short              resID;
        OSType             resType;

        codeH = Get1IndResource('aEff', short(i+1));
        if(!codeH) continue;

        GetResInfo(codeH, &resID, &resType, fragName);
        DetachResource(codeH);
        HLock(codeH);

        err = GetMemFragment(*codeH,
                             GetHandleSize(codeH),
                             fragName,
                             kPrivateCFragCopy,
                             &connID, (Ptr *) & mainAddr, errName);

        if(!err) {
           #ifdef __CFM__
           pluginmain = (PVSTMAIN)NewMachOFromCFM(mainAddr);
           #else
           pluginmain = (PVSTMAIN)mainAddr;
           #endif
        }
    }
    CloseResFile(resFileID);

    audiomaster = 
#ifdef __CFM__
        NewCFMFromMachO(Master);
#else
        Master;
#endif

#else
#error Platform not supported
#endif    

    if(pluginmain && audiomaster)
        return true;
    else {
        FreePlugin();
        return false;
    }
}

void VSTPlugin::FreePlugin()
{
#if FLEXT_OS == FLEXT_OS_WIN
    if(hdll) { FreeLibrary(hdll); hdll = NULL; }
#elif FLEXT_OS == FLEXT_OS_MAC
	
#ifdef __MACOSX__
#ifdef __CFM__
    if(audiomaster) DisposeCFMFromMachO(audiomaster);
    if(pluginmain) DisposeMachOFromCFM(pluginmain);
#endif
#endif

#else
#error Platform not supported
#endif    

    effect = NULL;
    audiomaster = NULL;
    pluginmain = NULL;
} 

/*
This is static to be able to communicate between the plugin methods 
and the static Audiomaster function
the this (plugin->user) pointer has not been initialized at the point it is needed
static should not be a problem, as we are single-threaded and it is immediately 
queried in a called function
*/
long VSTPlugin::uniqueid = 0;

std::string VSTPlugin::dllloading;

bool VSTPlugin::InstPlugin(long plugid)
{
    uniqueid = plugid;
    dllloading = dllname;

    FLEXT_ASSERT(pluginmain && audiomaster);

	//This calls the "main" function and receives the pointer to the AEffect structure.
    try { effect = pluginmain(audiomaster); }
    catch(std::exception &e) {
        flext::post("vst~ - caught exception while instantiating plugin: %s",e.what());
    }
    catch(...) {
        flext::post("vst~ - caught exception while instantiating plugin");
    }

    if(!effect) 
        return false;
    else if(effect->magic != kEffectMagic) {
		effect = NULL; 
	    return false;
    }
    return true;
}

bool VSTPlugin::Instance(const char *name,const char *subname)
{
    bool ok = false;
    FLEXT_ASSERT(effect == NULL);
    
    try {

/*
    if(!ok && dllname != name) {
        FreePlugin();
        // freshly load plugin
        ok = NewPlugin(name) && InstPlugin();
    }
*/
    ok = NewPlugin(name) && InstPlugin();

    if(ok && subname && *subname && Dispatch(effGetPlugCategory) == kPlugCategShell) {
        // sub plugin-name given -> scan plugs

        long plugid;
	    char tmp[64];

        // Waves5 continues with the next plug after the last loaded
        // that's not what we want - workaround: swallow all remaining
        while((plugid = Dispatch(effShellGetNextPlugin,0,0,tmp))) {}

        // restart from the beginning
	    while((plugid = Dispatch(effShellGetNextPlugin,0,0,tmp))) { 
		    // subplug needs a name
            FLEXT_LOG1("subplug %s",tmp);
            if(!strcmp(subname,tmp)) 
                // found
                break;
	    }

        // re-init with plugid set
        if(plugid) ok = InstPlugin(plugid);
    }

    if(ok) {
	    //init plugin 
	    effect->user = this;
	    ok = Dispatch(effOpen) == 0;
    }

    if(ok) {
	    ok = Dispatch(effIdentify) == 'NvEf';
    }

    if(ok) {
        *productname = 0;
	    long ret = Dispatch(effGetProductString,0,0,productname);

        if(!*productname) {
		    // no product name given by plugin -> extract it from the filename

		    std::string str1(dllname);
		    std::string::size_type slpos = str1.rfind('\\');
		    if(slpos == std::string::npos) {
			    slpos = str1.rfind('/');
			    if(slpos == std::string::npos)
				    slpos = 0;
			    else
				    ++slpos;
		    }
		    else
			    ++slpos;
		    std::string str2 = str1.substr(slpos);
		    int snip = str2.find('.');
            if( snip != std::string::npos )
			    str1 = str2.substr(0,snip);
		    else
			    str1 = str2;
		    strcpy(productname,str1.c_str());
	    }
    	
        if(*productname) {
            char tmp[512];
            sprintf(tmp,"vst~ - %s",productname);
            title = tmp;
        }
        else
            title = "vst~";

	    *vendorname = 0;
	    Dispatch(effGetVendorString,0,0,vendorname);
    }

    }
    catch(std::exception &e) {
        flext::post("vst~ - caught exception while loading plugin: %s",e.what());
        ok = false;
    }
    catch(...) {
        flext::post("vst~ - Caught exception while loading plugin");
        ok = false;
    }

    if(!ok) Free();
	return ok;
}

void VSTPlugin::Free() 
{
    // This should only also in destruction

    try {
	    if(effect) {
            FLEXT_ASSERT(!IsEdited());

            // shut down plugin
		    Dispatch(effMainsChanged, 0, 0);
		    Dispatch(effClose);
        }
    }
    catch(...) {}

    FreePlugin(); 
}

void VSTPlugin::DspInit(float sr,int blsz)
{
    try {
        // sample rate and block size must _first_ be set
	    Dispatch(effSetSampleRate,0,0,NULL,samplerate = sr);
	    Dispatch(effSetBlockSize, 0,blsz);
        // then signal that mains have changed!
        Dispatch(effMainsChanged,0,1);
    }
    catch(std::exception &e) {
        flext::post("vst~ - caught exception while initializing dsp: %s",e.what());
    }
    catch(...) {
        flext::post("vst~ - caught exception while initializing dsp");
    }
}

void VSTPlugin::ListPlugs(const t_symbol *sym) const
{
    if(responder) {
        if(Is() && Dispatch(effGetPlugCategory) == kPlugCategShell) {
            t_atom at;
            // sub plugin-name given -> scan plugs
	        char tmp[64];
	        // scan shell for subplugins
            while(Dispatch(effShellGetNextPlugin,0,0,tmp)) {
                SetString(at,tmp);
                responder->Respond(sym,1,&at);
            }
        }

        // bang
        responder->Respond(sym);
    }
}