/*
    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_Group.h"
#include "SC_SynthDef.h"
#include "SC_World.h"
#include "SC_WorldOptions.h"
#include "SC_Errors.h"
#include <stdio.h>
#include <stdexcept>
#include <limits.h>
#include "SC_Prototypes.h"
#include "SC_HiddenWorld.h"
#include "Unroll.h"

void Node_StateMsg(Node* inNode, int inState);

// create a new node
int Node_New(World* inWorld, NodeDef* def, int32 inID, Node** outNode) {
    if (inID < 0) {
        if (inID == -1) { // -1 means generate an id for the event
            HiddenWorld* hw = inWorld->hw;
            inID = hw->mHiddenID = (hw->mHiddenID - 8) | 0x80000000;
        } else {
            return kSCErr_ReservedNodeID;
        }
    }

    if (World_GetNode(inWorld, inID)) {
        return kSCErr_DuplicateNodeID;
    }

    Node* node = (Node*)World_Alloc(inWorld, def->mAllocSize);
    if (!node) {
        return kSCErr_OutOfRealTimeMemory;
    }
    node->mWorld = inWorld;
    node->mDef = def;
    node->mParent = nullptr;
    node->mPrev = nullptr;
    node->mNext = nullptr;
    node->mIsGroup = false;

    node->mID = inID;
    node->mHash = Hash(inID);
    if (!World_AddNode(inWorld, node)) {
        World_Free(inWorld, node);
        return kSCErr_TooManyNodes;
    }

    inWorld->hw->mRecentID = inID;

    *outNode = node;

    return kSCErr_None;
}

// node destructor
void Node_Dtor(Node* inNode) {
    Node_StateMsg(inNode, kNode_End);
    Node_Remove(inNode);
    World* world = inNode->mWorld;
    world->hw->mNodeLib->Remove(inNode);
    World_Free(world, inNode);
}

// remove a node from a group
void Node_Remove(Node* s) {
    Group* group = s->mParent;

    if (s->mPrev)
        s->mPrev->mNext = s->mNext;
    else if (group)
        group->mHead = s->mNext;

    if (s->mNext)
        s->mNext->mPrev = s->mPrev;
    else if (group)
        group->mTail = s->mPrev;

    s->mPrev = s->mNext = nullptr;
    s->mParent = nullptr;
}

void Node_RemoveID(Node* inNode) {
    if (inNode->mID == 0)
        return; // failed

    World* world = inNode->mWorld;
    if (!World_RemoveNode(world, inNode)) {
        int err = kSCErr_Failed; // shouldn't happen..
        throw err;
    }

    HiddenWorld* hw = world->hw;
    int id = hw->mHiddenID = (hw->mHiddenID - 8) | 0x80000000;
    inNode->mID = id;
    inNode->mHash = Hash(id);
    if (!World_AddNode(world, inNode)) {
        scprintf("mysterious failure in Node_RemoveID\n");
        Node_Delete(inNode);
        // enums are uncatchable. must throw an int.
        int err = kSCErr_Failed; // shouldn't happen..
        throw err;
    }

    // inWorld->hw->mRecentID = id;
}

// delete a node
void Node_Delete(Node* inNode) {
    if (inNode->mID == 0)
        return; // failed
    if (inNode->mIsGroup)
        Group_Dtor((Group*)inNode);
    else
        Graph_Dtor((Graph*)inNode);
}

// add a node after another one
void Node_AddAfter(Node* s, Node* afterThisOne) {
    if (!afterThisOne->mParent || s->mID == 0)
        return; // failed

    s->mParent = afterThisOne->mParent;
    s->mPrev = afterThisOne;
    s->mNext = afterThisOne->mNext;

    if (afterThisOne->mNext)
        afterThisOne->mNext->mPrev = s;
    else
        s->mParent->mTail = s;
    afterThisOne->mNext = s;
}

// add a node before another one
void Node_AddBefore(Node* s, Node* beforeThisOne) {
    if (!beforeThisOne->mParent || s->mID == 0)
        return; // failed

    s->mParent = beforeThisOne->mParent;
    s->mPrev = beforeThisOne->mPrev;
    s->mNext = beforeThisOne;

    if (beforeThisOne->mPrev)
        beforeThisOne->mPrev->mNext = s;
    else
        s->mParent->mHead = s;
    beforeThisOne->mPrev = s;
}

void Node_Replace(Node* s, Node* replaceThisOne) {
    // scprintf("->Node_Replace\n");
    Group* group = replaceThisOne->mParent;
    if (!group)
        return; // failed
    if (s->mID == 0)
        return;

    s->mParent = group;
    s->mPrev = replaceThisOne->mPrev;
    s->mNext = replaceThisOne->mNext;

    if (s->mPrev)
        s->mPrev->mNext = s;
    else
        group->mHead = s;

    if (s->mNext)
        s->mNext->mPrev = s;
    else
        group->mTail = s;

    replaceThisOne->mPrev = replaceThisOne->mNext = nullptr;
    replaceThisOne->mParent = nullptr;

    Node_Delete(replaceThisOne);
    // scprintf("<-Node_Replace\n");
}

// set a node's control so that it reads from a control bus - index argument
void Node_MapControl(Node* inNode, int inIndex, int inBus) {
    if (inNode->mIsGroup) {
        Group_MapControl((Group*)inNode, inIndex, inBus);
    } else {
        Graph_MapControl((Graph*)inNode, inIndex, inBus);
    }
}

// set a node's control so that it reads from a control bus - name argument
void Node_MapControl(Node* inNode, int32 inHash, int32* inName, int inIndex, int inBus) {
    if (inNode->mIsGroup) {
        Group_MapControl((Group*)inNode, inHash, inName, inIndex, inBus);
    } else {
        Graph_MapControl((Graph*)inNode, inHash, inName, inIndex, inBus);
    }
}

// set a node's control so that it reads from a control bus - index argument
void Node_MapAudioControl(Node* inNode, int inIndex, int inBus) {
    if (inNode->mIsGroup) {
        Group_MapAudioControl((Group*)inNode, inIndex, inBus);
    } else {
        Graph_MapAudioControl((Graph*)inNode, inIndex, inBus);
    }
}

// set a node's control so that it reads from a control bus - name argument
void Node_MapAudioControl(Node* inNode, int32 inHash, int32* inName, int inIndex, int inBus) {
    if (inNode->mIsGroup) {
        Group_MapAudioControl((Group*)inNode, inHash, inName, inIndex, inBus);
    } else {
        Graph_MapAudioControl((Graph*)inNode, inHash, inName, inIndex, inBus);
    }
}

// set a node's control value - index argument
void Node_SetControl(Node* inNode, int inIndex, float inValue) {
    if (inNode->mIsGroup) {
        Group_SetControl((Group*)inNode, inIndex, inValue);
    } else {
        Graph_SetControl((Graph*)inNode, inIndex, inValue);
    }
}

// set a node's control value - name argument
void Node_SetControl(Node* inNode, int32 inHash, int32* inName, int inIndex, float inValue) {
    if (inNode->mIsGroup) {
        Group_SetControl((Group*)inNode, inHash, inName, inIndex, inValue);
    } else {
        Graph_SetControl((Graph*)inNode, inHash, inName, inIndex, inValue);
    }
}

// this function can be installed using Node_SetRun to cause a node to do nothing
// during its execution time.
void Node_NullCalc(struct Node* /*inNode*/) {}

void Graph_FirstCalc(Graph* inGraph);
void Graph_NullFirstCalc(Graph* inGraph);

// if inRun is zero then the node's calc function is set to Node_NullCalc,
// otherwise its normal calc function is installed.
void Node_SetRun(Node* inNode, int inRun) {
    if (inRun) {
        if (inNode->mCalcFunc == &Node_NullCalc) {
            if (inNode->mIsGroup) {
                inNode->mCalcFunc = (NodeCalcFunc)&Group_Calc;
            } else {
                inNode->mCalcFunc = (NodeCalcFunc)&Graph_Calc;
            }
            Node_StateMsg(inNode, kNode_On);
        }
    } else {
        if (inNode->mCalcFunc != &Node_NullCalc) {
            if (!inNode->mIsGroup && inNode->mCalcFunc == (NodeCalcFunc)&Graph_FirstCalc) {
                inNode->mCalcFunc = (NodeCalcFunc)&Graph_NullFirstCalc;
            } else {
                inNode->mCalcFunc = (NodeCalcFunc)&Node_NullCalc;
            }
            Node_StateMsg(inNode, kNode_Off);
        }
    }
}


void Node_Trace(Node* inNode) {
    if (inNode->mIsGroup) {
        Group_Trace((Group*)inNode);
    } else {
        Graph_Trace((Graph*)inNode);
    }
}

void Node_End(Node* inNode) { inNode->mCalcFunc = (NodeCalcFunc)&Node_Delete; }


// send a trigger from a node to a client program.
// this function puts the trigger on a FIFO which is harvested by another thread that
// actually does the sending.
void Node_SendTrigger(Node* inNode, int triggerID, float value) {
    World* world = inNode->mWorld;
    if (!world->mRealTime)
        return;

    TriggerMsg msg;
    msg.mWorld = world;
    msg.mNodeID = inNode->mID;
    msg.mTriggerID = triggerID;
    msg.mValue = value;
    world->hw->mTriggers.Write(msg);
}

// Send a reply from a node to a client program.
//
// This function puts the reply on a FIFO which is harvested by another thread that
// actually does the sending.
//
// NOTE: Only to be called from the realtime thread.
void Node_SendReply(Node* inNode, int replyID, const char* cmdName, int numArgs, const float* values) {
    World* world = inNode->mWorld;
    if (!world->mRealTime)
        return;

    const int cmdNameSize = strlen(cmdName);
    void* mem = World_Alloc(world, cmdNameSize + numArgs * sizeof(float));
    if (mem == nullptr)
        return;

    NodeReplyMsg msg;
    msg.mWorld = world;
    msg.mNodeID = inNode->mID;
    msg.mID = replyID;
    msg.mValues = (float*)((char*)mem + cmdNameSize);
    memcpy(msg.mValues, values, numArgs * sizeof(float));
    msg.mNumArgs = numArgs;
    msg.mCmdName = (char*)mem;
    memcpy(msg.mCmdName, cmdName, cmdNameSize);
    msg.mCmdNameSize = cmdNameSize;
    msg.mRTMemory = mem;
    world->hw->mNodeMsgs.Write(msg);
}

void Node_SendReply(Node* inNode, int replyID, const char* cmdName, float value) {
    Node_SendReply(inNode, replyID, cmdName, 1, &value);
}

// notify a client program of a node's state change.
// this function puts the message on a FIFO which is harvested by another thread that
// actually does the sending.
void Node_StateMsg(Node* inNode, int inState) {
    if (inNode->mID < 0 && inState != kNode_Info)
        return; // no notification for negative IDs

    World* world = inNode->mWorld;
    if (!world->mRealTime)
        return;

    NodeEndMsg msg;
    msg.mWorld = world;
    msg.mNodeID = inNode->mID;
    msg.mGroupID = inNode->mParent ? inNode->mParent->mNode.mID : -1;
    msg.mPrevNodeID = inNode->mPrev ? inNode->mPrev->mID : -1;
    msg.mNextNodeID = inNode->mNext ? inNode->mNext->mID : -1;
    if (inNode->mIsGroup) {
        Group* group = (Group*)inNode;
        msg.mIsGroup = 1;
        msg.mHeadID = group->mHead ? group->mHead->mID : -1;
        msg.mTailID = group->mTail ? group->mTail->mID : -1;
    } else {
        msg.mIsGroup = 0;
        msg.mHeadID = -1;
        msg.mTailID = -1;
    }
    msg.mState = inState;
    world->hw->mNodeEnds.Write(msg);
}

#include "SC_Unit.h"

void Unit_DoneAction(int doneAction, Unit* unit) {
    switch (doneAction) {
    case 1:
        Node_SetRun(&unit->mParent->mNode, 0);
        break;
    case 2:
        Node_End(&unit->mParent->mNode);
        break;
    case 3: {
        Node_End(&unit->mParent->mNode);
        Node* prev = unit->mParent->mNode.mPrev;
        if (prev)
            Node_End(prev);
    } break;
    case 4: {
        Node_End(&unit->mParent->mNode);
        Node* next = unit->mParent->mNode.mNext;
        if (next)
            Node_End(next);
    } break;
    case 5: {
        Node_End(&unit->mParent->mNode);
        Node* prev = unit->mParent->mNode.mPrev;
        if (!prev)
            break;
        if (prev && prev->mIsGroup)
            Group_DeleteAll((Group*)prev);
        else
            Node_End(prev);
    } break;
    case 6: {
        Node_End(&unit->mParent->mNode);
        Node* next = unit->mParent->mNode.mNext;
        if (!next)
            break;
        if (next->mIsGroup)
            Group_DeleteAll((Group*)next);
        else
            Node_End(next);
    } break;
    case 7: {
        Node* node = &unit->mParent->mNode;
        while (node) {
            Node* prev = node->mPrev;
            Node_End(node);
            node = prev;
        }
    } break;
    case 8: {
        Node* node = &unit->mParent->mNode;
        while (node) {
            Node* next = node->mNext;
            Node_End(node);
            node = next;
        }
    } break;
    case 9: {
        Node_End(&unit->mParent->mNode);
        Node* prev = unit->mParent->mNode.mPrev;
        if (prev)
            Node_SetRun(prev, 0);
    } break;
    case 10: {
        Node_End(&unit->mParent->mNode);
        Node* next = unit->mParent->mNode.mNext;
        if (next)
            Node_SetRun(next, 0);
    } break;
    case 11: {
        Node_End(&unit->mParent->mNode);
        Node* prev = unit->mParent->mNode.mPrev;
        if (!prev)
            break;
        if (prev->mIsGroup)
            Group_DeepFreeGraphs((Group*)prev);
        else
            Node_End(prev);
    } break;
    case 12: {
        Node_End(&unit->mParent->mNode);
        Node* next = unit->mParent->mNode.mNext;
        if (!next)
            break;
        if (next->mIsGroup)
            Group_DeepFreeGraphs((Group*)next);
        else
            Node_End(next);
    } break;
    case 13: {
        Node* node = unit->mParent->mNode.mParent->mHead;
        while (node) {
            Node* next = node->mNext;
            Node_End(node);
            node = next;
        }
    } break;
    case 14:
        Node_End(&unit->mParent->mNode.mParent->mNode);
        break;
    case 15: {
        Node_End(&unit->mParent->mNode);
        Node* next = unit->mParent->mNode.mNext;
        if (next)
            Node_SetRun(next, 1);
    } break;
    }
}
