/**************************************************************************

    deviceman.cc  - The device manager, that hides the use of midiOut
    This file is part of LibKMid 0.9.5
    Copyright (C) 1997,98,99,2000  Antonio Larrosa Jimenez
    LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libtdemid.html

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    $Id$

    Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org>

***************************************************************************/
#include "deviceman.h"
#include "midiout.h"
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "sndcard.h"
#include "synthout.h"
#include "fmout.h"
#include "gusout.h"
#include "alsaout.h"
#include "midimapper.h"
#include "midispec.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_ALSA_ASOUNDLIB_H
#       define HAVE_ALSA_SUPPORT
#	include <alsa/asoundlib.h>
#elif defined(HAVE_SYS_ASOUNDLIB_H)
#       define HAVE_ALSA_SUPPORT
#	include <sys/asoundlib.h>
#else
#ifdef HAVE_LIBASOUND2
#       define HAVE_ALSA_SUPPORT
#      include <sound/asound.h>
#      include <sound/asequencer.h>
#elif defined(HAVE_LIBASOUND)
#       define HAVE_ALSA_SUPPORT
#       include <linux/asequencer.h>
#endif
#endif

#if 1
#include <kinstance.h>
#include <tdeglobal.h>
#include <tdeconfig.h>
#endif

//#define DEVICEMANDEBUG
//#define GENERAL_DEBUG_MESSAGES

SEQ_DEFINEBUF (4096);

#define CONTROLTIMER

#ifdef GENERAL_DEBUG_MESSAGES
void DEBUGPRINTF(const char *format)
{
  printf(format);
}

void DEBUGPRINTF(const char *format,int i)
{
  printf(format,i);
}

void DEBUGPRINTF(const char *format,const char *s)
{
  printf(format,s);
}

#else

void DEBUGPRINTF(const char *) { }
void DEBUGPRINTF(const char *,int ) { }
void DEBUGPRINTF(const char *,const char * ) { }

#endif


DeviceManager::DeviceManager(int def)
{
#if 1
  if (def==-1)
  {
    TDEInstance *tmp_instance=0L;
    if (!TDEGlobal::_instance) tmp_instance=new TDEInstance("nonKDEapp");
    TDEConfig *config = new TDEConfig("kcmmidirc", true);

    config->setGroup("Configuration");
    default_dev=config->readNumEntry("midiDevice",0);
    if ( default_dev < 0 )
       default_dev=0;
    TQString mapurl(config->readPathEntry("mapFilename"));
    if ((config->readBoolEntry("useMidiMapper", false))&&(!mapurl.isEmpty()))
    {
      mapper_tmp = new MidiMapper( mapurl.mid(mapurl.find(":")+1 ).local8Bit() );
    }
    else
      mapper_tmp = 0L;

    delete config;
    delete tmp_instance;
  }
  else
#endif
  {
    default_dev = def;
    mapper_tmp = 0L;
  }

  initialized=0;
  _ok=1;
  alsa=false;
  device = 0L;
  m_rate=0;
  convertrate=10;
  seqfd=-1;
  timerstarted=0;
  n_midi=0;
  n_synths=0;
  n_total=0;
  midiinfo=0L;
  synthinfo=0L;
  for (int i=0;i<16;i++) chn2dev[i]=default_dev;
}

DeviceManager::~DeviceManager(void)
{
  closeDev();
  if (device)
  {
    for (int i=0;i<n_total;i++)
      delete device[i];
    delete[] device;
    device=0L;
  }
#ifdef HAVE_OSS_SUPPORT
  delete[] midiinfo;
  delete[] synthinfo;
#endif
}

int DeviceManager::ok(void)
{
  int r=_ok;
  _ok=1;
  return r;
}

int DeviceManager::checkInit(void)
{
  if (initialized==0)
  {
    int r=initManager();
    if (default_dev>=n_total) default_dev=0;
    DEBUGPRINTF("check : %d\n",r);
    return r;
  }
  return 0;
}

void DeviceManager::checkAlsa(void)
{
#ifdef HAVE_SYS_STAT_H
  struct stat buf;
  stat("/proc/asound", &buf);
  if ((stat("/proc/asound", &buf) == 0 ) && (S_ISDIR(buf.st_mode)))
    alsa=true;
  else
    alsa=false;
#else
#warning "ALSA won't be found at runtime"
  alsa=false;
#endif
}

int DeviceManager::initManager(void)
{
  checkAlsa();

  if (!alsa)  // We are using OSS
  {
#ifdef HAVE_OSS_SUPPORT
    n_synths=0;
    n_midi=0;
    n_total=0;

    seqfd = open("/dev/sequencer", O_WRONLY | O_NONBLOCK, 0);
    if (seqfd==-1)
    {
      fprintf(stderr,"ERROR: Couldn't open /dev/sequencer to get some information\n");
      _ok=0;
      return -1;
    }
    ioctl(seqfd,SNDCTL_SEQ_NRSYNTHS,&n_synths);
    ioctl(seqfd,SNDCTL_SEQ_NRMIDIS,&n_midi);
    n_total=n_midi+n_synths;


    if (n_midi==0)
    {
      fprintf(stderr,"ERROR: There's no midi port\n");
      /* This could be a problem if the user don't have a synth neither,
	 but not having any of both things is unusual */
      //    _ok=0;
      //    return 1;
    }

    device=new MidiOut*[n_total];
    midiinfo=new midi_info[n_midi];
    synthinfo=new synth_info[n_synths];

    int i;
    for (i=0;i<n_midi;i++)
    {
      midiinfo[i].device=i;
      if (ioctl(seqfd,SNDCTL_MIDI_INFO,&midiinfo[i])!=-1)
      {
#ifdef GENERAL_DEBUG_MESSAGES
	printf("----\n");
	printf("Device : %d\n",i);
	printf("Name : %s\n",midiinfo[i].name);
	printf("Device type : %d\n",midiinfo[i].dev_type);
#endif
      }
      device[i]=new MidiOut(i);
    }

    for (i=0;i<n_synths;i++)
    {
      synthinfo[i].device=i;
      if (ioctl(seqfd,SNDCTL_SYNTH_INFO,&synthinfo[i])!=-1)
      {
#ifdef GENERAL_DEBUG_MESSAGES
	printf("----\n");
	printf("Device : %d\n",i);
	printf("Name : %s\n",synthinfo[i].name);
	switch (synthinfo[i].synth_type)
	{
	  case (SYNTH_TYPE_FM) : printf("FM\n");break;
	  case (SYNTH_TYPE_SAMPLE) : printf("Sample\n");break;
	  case (SYNTH_TYPE_MIDI) : printf("Midi\n");break;
	  default : printf("default type\n");break;
	};
	switch (synthinfo[i].synth_subtype)
	{
	  case (FM_TYPE_ADLIB) : printf("Adlib\n");break;
	  case (FM_TYPE_OPL3) : printf("Opl3\n");break;
	  case (MIDI_TYPE_MPU401) : printf("Mpu-401\n");break;
	  case (SAMPLE_TYPE_GUS) : printf("Gus\n");break;
	  default : printf("default subtype\n");break;
	}
#endif
	if (synthinfo[i].synth_type==SYNTH_TYPE_FM)
	  device[i+n_midi]=new FMOut(i,synthinfo[i].nr_voices);
	else if ((synthinfo[i].synth_type==SYNTH_TYPE_SAMPLE)&&
	    (synthinfo[i].synth_subtype==SAMPLE_TYPE_GUS))
	  device[i+n_midi]=new GUSOut(i,synthinfo[i].nr_voices);
	else
	  device[i+n_midi]=new SynthOut(i);
      }
    }

    close(seqfd);
#else // There's no OSS support and ALSA wasn't detected
      // It must be one of those systems coolo is using :-)

    n_synths=0;
    n_midi=0;
    n_total=0;
    device=0L;
    midiinfo=0L;
    synthinfo=0L;

#endif

  }
  else
  {  // We are using ALSA

#ifdef HAVE_ALSA_SUPPORT
    int  client, port;
#ifdef HAVE_LIBASOUND2
    snd_seq_t *handle=0;
    snd_seq_client_info_t *clienti;
    snd_seq_client_info_malloc(&clienti);
    snd_seq_port_info_t *porti;
    snd_seq_port_info_malloc(&porti);

    snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0);
    if (!handle) { printf("handle==0\n"); return -1; };
    snd_seq_system_info_t *info;
    snd_seq_system_info_malloc(&info);
    if (!info) { printf("info==0\n"); return -1; };
    snd_seq_system_info(handle, info);

    n_total=0;
    n_midi=0;
    n_synths=0;

    device=new MidiOut*[snd_seq_system_info_get_clients(info)*snd_seq_system_info_get_ports(info)];
    unsigned int k=SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE ;
    for (client=0 ; client<snd_seq_system_info_get_clients(info) ; client++)
    {
      snd_seq_get_any_client_info(handle, client, clienti);
      for (port=0 ; port<snd_seq_client_info_get_num_ports(clienti) ; port++)
      {
        snd_seq_get_any_port_info(handle, client, port, porti);
        if (( snd_seq_port_info_get_capability(porti) & k ) == k)
        {
          device[n_midi]=new AlsaOut(n_midi,client, port, snd_seq_client_info_get_name(clienti), snd_seq_port_info_get_name(porti));
          n_midi++;
        };
      }
    }
    snd_seq_client_info_free(clienti);
    snd_seq_port_info_free(porti);
    snd_seq_system_info_free(info);
#else
    snd_seq_t *handle=0;
    snd_seq_client_info_t clienti;
    snd_seq_port_info_t porti;

    snd_seq_open(&handle, SND_SEQ_OPEN);
    if (!handle) { printf("handle(2)==0\n"); return -1; };

    snd_seq_system_info_t info;
    info.clients=info.ports=0;
    snd_seq_system_info(handle, &info);

    n_total=0;
    n_midi=0;
    n_synths=0;

    device=new MidiOut*[info.clients*info.ports];
    unsigned int k=SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE ;
    for (client=0 ; client<info.clients ; client++)
    {
      snd_seq_get_any_client_info(handle, client, &clienti);
      for (port=0 ; port<clienti.num_ports ; port++)
      {
	snd_seq_get_any_port_info(handle, client, port, &porti);
	if (( porti.capability & k ) == k)
	{
	  device[n_midi]=new AlsaOut(n_midi,client, port, clienti.name, porti.name);
	  n_midi++;
	};
      }
    }
#endif
    n_total=n_midi;

    snd_seq_close(handle);
#else

    // Note: Please don't add i18n for the text below, thanks :)

    fprintf(stderr,"Sorry, this KMid version was compiled without \n");
    fprintf(stderr,"ALSA support but you're using ALSA . \n");
    fprintf(stderr,"Please compile KMid for yourself or tell the people\n");
    fprintf(stderr,"at your Linux distribution to compile it themselves\n");
#endif
  }

  if (mapper_tmp!=0L) setMidiMap(mapper_tmp);

  initialized=1;

  return 0;
}

void DeviceManager::openDev(void)
{
  if (checkInit()<0)
  {
    DEBUGPRINTF("DeviceManager::openDev : Not initialized\n");
    _ok = 0;
    return;
  }
  _ok=1;

  if (!alsa)
  {
#ifdef HAVE_OSS_SUPPORT
    seqfd = open("/dev/sequencer", O_WRONLY | O_NONBLOCK, 0);
    if (seqfd==-1)
    {
      fprintf(stderr,"Couldn't open the MIDI sequencer device (/dev/sequencer)\n");
      _ok=0;
      return;
    }
    _seqbufptr = 0;
    ioctl(seqfd,SNDCTL_SEQ_RESET);
    //ioctl(seqfd,SNDCTL_SEQ_PANIC);
    m_rate=0;
    int r=ioctl(seqfd,SNDCTL_SEQ_CTRLRATE,&m_rate);
    if ((r==-1)||(m_rate<=0)) m_rate=HZ;
    
    convertrate=1000/m_rate;

#endif
  }
  else seqfd=0L; // ALSA

//  DEBUGPRINTF("Opening devices : ");
  for (int i=0;i<n_total;i++)
  {
    device[i]->openDev(seqfd);
//    DEBUGPRINTF("%s ",device[i]->deviceName());
  }
//  DEBUGPRINTF("\n");
  for (int i=0;i<n_total;i++) if (!device[i]->ok()) _ok=0;
  if (_ok==0)
  {
    for (int i=0;i<n_total;i++) device[i]->closeDev();
//    DEBUGPRINTF("DeviceMan :: ERROR : Closing devices\n");
    return;
  }

//  DEBUGPRINTF("Devices opened\n");
}

void DeviceManager::closeDev(void)
{
  if (alsa)
  {
   if (device) 
     for (int i=0;i<n_total;i++) 
       if (device[i]) device[i]->closeDev();

   return;
  }

#ifdef HAVE_OSS_SUPPORT
  if (seqfd==-1) return;
  tmrStop(); 
  if (device)
    for (int i=0;i<n_total;i++)
       if (device[i]) device[i]->closeDev();
  /*
     DEBUGPRINTF("Closing devices : ");
     if (device!=NULL) for (int i=0;i<n_total;i++)
     {
       device[i]->initDev();
       DEBUGPRINTF("%s ",device[i]->deviceName());

  //	device[i]->closeDev();
  };
  DEBUGPRINTF("\n");
   */
  close(seqfd);
  seqfd=-1;
#endif
}

void DeviceManager::initDev(void)
{
  if (device!=0L)
  {
//    DEBUGPRINTF("Initializing devices :");
    for (int i=0;i<n_total;i++)
    {
      device[i]->initDev();
      DEBUGPRINTF("%s ",device[i]->deviceName());
    }
    DEBUGPRINTF("\n");
  }
}

void DeviceManager::noteOn         ( uchar chn, uchar note, uchar vel )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->noteOn(chn,note,vel);
}
void DeviceManager::noteOff        ( uchar chn, uchar note, uchar vel )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->noteOff(chn,note,vel);
}
void DeviceManager::keyPressure    ( uchar chn, uchar note, uchar vel )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->keyPressure(chn,note,vel);
}
void DeviceManager::chnPatchChange ( uchar chn, uchar patch )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->chnPatchChange(chn,patch);
}
void DeviceManager::chnPressure    ( uchar chn, uchar vel )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->chnPressure(chn,vel);
}
void DeviceManager::chnPitchBender ( uchar chn, uchar lsb,  uchar msb )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->chnPitchBender(chn,lsb,msb);
}
void DeviceManager::chnController  ( uchar chn, uchar ctl , uchar v )
{
  MidiOut *midi=chntodev(chn);
  if (midi) midi->chnController(chn,ctl,v);
}
void DeviceManager::sysEx          ( uchar *data,ulong size)
{
  for (int i=0;i<n_midi;i++)
    device[i]->sysex(data,size);
}

void DeviceManager::wait (double ticks)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->wait(ticks); return; };
#endif

#ifdef HAVE_OSS_SUPPORT
  unsigned long int t=(unsigned long int)(ticks/convertrate);
  if (lastwaittime==t) return;
  lastwaittime=t;
  SEQ_WAIT_TIME(t);
  SEQ_DUMPBUF();     
#endif
}

//void DeviceManager::tmrSetTempo(int v)
void DeviceManager::tmrSetTempo(int v)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->tmrSetTempo(v); return; }
#endif

#ifdef HAVE_OSS_SUPPORT
  SEQ_SET_TEMPO(v);
  SEQ_DUMPBUF();
#endif
}

void DeviceManager::tmrStart(long int
#ifdef HAVE_ALSA_SUPPORT
tpcn /*name the argument only if it is used*/
#endif
)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->tmrStart(tpcn); return; }
#endif

#ifdef HAVE_OSS_SUPPORT
#ifdef CONTROLTIMER
  if (!timerstarted)
  {
      SEQ_START_TIMER();
      SEQ_DUMPBUF();
      timerstarted=1;
  }
  lastwaittime=0;
#else
  SEQ_START_TIMER();
  SEQ_DUMPBUF();
#endif          
#endif
}

void DeviceManager::tmrStop(void)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->tmrStop(); return; }
#endif

#ifdef HAVE_OSS_SUPPORT
#ifdef CONTROLTIMER
  if (timerstarted)
  {
    SEQ_STOP_TIMER();
    SEQ_DUMPBUF();
    timerstarted=0;
  }
#else
  SEQ_STOP_TIMER();
  SEQ_DUMPBUF();
#endif          
#endif
}

void DeviceManager::tmrContinue(void)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->tmrContinue(); return; }
#endif

#ifdef HAVE_OSS_SUPPORT
#ifdef CONTROLTIMER
  if (timerstarted)
  {
    SEQ_CONTINUE_TIMER();
    SEQ_DUMPBUF();
  }
#else
  SEQ_CONTINUE_TIMER();
  SEQ_DUMPBUF();
#endif        
#endif
}

void DeviceManager::sync(bool f)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa) { ((AlsaOut *)device[default_dev])->sync(f); return ; };
#endif

#ifdef HAVE_OSS_SUPPORT
#ifdef DEVICEMANDEBUG
  printf("Sync %d\n",f);
#endif
  if (f)
  {
    seqbuf_clean();
    /* If you have any problem, try removing the next 2 lines,
       I though they would be useful here but the may have side effects */
    ioctl(seqfd,SNDCTL_SEQ_RESET);
    ioctl(seqfd,SNDCTL_SEQ_PANIC);
  }
  else
  {
    seqbuf_dump();
    ioctl(seqfd, SNDCTL_SEQ_SYNC);
  };
#endif
}

void DeviceManager::seqbuf_dump (void)
{
  if (!alsa)
  {
#ifdef HAVE_OSS_SUPPORT
    if (_seqbufptr)
    {
      int r=0;
      unsigned char *sb=_seqbuf;
      int w=_seqbufptr;
      r=write (seqfd, _seqbuf, _seqbufptr);
#ifdef DEVICEMANDEBUG
      printf("%d == %d\n",r,w);
      printf("%d\n",(errno==EAGAIN)? 1 : 0);
#endif
      while (((r == -1)&&(errno==EAGAIN))||(r != w))
      {
	if ((r==-1)&&(errno==EAGAIN))
	{
	  usleep(1);
	}
	else if ((r>0)&&(r!=w))
	{
	  w-=r;
	  sb+=r;
	}
	r=write (seqfd, sb, w);
#ifdef DEVICEMANDEBUG
	printf("%d == %d\n",r,w);
	printf("%d\n",(errno==EAGAIN)? 1 : 0);
#endif
      }
    }
    /*
     *   if (_seqbufptr)
     *       if (write (seqfd, _seqbuf, _seqbufptr) == -1)
     *       {
     *           printf("Error writing to /dev/sequencer in deviceManager::seqbuf_dump\n");
     *           perror ("write /dev/sequencer in seqbuf_dump\n");
     *           exit (-1);
     *       }
     */
    _seqbufptr = 0;
#endif
  }
}

void DeviceManager::seqbuf_clean(void)
{
#ifdef HAVE_ALSA_SUPPORT
  if (alsa)
    ((AlsaOut *)device[default_dev])->seqbuf_clean();
  else
#endif
#ifdef HAVE_OSS_SUPPORT
    _seqbufptr=0;
#endif
}


const char *DeviceManager::name(int i)
{
#ifdef HAVE_OSS_SUPPORT
  if (checkInit()<0) {_ok = 0; return NULL;}

  if (alsa)
  {
    if (i<n_midi) return device[i]->deviceName(); 
  }
  else
  {
    if (i<n_midi) return midiinfo[i].name;
    if (i<n_midi+n_synths) return synthinfo[i-n_midi].name;
  };
#endif
  return (char *)"";
}

const char *DeviceManager::type(int i)
{
#ifdef HAVE_OSS_SUPPORT
  if (checkInit()<0) {_ok = 0; return NULL;}

  if (alsa)
  {
    if (i<n_midi) return "ALSA device";
  }
  else
  {
    if (i<n_midi)
    {
      return "External Midi Port";
    }
    if (i<n_midi+n_synths)
    {
      switch (synthinfo[i-n_midi].synth_subtype)
      {
        case (FM_TYPE_ADLIB) : return "Adlib";break;
        case (FM_TYPE_OPL3) : return "FM";break;
        case (MIDI_TYPE_MPU401) : return "MPU 401";break;
        case (SAMPLE_TYPE_GUS) : return "GUS";break;
      }
    }
  }
#endif
  return "";
}

int DeviceManager::defaultDevice(void)
{
  return default_dev;
}

void DeviceManager::setDefaultDevice(int i)
{
  if (i>=n_total) return;
  default_dev=i;
  for (int i=0;i<16;i++) chn2dev[i]=default_dev;
}

const char *DeviceManager::midiMapFilename(void)
{
  if (device==0L) return "";
  if (default_dev>=n_total) return "";
  return (device[default_dev]!=NULL) ?
    device[default_dev]->midiMapFilename() : "";
}

void DeviceManager::setMidiMap(MidiMapper *map)
{
  if (map==NULL) return;
  mapper_tmp=map;
  if (default_dev>=n_total) {default_dev=0;return;};
  if ((device==0L)||(device[default_dev]==NULL))
    return;
  device[default_dev]->setMidiMapper(map);
}

int DeviceManager::setPatchesToUse(int *patchesused)
{
  if (checkInit()<0) return -1;
  if ((device==0L)||(device[default_dev]==NULL))
    return 0;

  if ((device[default_dev]->deviceType())==KMID_GUS)
  {
    GUSOut *gus=(GUSOut *)device[default_dev];
    gus->setPatchesToUse(patchesused);
  }
  return 0;
}

void DeviceManager::setVolumePercentage(int v)
{
  if (device!=0L)
  {
    for (int i=0;i<n_total;i++)
    {
      device[i]->setVolumePercentage(v);
    }
  }
}

void DeviceManager::setDeviceNumberForChannel(int chn, int dev)
{
  chn2dev[chn]=dev;
}

void DeviceManager::allNotesOff(void)
{
  for (int i=0;i<n_midi;i++)
    device[i]->allNotesOff();
}