/*
    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_Lib.h"
#include "SC_CoreAudio.h"
#include "SC_HiddenWorld.h"
#include "SC_Graph.h"
#include "SC_GraphDef.h"
#include "SC_Group.h"
#include "SC_UnitDef.h"
#include <stdexcept>
#include "SC_Lib_Cintf.h"
#include "SC_SequencedCommand.h"
#include <new>
#include "SC_Prototypes.h"
#include "scsynthsend.h"
#include "SC_WorldOptions.h"
#include "SC_Version.hpp"

extern int gMissingNodeID;

// returns number of bytes in an OSC string.
int OSCstrlen(char* strin);

Node* Msg_GetNode(World* inWorld, sc_msg_iter& msg) {
    Node* node;
    if (msg.nextTag('i') == 's') {
        const char* loc = msg.gets();
        int32 nodeID = msg.geti();
        gMissingNodeID = nodeID;
        node = World_GetNode(inWorld, nodeID);
        while (*loc) {
            if (!node)
                return nullptr;
            switch (*loc) {
            case 'h':
                if (!node->mIsGroup)
                    return nullptr;
                node = ((Group*)node)->mHead;
                break;
            case 't':
                if (!node->mIsGroup)
                    return nullptr;
                node = ((Group*)node)->mTail;
                break;
            case 'u':
                node = &node->mParent->mNode;
                break;
            case 'p':
                node = node->mPrev;
                break;
            case 'n':
                node = node->mNext;
                break;
            }
            loc++;
        }
    } else {
        int32 nodeID = msg.geti();
        gMissingNodeID = nodeID;
        node = World_GetNode(inWorld, nodeID);
    }
    return node;
}

Group* Msg_GetGroup(World* inWorld, sc_msg_iter& msg) {
    Node* node = Msg_GetNode(inWorld, msg);
    return node && node->mIsGroup ? (Group*)node : nullptr;
}

Graph* Msg_GetGraph(World* inWorld, sc_msg_iter& msg) {
    Node* node = Msg_GetNode(inWorld, msg);
    return !node || node->mIsGroup ? nullptr : (Graph*)node;
}

SCErr meth_none(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_none(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) { return kSCErr_None; }

SCErr meth_sync(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_sync(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(SyncCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}

SCErr meth_b_alloc(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_alloc(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufAllocCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}

SCErr meth_b_free(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_free(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufFreeCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_close(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_close(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufCloseCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_allocRead(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_allocRead(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufAllocReadCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_read(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_read(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufReadCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_allocReadChannel(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_allocReadChannel(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufAllocReadChannelCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_readChannel(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_readChannel(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufReadChannelCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_write(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_write(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufWriteCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_b_zero(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_zero(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufZeroCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}


SCErr meth_u_cmd(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_u_cmd(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    return Unit_DoCmd(inWorld, inSize, inData);
};

SCErr meth_cmd(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_cmd(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    return PlugIn_DoCmd(inWorld, inSize, inData, inReply);
};


/*
SCErr meth_n_cmd(World *inWorld, int inSize, char *inData, ReplyAddress *inReply);
SCErr meth_n_cmd(World *inWorld, int inSize, char *inData, ReplyAddress *inReply)
{
    sc_msg_iter msg(inSize, inData);
    Node *node = Msg_GetNode(inWorld, msg);
    if (!node) return kSCErr_NodeNotFound;

    char *args = msg.rdpos;
    int arglen = msg.remain();

//!!	(node->mDef->fNodeCmd)(node, arglen, args);

    return kSCErr_None;
}
*/

SCErr meth_n_trace(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_trace(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Node_Trace(node);
    }

    return kSCErr_None;
}

SCErr meth_g_dumpTree(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_dumpTree(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        int32 flag = msg.geti();
        if (flag) {
            Group_DumpTreeAndControls(group);
        } else {
            Group_DumpTree(group);
        }
    }

    return kSCErr_None;
}

// SCErr meth_g_queryTree(World *inWorld, int inSize, char *inData, ReplyAddress *inReply);
// SCErr meth_g_queryTree(World *inWorld, int inSize, char *inData, ReplyAddress* inReply)
//{
//	sc_msg_iter msg(inSize, inData);
//	while (msg.remain()) {
//		Group *group = Msg_GetGroup(inWorld, msg);
//		if (!group) return kSCErr_GroupNotFound;
//
//		// first count the total number of nodes to know how many tags the packet should have
//		int numNodes = 1; // include this one
//
//		Group_CountNodes(group, &numNodes);
//
//		big_scpacket packet;
//		packet.adds("/g_queryTree.reply");
//		packet.maketags(numNodes * 3 + 1);
//		packet.addtag(',');
//
//		Group_QueryTree(group, &packet);
//
//		if (packet.size()) {
//			CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
//		}
//	}
//	return kSCErr_None;
//}

SCErr meth_g_queryTree(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_queryTree(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        big_scpacket packet;
        packet.adds("/g_queryTree.reply");
        int32 flag = msg.geti();
        if (flag) {
            // first count the total number of nodes to know how many tags the packet should have
            int numNodes = 1; // include this one
            int numControlsAndDefs = 0;
            Group_CountNodeAndControlTags(group, &numNodes, &numControlsAndDefs);
            // nodeID and numChildren + numControlsAndDefs + controlFlag
            packet.maketags(numNodes * 2 + numControlsAndDefs + 2);
            packet.addtag(',');
            packet.addtag('i');
            packet.addi(1); // include controls flag
            Group_QueryTreeAndControls(group, &packet);
        } else {
            // first count the total number of nodes to know how many tags the packet should have
            int numNodeTags = 2; // include this one
            Group_CountNodeTags(group, &numNodeTags);
            packet.maketags(numNodeTags + 2); // nodeID and numChildren
            packet.addtag(',');
            packet.addtag('i');
            packet.addi(0); // include controls flag
            Group_QueryTree(group, &packet);
        }

        if (packet.size()) {
            CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
        }
    }
    return kSCErr_None;
}


SCErr meth_n_run(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_run(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        int32 run = msg.geti();

        Node_SetRun(node, run);
    }

    return kSCErr_None;
}

SCErr meth_n_map(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_map(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain() >= 8) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int bus = msg.geti();
            Node_MapControl(node, Hash(name), name, 0, bus);
        } else {
            int32 index = msg.geti();
            int32 bus = msg.geti();
            Node_MapControl(node, index, bus);
        }
    }
    return kSCErr_None;
}

SCErr meth_n_mapn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_mapn(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain() >= 12) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            int bus = msg.geti();
            int n = msg.geti();
            for (int i = 0; i < n; ++i) {
                Node_MapControl(node, hash, name, i, bus == -1 ? -1 : bus + i);
            }
        } else {
            int32 index = msg.geti();
            int32 bus = msg.geti();
            int n = msg.geti();
            for (int i = 0; i < n; ++i) {
                Node_MapControl(node, index + i, bus == -1 ? -1 : bus + i);
            }
        }
    }
    return kSCErr_None;
}

SCErr meth_n_mapa(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_mapa(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain() >= 8) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int bus = msg.geti();
            Node_MapAudioControl(node, Hash(name), name, 0, bus);
        } else {
            int32 index = msg.geti();
            int32 bus = msg.geti();
            Node_MapAudioControl(node, index, bus);
        }
    }
    return kSCErr_None;
}

SCErr meth_n_mapan(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_mapan(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain() >= 12) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            int bus = msg.geti();
            int n = msg.geti();
            for (int i = 0; i < n; ++i) {
                Node_MapAudioControl(node, hash, name, i, bus == -1 ? -1 : bus + i);
            }
        } else {
            int32 index = msg.geti();
            int32 bus = msg.geti();
            int n = msg.geti();
            for (int i = 0; i < n; ++i) {
                Node_MapAudioControl(node, index + i, bus == -1 ? -1 : bus + i);
            }
        }
    }
    return kSCErr_None;
}

SCErr meth_n_set(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_set(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;
    while (msg.remain() >= 8) {
        int i = 0;
        int loop = 0;
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            do {
                switch (msg.nextTag('f')) {
                case 'f':
                case 'i': {
                    float32 value = msg.getf();
                    Node_SetControl(node, hash, name, i, value);
                    ++i;
                    break;
                }
                case 's': {
                    const char* string = msg.gets();
                    if (*string == 'c') {
                        int bus = sc_atoi(string + 1);
                        Node_MapControl(node, hash, name, i, bus);
                        ++i;
                    }
                    if (*string == 'a') {
                        int bus = sc_atoi(string + 1);
                        Node_MapAudioControl(node, hash, name, i, bus);
                        ++i;
                    }
                    break;
                }
                case ']':
                    msg.count++;
                    loop--;
                    break;
                case '[':
                    msg.count++;
                    loop++;
                    break;
                }
            } while (loop);
        } else {
            int32 index = msg.geti();
            do {
                switch (msg.nextTag('f')) {
                case 'f':
                case 'i': {
                    float32 value = msg.getf();
                    Node_SetControl(node, index + i, value);
                    ++i;
                    break;
                }
                case 's': {
                    const char* string = msg.gets();
                    if (*string == 'c') {
                        int bus = sc_atoi(string + 1);
                        Node_MapControl(node, index + i, bus);
                        ++i;
                    }
                    if (*string == 'a') {
                        int bus = sc_atoi(string + 1);
                        Node_MapAudioControl(node, index + i, bus);
                        ++i;
                    }
                    break;
                }
                case ']':
                    msg.count++;
                    loop--;
                    break;
                case '[':
                    msg.count++;
                    loop++;
                    break;
                }
            } while (loop);
        }
    }

    //{
    //	int i = 0;
    //	int loop = 0;
    //	if (msg.nextTag('i') == 's') {
    //	    int32* name = msg.gets4();
    //	    int32 hash = Hash(name);
    //	    if (msg.nextTag('f') == '[' ) {
    //		msg.count++;
    //		loop = 1;
    //	    }
    //	    do {
    //		if (msg.nextTag('f') == 's' ) {
    //		    const char* string = msg.gets();
    //		    if ( *string == 'c') {
    //			int bus = sc_atoi(string+1);
    //			Node_MapControl(node, hash, name, i, bus);
    //		    }
    //		} else {
    //		    if (msg.nextTag('f') == ']' ) {
    //			msg.count++;
    //			loop = 0;
    //		    } else {
    //			float32 value = msg.getf();
    //			Node_SetControl(node, hash, name, i, value);
    //		    }
    //		}
    //		++i;
    //	    } while (loop);
    //	} else {
    //	    int32 index = msg.geti();
    //	    if (msg.nextTag('f') == '[' ) {
    //		msg.count++;
    //		loop = 1;
    //	    }
    //	    do {
    //		if (msg.nextTag('f') == 's') {
    //		    const char* string = msg.gets();
    //		    if (*string == 'c') {
    //			int bus = sc_atoi(string+1);
    //			Node_MapControl(node, index + i, bus);
    //		    }
    //		} else {
    //		    if (msg.nextTag('f') == ']' ) {
    //			msg.count++;
    //			loop = 0;
    //		    } else {
    //			float32 value = msg.getf();
    //			Node_SetControl(node, index + i, value);
    //		    }
    //		}
    //		++i;
    //	    } while (loop);
    //	}
    //    }
    return kSCErr_None;
}

SCErr meth_n_setn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_setn(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain()) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            int32 n = msg.geti();
            for (int i = 0; msg.remain() && i < n; ++i) {
                if (msg.nextTag('f') == 's') {
                    const char* string = msg.gets();
                    if (*string == 'c') {
                        int bus = sc_atoi(string + 1);
                        Node_MapControl(node, hash, name, i, bus);
                    }
                    if (*string == 'a') {
                        int bus = sc_atoi(string + 1);
                        Node_MapAudioControl(node, hash, name, i, bus);
                    }

                } else {
                    float32 value = msg.getf();
                    Node_SetControl(node, hash, name, i, value);
                }
            }
        } else {
            int32 index = msg.geti();
            int32 n = msg.geti();
            for (int i = 0; msg.remain() && i < n; ++i) {
                if (msg.nextTag('f') == 's') {
                    const char* string = msg.gets();
                    if (*string == 'c') {
                        int bus = sc_atoi(string + 1);
                        Node_MapControl(node, index + i, bus);
                    }
                    if (*string == 'a') {
                        int bus = sc_atoi(string + 1);
                        Node_MapAudioControl(node, index + i, bus);
                    }
                } else {
                    float32 value = msg.getf();
                    Node_SetControl(node, index + i, value);
                }
            }
        }
    }
    return kSCErr_None;
}

SCErr meth_n_fill(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_fill(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    Node* node = Msg_GetNode(inWorld, msg);
    if (!node)
        return kSCErr_NodeNotFound;

    while (msg.remain() >= 12) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            int32 n = msg.geti();
            float32 value = msg.getf();

            for (int i = 0; i < n; ++i) {
                Node_SetControl(node, hash, name, i, value);
            }

            if (msg.nextTag('f') == 's') {
                const char* string = msg.gets();
                if (*string == 'c') {
                    int bus = sc_atoi(string + 1);
                    for (int i = 0; i < n; ++i) {
                        Node_MapControl(node, hash, name, i, bus + i);
                    }
                }
            } else {
                float32 value = msg.getf();
                for (int i = 0; i < n; ++i) {
                    Node_SetControl(node, hash, name, i, value);
                }
            }
        } else {
            int32 index = msg.geti();
            int32 n = msg.geti();
            float32 value = msg.getf();

            for (int i = 0; i < n; ++i) {
                Node_SetControl(node, index + i, value);
            }
            if (msg.nextTag('f') == 's') {
                const char* string = msg.gets();
                if (*string == 'c') {
                    int bus = sc_atoi(string + 1);
                    for (int i = 0; i < n; ++i) {
                        Node_MapControl(node, index + i, bus + i);
                    }
                }
            } else {
                float32 value = msg.getf();
                for (int i = 0; i < n; ++i) {
                    Node_SetControl(node, index + i, value);
                }
            }
        }
    }

    return kSCErr_None;
}


SCErr meth_n_query(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_query(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Node_StateMsg(node, kNode_Info);
    }
    return kSCErr_None;
}

SCErr meth_b_query(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_query(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    small_scpacket packet;

    int numbufs = msg.remain() >> 2;
    packet.adds("/b_info");
    packet.maketags(numbufs * 4 + 1);
    packet.addtag(',');

    while (msg.remain()) {
        int bufindex = msg.geti();
        SndBuf* buf = World_GetBuf(inWorld, bufindex);

        packet.addtag('i');
        packet.addtag('i');
        packet.addtag('i');
        packet.addtag('f');
        packet.addi(bufindex);
        packet.addi(buf->frames);
        packet.addi(buf->channels);
        packet.addf(buf->samplerate);
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}


SCErr meth_d_load(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_d_load(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(LoadSynthDefCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_d_recv(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_d_recv(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(RecvSynthDefCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_d_loadDir(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_d_loadDir(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(LoadSynthDefDirCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}

SCErr meth_d_freeAll(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_d_freeAll(World* inWorld, int /*inSize*/, char* /*inData*/, ReplyAddress* /*inReply*/) {
    World_FreeAllGraphDefs(inWorld);
    return kSCErr_None;
}

SCErr meth_d_free(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_d_free(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        int32* defname = msg.gets4();
        if (!defname)
            return kSCErr_SynthDefNotFound;
        SCErr err = GraphDef_Remove(inWorld, defname);
        if (err != kSCErr_None)
            return err;
    }
    return kSCErr_None;
}


SCErr meth_s_new(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_s_new(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    SCErr err;
    sc_msg_iter msg(inSize, inData);
    int32* defname = msg.gets4();
    if (!defname)
        return kSCErr_WrongArgType;

    int32 nodeID = msg.geti();
    int32 addAction = msg.geti();

    GraphDef* def = World_GetGraphDef(inWorld, defname);
    if (!def) {
        scprintf("*** ERROR: SynthDef %s not found\n", (char*)defname);
        return kSCErr_SynthDefNotFound;
    }

    Graph* graph = nullptr;
    switch (addAction) {
    case 0: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, true); // true for normal args
        if (err)
            return err;
        if (!graph)
            return kSCErr_Failed;
        Group_AddHead(group, &graph->mNode);
    } break;
    case 1: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, true);
        if (err)
            return err;
        Group_AddTail(group, &graph->mNode);
    } break;
    case 2: {
        Node* beforeThisNode = Msg_GetNode(inWorld, msg);
        if (!beforeThisNode)
            return kSCErr_NodeNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, true);
        if (err)
            return err;
        Node_AddBefore(&graph->mNode, beforeThisNode);
    } break;
    case 3: {
        Node* afterThisNode = Msg_GetNode(inWorld, msg);
        if (!afterThisNode)
            return kSCErr_NodeNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, true);
        if (err)
            return err;
        Node_AddAfter(&graph->mNode, afterThisNode);
    } break;
    case 4: {
        Node* replaceThisNode = Msg_GetNode(inWorld, msg);
        if (!replaceThisNode)
            return kSCErr_NodeNotFound;
        Node_RemoveID(replaceThisNode);

        err = Graph_New(inWorld, def, nodeID, &msg, &graph, true);
        if (err)
            return err;
        Node_Replace(&graph->mNode, replaceThisNode);
    } break;
    default:
        return kSCErr_Failed;
    }
    Node_StateMsg(&graph->mNode, kNode_Go);
    return kSCErr_None;
}

SCErr meth_s_newargs(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_s_newargs(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    SCErr err;
    sc_msg_iter msg(inSize, inData);
    int32* defname = msg.gets4();
    if (!defname)
        return kSCErr_WrongArgType;

    int32 nodeID = msg.geti();
    int32 addAction = msg.geti();

    GraphDef* def = World_GetGraphDef(inWorld, defname);
    if (!def) {
        scprintf("*** ERROR: SynthDef %s not found\n", (char*)defname);
        return kSCErr_SynthDefNotFound;
    }

    Graph* graph = nullptr;
    switch (addAction) {
    case 0: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, false); // false for setn type args
        if (err)
            return err;
        if (!graph)
            return kSCErr_Failed;
        Group_AddHead(group, &graph->mNode);
    } break;
    case 1: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, false);
        if (err)
            return err;
        Group_AddTail(group, &graph->mNode);
    } break;
    case 2: {
        Node* beforeThisNode = Msg_GetNode(inWorld, msg);
        if (!beforeThisNode)
            return kSCErr_NodeNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, false);
        if (err)
            return err;
        Node_AddBefore(&graph->mNode, beforeThisNode);
    } break;
    case 3: {
        Node* afterThisNode = Msg_GetNode(inWorld, msg);
        if (!afterThisNode)
            return kSCErr_NodeNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, false);
        if (err)
            return err;
        Node_AddAfter(&graph->mNode, afterThisNode);
    } break;
    case 4: {
        Node* replaceThisNode = Msg_GetNode(inWorld, msg);
        if (!replaceThisNode)
            return kSCErr_NodeNotFound;
        err = Graph_New(inWorld, def, nodeID, &msg, &graph, false);
        if (err)
            return err;
        Node_Replace(&graph->mNode, replaceThisNode);
    } break;
    default:
        return kSCErr_Failed;
    }
    Node_StateMsg(&graph->mNode, kNode_Go);
    return kSCErr_None;
}

SCErr meth_g_new(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_new(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    SCErr err;

    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        int32 newGroupID = msg.geti();
        int32 addAction = msg.geti();

        Group* newGroup = nullptr;
        switch (addAction) {
        case 0: {
            Group* group = Msg_GetGroup(inWorld, msg);
            if (!group)
                return kSCErr_GroupNotFound;
            err = Group_New(inWorld, newGroupID, &newGroup);
            if (err) {
                if (err == kSCErr_DuplicateNodeID) {
                    newGroup = World_GetGroup(inWorld, newGroupID);
                    if (!newGroup || !newGroup->mNode.mParent || newGroup->mNode.mParent != group)
                        return err;
                } else
                    return err;
            } else {
                Group_AddHead(group, &newGroup->mNode);
            }
        } break;
        case 1: {
            Group* group = Msg_GetGroup(inWorld, msg);
            if (!group)
                return kSCErr_GroupNotFound;
            err = Group_New(inWorld, newGroupID, &newGroup);
            if (err) {
                if (err == kSCErr_DuplicateNodeID) {
                    newGroup = World_GetGroup(inWorld, newGroupID);
                    if (!newGroup || !newGroup->mNode.mParent || newGroup->mNode.mParent != group)
                        return err;
                } else
                    return err;
            } else {
                Group_AddTail(group, &newGroup->mNode);
            }
        } break;
        case 2: {
            Node* beforeThisNode = Msg_GetNode(inWorld, msg);
            if (!beforeThisNode)
                return kSCErr_TargetNodeNotFound;
            err = Group_New(inWorld, newGroupID, &newGroup);
            if (err) {
                if (err == kSCErr_DuplicateNodeID) {
                    newGroup = World_GetGroup(inWorld, newGroupID);
                    if (!newGroup || !newGroup->mNode.mParent
                        || newGroup->mNode.mParent->mNode.mID != beforeThisNode->mParent->mNode.mID)
                        return err;
                } else
                    return err;
            } else {
                Node_AddBefore(&newGroup->mNode, beforeThisNode);
            }
        } break;
        case 3: {
            Node* afterThisNode = Msg_GetNode(inWorld, msg);
            if (!afterThisNode)
                return kSCErr_TargetNodeNotFound;
            err = Group_New(inWorld, newGroupID, &newGroup);
            if (err) {
                if (err == kSCErr_DuplicateNodeID) {
                    newGroup = World_GetGroup(inWorld, newGroupID);
                    if (!newGroup || !newGroup->mNode.mParent
                        || newGroup->mNode.mParent->mNode.mID != afterThisNode->mParent->mNode.mID)
                        return err;
                } else
                    return err;
            } else {
                Node_AddAfter(&newGroup->mNode, afterThisNode);
            }
        } break;
        case 4: {
            Node* replaceThisNode = Msg_GetNode(inWorld, msg);
            if (!replaceThisNode)
                return kSCErr_TargetNodeNotFound;
            if (replaceThisNode->mID == 0)
                return kSCErr_ReplaceRootGroup;
            Node_RemoveID(replaceThisNode);

            err = Group_New(inWorld, newGroupID, &newGroup);
            if (err)
                return err;
            Node_Replace(&newGroup->mNode, replaceThisNode);
        } break;
        default:
            return kSCErr_Failed;
        }

        Node_StateMsg(&newGroup->mNode, kNode_Go);
    }

    return kSCErr_None;
}

SCErr meth_p_new(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_p_new(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    /* we emulate the concept of parallel groups by using sequential groups */
    return meth_g_new(inWorld, inSize, inData, inReply);
}

SCErr meth_n_free(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_free(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Node_Delete(node);
    }

    return kSCErr_None;
}

SCErr meth_n_before(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_before(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    Node* prevNode = nullptr;
    Node* prevTarget = nullptr;
    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        Node* target = Msg_GetNode(inWorld, msg);

        if (!node || !target)
            continue; // tolerate failure

        if (prevNode && prevNode != node) {
            // move the last pair that succeeded
            Node_Remove(prevNode);
            Node_AddBefore(prevNode, prevTarget);
            Node_StateMsg(prevNode, kNode_Move);
        }

        prevNode = node;
        prevTarget = target;
    }
    if (prevNode && prevNode != prevTarget) {
        // move the last pair that succeeded
        Node_Remove(prevNode);
        Node_AddBefore(prevNode, prevTarget);
        Node_StateMsg(prevNode, kNode_Move);
    }

    return kSCErr_None;
}

SCErr meth_n_after(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_after(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    Node* prevNode = nullptr;
    Node* prevTarget = nullptr;
    while (msg.remain()) {
        Node* node = Msg_GetNode(inWorld, msg);
        Node* target = Msg_GetNode(inWorld, msg);

        if (!node || !target)
            continue; // tolerate failure

        if (prevNode && prevNode != node) {
            // move the last pair that succeeded
            Node_Remove(prevNode);
            Node_AddAfter(prevNode, prevTarget);
            Node_StateMsg(prevNode, kNode_Move);
        }

        prevNode = node;
        prevTarget = target;
    }
    if (prevNode) {
        // move the last pair that succeeded
        Node_Remove(prevNode);
        Node_AddAfter(prevNode, prevTarget);
        Node_StateMsg(prevNode, kNode_Move);
    }

    return kSCErr_None;
}

SCErr meth_n_order(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_n_order(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    Node* prevNode = nullptr;
    Node* node = nullptr;

    sc_msg_iter msg(inSize, inData);
    int32 addAction = msg.geti();

    // place the first node in the list based on target and addAction
    switch (addAction) {
    case 0: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        while (!node && msg.remain()) {
            node = Msg_GetNode(inWorld, msg);
            if (!node)
                scprintf("Warning Node not found\n");
        }
        if (!node)
            return kSCErr_NodeNotFound;

        Group* prevGroup = node->mParent;

        Node_Remove(node);

        Group_AddHead(group, node);

        if (group != prevGroup) {
            Node_StateMsg(node, kNode_Move);
        }

        prevNode = node;

    } break;
    case 1: {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;
        while (!node && msg.remain()) {
            node = Msg_GetNode(inWorld, msg);
            if (!node)
                scprintf("Warning Node not found\n");
        }
        if (!node)
            return kSCErr_NodeNotFound;

        Group* prevGroup = node->mParent;

        Node_Remove(node);

        Group_AddTail(group, node);

        if (group != prevGroup) {
            Node_StateMsg(node, kNode_Move);
        }

        prevNode = node;

    } break;
    case 2: {
        Node* beforeNode = Msg_GetNode(inWorld, msg);
        if (!beforeNode)
            return kSCErr_TargetNodeNotFound;
        while (!node && msg.remain()) {
            node = Msg_GetNode(inWorld, msg);
            if (!node)
                scprintf("Warning Node not found\n");
        }
        if (!node)
            return kSCErr_NodeNotFound;


        Node_Remove(node);
        Node_AddBefore(node, beforeNode);
        Node_StateMsg(node, kNode_Move);

        prevNode = node;
    } break;
    case 3: {
        Node* afterNode = Msg_GetNode(inWorld, msg);
        if (!afterNode)
            return kSCErr_TargetNodeNotFound;
        while (!node && msg.remain()) {
            node = Msg_GetNode(inWorld, msg);
            if (!node)
                scprintf("Warning Node not found\n");
        }
        if (!node)
            return kSCErr_NodeNotFound;


        Node_Remove(node);
        Node_AddAfter(node, afterNode);
        Node_StateMsg(node, kNode_Move);

        prevNode = node;
    } break;
    default:
        return kSCErr_Failed;
    }

    // now iterate through in order
    while (msg.remain()) {
        node = Msg_GetNode(inWorld, msg);
        if (!node) {
            scprintf("Warning Node not found\n");
            continue;
        }
        Node_Remove(node);
        Node_AddAfter(node, prevNode);
        Node_StateMsg(node, kNode_Move);

        prevNode = node;
    }

    return kSCErr_None;
}

SCErr meth_g_head(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_head(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;

        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Node_Remove(node);
        Group_AddHead(group, node);

        Node_StateMsg(node, kNode_Move);
    }
    return kSCErr_None;
}

SCErr meth_g_tail(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_tail(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;

        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Node_Remove(node);
        Group_AddTail(group, node);

        Node_StateMsg(node, kNode_Move);
    }
    return kSCErr_None;
}

SCErr meth_g_insert(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_insert(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;

        Node* node = Msg_GetNode(inWorld, msg);
        if (!node)
            return kSCErr_NodeNotFound;

        Group* prevGroup = node->mParent;

        int index = msg.geti();

        Node_Remove(node);
        Group_Insert(group, node, index);

        if (group != prevGroup) {
            Node_StateMsg(node, kNode_Move);
        }
    }
    return kSCErr_None;
}

SCErr meth_g_freeAll(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_freeAll(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;

        Group_DeleteAll(group);
    }
    return kSCErr_None;
}

SCErr meth_g_deepFree(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_g_deepFree(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Group* group = Msg_GetGroup(inWorld, msg);
        if (!group)
            return kSCErr_GroupNotFound;

        Group_DeepFreeGraphs(group);
    }
    return kSCErr_None;
}


SCErr meth_status(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_status(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(AudioStatusCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}

SCErr meth_quit(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_quit(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(AudioQuitCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}

SCErr meth_clearSched(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_clearSched(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    if (inWorld->mRealTime) {
        inWorld->hw->mAudioDriver->ClearSched();
    }
    return kSCErr_None;
}

SCErr meth_dumpOSC(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_dumpOSC(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    inWorld->mDumpOSC = msg.geti();
    return kSCErr_None;
}

SCErr meth_version(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_version(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    small_scpacket packet;
    packet.adds("/version.reply");
    packet.maketags(7);
    packet.addtag(',');
    packet.addtag('s');
    packet.adds("scsynth");
    packet.addtag('i');
    packet.addi(SC_VersionMajor);
    packet.addtag('i');
    packet.addi(SC_VersionMinor);
    packet.addtag('s');
    packet.adds(SC_VersionPostfix);
    packet.addtag('s');
    packet.adds(SC_BranchOrTag);
    packet.addtag('s');
    packet.adds(SC_CommitHash);

    CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);

    return kSCErr_None;
}

SCErr meth_b_set(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_set(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    int bufindex = msg.geti();
    SndBuf* buf = World_GetBuf(inWorld, bufindex);
    if (!buf)
        return kSCErr_Failed;

    float* data = buf->data;
    uint32 numSamples = buf->samples;

    while (msg.remain() >= 8) {
        uint32 sampleIndex = msg.geti();
        float32 value = msg.getf();
        if (sampleIndex < numSamples) {
            data[sampleIndex] = value;
        } else
            return kSCErr_IndexOutOfRange;
    }
    return kSCErr_None;
}

SCErr meth_b_setn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_setn(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    int bufindex = msg.geti();
    SndBuf* buf = World_GetBuf(inWorld, bufindex);
    if (!buf)
        return kSCErr_Failed;

    float* data = buf->data;
    int numSamples = buf->samples;

    while (msg.remain()) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        int32 end = start + n - 1;

        if (end < 0 || start >= numSamples)
            continue;

        start = sc_clip(start, 0, numSamples - 1);
        end = sc_clip(end, 0, numSamples - 1);

        for (int i = start; msg.remain() && i <= end; ++i) {
            float32 value = msg.getf();
            data[i] = value;
        }
    }

    return kSCErr_None;
}


SCErr meth_b_fill(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_fill(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    int bufindex = msg.geti();
    SndBuf* buf = World_GetBuf(inWorld, bufindex);
    if (!buf)
        return kSCErr_Failed;

    float* data = buf->data;
    int numSamples = buf->samples;

    while (msg.remain() >= 12) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        float32 value = msg.getf();
        int32 end = start + n - 1;

        if (end < 0 || start >= numSamples)
            continue;

        start = sc_clip(start, 0, numSamples - 1);
        end = sc_clip(end, 0, numSamples - 1);

        for (int i = start; i <= end; ++i)
            data[i] = value;
    }

    return kSCErr_None;
}

SCErr meth_b_gen(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_gen(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(BufGenCmd, inWorld, inSize, inData, inReply);

    return kSCErr_None;
}


SCErr meth_c_set(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_c_set(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    float* data = inWorld->mControlBus;
    int32* touched = inWorld->mControlBusTouched;
    int32 bufCounter = inWorld->mBufCounter;
    uint32 maxIndex = inWorld->mNumControlBusChannels;

    while (msg.remain() >= 8) {
        uint32 index = msg.geti();
        float32 value = msg.getf();
        if (index < maxIndex) {
            data[index] = value;
            touched[index] = bufCounter;
        } else
            return kSCErr_IndexOutOfRange;
    }
    return kSCErr_None;
}

SCErr meth_c_setn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_c_setn(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    float* data = inWorld->mControlBus;
    int32* touched = inWorld->mControlBusTouched;
    int32 bufCounter = inWorld->mBufCounter;
    int maxIndex = inWorld->mNumControlBusChannels;

    while (msg.remain()) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        int32 end = start + n - 1;

        if (start < 0 || end >= maxIndex || start > end)
            return kSCErr_IndexOutOfRange;

        for (int i = start; msg.remain() && i <= end; ++i) {
            float32 value = msg.getf();
            data[i] = value;
            touched[i] = bufCounter;
        }
    }

    return kSCErr_None;
}


SCErr meth_c_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_c_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    float* data = inWorld->mControlBus;
    uint32 maxIndex = inWorld->mNumControlBusChannels;

    int numheads = msg.remain() >> 2;

    big_scpacket packet;
    packet.adds("/c_set");
    packet.maketags(numheads * 2 + 1);
    packet.addtag(',');

    while (msg.remain() >= 4) {
        uint32 index = msg.geti();
        if (index >= maxIndex)
            return kSCErr_IndexOutOfRange;
        packet.addtag('i');
        packet.addtag('f');
        packet.addi(index);
        packet.addf(data[index]);
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}

SCErr meth_c_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_c_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    float* data = inWorld->mControlBus;
    int maxIndex = inWorld->mNumControlBusChannels;

    // figure out how many tags to allocate
    int numcontrols = 0;
    int numheads = msg.remain() >> 3;

    while (msg.remain()) {
        msg.geti(); // skip start
        int32 n = msg.geti();
        numcontrols += n;
    }

    big_scpacket packet;
    packet.adds("/c_setn");
    packet.maketags(numheads * 2 + numcontrols + 1);
    packet.addtag(',');

    // start over at beginning of message
    msg.init(inSize, inData);

    while (msg.remain()) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        int32 end = start + n - 1;

        if (start < 0 || end >= maxIndex || start > end)
            return kSCErr_IndexOutOfRange;

        packet.addtag('i');
        packet.addtag('i');
        packet.addi(start);
        packet.addi(n);

        for (int i = start; i <= end; ++i) {
            packet.addtag('f');
            packet.addf(data[i]);
        }
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}


SCErr meth_c_fill(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_c_fill(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);

    float* data = inWorld->mControlBus;
    int32* touched = inWorld->mControlBusTouched;
    int32 bufCounter = inWorld->mBufCounter;
    int maxIndex = inWorld->mNumControlBusChannels;

    while (msg.remain() >= 12) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        float32 value = msg.getf();
        int32 end = start + n - 1;

        if (end < 0 || start >= maxIndex)
            continue;

        start = sc_clip(start, 0, maxIndex - 1);
        end = sc_clip(end, 0, maxIndex - 1);

        for (int i = start; i <= end; ++i) {
            data[i] = value;
            touched[i] = bufCounter;
        }
    }

    return kSCErr_None;
}


SCErr meth_b_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    int bufindex = msg.geti();
    SndBuf* buf = World_GetBuf(inWorld, bufindex);
    if (!buf)
        return kSCErr_Failed;

    float* data = buf->data;
    uint32 maxIndex = buf->samples;

    int numheads = msg.remain() >> 2;

    big_scpacket packet;
    packet.adds("/b_set");
    packet.maketags(numheads * 2 + 2);
    packet.addtag(',');
    packet.addtag('i');
    packet.addi(bufindex);

    while (msg.remain() >= 4) {
        uint32 index = msg.geti();
        if (index >= maxIndex)
            return kSCErr_IndexOutOfRange;
        packet.addtag('i');
        packet.addtag('f');
        packet.addi(index);
        packet.addf(data[index]);
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}

SCErr meth_b_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_b_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    int bufindex = msg.geti();
    SndBuf* buf = World_GetBuf(inWorld, bufindex);
    if (!buf)
        return kSCErr_Failed;

    float* data = buf->data;
    int32 maxIndex = buf->samples;

    // figure out how many tags to allocate
    int numcontrols = 0;
    int numheads = msg.remain() >> 3;

    while (msg.remain()) {
        msg.geti(); // skip start
        int32 n = msg.geti();
        numcontrols += n;
    }

    big_scpacket packet;
    packet.adds("/b_setn");
    packet.maketags(numheads * 2 + numcontrols + 2);
    packet.addtag(',');

    // start over at beginning of message
    msg.init(inSize, inData);
    msg.geti(); // skip buf index

    packet.addtag('i');
    packet.addi(bufindex);

    while (msg.remain()) {
        int32 start = msg.geti();
        int32 n = msg.geti();
        int32 end = start + n - 1;

        if (start < 0 || end >= maxIndex || start > end)
            return kSCErr_IndexOutOfRange;

        packet.addtag('i');
        packet.addtag('i');
        packet.addi(start);
        packet.addi(n);

        for (int i = start; i <= end; ++i) {
            packet.addtag('f');
            packet.addf(data[i]);
        }
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}


SCErr meth_s_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_s_get(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    Graph* graph = Msg_GetGraph(inWorld, msg);
    if (!graph)
        return kSCErr_NodeNotFound;

    int numheads = msg.tags ? strlen(msg.tags) - 1 : msg.remain() >> 2;

    big_scpacket packet;
    packet.adds("/n_set");
    packet.maketags(numheads * 2 + 2);
    packet.addtag(',');
    packet.addtag('i');
    packet.addi(graph->mNode.mID);

    while (msg.remain() >= 4) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            float32 value = 0.f;
            Graph_GetControl(graph, hash, name, 0, value);
            packet.addtag('s');
            packet.addtag('f');
            packet.adds((char*)name);
            packet.addf(value);
        } else {
            int32 index = msg.geti();
            float32 value = 0.f;
            Graph_GetControl(graph, index, value);
            packet.addtag('i');
            packet.addtag('f');
            packet.addi(index);
            packet.addf(value);
        }
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}

SCErr meth_s_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_s_getn(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);

    Graph* graph = Msg_GetGraph(inWorld, msg);
    if (!graph)
        return kSCErr_NodeNotFound;

    // figure out how many tags to allocate
    int numcontrols = 0;
    int numheads = msg.tags ? (strlen(msg.tags) - 1) >> 1 : msg.remain() >> 3;

    while (msg.remain()) {
        msg.geti(); // skip start
        int32 n = msg.geti();
        numcontrols += n;
    }

    big_scpacket packet;
    packet.adds("/n_setn");
    packet.maketags(numheads * 2 + numcontrols + 2);
    packet.addtag(',');

    // start over at beginning of message
    msg.init(inSize, inData);
    msg.geti(); // skip buf index

    packet.addtag('i');
    packet.addi(graph->mNode.mID);

    while (msg.remain()) {
        if (msg.nextTag('i') == 's') {
            int32* name = msg.gets4();
            int32 hash = Hash(name);
            int32 n = msg.geti();
            packet.addtag('s');
            packet.addtag('i');
            packet.adds((char*)name);
            packet.addi(n);
            for (int i = 0; i < n; ++i) {
                float32 value = 0.f;
                Graph_GetControl(graph, hash, name, i, value);
                packet.addtag('f');
                packet.addf(value);
            }
        } else {
            int32 index = msg.geti();
            int32 n = msg.geti();
            packet.addtag('i');
            packet.addtag('i');
            packet.addi(index);
            packet.addi(n);
            for (int i = 0; i < n; ++i) {
                float32 value = 0.f;
                Graph_GetControl(graph, index + i, value);
                packet.addtag('f');
                packet.addf(value);
            }
        }
    }

    if (packet.size()) {
        CallSequencedCommand(SendReplyCmd, inWorld, packet.size(), packet.data(), inReply);
    }

    return kSCErr_None;
}


SCErr meth_s_noid(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_s_noid(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    sc_msg_iter msg(inSize, inData);
    while (msg.remain()) {
        Graph* graph = Msg_GetGraph(inWorld, msg);
        if (!graph)
            continue;

        Node_RemoveID(&graph->mNode);
    }

    return kSCErr_None;
}


SCErr meth_notify(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_notify(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    CallSequencedCommand(NotifyCmd, inWorld, inSize, inData, inReply);
    return kSCErr_None;
}

SCErr meth_error(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
SCErr meth_error(World* inWorld, int inSize, char* inData, ReplyAddress* /*inReply*/) {
    sc_msg_iter msg(inSize, inData);
    int mode = msg.geti();

    //	inWorld->mLocalErrorNotification = mode;
    //		// if non-zero, new state should be saved permanently
    //	if(mode) { inWorld->mErrorNotification = mode; };

    // -1 = bundle off, -2 = bundle on, 0 = permanent off, 1 = permanent on
    switch (mode) {
    case -1:
        inWorld->mLocalErrorNotification += 1;
        break;
    case -2:
        inWorld->mLocalErrorNotification -= 1;
        break;
    case 0:
        inWorld->mErrorNotification = 0;
        break;
    case 1:
        inWorld->mErrorNotification = 1;
    };
    return kSCErr_None;
}

#define NEW_COMMAND(name) NewCommand(#name, cmd_##name, meth_##name)

void initMiscCommands();
void initMiscCommands() {
    // nrt
    NEW_COMMAND(none);
    NEW_COMMAND(notify);
    NEW_COMMAND(status);
    NEW_COMMAND(quit);
    NEW_COMMAND(clearSched);
    NEW_COMMAND(version);

    NEW_COMMAND(d_recv);
    NEW_COMMAND(d_load);
    NEW_COMMAND(d_loadDir);
    NEW_COMMAND(d_freeAll);
    NEW_COMMAND(d_free);

    NEW_COMMAND(s_new);
    NEW_COMMAND(s_newargs);

    NEW_COMMAND(n_trace);
    NEW_COMMAND(n_free);
    NEW_COMMAND(n_run);

    NEW_COMMAND(u_cmd);
    NEW_COMMAND(cmd);

    // NEW_COMMAND(n_cmd);
    NEW_COMMAND(n_map);
    NEW_COMMAND(n_mapn);
    NEW_COMMAND(n_mapa);
    NEW_COMMAND(n_mapan);
    NEW_COMMAND(n_set);
    NEW_COMMAND(n_setn);
    NEW_COMMAND(n_fill);

    NEW_COMMAND(n_before);
    NEW_COMMAND(n_after);
    NEW_COMMAND(n_order);

    NEW_COMMAND(g_new);
    NEW_COMMAND(g_head);
    NEW_COMMAND(g_tail);
    NEW_COMMAND(g_freeAll);
    NEW_COMMAND(g_deepFree);

    NEW_COMMAND(p_new);

    NEW_COMMAND(b_alloc);
    NEW_COMMAND(b_allocRead);
    NEW_COMMAND(b_allocReadChannel);

    NEW_COMMAND(b_read);
    NEW_COMMAND(b_readChannel);
    NEW_COMMAND(b_write);

    NEW_COMMAND(b_free);
    NEW_COMMAND(b_close);

    NEW_COMMAND(b_zero);
    NEW_COMMAND(b_set);
    NEW_COMMAND(b_setn);
    NEW_COMMAND(b_fill);
    NEW_COMMAND(b_gen);

    NEW_COMMAND(c_set);
    NEW_COMMAND(c_setn);
    NEW_COMMAND(c_fill);

    NEW_COMMAND(dumpOSC);

    NEW_COMMAND(c_get);
    NEW_COMMAND(c_getn);
    NEW_COMMAND(b_get);
    NEW_COMMAND(b_getn);
    NEW_COMMAND(s_get);
    NEW_COMMAND(s_getn);

    NEW_COMMAND(n_query);
    NEW_COMMAND(b_query);

    NEW_COMMAND(s_noid);
    NEW_COMMAND(sync);
    NEW_COMMAND(g_dumpTree);
    NEW_COMMAND(g_queryTree);

    NEW_COMMAND(error);
}
