<reponame>jimfinnis/jackmix<filename>screenchain.cpp
/**
 * @file screenchain.cpp
 * @brief  Brief description of file.
 *
 */
#include "monitor.h"
#include "process.h"
#include "proccmds.h"

#include "screenmain.h"
#include "screenchain.h"

#include <sstream>
#include <ncurses.h>
#include <unistd.h>

using namespace std;

ChainScreen scrChain;

// the chain we are currently viewing
static ChainEditData *chainData=NULL;
static volatile bool forceRegen=false;
// the effect within the chain we are currently viewing
static int cureffect=0;
// the parameter within the effect we are currently editing
static int cureffectparam=0;
// the chain we are currently viewing
static int curchain=0;

// what are we doing? Scrolling through chains, or looking at the effects?
enum ChainListMode{Chain,Effects,Params};
static ChainListMode chainListMode=Chain;


static void signalRegen(InputManager *im){
    im->setStatus("REGENERATING",1);
    usleep(10000);
    forceRegen=true;
}


static void regenChainData(int idx){
    if(chainData)
        delete chainData;
    if(idx<(int)chainlist.size() && idx>=0)
        chainData = chainlist[idx]->createEditData();
    else
        chainData = NULL;
    cureffect=0;
    cureffectparam=0;
}

void ChainScreen::display(MonitorData *d){
    
    MonitorThread::get()->lock();
    if(curchain>=(int)chainlist.size())
        curchain=((int)chainlist.size())-1;
    
    
    // if we somehow have a no current chain but there are
    // chains, pick the last one - it generally means one
    // was added and we haven't noticed yet. Also force regen.
    if(curchain<0 && chainlist.size()){
        curchain = chainlist.size()-1;
        forceRegen=true;
    }
    
    if(forceRegen){
        regenChainData(curchain);
        forceRegen=false;
    }
    MonitorThread::get()->unlock();
    
    int w = MonitorThread::get()->w;
    title("CHAIN LIST");
    int chainHilight = COLOR_PAIR(PAIR_HILIGHT);
    int effectHilight = COLOR_PAIR(PAIR_HILIGHT);
    int paramHilight = COLOR_PAIR(PAIR_HILIGHT);
    
    switch(chainListMode){
    case Chain:chainHilight|=A_BOLD;break;
    case Effects:effectHilight|=A_BOLD;break;
    case Params:paramHilight|=A_BOLD;break;
    }
    
    MonitorThread::get()->lock();
    // list
    for(unsigned int i=0;i<chainlist.size();i++){
        string n = chainlist[i]->name.c_str();
        n.resize(20,' ');
        attrset((int)i==curchain ? chainHilight : COLOR_PAIR(0));
        mvaddstr(i+1,0,n.c_str());
    }
    //chain
    if(chainData){
        int x=25;
        int y=1;
        
        attrset(COLOR_PAIR(0));
        mvaddstr(y++,x,"left output from ");
        attrset(COLOR_PAIR(0)|A_BOLD);
        addstr(chainData->leftouteffect.c_str());
        addstr(":");
        addstr(chainData->leftoutport.c_str());
        attrset(COLOR_PAIR(0));
        mvaddstr(y++,x,"right output from ");
        attrset(COLOR_PAIR(0)|A_BOLD);
        addstr(chainData->rightouteffect.c_str());
        addstr(":");
        addstr(chainData->rightoutport.c_str());
        y++;
        attrset(COLOR_PAIR(0)|A_BOLD);
        mvaddstr(y++,x,"Effects:");
        // each effect
        for(unsigned int i=0;i<chainData->fx.size();i++){
            PluginInstance *fx= chainData->fx[i];
            vector<InputConnectionData> *icd = 
                  (*chainData->inputConnData)[i];
            
            // name of effect
            attrset(cureffect==(int)i?effectHilight:COLOR_PAIR(0));
            stringstream ss;
            ss << fx->name << "(type: "+fx->p->label+")";
            mvaddstr(y++,x,ss.str().c_str());
            
            // input connections
            for(unsigned int c=0;c<icd->size();c++){
                InputConnectionData &id = (*icd)[c];
                const char *portName = fx->p->desc->PortNames[id.port];
                attrset(COLOR_PAIR(0));
                mvaddstr(y++,x+20,portName);
                addstr(" from ");
                switch(id.channel){
                case 0:
                    attrset(COLOR_PAIR(PAIR_REDTEXT));
                    addstr("LEFT");break;
                case 1:
                    attrset(COLOR_PAIR(PAIR_REDTEXT));
                    addstr("RIGHT");break;
                case 2:
                    attrset(COLOR_PAIR(PAIR_REDTEXT));
                    addstr("ZERO");break;
                default:
                    addstr(id.fromeffect.c_str());
                    addstr(":");
                    addstr(id.fromport.c_str());
                    break;
                }
            }
        }
        
        attrset(COLOR_PAIR(0));
        
        // current effect parameters
        y+=5;
        PluginInstance *fx;
        if(cureffect>=0 && cureffect<(int)chainData->fx.size())
            fx = chainData->fx[cureffect];
        else
            fx = NULL;
        if(fx){
            // might get weirded by a chain of chain or effect
            if(cureffectparam>=(int)fx->paramsList.size())
                cureffectparam=0;
            
            for(int i=0;i<(int)fx->paramsList.size();i++){
                string name = fx->paramsList[i];
                Value *v = fx->paramsMap[name];
                attrset(i==cureffectparam?paramHilight:COLOR_PAIR(0));
                mvaddstr(y,x,name.c_str());
                stringstream ss;
                ss << v->get() << "  range: "<< v->mn << " to " << v->mx;
                mvaddstr(y,x+50,ss.str().c_str());
                drawHorzBar(y+1,x,1,w-x-5,v->get(),v,Green,
                            i==cureffectparam && chainListMode==Params);
                
                y+=2;
            }
        }
    } else {
        attrset(COLOR_PAIR(PAIR_REDTEXT));
        mvaddstr(1,25,"No chain data");
        attrset(COLOR_PAIR(0));
    }
        
    MonitorThread::get()->unlock();
    attrset(COLOR_PAIR(0));
}

void ChainScreen::onEntry(){
    regenChainData(curchain);
}

void ChainScreen::addEffect(InputManager *im){
    bool ab;
    string ename = im->getFromList("Select effect (or CTRL-G to abort)",PluginMgr::pluginNames,&ab);
    if(ab||ename=="")return;
    
    try {
        PluginData *p = PluginMgr::getPlugin(ename);
        string iname = im->getString("Name",&ab);
        if(ab || iname=="")return;
        
        for(unsigned int i=0;i<chainData->fx.size();i++){
            if(chainData->fx[i]->name == iname){
                im->setStatus("effect instance of this name exists already",3);
                return;
            }
        }
        
        ProcessCommand cmd(ProcessCommandType::AddEffect);
        cmd.pld = p;
        cmd.arg0 = curchain;
        cmd.setstr(iname);
        Process::writeCmd(cmd);
        // have to wait for the chain to be added to regen data?
        //        im->getKey("Press key to regen");
        signalRegen(im);
    }
    catch(string s){
        im->setStatus(s.c_str(),5);
    }
}

static void commandParamNudge(float v){
    if(chainData && cureffect>=0 && cureffect<(int)chainData->fx.size()){
        PluginInstance *fx = chainData->fx[cureffect];
        if(cureffectparam>=0 && cureffectparam<(int)fx->paramsList.size()){
            Value *p = fx->paramsMap[fx->paramsList[cureffectparam]];
            ProcessCommand cmd(ProcessCommandType::NudgeValue);
            cmd.setvalptr(p)->setfloat(v);
            Process::writeCmd(cmd);
        }
    }
}

static void setParam(InputManager *im){
    if(chainData && cureffect>=0 && cureffect<(int)chainData->fx.size()){
        PluginInstance *fx = chainData->fx[cureffect];
        if(cureffectparam>=0 && cureffectparam<(int)fx->paramsList.size()){
            Value *p = fx->paramsMap[fx->paramsList[cureffectparam]];
            im->editVal(fx->paramsList[cureffectparam],p);
        }
    }
}

void ChainScreen::remapInput(InputManager *im){
    PluginInstance *fx;
    bool ab;
    // initial command, needs filling in.
    ProcessCommand cmd(ProcessCommandType::RemapInput);
    
    if(cureffect>=0 && cureffect<(int)chainData->fx.size())
        fx = chainData->fx[cureffect];
    else {
        im->setStatus("no effect selected",3);return;
    }
    
    vector<InputConnectionData> *icd = 
          (*chainData->inputConnData)[cureffect];
    
    stringstream ss;
    ss << "Select input for " << fx->name << " to remap";
    
    // build list of inputs
    vector<string> strs;
    for(unsigned int c=0;c<icd->size();c++){
        InputConnectionData &id = (*icd)[c];
        const char *portName = fx->p->desc->PortNames[id.port];
        strs.push_back(portName);
    }
    string inputToChange = im->getFromList(ss.str(),strs,&ab);
    if(ab || inputToChange=="")return;
    
    // fill in that part of the effect
    cmd.setarg0(curchain)->
          setstr(fx->name)->
          setstr2(inputToChange);
    
    char cc = im->getKey("From left or right chain input, zero, or effect output?","lrez");
    
    if(cc=='l')
        cmd.arg1=0;
    else if(cc=='r')
        cmd.arg1=1;
    else if(cc=='z')
        cmd.arg1=2;
    else {
        cmd.arg1=-1;
        
        // need to get an effect/output to come from
        // get a list of effects
        strs.clear();
        for(unsigned int i=0;i<chainData->fx.size();i++){
        strs.push_back(chainData->fx[i]->name);
        }
        ss.str("");
        ss << "select which effect " << inputToChange << " will come from";
        string effectForOutput = im->getFromList(ss.str(),strs,&ab);
        if(effectForOutput=="" || ab)return;
    
        // find that effect
        PluginInstance *outEffect=NULL;
        for(unsigned int i=0;i<chainData->fx.size();i++){
            if(chainData->fx[i]->name == effectForOutput){
                outEffect = chainData->fx[i];break;
            }
        }
        if(!outEffect){
            im->setStatus("weird, can't find that effect in my lists..",5);
            return;
        }
    
        // now get a list of that effect's outputs
        strs.clear();
        for(unsigned int i=0;i<outEffect->p->desc->PortCount;i++){
            if(LADSPA_IS_PORT_OUTPUT(outEffect->p->desc->PortDescriptors[i])
               && LADSPA_IS_PORT_AUDIO(outEffect->p->desc->PortDescriptors[i])){
                strs.push_back(outEffect->p->desc->PortNames[i]);
            }
        }
        ss.str("");
        ss << "Effect " << fx->name << ":" << inputToChange << " to come from " <<
              effectForOutput << ": ?";
        string portForOutput = im->getFromList(ss.str(),strs,&ab);
        if(portForOutput=="" || ab)return;
        
        cmd.setstr3(effectForOutput)->setstr4(portForOutput);
    }
    
    // and send
    Process::writeCmd(cmd);
    signalRegen(im);
}

void ChainScreen::remapOutput(InputManager *im){
    PluginInstance *fx;
    bool ab;
    
    if(cureffect>=0 && cureffect<(int)chainData->fx.size())
        fx = chainData->fx[cureffect];
    else {
        im->setStatus("no effect selected",3);return;
    }
    
    // first pick an output
    
    char c = im->getKey("Remapping an output of this chain. Enter which one (or 'a' to abort)","lra");
    if(c=='a')return;
    
    int chan = (c=='l')?0:1;
    
    vector<string> strs;
    for(unsigned int i=0;i<fx->p->desc->PortCount;i++){
        if(LADSPA_IS_PORT_OUTPUT(fx->p->desc->PortDescriptors[i])
           && LADSPA_IS_PORT_AUDIO(fx->p->desc->PortDescriptors[i])){
            strs.push_back(fx->p->desc->PortNames[i]);
        }
    }
    stringstream ss;
    ss << "Remapping " << (chan?"R":"L") << " chain output to an output of " 
          << fx->name << ". Which?";
    string port = im->getFromList(ss.str(),strs,&ab);
    if(ab || port=="")return;
    
    
    ProcessCommand cmd(ProcessCommandType::RemapOutput);
    cmd.setarg0(curchain)->setstr(fx->name)->setarg1(chan)->setstr2(port);
    
    Process::writeCmd(cmd);
    signalRegen(im);
}

void ChainScreen::flow(InputManager *im){
    int c = im->getKey();
    PluginInstance *fx;
    bool ab;
    string name;
    
    im->lock();
    if(curchain>=(int)chainlist.size())
        curchain=((int)chainlist.size())-1;
    im->unlock();
    
    switch(c){
    case 'q':
    case 10:
        im->pop(); // back to the previous screen
        break;
    case 'a':
        name = im->getString("Chain name",&ab);
        if(!ab && name!=""){
            if(ChainInterface::findornull(name)){
                im->setStatus("chain already exists",4);
            } else {
                ProcessCommand cmd(ProcessCommandType::AddChain);
                cmd.setstr(name);
                Process::writeCmd(cmd);
                signalRegen(im);
            }
        }
        break;
    case 'i':
        remapInput(im);
        break;
    case 'o':
        remapOutput(im);
        break;
    case 'e':
        if(curchain>=0 && chainData)
            addEffect(im);
        break;
    case 'c':
        if(chainListMode == Params && chainData && cureffect>=0 && cureffect<(int)chainData->fx.size()){
            PluginInstance *fx = chainData->fx[cureffect];
            if(cureffectparam>=0 && cureffectparam<(int)fx->paramsList.size()){
                Value *p = fx->paramsMap[fx->paramsList[cureffectparam]];
                commandAddCtrl(p);
            }
        }
        break;
    case 'r':
        if(chainListMode == Params && chainData && cureffect>=0 && cureffect<(int)chainData->fx.size()){
            PluginInstance *fx = chainData->fx[cureffect];
            if(cureffectparam>=0 && cureffectparam<(int)fx->paramsList.size()){
                Value *p = fx->paramsMap[fx->paramsList[cureffectparam]];
                if(p->getCtrl()){
                    if(im->getKey("Delete controller association - are you sure?","yn")=='y'){
                        ProcessCommand cmd(ProcessCommandType::DeleteCtrlAssoc);
                        cmd.setctrl(p->getCtrl())->setvalptr(p);
                        Process::writeCmd(cmd);
                    }
                }
            }
        }
        break;
    case 9: // tab
        switch(chainListMode){
        case Chain:chainListMode=Effects;break;
        case Effects:chainListMode=Params;break;
        case Params:chainListMode=Chain;break;
        }
        break;
    case KEY_DC:
        switch(chainListMode){
        case Chain:
            if(chainData && im->getKey("Delete chain - are you sure?","yn")=='y'){
                ProcessCommand cmd(ProcessCommandType::DeleteChain);
                cmd.setarg0(curchain);
                Process::writeCmd(cmd);
                signalRegen(im);
            }
            break;
        case Effects:
            if(chainData && cureffect>=0 && cureffect<(int)chainData->fx.size()){
                if(im->getKey("Delete effect - are you sure?","yn")=='y'){
                    ProcessCommand cmd(ProcessCommandType::DeleteEffect);
                    cmd.setarg0(curchain)->setarg1(cureffect);
                    Process::writeCmd(cmd);
                    signalRegen(im);
                }
            }
            break;
        default:break;
        }
        break;
    case 'h':
        im->push();
        im->go(&scrHelp);
        break;
    case KEY_UP:
        im->lock();
        switch(chainListMode){
        case Chain:curchain--;
            if(curchain<0)curchain=((int)chainlist.size())-1;
            signalRegen(im);
            break;
        case Effects:
            cureffect--;
            if(cureffect<0)cureffect=0;
            break;
        case Params:
            cureffectparam--;
            if(cureffectparam<0)cureffectparam=0;
            break;
        }
        im->unlock();
        break;
    case KEY_DOWN:
        im->lock();
        switch(chainListMode){
        case Chain:curchain++;
            if(curchain>=(int)chainlist.size())
                curchain=0;
            signalRegen(im);
            break;
        case Effects:
            cureffect++;
            if(chainData && cureffect>=(int)chainData->fx.size())
                cureffect=0;
            break;
        case Params:
            if(chainData && cureffect>=0 &&
               cureffect<(int)chainData->fx.size())
                fx = chainData->fx[cureffect];
            else
                fx = NULL;
            if(fx){
                cureffectparam++;
                if(cureffectparam>=(int)fx->paramsList.size())
                    cureffectparam=0;
            }
            break;
        }
        im->unlock();
        break;
    case KEY_LEFT:
        if(chainListMode==Params)
            commandParamNudge(-1);
        break;
    case KEY_RIGHT:
        if(chainListMode==Params)
            commandParamNudge(1);
        break;
    case 'v':
        setParam(im);
        break;
    default:break;
    }
}
