//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: route.cpp,v 1.18.2.3 2008/05/21 00:28:52 terminator356 Exp $
//
//  (C) Copyright 2003-2004 Werner Schweer (ws@seh.de)
//  (C) Copyright 2011 Tim E. Real (terminator356 on sourceforge)
//
//  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; 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 <QWidget>

#include "song.h"
#include "route.h"
#include "node.h"
#include "audio.h"
#include "track.h"
#include "synth.h"
#include "audiodev.h"
#include "xml.h"
#include "midiport.h"
#include "driver/jackmidi.h"
#include "driver/alsamidi.h"

//#define ROUTE_DEBUG 

namespace MusECore {

const QString ROUTE_MIDIPORT_NAME_PREFIX = "MusE MidiPort ";

//---------------------------------------------------------
//   Route
//---------------------------------------------------------

Route::Route(void* t, int ch)
      {
      jackPort = t;
      midiPort = -1;
      channel  = ch;
      channels = -1;
      remoteChannel = -1;
      type     = JACK_ROUTE;
      }

Route::Route(Track* t, int ch, int chans)
      {
      track    = t;
      midiPort = -1;
      channel  = ch;
      channels = chans;
      remoteChannel = -1;
      type     = TRACK_ROUTE;
      }

Route::Route(MidiDevice* d, int ch)
{
      device   = d;  
      midiPort = -1;
      channel  = ch;
      channels = -1;
      remoteChannel = -1;
      type    = MIDI_DEVICE_ROUTE; 
}

Route::Route(int port, int ch)  
{
      track    = 0;
      midiPort = port; 
      channel  = ch;
      channels = -1;
      remoteChannel = -1;
      type    = MIDI_PORT_ROUTE;     
}

Route::Route(const QString& s, bool dst, int ch, int rtype)
    {
      Route node(name2route(s, dst, rtype));
      channel  = node.channel;
      if(channel == -1)
        channel = ch;
      channels = node.channels;
      remoteChannel = node.remoteChannel;
      type = node.type;
      if(type == TRACK_ROUTE)
      {
        track = node.track;
        midiPort = -1;
      }
      else
      if(type == JACK_ROUTE)
      {  
        jackPort = node.jackPort;
        midiPort = -1;
      }
      else
      if(type == MIDI_DEVICE_ROUTE)  
      {
        device = node.device;     
        midiPort = -1;
      }  
      else
      if(type == MIDI_PORT_ROUTE)    
      {
        track = 0;
        midiPort = node.midiPort;    
      }  
    }

Route::Route()
      {
      track    = 0;
      midiPort = -1;
      channel  = -1;
      channels = -1;
      remoteChannel = -1;
      type     = TRACK_ROUTE;
      }

//---------------------------------------------------------
//   addRoute
//---------------------------------------------------------

void addRoute(Route src, Route dst)
{
      #ifdef ROUTE_DEBUG
      fprintf(stderr, "addRoute:\n");
      #endif
      
      if (!src.isValid() || !dst.isValid())
      {
            if(!src.isValid())
              fprintf(stderr, "addRoute: invalid src\n");
            if(!dst.isValid())
              fprintf(stderr, "addRoute: invalid dst\n");
            return;
      }
      
//      printf("addRoute %d.%d:<%s> %d.%d:<%s>\n",
//         src.type, src.channel, src.name().toLatin1().constData(),
//         dst.type, dst.channel, dst.name().toLatin1().constData());
      if (src.type == Route::JACK_ROUTE) 
      {           
            if (dst.type == Route::TRACK_ROUTE) 
            {
              if (dst.track->type() != Track::AUDIO_INPUT) 
              {
                  fprintf(stderr, "addRoute: source is jack, dest:%s is track but not audio input\n", dst.track->name().toLatin1().constData());
                  return;
              }
              if (dst.channel < 0) 
              {
                  fprintf(stderr, "addRoute: source is jack, dest:%s is track but invalid channel:%d\n", dst.track->name().toLatin1().constData(), dst.channel);
                  return;
              }
              
              src.channel = dst.channel;
              RouteList* inRoutes = dst.track->inRoutes();
              for (ciRoute i = inRoutes->begin(); i != inRoutes->end(); ++i) 
              {
                if (*i == src)    // route already there
                {
                  fprintf(stderr, "addRoute: src track route already exists.\n");
                  return;
                }  
              }
              #ifdef ROUTE_DEBUG
              fprintf(stderr, "addRoute: src Jack dst track name: %s pushing source route\n", dst.track->name().toLatin1().constData());
              #endif
              inRoutes->push_back(src);
            }  
            else
            if (dst.type == Route::MIDI_DEVICE_ROUTE) 
            {
              if(dst.device->deviceType() == MidiDevice::JACK_MIDI)
              {
                src.channel = dst.channel;
                RouteList* routes = dst.device->inRoutes();
                for (ciRoute i = routes->begin(); i != routes->end(); ++i) 
                {
                  if (*i == src)    // route already there
                  {
                    fprintf(stderr, "addRoute: src Jack midi route already exists.\n");
                    return;
                  }  
                }
                #ifdef ROUTE_DEBUG
                fprintf(stderr, "addRoute: src Jack dst Jack midi name: %s pushing source route\n", dst.device->name().toLatin1().constData());
                #endif
                routes->push_back(src);
              }  
              else
              {
                fprintf(stderr, "addRoute: source is Jack, but destination is not jack midi - type:%d\n", dst.device->deviceType());
                return;
              }
            }  
            else
            {
              fprintf(stderr, "addRoute: source is Jack, but destination is not track or midi - type:%d \n", dst.type);
              return;
            }
      }
      else if (dst.type == Route::JACK_ROUTE) 
      {
            if (src.type == Route::TRACK_ROUTE) 
            {
              if (src.track->type() != Track::AUDIO_OUTPUT) 
              {
                    fprintf(stderr, "addRoute: destination is jack, source is track but not audio output\n");
                    return;
              }
              if (src.channel < 0) 
              {
                    fprintf(stderr, "addRoute: destination is jack, source:%s is track but invalid channel:%d\n", src.track->name().toLatin1().constData(), src.channel);
                    return;
              }
              
              RouteList* outRoutes = src.track->outRoutes();
              dst.channel = src.channel;
              
              for (ciRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
              {
                    if (*i == dst)    // route already there
                    {
                          #ifdef ROUTE_DEBUG
                          fprintf(stderr, "addRoute: dst track route already exists.\n");
                          #endif
                          return;
                    }      
              }
              #ifdef ROUTE_DEBUG
              fprintf(stderr, "addRoute: dst Jack src track name: %s pushing destination route\n", src.track->name().toLatin1().constData());
              #endif
              outRoutes->push_back(dst);
            }
            else
            if (src.type == Route::MIDI_DEVICE_ROUTE) 
            {
              if(src.device->deviceType() == MidiDevice::JACK_MIDI)
              {
                dst.channel = src.channel;
                RouteList* routes = src.device->outRoutes();
                for (ciRoute i = routes->begin(); i != routes->end(); ++i) 
                {
                  if (*i == dst)    // route already there
                  {
                    fprintf(stderr, "addRoute: dst Jack midi route already exists.\n");
                    return;
                  }  
                }
                #ifdef ROUTE_DEBUG
                fprintf(stderr, "addRoute: dst Jack src Jack midi name: %s pushing destination route\n", src.device->name().toLatin1().constData());
                #endif
                if(src.device->midiPort() != -1)
                  // Initializations sysex etc. need to be sent to the new connection.
                  MusEGlobal::midiPorts[src.device->midiPort()].clearInitSent();  
                routes->push_back(dst);
              }
              else  
              {
                fprintf(stderr, "addRoute: destination is Jack, but source is not jack midi - type:%d\n", src.device->deviceType());
                return;
              }
            }
            else
            {
              fprintf(stderr, "addRoute: destination is Jack, but source is not track or midi - type:%d \n", src.type);
              return;
            }
      }
      else if(src.type == Route::MIDI_PORT_ROUTE)  
      {
            if(dst.type != Route::TRACK_ROUTE)
            {
              fprintf(stderr, "addRoute: source is midi port:%d, but destination is not track\n", src.midiPort);
              return;
            }
            
            MidiPort *mp = &MusEGlobal::midiPorts[src.midiPort];
            
            // Do not allow synth ports to connect to any track. It may be useful in some cases, 
            //  may be desired later, but for now it's just a routing hassle.  p4.0.35 
            if(mp->device() && mp->device()->isSynti())
              return;
            
            if(dst.channel < 1 || dst.channel >= (1 << MIDI_CHANNELS))
            {
              fprintf(stderr, "addRoute: source is midi port:%d, but destination channel mask:%d out of range\n", src.midiPort, dst.channel);
              return;
            }
                        
            src.channel = dst.channel;
            RouteList* outRoutes = mp->outRoutes();
            iRoute ir = outRoutes->begin();                                      
            for ( ; ir != outRoutes->end(); ++ir) 
            {
              if (ir->type == Route::TRACK_ROUTE && ir->track == dst.track)     // Does a route to the track exist?
              {
                ir->channel |= dst.channel;    // Bitwise OR the desired channel bit with the existing bit mask.
                break;
              }      
            }
            #ifdef ROUTE_DEBUG
            fprintf(stderr, "addRoute: src midi port:%d dst track name:%s pushing dst and src routes\n", src.midiPort, dst.track->name().toLatin1().constData());
            #endif
            
            if(ir == outRoutes->end())    // Only if route not found, add the route, with the requested channel bits as mask to start with. 
              outRoutes->push_back(dst);
              
            RouteList* inRoutes = dst.track->inRoutes();
            
            // Make sure only one single route, with a channel mask, can ever exist.
            ir = inRoutes->begin();
            for ( ; ir != inRoutes->end(); ++ir)         
            {
              if (ir->type == Route::MIDI_PORT_ROUTE && ir->midiPort == src.midiPort)  // Does a route to the midi port exist?
              {
                ir->channel |= src.channel;    // Bitwise OR the desired channel bit with the existing bit mask.
                break;
              }      
            }
            
            if(ir == inRoutes->end())    // Only if route not found, add the route, with the requested channel bits as mask to start with. 
              inRoutes->push_back(src);
      }
      else if(dst.type == Route::MIDI_PORT_ROUTE)  
      {
            if(src.type != Route::TRACK_ROUTE)
            {
              fprintf(stderr, "addRoute: destination is midi port:%d, but source is not track\n", dst.midiPort);
              return;
            }
            if(src.channel < 1 || src.channel >= (1 << MIDI_CHANNELS))
            {
              fprintf(stderr, "addRoute: destination is midi port:%d, but source channel mask:%d out of range\n", dst.midiPort, src.channel);
              return;
            }
            
            dst.channel = src.channel;
            RouteList* outRoutes = src.track->outRoutes();
      
            iRoute ir = outRoutes->begin();                                      
            for ( ; ir != outRoutes->end(); ++ir) 
            {
              if (ir->type == Route::MIDI_PORT_ROUTE && ir->midiPort == dst.midiPort)     // Does a route to the midi port exist?
              {
                ir->channel |= dst.channel;    // Bitwise OR the desired channel bit with the existing bit mask.
                break;
              }      
            }
            
            if(ir == outRoutes->end())    // Only if route not found, add the route, with the requested channel bits as mask to start with. 
              outRoutes->push_back(dst);
            
            MidiPort *mp = &MusEGlobal::midiPorts[dst.midiPort];
            
            #ifdef ROUTE_DEBUG
            fprintf(stderr, "addRoute: src track:%s dst midi port:%d pushing dst and src routes\n", src.track->name().toLatin1().constData(), dst.midiPort);
            #endif
            RouteList* inRoutes = mp->inRoutes();
              
            // Make sure only one single route, with a channel mask, can ever exist.
            ir = inRoutes->begin();
            for ( ; ir != inRoutes->end(); ++ir)         
            {
              if (ir->type == Route::TRACK_ROUTE && ir->track == src.track)  // Does a route to the track exist?
              {
                ir->channel |= src.channel;    // Bitwise OR the desired channel bit with the existing bit mask.
                break;
              }      
            }
            
            if(ir == inRoutes->end())    // Only if route not found, add the route, with the requested channel bits as mask to start with. 
              inRoutes->push_back(src);
      }
      else 
      {
        if(src.type != Route::TRACK_ROUTE || dst.type != Route::TRACK_ROUTE)  
        {
          fprintf(stderr, "addRoute: source or destination are not track routes\n");
          return;
        }
        
        RouteList* outRoutes = src.track->outRoutes();
        
        //
        // Must enforce to ensure channel and channels are valid if defaults of -1 passed.
        //
        if(src.track->type() == Track::AUDIO_SOFTSYNTH)
        {
          if(src.channel == -1)
            src.channel = 0;
          if(src.channels == -1)
            src.channels = src.track->channels();  
          dst.channel = src.channel;
          dst.channels = src.channels;
          dst.remoteChannel = src.remoteChannel;
        }
        
        for (ciRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
        {
              if (*i == dst)    // route already there
              // TODO: DELETETHIS all these comments wtf?
              //if (i->type == dst.type && i->channel == dst.channel)    
              {
                //if(i->type == Route::TRACK_ROUTE)
                {
                  //if(i->track == dst.track)
                  {
                    //if(i->channels == dst.channels)
                    {
                      //#ifdef ROUTE_DEBUG
                      fprintf(stderr, "addRoute: src track route already exists.\n");
                      //#endif
                      return;
                    }
                    //else
                    //{
                    
                    //}
                  }
                }
              }      
        }
        outRoutes->push_back(dst);
        RouteList* inRoutes;
        
        #ifdef ROUTE_DEBUG
        fprintf(stderr, "addRoute: src track ch:%d chs:%d remch:%d  dst track ch:%d chs:%d remch:%d name: %s pushing dest and source routes\n", 
          src.channel, src.channels, src.remoteChannel, dst.channel, dst.channels, dst.remoteChannel, dst.track->name().toLatin1().constData());
        #endif
        inRoutes = dst.track->inRoutes();
        
        // make sure AUDIO_AUX is processed last
        if (src.track->type() == Track::AUDIO_AUX)       // REMOVE Tim. This special aux code may not be useful or needed now.
              inRoutes->push_back(src);                  // so DELETETHIS?
        else
              inRoutes->insert(inRoutes->begin(), src);
        
        // Is the source an Aux Track or else does it have Aux Tracks routed to it?
        // Update the destination track's aux ref count, and all tracks it is routed to.
        if(src.track->auxRefCount())
            src.track->updateAuxRoute( src.track->auxRefCount(), dst.track );
        else 
        if(src.track->type() == Track::AUDIO_AUX)
            src.track->updateAuxRoute( 1, dst.track );
      }
}

//---------------------------------------------------------
//   removeRoute
//---------------------------------------------------------

void removeRoute(Route src, Route dst)
{
      if (src.type == Route::JACK_ROUTE) 
      {
            if(!dst.isValid())
            {
              printf("removeRoute: source is jack, invalid destination\n");
              return;
            }
            
            if (dst.type == Route::TRACK_ROUTE) 
            {
              if (dst.track->type() != Track::AUDIO_INPUT) 
              {
                    fprintf(stderr, "removeRoute: source is jack, destination is track but not audio input\n");
                    return;
              }
              RouteList* inRoutes = dst.track->inRoutes();
              iRoute i;
              for (i = inRoutes->begin(); i != inRoutes->end(); ++i) 
              {
                    if (*i == src) 
                    {
                          inRoutes->erase(i);
                          break;
                    }
              }
            }  
            else
            if (dst.type == Route::MIDI_DEVICE_ROUTE) 
            {
              RouteList* routes = dst.device->inRoutes();
              iRoute i;
              for (i = routes->begin(); i != routes->end(); ++i) 
              {
                    if (*i == src) 
                    {
                          routes->erase(i);
                          break;
                    }
              }
            }  
            else
            {
                  fprintf(stderr, "removeRoute: source is jack, destination unknown\n");
                  return;
            }
      }
      else if (dst.type == Route::JACK_ROUTE) 
      {
            if(!src.isValid())
            {
              printf("removeRoute: destination is jack, invalid source\n");
              return;
            }
            
            if (src.type == Route::TRACK_ROUTE) 
            {
              if (src.track->type() != Track::AUDIO_OUTPUT) 
              {
                    fprintf(stderr, "removeRoute: destination is jack, source is track but not audio output\n");
                    return;
              }
              RouteList* outRoutes = src.track->outRoutes();
              iRoute i;
              for (i = outRoutes->begin(); i != outRoutes->end(); ++i) 
              {
                    if (*i == dst) {
                          outRoutes->erase(i);
                          break;
                          }
              }
            }  
            else
            if (src.type == Route::MIDI_DEVICE_ROUTE) 
            {
              RouteList* routes = src.device->outRoutes();
              iRoute i;
              for (i = routes->begin(); i != routes->end(); ++i) 
              {
                    if (*i == dst) {
                          routes->erase(i);
                          break;
                          }
              }
            }  
            else
            {
                  fprintf(stderr, "removeRoute: destination is jack, source unknown\n");
                  return;
            }
      }
      else if(src.type == Route::MIDI_PORT_ROUTE)  
      {
        if(dst.type != Route::TRACK_ROUTE)
        {
          fprintf(stderr, "removeRoute: source is midi port:%d, but destination is not track\n", src.midiPort);
          return;
        }
        
        if(src.isValid())
        {
          MidiPort *mp = &MusEGlobal::midiPorts[src.midiPort];
          RouteList* outRoutes = mp->outRoutes();
          for (iRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
          {
            if(i->type == Route::TRACK_ROUTE && i->track == dst.track)  // Is there a route to the track?
            {
              //printf("i->channel:%x dst.channel:%x\n", i->channel, dst.channel);   
              i->channel &= ~dst.channel;        // Unset the desired channel bits.
              if(i->channel == 0)                // Only if there are no channel bits set, erase the route.
              {
                //printf("erasing out route from midi port:%d\n", src.midiPort);   
                outRoutes->erase(i);
              }  
              
              break;  // For safety, keep looking and remove any more found.
                      // No, must break, else crash. There should only be one route anyway...
            }
          }
        }
        else
          printf("removeRoute: source is midi port:%d but invalid\n", src.midiPort); 
        
        if(dst.isValid())
        {
          RouteList* inRoutes = dst.track->inRoutes();
          for (iRoute i = inRoutes->begin(); i != inRoutes->end(); ++i) 
          {
            if (i->type == Route::MIDI_PORT_ROUTE && i->midiPort == src.midiPort)  // Is there a route to the midi port?
            {
              i->channel &= ~src.channel;        // Unset the desired channel bits.
              if(i->channel == 0)                // Only if there are no channel bits set, erase the route.
                inRoutes->erase(i);
              
              break;  // For safety, keep looking and remove any more found.
                      // No, must break, else crash. There should only be one route anyway...
            }
          }
        }
        else
          printf("removeRoute: source is midi port:%d but destination track invalid\n", src.midiPort);
      }      
      else if(dst.type == Route::MIDI_PORT_ROUTE)  
      {
        if(src.type != Route::TRACK_ROUTE)
        {
          fprintf(stderr, "removeRoute: destination is midi port:%d, but source is not track\n", dst.midiPort);
          return;
        }
        
        if(src.isValid())
        {
          RouteList* outRoutes = src.track->outRoutes();
          for (iRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
          {
            if (i->type == Route::MIDI_PORT_ROUTE && i->midiPort == dst.midiPort)  // Is there a route to the midi port?
            {
              i->channel &= ~dst.channel;        // Unset the desired channel bits.
              if(i->channel == 0)                // Only if there are no channel bits set, erase the route.
                  outRoutes->erase(i);
                  
              break;  // For safety, keep looking and remove any more found.
                      // No, must break, else crash. There should only be one route anyway...
            }
          }
        }  
        else
          printf("removeRoute: destination is midi port:%d but source track is invalid\n", dst.midiPort);
        
        if(dst.isValid())
        {
          MidiPort *mp = &MusEGlobal::midiPorts[src.midiPort];
          RouteList* inRoutes = mp->inRoutes();
          for (iRoute i = inRoutes->begin(); i != inRoutes->end(); ++i) 
          {
            if (i->type == Route::TRACK_ROUTE && i->track == src.track)  // Is there a route to the track?
            {
              i->channel &= ~src.channel;        // Unset the desired channel bits.
              if(i->channel == 0)                // Only if there are no channel bits set, erase the route.
                inRoutes->erase(i);
                  
              break;  // For safety, keep looking and remove any more found.
                      // No, must break, else crash. There should only be one route anyway...
            }
          }
        }  
        else
          printf("removeRoute: destination is midi port:%d but invalid\n", dst.midiPort);
      }
      else 
      {
            if(src.type != Route::TRACK_ROUTE || dst.type != Route::TRACK_ROUTE)  
            {
              fprintf(stderr, "removeRoute: source and destination are not tracks\n");
              return;
            }
            
            // Is the source an Aux Track or else does it have Aux Tracks routed to it?
            // Update the destination track's aux ref count, and all tracks it is routed to.
            if(src.isValid() && dst.isValid())
            {
              if(src.track->auxRefCount())
                src.track->updateAuxRoute( -src.track->auxRefCount(), dst.track );
              else
              if(src.track->type() == Track::AUDIO_AUX)
                src.track->updateAuxRoute( -1, dst.track );
            }
            
            if(src.isValid())
            {
              RouteList* outRoutes = src.track->outRoutes();
              for (iRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
              {
                    if (*i == dst) {
                          outRoutes->erase(i);
                          break;
                          }
              }
            }  
            else
              printf("removeRoute: source is track but invalid\n");
            
            if(dst.isValid())
            {
              RouteList* inRoutes;
              
              inRoutes = dst.track->inRoutes();
              for (iRoute i = inRoutes->begin(); i != inRoutes->end(); ++i) 
              {
                    if (*i == src) {
                          inRoutes->erase(i);
                          break;
                          }
              }
            }  
            else
              printf("removeRoute: source is track but destination invalid\n");
      }
}

//---------------------------------------------------------
//   removeAllRoutes
//   If src is valid, disconnects all output routes from it.
//   If dst is valid, disconnects all input routes to it.
//   src and dst Route are used SIMPLY because Route provides convenient way to 
//    specify the different pointer types (track, port, jack)
//   This routine will ONLY look at the pointer, not the channel or port etc...
//   So far it only works with MidiDevice <-> Jack.
//---------------------------------------------------------

void removeAllRoutes(Route src, Route dst)
{
    // TODO: Is the source an Aux Track or else does it have Aux Tracks routed to it?
    // Update the destination track's aux ref count, and all tracks it is routed to.
    /* if(src.isValid() && dst.isValid()) DELETETHIS 8
    {
      if(src.track->auxRefCount())
        dst.track->updateAuxStates( -src.track->auxRefCount() );
      else  
      if(src.track->type() == Track::TrackType::AUDIO_AUX))
        dst.track->updateAuxStates( -1 );
    }  */

    if(src.isValid())  
    {
      if(src.type == Route::MIDI_DEVICE_ROUTE)
        src.device->outRoutes()->clear();
      else
        printf("removeAllRoutes: source is not midi device\n");
    }  
      
    if(dst.isValid())  
    {
      if(dst.type == Route::MIDI_DEVICE_ROUTE)
        dst.device->inRoutes()->clear();
      else
        printf("removeAllRoutes: dest is not midi device\n");
    }  
}

//---------------------------------------------------------
//   track2name
//    create string name representation for audio node
//---------------------------------------------------------

static QString track2name(const Track* n)
      {
      if (n == 0)
            return QWidget::tr("None");
      return n->name();
      }

//---------------------------------------------------------
//   name
//    create string name representation for audio node
//---------------------------------------------------------

QString Route::name() const
{
      if(type == MIDI_DEVICE_ROUTE) 
      {
        if(device)
        {
          // DELETETHIS 6
          // For unified jack in/out devices, the actual port names are now different from device name.
          // Like this:   device: "MyJackDevice1" ->  inport: "MyJackDevice1_in"  outport: "MyJackDevice1_out"
          /*  
          if(device->deviceType() == MidiDevice::JACK_MIDI)
            return MusEGlobal::audioDevice->portName(device->clientPort());
          else
          */
          
          return device->name();
        }
        return QWidget::tr("None");
      }
      else
      if(type == JACK_ROUTE) 
      {
        if (!MusEGlobal::checkAudioDevice()) return "";
        return MusEGlobal::audioDevice->portName(jackPort);
      }
      else
      if(type == MIDI_PORT_ROUTE) 
      {
        return ROUTE_MIDIPORT_NAME_PREFIX + QString().setNum(midiPort);
      }
      else
        return track2name(track);
}

//---------------------------------------------------------
//   name2route
//---------------------------------------------------------

Route name2route(const QString& rn, bool /*dst*/, int rtype)
{
  int channel = -1;
  QString s(rn);
  // Support old route style in med files. Obsolete.
  if (rn[0].isNumber() && rn[1]==':') 
  {
    channel = rn[0].toAscii() - int('1');
    s = rn.mid(2);
  }
  
  if(rtype == -1)
  {  
      if(MusEGlobal::checkAudioDevice())
      {
        void* p = MusEGlobal::audioDevice->findPort(s.toLatin1().constData());
        if(p)
          return Route(p, channel);
      }
      
      TrackList* tl = MusEGlobal::song->tracks();
      for(iTrack i = tl->begin(); i != tl->end(); ++i) 
      {
        if((*i)->isMidiTrack())
        {
          MidiTrack* track = (MidiTrack*)*i;
          if(track->name() == s)
            return Route(track, channel);
        }
        else
        {  
          AudioTrack* track = (AudioTrack*)*i;
          if(track->name() == s)
            return Route(track, channel);
        }      
      }
      
      for(iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i) 
      {
        if((*i)->name() == s)
            return Route(*i, channel);
      }
    
      if(s.left(ROUTE_MIDIPORT_NAME_PREFIX.length()) == ROUTE_MIDIPORT_NAME_PREFIX)
      {
        bool ok = false;
        int port = s.mid(ROUTE_MIDIPORT_NAME_PREFIX.length()).toInt(&ok);
        if(ok)
          return Route(port, channel);
      }
  }
  else
  {
      if(rtype == Route::TRACK_ROUTE)
      {  
        TrackList* tl = MusEGlobal::song->tracks();
        for(iTrack i = tl->begin(); i != tl->end(); ++i) 
        {
          if((*i)->isMidiTrack())
          {
            MidiTrack* track = (MidiTrack*)*i;
            if(track->name() == s)
              return Route(track, channel);
          }
          else
          {  
            AudioTrack* track = (AudioTrack*)*i;
            if(track->name() == s)
              return Route(track, channel);
          }      
        }
      }
      else
      //if((rtype == Route::JACK_MIDI_ROUTE) || (rtype == Route::ALSA_MIDI_ROUTE))
      // TODO Distinguish the device types
      if(rtype == Route::MIDI_DEVICE_ROUTE)
      {  
        for(iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i) 
        {
          if((*i)->name() == s)
            return Route(*i, channel);
          
          /* DELETETHIS 16?
          MidiJackDevice* jmd = dynamic_cast<MidiJackDevice*>(*i);
          if(jmd)
          {     
            if(jmd->name() == s)
            //if (jmd->name() == rn)
                return Route(jmd);
          }      
          MidiAlsaDevice* amd = dynamic_cast<MidiAlsaDevice*>(*i);
          if(amd)
          {     
            // TODO
            if(amd->name() == s)
            //if (amd->name() == rn)
                return Route(amd);
          }      
          */
        }
      }
      else
      if(rtype == Route::JACK_ROUTE)
      {  
        if(MusEGlobal::checkAudioDevice())
        {
          void* p = MusEGlobal::audioDevice->findPort(s.toLatin1().constData());
          if(p)
            return Route(p, channel);
        }      
      }
      else
      if(rtype == Route::MIDI_PORT_ROUTE) 
      {  
        if(s.left(ROUTE_MIDIPORT_NAME_PREFIX.length()) == ROUTE_MIDIPORT_NAME_PREFIX)
        {
          bool ok = false;
          int port = s.mid(ROUTE_MIDIPORT_NAME_PREFIX.length()).toInt(&ok);
          if(ok)
            return Route(port, channel);
        }
      }
  }
  
  printf("  name2route: <%s> not found\n", rn.toLatin1().constData());
  return Route((Track*) 0, channel);
}

//---------------------------------------------------------
//   checkRoute
//    return true if route is valid
//---------------------------------------------------------

bool checkRoute(const QString& s, const QString& d)
      {
      Route src(s, false, -1);
      Route dst(d, true, -1);

      if (!(src.isValid() && dst.isValid()) || (src == dst))
            return false;
      if (src.type == Route::JACK_ROUTE) 
      {
            if (dst.type == Route::TRACK_ROUTE) 
            {
              if (dst.track->type() != Track::AUDIO_INPUT) {
                    return false;
                    }
              src.channel = dst.channel;
              RouteList* inRoutes = dst.track->inRoutes();
              for (ciRoute i = inRoutes->begin(); i != inRoutes->end(); ++i) 
              {
                    if (*i == src) {   // route already there
                          return false;
                          }
              }
            }
            else
            if (dst.type == Route::MIDI_DEVICE_ROUTE) 
            {
              src.channel = -1;
              RouteList* routes = dst.device->inRoutes();
              for (ciRoute i = routes->begin(); i != routes->end(); ++i) 
              {
                    if (*i == src) {   // route already there
                          return false;
                          }
              }
            }
            else
              return false;
      }  
      else if (dst.type == Route::JACK_ROUTE) 
      {
            if (src.type == Route::TRACK_ROUTE) 
            {
              if (src.track->type() != Track::AUDIO_OUTPUT) {
                    return false;
                    }
              RouteList* outRoutes = src.track->outRoutes();
              dst.channel = src.channel;
              for (ciRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
              {
                    if (*i == dst) {   // route already there
                          return false;
                          }
              }
            }
            else
            if (src.type == Route::MIDI_DEVICE_ROUTE) 
            {
              RouteList* routes = src.device->outRoutes();
              dst.channel = -1;
              for (ciRoute i = routes->begin(); i != routes->end(); ++i) 
              {
                    if (*i == dst) {   // route already there
                          return false;
                          }
              }
            }
            else
              return false;
      }  
      else if (src.type == Route::MIDI_PORT_ROUTE) 
      {
            RouteList* outRoutes = MusEGlobal::midiPorts[src.midiPort].outRoutes();
            for (ciRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
            {
                  if (*i == dst) {   // route already there
                        return false;
                        }
            }
      }
      else 
      {
            RouteList* outRoutes = (src.type == Route::MIDI_DEVICE_ROUTE) ? src.device->outRoutes() : src.track->outRoutes();
            
            for (ciRoute i = outRoutes->begin(); i != outRoutes->end(); ++i) 
            {
                  if (*i == dst) {   // route already there
                        return false;
                        }
            }
      }
      return true;
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Route::read(Xml& xml)
{
      QString s;
      int dtype = MidiDevice::ALSA_MIDI;
      int port = -1;                             
      unsigned char rtype = Route::TRACK_ROUTE;  
      
      for (;;) 
      {
            const QString& tag = xml.s1();
            Xml::Token token = xml.parse();
            switch (token) 
            {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Attribut:
                        #ifdef ROUTE_DEBUG
                        printf("Route::read(): attribute:%s\n", tag.toLatin1().constData());
                        #endif
                        if(tag == "type")
                          rtype = xml.s2().toInt();
                        else
                        if(tag == "devtype")
                        {
                          dtype = xml.s2().toInt();
                          rtype = Route::MIDI_DEVICE_ROUTE;
                        }
                        else
                        if(tag == "name")
                          s = xml.s2();
                        else
                        if(tag == "mport") 
                        {
                          port = xml.s2().toInt();
                          rtype = Route::MIDI_PORT_ROUTE;
                        }
                        else  
                          printf("Route::read(): unknown attribute:%s\n", tag.toLatin1().constData());
                        break;
                  case Xml::TagEnd:
                        #ifdef ROUTE_DEBUG
                        printf("Route::read(): tag end type:%d channel:%d name:%s\n", rtype, channel, s.toLatin1().constData());
                        #endif
                        if(rtype == MIDI_PORT_ROUTE)  
                        {
                          if(port >= 0 && port < MIDI_PORTS)
                          {
                            type = rtype;
                            midiPort = port;
                          }
                          else
                            printf("Route::read(): midi port <%d> out of range\n", port);
                        }
                        else
                        if(!s.isEmpty())
                        {
                          if(rtype == TRACK_ROUTE) 
                          {
                            TrackList* tl = MusEGlobal::song->tracks();
                            iTrack i = tl->begin();
                            for ( ; i != tl->end(); ++i) 
                            {
                              Track* t = *i;
                              if (t->name() == s) 
                              {
                                track = t;
                                type = rtype;
                                break;
                              }
                            }
                            if(i == tl->end())
                              printf("Route::read(): track <%s> not found\n", s.toLatin1().constData());
                          }
                          else
                          if(rtype == JACK_ROUTE) 
                          {
                            void* jport = 0;
                            if (MusEGlobal::audioDevice) // fix crash if jack is zombified at this point
                              jport=MusEGlobal::audioDevice->findPort(s.toLatin1().constData());
                            if(jport == 0)
                              printf("Route::read(): jack port <%s> not found\n", s.toLatin1().constData());
                            else
                            {
                              jackPort = jport;
                              type = rtype;
                            }
                          }
                          else
                          if(rtype == MIDI_DEVICE_ROUTE)
                          {
                            iMidiDevice imd = MusEGlobal::midiDevices.begin();
                            for( ; imd != MusEGlobal::midiDevices.end(); ++imd) 
                            {
                              MidiDevice* md = *imd;
                              if(md->name() == s && md->deviceType() == dtype) 
                              {
                                // We found a device, but if it is not in use by the song (port is -1), ignore it. 
                                // This prevents loading and propagation of bogus routes in the med file.
                                if(md->midiPort() == -1)
                                  break;
                                
                                device = md;
                                type = rtype;
                                break;
                              }
                            }
                            if(imd == MusEGlobal::midiDevices.end())
                              printf("Route::read(): midi device <%s> not found\n", s.toLatin1().constData());
                          }
                        }
                        return;
                  default:
                        break;
            }
      }
}


//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Song::readRoute(Xml& xml)
{
      QString src;
      QString dst;
      int ch          = -1;
      int chs         = -1;
      int remch       = -1;

      Route sroute, droute;
      
      for (;;) 
      {
            const QString& tag = xml.s1();
            Xml::Token token = xml.parse();
            switch (token) 
            {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        // 2010/02/03 Support old routes in med files. Now obsolete!
                        if (tag == "srcNode")
                              src = xml.parse1();
                        else if (tag == "dstNode")
                              dst = xml.parse1();
                        // Support new routes.
                        else if (tag == "source")
                        {
                              sroute.read(xml);
                              sroute.channel       = ch;
                              sroute.channels      = chs;
                              sroute.remoteChannel = remch;
                        }
                        else if (tag == "dest")
                        {
                              droute.read(xml);
                              droute.channel       = ch;
                              droute.channels      = chs;
                              droute.remoteChannel = remch;
                        }      
                        else
                              xml.unknown("readRoute");
                        break;
                  case Xml::Attribut:
                        #ifdef ROUTE_DEBUG
                        printf("Song::readRoute(): attribute:%s\n", tag.toLatin1().constData());
                        #endif
                        if(tag == "channel")
                          ch = xml.s2().toInt();
                        else
                        if(tag == "channels")
                          chs = xml.s2().toInt();
                        else
                        if(tag == "remch")
                          remch = xml.s2().toInt();
                        else
                        if(tag == "channelMask")           // New channel mask for midi port-track routes.
                          ch = xml.s2().toInt();               
                        else
                          printf("Song::readRoute(): unknown attribute:%s\n", tag.toLatin1().constData());
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "Route") 
                        {
                          // Support old routes in med files. Now obsolete!
                          if(!src.isEmpty() && !dst.isEmpty())
                          {
                            Route s = name2route(src, false);
                            Route d = name2route(dst, true);
                            addRoute(s, d);
                          }  
                          else
                          // Support new routes.
                          if(sroute.isValid() && droute.isValid())
                          {    
                            // Support pre- 1.1-RC2 midi-device-to-track routes. Obsolete. Replaced with midi port routes.
                            if(sroute.type == Route::MIDI_DEVICE_ROUTE && droute.type == Route::TRACK_ROUTE) 
                            {
                              if(sroute.device->midiPort() >= 0 && sroute.device->midiPort() < MIDI_PORTS
                                 && ch >= 0 && ch < MIDI_CHANNELS)         
                              {
                                sroute.midiPort = sroute.device->midiPort();
                                sroute.device = 0;
                                sroute.type = Route::MIDI_PORT_ROUTE;
                                
                                sroute.channel = 1 << ch;                  // Convert to new bit-wise channel mask.
                                droute.channel = sroute.channel;
                                
                                addRoute(sroute, droute);
                              }
                              else  
                                printf("  Warning - device:%s to track route, no device midi port or chan:%d out of range. Ignoring route!\n", 
                                       sroute.device->name().toLatin1().constData(), ch);
                            }
                            else if(sroute.type == Route::TRACK_ROUTE && droute.type == Route::MIDI_DEVICE_ROUTE) 
                            {
                              if(droute.device->midiPort() >= 0 && droute.device->midiPort() < MIDI_PORTS
                                 && ch >= 0 && ch < MIDI_CHANNELS)        
                              {
                                droute.midiPort = droute.device->midiPort();
                                droute.device = 0;
                                droute.type = Route::MIDI_PORT_ROUTE;
                                
                                droute.channel = 1 << ch;                  // Convert to new bit-wise channel mask.
                                sroute.channel = droute.channel;
                                
                                addRoute(sroute, droute);
                              }  
                              else  
                                printf("  Warning - track to device:%s route, no device midi port or chan:%d out of range. Ignoring route!\n", 
                                       droute.device->name().toLatin1().constData(), ch);
                            }
                            else
                            {
                              addRoute(sroute, droute);
                            }  
                          }
                          else
                            printf("  Warning - route invalid. Ignoring route!\n");
                          
                          return;
                        }
                  default:
                        break;
             }
      }
}

//---------------------------------------------------------
//   removeRoute
//---------------------------------------------------------

void RouteList::removeRoute(const Route& r)
      {
      for (iRoute i = begin(); i != end(); ++i) {
            if (r == *i) {
                  erase(i);
                  return;
                  }
            }
      printf("internal error: cannot remove Route\n");
      }

//---------------------------------------------------------
//   dump
//---------------------------------------------------------

void Route::dump() const
{
      if (type == TRACK_ROUTE)
      {
        if(track)
          printf("Route dump: track <%s> channel %d channels %d\n", track->name().toLatin1().constData(), channel, channels);
      }      
      else 
      if (type == JACK_ROUTE)
      {
        if(MusEGlobal::checkAudioDevice())
          printf("Route dump: jack audio port <%s> channel %d\n", MusEGlobal::audioDevice->portName(jackPort).toLatin1().constData(), channel);
      }
      else 
      if (type == MIDI_PORT_ROUTE) 
      {
        printf("Route dump: midi port <%d> channel mask %d\n", midiPort, channel);
      }
      else
      if (type == MIDI_DEVICE_ROUTE)
      {
        printf("Route dump: ");
        if(device)
        {
          if(device->deviceType() == MidiDevice::JACK_MIDI)
          {
            if(MusEGlobal::checkAudioDevice())
            {  
              printf("jack midi device <%s> ", device->name().toLatin1().constData());
              if(device->inClientPort())
                printf("input port <%s> ", 
                       MusEGlobal::audioDevice->portName(device->inClientPort()).toLatin1().constData());
              if(device->outClientPort())
                printf("output port <%s> ", 
                       MusEGlobal::audioDevice->portName(device->outClientPort()).toLatin1().constData());
            }           
          }
          else
          if(device->deviceType() == MidiDevice::ALSA_MIDI)
            printf("alsa midi device <%s> ", device->name().toLatin1().constData());
          else
          if(device->deviceType() == MidiDevice::SYNTH_MIDI)
            printf("synth midi device <%s> ", device->name().toLatin1().constData());
          else
            printf("is midi but unknown device type:%d, ", device->deviceType());
        }
        else
          printf("is midi but invalid device, ");
          
        printf("channel:%d\n", channel);
      }
      else
        printf("Route dump: unknown route type:%d\n", type);
}

//---------------------------------------------------------
//   operator==
//---------------------------------------------------------

bool Route::operator==(const Route& a) const
{
      if ((type == a.type) && (channel == a.channel)) 
      {
            if (type == TRACK_ROUTE)
            {
                  return track == a.track && channels == a.channels && remoteChannel == a.remoteChannel;
            }
            else 
            if(channel == a.channel)
            {
              if (type == JACK_ROUTE)
              {
                    //if (!MusEGlobal::checkAudioDevice()) return false; DELETETHIS? or keep?
                    //else return MusEGlobal::audioDevice->portName(jackPort) == MusEGlobal::audioDevice->portName(a.jackPort);
                    return jackPort == a.jackPort;    // Simplified.
              }
              else 
              if (type == MIDI_PORT_ROUTE) 
              {
                return midiPort == a.midiPort;
              }
              else 
              if (type == MIDI_DEVICE_ROUTE)
              {
                return device == a.device;
              }
            }    
      }
      return false;
}

/* yaaaay, depth search. just wrote an exam about that. but DELETETHIS, looks unused
//---------------------------------------------------------
//   isCircularRoute
//   Recursive.
//   If dst is valid, start traversal from there, not from src.
//   Returns true if circular.
//---------------------------------------------------------

bool isCircularRoutePath(Track* src, Track* dst)
{
  bool rv = false;
  
  if(dst)
  {  
    src->setNodeTraversed(true);
    rv = isCircularRoutePath(dst, NULL);
    src->setNodeTraversed(false);
    return rv;
  }
  
  if(src->nodeTraversed())
    return true;
  
  src->setNodeTraversed(true);
  
  RouteList* orl = src->outRoutes();
  for (iRoute i = orl->begin(); i != orl->end(); ++i) 
  {
    if( !(*i).isValid() || (*i).type != Route::TRACK_ROUTE )
      continue;
    Track* t = (*i).track;
    //if(t->isMidiTrack())
    //  continue;
    rv = isCircularRoutePath(src, NULL);
    if(rv)
      break; 
  }
  
  src->setNodeTraversed(false);
  return rv;
}
*/


} // namespace MusECore
