/*
    Abstract interpreter interface.
    Copyright (c) 2003 2004 stefan kersten.

    ====================================================================

    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "SC_LanguageClient.h"
#include "SC_Lock.h"
#include <cstring>
#include <string>
#include <cerrno>

#ifdef _WIN32
#    include <stdio.h>
#    include <direct.h>
#    define snprintf _snprintf
#    ifndef PATH_MAX
#        define PATH_MAX _MAX_PATH
#    endif
#else
#    include <unistd.h>
#endif

#include "PyrObject.h"
#include "PyrKernel.h"
#include "PyrPrimitive.h"
#include "PyrSched.h"
#include "GC.h"
#include "VMGlobals.h"
#include "SCBase.h"
#include "SC_StringBuffer.h"

void closeAllGUIScreens();
void initGUI();
void initGUIPrimitives();

extern PyrString* newPyrStringN(class PyrGC* gc, long length, long flags, long collect);

// =====================================================================
// SC_LanguageClient
// =====================================================================

SC_LanguageClient* gInstance = 0;
SC_Lock gInstanceMutex;

class HiddenLanguageClient {
public:
    HiddenLanguageClient(): mPostFile(0), mScratch(0), mRunning(false) {}

    std::string mName;
    FILE* mPostFile;
    SC_StringBuffer mScratch;
    bool mRunning;
};

SC_LanguageClient::SC_LanguageClient(const char* name) {
    mHiddenClient = new HiddenLanguageClient;

    lockInstance();

    if (gInstance) {
        unlockInstance();
        fprintf(stderr, "SC_LanguageClient already running\n");
        abort();
    }

    mHiddenClient->mName = name;
    gInstance = this;

    unlockInstance();
}

SC_LanguageClient::~SC_LanguageClient() {
    lockInstance();
    gInstance = nullptr;
    delete mHiddenClient;
    unlockInstance();
}

void SC_LanguageClient::initRuntime(const Options& opt) {
    // start virtual machine
    if (!mHiddenClient->mRunning) {
        mHiddenClient->mRunning = true;
        if (opt.mRuntimeDir) {
            int err = chdir(opt.mRuntimeDir);
            if (err)
                error("Cannot change to runtime directory: %s", strerror(errno));
        }
        pyr_init_mem_pools(opt.mMemSpace, opt.mMemGrow);
        init_OSC(opt.mPort);
        schedInit();
        onInitRuntime();
    }
}

void SC_LanguageClient::shutdownRuntime() {
    schedCleanup();
    cleanup_OSC();
}

void SC_LanguageClient::compileLibrary(bool standalone) { ::compileLibrary(standalone); }

extern void shutdownLibrary();
void SC_LanguageClient::shutdownLibrary() {
    ::shutdownLibrary();
    flush();
}

void SC_LanguageClient::recompileLibrary(bool standalone) { compileLibrary(standalone); }

void SC_LanguageClient::setCmdLine(const char* buf, size_t size) {
    if (isLibraryCompiled()) {
        lock();
        if (isLibraryCompiled()) {
            VMGlobals* g = gMainVMGlobals;

            PyrString* strobj = newPyrStringN(g->gc, size, 0, true);
            memcpy(strobj->s, buf, size);

            SetObject(&slotRawInterpreter(&g->process->interpreter)->cmdLine, strobj);
            g->gc->GCWriteNew(slotRawObject(&g->process->interpreter),
                              strobj); // we know strobj is white so we can use GCWriteNew
        }
        unlock();
    }
}

void SC_LanguageClient::setCmdLine(const char* str) { setCmdLine(str, strlen(str)); }

void SC_LanguageClient::setCmdLinef(const char* fmt, ...) {
    SC_StringBuffer& scratch = mHiddenClient->mScratch;
    va_list ap;
    va_start(ap, fmt);
    scratch.reset();
    scratch.vappendf(fmt, ap);
    va_end(ap);
    setCmdLine(scratch.getData());
}

void SC_LanguageClient::runLibrary(PyrSymbol* symbol) {
    lock();
    ::runLibrary(symbol);
    unlock();
}

void SC_LanguageClient::runLibrary(const char* methodName) {
    lock();
    ::runLibrary(getsym(methodName));
    unlock();
}

void SC_LanguageClient::executeFile(const char* fileName) {
    std::string escaped_file_name(fileName);
    int i = 0;
    while (i < escaped_file_name.size()) {
        if (escaped_file_name[i] == '\\')
            escaped_file_name.insert(++i, 1, '\\');
        ++i;
    }

    setCmdLinef("thisProcess.interpreter.executeFile(\"%s\")", escaped_file_name.c_str());
    runLibrary(s_interpretCmdLine);
}

void SC_LanguageClient::snprintMemArg(char* dst, size_t size, int arg) {
    int rem = arg;
    int mod = 0;
    const char* modstr = "";

    while (((rem % 1024) == 0) && (mod < 4)) {
        rem /= 1024;
        mod++;
    }

    switch (mod) {
    case 0:
        modstr = "";
        break;
    case 1:
        modstr = "k";
        break;
    case 2:
        modstr = "m";
        break;
    case 3:
        modstr = "g";
        break;
    default:
        rem = arg;
        modstr = "";
        break;
    }

    snprintf(dst, size, "%d%s", rem, modstr);
}

bool SC_LanguageClient::parseMemArg(const char* arg, int* res) {
    long value, factor = 1;
    char* endPtr = 0;

    if (*arg == '\0')
        return false;

    value = strtol(arg, &endPtr, 0);

    char spec = *endPtr++;
    if (spec != '\0') {
        if (*endPtr != '\0')
            // trailing characters
            return false;

        switch (spec) {
        case 'k':
            factor = 1024;
            break;
        case 'm':
            factor = 1024 * 1024;
            break;
        default:
            // invalid mem spec
            return false;
        }
    }

    *res = value * factor;

    return true;
}

bool SC_LanguageClient::parsePortArg(const char* arg, int* res) {
    long value;
    char* endPtr;

    if (*arg == '\0')
        return false;

    value = strtol(arg, &endPtr, 0);

    if ((*endPtr != '\0') || (value < 0) || (value > 65535))
        return false;

    *res = value;

    return true;
}

void SC_LanguageClient::tick() {
    if (trylock()) {
        if (isLibraryCompiled()) {
            ::runLibrary(s_tick);
        }
        unlock();
    }
    flush();
}

bool SC_LanguageClient::tickLocked(double* nextTime) {
    if (isLibraryCompiled()) {
        ::runLibrary(s_tick);
    }

    return slotDoubleVal(&gMainVMGlobals->result, nextTime) == errNone;
}

void SC_LanguageClient::onInitRuntime() {}

void SC_LanguageClient::onLibraryStartup() {}

void SC_LanguageClient::onLibraryShutdown() {}

void SC_LanguageClient::onInterpStartup() {}

// runLibrary methods
void SC_LanguageClient::interpretCmdLine() { runLibrary(s_interpretCmdLine); }
void SC_LanguageClient::interpretPrintCmdLine() { runLibrary(s_interpretPrintCmdLine); }
void SC_LanguageClient::runMain() { runLibrary(s_run); }
void SC_LanguageClient::stopMain() { runLibrary(s_stop); }

// locks
void SC_LanguageClient::lock() { gLangMutex.lock(); }
bool SC_LanguageClient::trylock() { return gLangMutex.try_lock(); }
void SC_LanguageClient::unlock() { gLangMutex.unlock(); }

SC_LanguageClient* SC_LanguageClient::instance() { return gInstance; }

void SC_LanguageClient::lockInstance() { gInstanceMutex.lock(); }
void SC_LanguageClient::unlockInstance() { gInstanceMutex.unlock(); }

extern bool compiledOK;

const char* SC_LanguageClient::getName() const { return mHiddenClient->mName.c_str(); }

FILE* SC_LanguageClient::getPostFile() { return mHiddenClient->mPostFile; }
void SC_LanguageClient::setPostFile(FILE* file) { mHiddenClient->mPostFile = file; }

bool SC_LanguageClient::isLibraryCompiled() { return compiledOK; }

int SC_LanguageClient::run(int argc, char** argv) {
    throw std::runtime_error("SC_LanguageClient::run only supported on terminal client");
}

// =====================================================================
// library functions
// =====================================================================

void setPostFile(FILE* file) { SC_LanguageClient::instance()->setPostFile(file); }

int vpost(const char* fmt, va_list ap) {
    char buf[512];
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    if (n > 0) {
        SC_LanguageClient* client = SC_LanguageClient::lockedInstance();
        if (client)
            client->postText(buf, sc_min(n, sizeof(buf) - 1));
        SC_LanguageClient::unlockInstance();
    }
    return 0;
}

void post(const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vpost(fmt, ap);
    va_end(ap);
}

void postfl(const char* fmt, ...) {
    char buf[512];
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    if (n > 0) {
        SC_LanguageClient* client = SC_LanguageClient::lockedInstance();
        if (client)
            client->postFlush(buf, sc_min(n, sizeof(buf) - 1));
        SC_LanguageClient::unlockInstance();
    }
    va_end(ap);
}

void postText(const char* str, long len) {
    SC_LanguageClient* client = SC_LanguageClient::lockedInstance();
    if (client)
        client->postFlush(str, len);
    SC_LanguageClient::unlockInstance();
}

void postChar(char c) {
    SC_LanguageClient* client = SC_LanguageClient::lockedInstance();
    if (client)
        client->postFlush(&c, sizeof(char));
    SC_LanguageClient::unlockInstance();
}

void error(const char* fmt, ...) {
    char buf[512];
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    if (n > 0) {
        SC_LanguageClient* client = SC_LanguageClient::lockedInstance();
        if (client)
            client->postError(buf, sc_min(n, sizeof(buf) - 1));
        SC_LanguageClient::unlockInstance();
    }
    va_end(ap);
}

void flushPostBuf(void) { SC_LanguageClient::instance()->flush(); }

void closeAllGUIScreens() { SC_LanguageClient::instance()->onLibraryShutdown(); }

void initGUI() { SC_LanguageClient::instance()->onInterpStartup(); }

void initGUIPrimitives() { SC_LanguageClient::instance()->onLibraryStartup(); }

long scMIDIout(int port, int len, int statushi, int chan, int data1, int data2);
long scMIDIout(int port, int len, int statushi, int chan, int data1, int data2) { return 0; }
// EOF
