/************************************************************************
 ************************************************************************
 FAUST API Architecture File
 Copyright (C) 2017 GRAME, Romain Michon, CCRMA - Stanford University
 Copyright (C) 2014-2017 GRAME, Centre National de Creation Musicale
 ---------------------------------------------------------------------
 This Architecture section 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 3 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, see <http://www.gnu.org/licenses/>.
 
 EXCEPTION : As a special exception, you may create a larger work
 that contains this FAUST architecture section and distribute
 that work under terms of your choice, so long as this FAUST
 architecture section is not modified.
 ************************************************************************
 ************************************************************************/

#include <string>
#include "DspFaustNode.h"

using namespace v8;

Persistent<Function> DspFaustNode::constructor;

bool isInteger(const std::string& s)
{
    if(s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) return false;
    char* p;
    strtol(s.c_str(), &p, 10);
    return (*p == 0);
}

DspFaustNode::DspFaustNode() : DspFaust() {}

DspFaustNode::DspFaustNode(int sample_rate, int buffer_size) :
DspFaust(sample_rate, buffer_size) {}

DspFaustNode::~DspFaustNode() {}

void DspFaustNode::Init(Handle<Object> exports)
{
    Isolate* isolate = Isolate::GetCurrent();
    
    // Prepare constructor template
    Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
    tpl->SetClassName(String::NewFromUtf8(isolate, "DspFaustNode"));
    tpl->InstanceTemplate()->SetInternalFieldCount(2);
    
    // Prototypes
    NODE_SET_PROTOTYPE_METHOD(tpl, "destroy", destroy);
    NODE_SET_PROTOTYPE_METHOD(tpl, "start", start);
    NODE_SET_PROTOTYPE_METHOD(tpl, "stop", stop);
    NODE_SET_PROTOTYPE_METHOD(tpl, "isRunning", isRunning);
    NODE_SET_PROTOTYPE_METHOD(tpl, "keyOn", keyOn);
    NODE_SET_PROTOTYPE_METHOD(tpl, "keyOff", keyOff);
    NODE_SET_PROTOTYPE_METHOD(tpl, "newVoice", newVoice);
    NODE_SET_PROTOTYPE_METHOD(tpl, "deleteVoice", deleteVoice);
    NODE_SET_PROTOTYPE_METHOD(tpl, "allNotesOff", allNotesOff);
    NODE_SET_PROTOTYPE_METHOD(tpl, "propagateMidi", propagateMidi);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getJSONUI", getJSONUI);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getJSONMeta", getJSONMeta);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamsCount", getParamsCount);
    NODE_SET_PROTOTYPE_METHOD(tpl, "setParamValue", setParamValue);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamValue", getParamValue);
    NODE_SET_PROTOTYPE_METHOD(tpl, "setVoiceParamValue", setVoiceParamValue);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getVoiceParamValue", getVoiceParamValue);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamAddress", getParamAddress);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getVoiceParamAddress", getVoiceParamAddress);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamMin", getParamMin);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamMax", getParamMax);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getParamInit", getParamInit);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getMetadata", getMetadata);
    NODE_SET_PROTOTYPE_METHOD(tpl, "propagateAcc", propagateAcc);
    NODE_SET_PROTOTYPE_METHOD(tpl, "setAccConverter", setAccConverter);
    NODE_SET_PROTOTYPE_METHOD(tpl, "propagateGyr", propagateGyr);
    NODE_SET_PROTOTYPE_METHOD(tpl, "setGyrConverter", setGyrConverter);
    NODE_SET_PROTOTYPE_METHOD(tpl, "getCPULoad", getCPULoad);
    
    constructor.Reset(isolate, tpl->GetFunction());
    exports->Set(String::NewFromUtf8(isolate, "DspFaustNode"),
                 tpl->GetFunction());
}

void DspFaustNode::New(const FunctionCallbackInfo<Value>& args)
{
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);
    
    DspFaustNode* dspFaustNode;
    if (args[0]->IsUndefined()) { // if arg 0 is undefined then should be same for arg1
        dspFaustNode = new DspFaustNode();
    } else {
        dspFaustNode = new DspFaustNode((int)args[0]->NumberValue(),
                                        (int)args[1]->NumberValue());
    }
    dspFaustNode->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
}

void DspFaustNode::destroy(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    fNode->persistent().Reset();
    delete fNode;
}

void DspFaustNode::start(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(Boolean::New(isolate, fNode->DspFaust::start()));
}

void DspFaustNode::stop(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    fNode->DspFaust::stop();
}

void DspFaustNode::isRunning(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(Boolean::New(isolate, fNode->DspFaust::isRunning()));
}

void DspFaustNode::keyOn(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        int pitch = (int) args[0]->NumberValue();
        int velocity = (int) args[1]->NumberValue();
        args.GetReturnValue().Set(
                                  Number::New(isolate, fNode->DspFaust::keyOn(pitch,velocity)));
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::keyOff(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        int pitch = (int) args[0]->NumberValue();
        args.GetReturnValue().Set(
                                  Integer::New(isolate, fNode->DspFaust::keyOff(pitch)));
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::newVoice(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(Number::New(isolate,fNode->DspFaust::newVoice()));
}

void DspFaustNode::deleteVoice(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        unsigned long voice = (unsigned long) args[0]->NumberValue();
        args.GetReturnValue().Set(
                                  Integer::New(isolate, fNode->DspFaust::deleteVoice(voice)));
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::allNotesOff(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    fNode->DspFaust::allNotesOff();
}

void DspFaustNode::propagateMidi(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() &&
       !args[1]->IsUndefined() &&
       !args[2]->IsUndefined() &&
       !args[3]->IsUndefined() &&
       !args[4]->IsUndefined() &&
       !args[5]->IsUndefined()) {
        int count = (int) args[0]->NumberValue();
        double time = (int) args[1]->NumberValue();
        int type = (int) args[2]->NumberValue();
        int channel = (int) args[3]->NumberValue();
        int data1 = (int) args[4]->NumberValue();
        int data2 = (int) args[5]->NumberValue();
        fNode->DspFaust::propagateMidi(count,time,type,channel,data1,data2);
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getJSONUI(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(String::NewFromUtf8(isolate,fNode->DspFaust::getJSONUI()));
}

void DspFaustNode::getJSONMeta(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(String::NewFromUtf8(isolate,fNode->DspFaust::getJSONMeta()));
}

void DspFaustNode::getParamsCount(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(Integer::New(isolate,fNode->DspFaust::getParamsCount()));
}

void DspFaustNode::setParamValue(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        float paramValue = (float) args[1]->NumberValue();
        if (isInteger(paramAddress)) {
            fNode->DspFaust::setParamValue(std::stoi(paramAddress),paramValue);
        } else {
            fNode->DspFaust::setParamValue(paramAddress.c_str(),paramValue);
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getParamValue(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        if (isInteger(paramAddress)) {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamValue(std::stoi(paramAddress))));
        } else {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamValue(paramAddress.c_str())));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::setVoiceParamValue(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined() && !args[2]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        unsigned long voice = (unsigned long) args[1]->NumberValue();
        float paramValue = (float) args[2]->NumberValue();
        if (isInteger(paramAddress)) {
            fNode->DspFaust::setVoiceParamValue(std::stoi(paramAddress),voice,paramValue);
        } else {
            fNode->DspFaust::setVoiceParamValue(paramAddress.c_str(),voice,paramValue);
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getVoiceParamValue(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if(!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        unsigned long voice = (unsigned long) args[1]->NumberValue();
        if (isInteger(paramAddress)) {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getVoiceParamValue(std::stoi(paramAddress),voice)));
        } else {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getVoiceParamValue(paramAddress.c_str(),voice)));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getParamAddress(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        int paramID = (int) args[0]->NumberValue();
        args.GetReturnValue().Set(String::NewFromUtf8(isolate,
                                                      fNode->DspFaust::getParamAddress(paramID)));
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getVoiceParamAddress(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        int paramID = (int) args[0]->NumberValue();
        unsigned long voice = (unsigned long) args[1]->NumberValue();
        const char* address = fNode->DspFaust::getVoiceParamAddress(paramID,voice);
        // TODO: smothing's not working here but don't really know what. probably
        // some typing issue
        printf("Toto: %s\n",address);
        //args.GetReturnValue().Set(String::NewFromUtf8(isolate,
        //  fNode->DspFaust::getVoiceParamAddress(paramID,voice)));
        args.GetReturnValue().Set(String::NewFromUtf8(isolate,(char*)address));
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getParamMin(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if(!args[0]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        if(isInteger(paramAddress)) {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamMin(std::stoi(paramAddress))));
        } else {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamMin(paramAddress.c_str())));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getParamMax(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        if (isInteger(paramAddress)) {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamMax(std::stoi(paramAddress))));
        } else {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamMax(paramAddress.c_str())));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getParamInit(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        if(isInteger(paramAddress)) {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamInit(std::stoi(paramAddress))));
        } else {
            args.GetReturnValue().Set(Number::New(isolate,
                                                  fNode->DspFaust::getParamInit(paramAddress.c_str())));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getMetadata(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if(!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        std::string paramAddress(*v8::String::Utf8Value(args[0]->ToString()));
        std::string key(*v8::String::Utf8Value(args[1]->ToString()));
        if (isInteger(paramAddress)) {
            args.GetReturnValue().Set(String::NewFromUtf8(isolate,
                                                          fNode->DspFaust::getMetadata(std::stoi(paramAddress),key.c_str())));
        } else {
            args.GetReturnValue().Set(String::NewFromUtf8(isolate,
                                                          fNode->DspFaust::getMetadata(paramAddress.c_str(),key.c_str())));
        }
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::propagateAcc(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        int acc = (int) args[0]->NumberValue();
        float value = (float) args[1]->NumberValue();
        fNode->DspFaust::propagateAcc(acc,value);
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::setAccConverter(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() &&
       !args[1]->IsUndefined() &&
       !args[2]->IsUndefined() &&
       !args[3]->IsUndefined() &&
       !args[4]->IsUndefined() &&
       !args[5]->IsUndefined()) {
        int p = (int) args[0]->NumberValue();
        int acc = (int) args[1]->NumberValue();
        int curve = (int) args[2]->NumberValue();
        float amin = (float) args[3]->NumberValue();
        float amid = (float) args[4]->NumberValue();
        float amax = (float) args[5]->NumberValue();
        fNode->DspFaust::setAccConverter(p,acc,curve,amin,amid,amax);
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::propagateGyr(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() && !args[1]->IsUndefined()) {
        int gyr = (int) args[0]->NumberValue();
        float value = (float) args[1]->NumberValue();
        fNode->DspFaust::propagateGyr(gyr,value);
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::setGyrConverter(const v8::FunctionCallbackInfo<v8::Value>& args) {
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    if (!args[0]->IsUndefined() &&
       !args[1]->IsUndefined() &&
       !args[2]->IsUndefined() &&
       !args[3]->IsUndefined() &&
       !args[4]->IsUndefined() &&
       !args[5]->IsUndefined()) {
        int p = (int) args[0]->NumberValue();
        int gyr = (int) args[1]->NumberValue();
        int curve = (int) args[2]->NumberValue();
        float amin = (float) args[3]->NumberValue();
        float amid = (float) args[4]->NumberValue();
        float amax = (float) args[5]->NumberValue();
        fNode->DspFaust::setGyrConverter(p,gyr,curve,amin,amid,amax);
    } else {
        printf("Missing argument\n");
    }
}

void DspFaustNode::getCPULoad(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    DspFaustNode* fNode = ObjectWrap::Unwrap<DspFaustNode>(args.Holder());
    args.GetReturnValue().Set(Integer::New(isolate,fNode->DspFaust::getCPULoad()));
}
