//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: midiserial.cpp,v 1.1.1.1 2003/10/29 10:06:12 wschweer Exp $
//  (C) Copyright 1999 Werner Schweer (ws@seh.de)
//=========================================================

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include <qstring.h>
#include "globals.h"
#include "midiserial.h"
#include "xml.h"

SerialDeviceList serialDevices;

//---------------------------------------------------------
//   writeSerialPorts
//---------------------------------------------------------

void writeSerialPorts(int level, Xml& xml)
      {
      if (serialDevices.empty())
            return;
      for (iSerialDevice i = serialDevices.begin(); i != serialDevices.end(); ++i) {
            xml.tag(level++, "serialPort");
            xml.strTag(level, "name", (*i)->name());
            xml.strTag(level, "path", (*i)->path());
            xml.intTag(level, "type", (*i)->type());
            xml.intTag(level, "speed", (*i)->speed());
            xml.intTag(level, "handshaking", (*i)->handshaking());
            xml.intTag(level, "ports", (*i)->vports());
            xml.etag(level--, "serialPort");
            }
      }

//---------------------------------------------------------
//   deleteSerialDevice
//    return true on error
//---------------------------------------------------------

bool deleteSerialDevice(const QString& path)
      {
      for (iSerialDevice i = serialDevices.begin(); i != serialDevices.end(); ++i) {
            if ((*i)->path() == path) {
                  if ((*i)->fd() != -1) {
printf("deleteSerialDevice:busy %s\n", path.latin1());
                        return true;
                        }
                  serialDevices.erase(i);
                  return false;
                  }
            }
      return false;     // ignore "not found" error
      }

//---------------------------------------------------------
//   readSerialPort
//---------------------------------------------------------

void readSerialPort(Xml& xml)
      {
      SerialDevice* port = new SerialDevice;

      for (;;) {
            Xml::Token token = xml.parse();
            QString tag = xml.s1();
            if (token == Xml::Error || token == Xml::End)
                  break;
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "name")
                              port->setName(xml.parse1());
                        else if (tag == "path")
                              port->setPath(xml.parse1());
                        else if (tag == "type")
                              port->setType(SerialType(xml.parseInt()));
                        else if (tag == "speed")
                              port->setSpeed(xml.parseInt());
                        else if (tag == "handshaking")
                              port->setHandshaking(xml.parseInt());
                        else if (tag == "ports")
                              port->setVports(xml.parseInt());
                        else
                              xml.unknown("serialPort");
                        break;
                  case Xml::TagEnd:
                        if (tag == "serialPort") {
                              serialDevices.push_back(port);
                              for (int i = 0; i < port->vports(); ++i) {
                                    QString name = port->name();
                                    if (port->vports() > 1) {
                                          QString pn;
                                          pn.setNum(i+1);
                                          name += "-" + pn;
                                          }
                                    MidiSerialDevice* dev = new MidiSerialDevice(port, i, name);
                                    midiDevices.add(dev);
                                    }
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   SerialDevice
//---------------------------------------------------------

SerialDevice::SerialDevice(const QString& p, SerialType t,
   int s, int vd)
      {
      _type   = t;
      _path   = p;
      _fd     = -1;
      refs    = 0;
      _speed  = s;
      _vports = vd;
      curPort = (_vports == 1) ? 0 : -1;
      }

SerialDevice::SerialDevice()
      {
      _type = RAW_DEVICE;
      _fd   = -1;
      refs  = 0;
      curPort = -1;
      }

SerialDevice::~SerialDevice()
      {
      close(0);
      }

//---------------------------------------------------------
//   setVports
//---------------------------------------------------------

void SerialDevice::setVports(int v)
      {
      _vports = v;
      if (_vports == 1)
            curPort = 0;
      }

//---------------------------------------------------------
//   incRef
//---------------------------------------------------------

void SerialDevice::incRef(int val)
      {
      refs += val;
      if (refs <= 0) {
//            printf("incRef %d: close\n", val);
            close(_fd);
            refs = 0;
            }
      }

//---------------------------------------------------------
//   open
//---------------------------------------------------------

const QString SerialDevice::open(int)
      {
      if (_fd == -1) {
            _fd = ::open(_path.latin1(), O_RDWR | O_NDELAY);
            if (_fd == -1)
                  return QString(strerror(errno));
            if (_type == SERIAL_PORT) {
                  struct termios term;
                  if (tcgetattr(_fd, &term) == -1) {
                        printf("serialOpen(%s): tcgetattr failed: %s\n",
                           _path.latin1(), strerror(errno));
                        int e = errno;
                        ::close(_fd);
                        _fd = -1;
                        return QString(strerror(e));
                        }
                  term.c_iflag = IGNBRK;
                  term.c_oflag &= ~OPOST;
                  term.c_cflag = CS8 | CREAD | CLOCAL;
                  term.c_lflag = 0;
                  term.c_cc[VTIME] = 0;
                  term.c_cc[VMIN]  = 0;
                  cfsetispeed(&term, _speed);
                  cfsetospeed(&term, _speed);
                  if (tcsetattr(_fd, TCSANOW, &term) == -1) {
                        printf("serialOpen(%s): tcsetattr failed: %s\n",
                           _path.latin1(), strerror(errno));
                        int e = errno;
                        ::close(_fd);
                        _fd = -1;
                        return QString(strerror(e));
                        }
                  }
            }
      return QString((_fd != -1) ? "OK" : strerror(errno));
      }

//---------------------------------------------------------
//   close
//---------------------------------------------------------

void SerialDevice::close(int)
      {
      if (_fd != -1) {
            printf("close <%s>: %d\n", _path.latin1(), _fd);
            ::close(_fd);
            }
      _fd = -1;
      }

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

int SerialDevice::read(int, unsigned char* buffer, int n)
      {
      if (_fd == -1)
            return 0;
      return ::read(_fd, buffer, n);
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

int SerialDevice::write(int id, const unsigned char* buffer, int n)
      {
      if (n == 0 || _fd == -1)
            return n;
      if (curPort != id) {
            char nbuffer[2];
            nbuffer[0] = 0xf5;
            nbuffer[1] = id;
            for (int i = 0; i < 20; ++i) {
                  int rv = ::write(_fd, nbuffer, 2);
                  if (midiOutputTrace)
                        printf("MidiSerial(%s,%d): write 2: 0x%02x 0x%02x\n",
                           _path.latin1(), id, nbuffer[0] & 0xff, nbuffer[1] & 0xff);
                  if (!((rv == -1) && (errno == EAGAIN))) {
                        if (rv == -1) {
                              printf("MidiSerial(%s): write(%d,xx,%d) <%s>\n",
                                 _path.latin1(), _fd, 2, strerror(errno));
                              exit(-1);
                              }
                        break;
                        }
                  if (midiOutputTrace)
                        printf("MidiSerial(%s) write(%d,xx,%d) <%s>\n",
                           _path.latin1(), _fd, 2, strerror(errno));
                  usleep(1000);
                  }
            curPort = id;
            }
      if (midiOutputTrace) {
            printf("MidiSerial<%s>: write %d: ", _path.latin1(), n);
            for (int i = 0; i < n; ++i)
                  printf("%02x ", buffer[i]);
            printf("\n");
            }
      int nn = ::write(_fd, buffer, n);
      if (n != nn) {
            printf("MidiSerial<%s>: write %d returns %d\n",
               _path.latin1(), n, nn);
            }
      return nn;
      }

//---------------------------------------------------------
//   MidiSerial
//---------------------------------------------------------

MidiSerialDevice::MidiSerialDevice(SerialDevice* d, int vd,
   const QString& n) : MidiRawDevice(n)
      {
// printf("add device %s\n", n.latin1());
      dev     = d;
      dev->incRef(1);
      vdev    = vd;
      _rwFlags = 0x3;
      }

MidiSerialDevice::~MidiSerialDevice()
      {
      dev->incRef(-1);
      }

//---------------------------------------------------------
//   initMidiSerial
//    check for raw midi ports
//          /dev/midiXX
//          /dev/sound/midiXX
//    return true on error
//---------------------------------------------------------

bool initMidiSerial()
      {
      const char* names[] = {
            "midi%02d", "midi%d"
            };
      int devicesFound = 0;
      for (unsigned k = 0; k < sizeof(names)/sizeof(*names); ++k) {
            for (int i = 0; i < 8; ++i) {
                  char buffer[32];
                  const char* path;
                  sprintf(buffer, names[k], i);
                  path = "/dev";
                  char pathbuffer[64];
                  sprintf(pathbuffer, "%s/%s", path, buffer);
                  int fd = ::open(pathbuffer, O_RDWR | O_NDELAY);
                  if (fd == -1 && (errno == ENOENT || errno == ENXIO)) {
                        path = "/dev/sound";
                        sprintf(pathbuffer, "%s/%s", path, buffer);
                        fd = ::open(pathbuffer, O_RDWR | O_NDELAY);
                        }
                  if (fd != -1) {
                        SerialDevice* sd = new SerialDevice(QString(pathbuffer), RAW_DEVICE, 0, 1);
                        MidiSerialDevice* dev = new MidiSerialDevice(sd, 0, QString(buffer));
                        midiDevices.add(dev);
                        close(fd);
                        ++devicesFound;
                        }
                  }
            }
      return devicesFound == 0;
      }

