#ifndef _ALSAMANAGER_CPP_
#define _ALSAMANAGER_CPP_

#include "config.h"

#ifdef HAVE_ALSA

#include <iostream.h>

#include "glib.h"
#include "alsaIntl.h"
#include <errno.h>
#include <sys/asoundlib.h>
#include <stdio.h>
#include "alsaSeqlib.h"
#include "alsaSeqpriv.h"
#include <stdlib.h>
#include <string.h>

#include "alsaManager.h"


#define ADDR_PARTS 4 /* Number of part in a port description addr 1:2:3:4 */
#define SEP ", \t"	/* Separators for port description */

#define MD_TYPE_PART (0 + MD_CONTAINER_BEGIN)
#define MD_TYPE_ROOT (1 + MD_CONTAINER_BEGIN)
#define MD_TYPE_KEYTOUCH 2
#define MD_TYPE_TEXT 3
#define MD_TYPE_PITCH 4
#define MD_TYPE_PROGRAM 5
#define MD_TYPE_META 6
#define MD_TYPE_PRESSURE 7
#define MD_TYPE_NOTE 8
#define MD_TYPE_ELEMENT 9
#define MD_TYPE_SMPTEOFFSET 10
#define MD_TYPE_TEMPO 11
#define MD_TYPE_TEMPOMAP (12 + MD_CONTAINER_BEGIN)
#define MD_TYPE_SYSEX 13
#define MD_TYPE_TRACK (14 + MD_CONTAINER_BEGIN)
#define MD_TYPE_KEYSIG 15
#define MD_TYPE_TIMESIG 16
#define MD_TYPE_CONTAINER 17
#define MD_TYPE_MAP 18
#define MD_TYPE_CONTROL 19

/*
 * Read a list of client/port specifications and return an
 * array of snd_seq_addr_t that describes them.
 * 
 *  Arguments:
 *    portdesc  - 
seq_context_t * AlsaManager::openports(char *portdesc) {
  char *astr;
  char *cp;
  seq_context_t *ctxp;
  snd_seq_addr_t *addr;
  snd_seq_addr_t *ap;
  int a[ADDR_PARTS];
  int count, naddr;
  int i;

  if (portdesc == NULL) return NULL;
  ctxp = seq_create_context();
  addr = g_new(snd_seq_addr_t, strlen(portdesc));
  
  naddr = 0;
  ap = addr;
  for (astr = strtok(portdesc, SEP); astr; astr = strtok(NULL, SEP)) {
    for (cp = astr, count = 0; cp && *cp; cp++) {
      if (count < ADDR_PARTS) a[count++] = atoi(cp);
      cp = strchr(cp, ':');
      if (cp == NULL) break;
    }
    switch (count) {
    case 2:
      ap->client = a[0];
      ap->port = a[1];
      break;
    default:
      printf("Addresses in %d parts not supported yet\n", count);
      break;
    }
    ap++;
    naddr++;
  }
  count = 0;
  for (i = 0; i < naddr; i++) {
    cout << "* " << i << ", " << (int) addr[i].client << ", " << (int) addr[i].port <<endl;
    int  err;
    err = seq_connect_add(ctxp, addr[i].client, addr[i].port);
    if (err < 0) {
      fprintf(stderr, _("Could not connect to port %d:%d\n"), addr[i].client, addr[i].port);
    } else count++;
  }
  g_free(addr);
  if (count == 0) {
    seq_free_context(ctxp);
    return NULL;
  }
  return ctxp;
}
 */


void AlsaManager::initDeviceList() {
  snd_seq_client_info_t cinfo;
  snd_seq_port_info_t pinfo;
  snd_seq_system_info_t sysinfo;
  int  client;
  int  port;
  int  err;
  unsigned int cap;
  snd_seq_t *handle;

  err = snd_seq_open(&handle, SND_SEQ_OPEN);
  if (err < 0) printf("Could not open sequencer %s", snd_strerror(errno));
  
  err = snd_seq_system_info(handle, &sysinfo);
  if (err < 0) printf("Could not get sequencer information %s",snd_strerror(err));

  _devnum = 0; // this only counts the number of devices:
  for (client = 0; client < sysinfo.clients; client++) {
    err = snd_seq_get_any_client_info(handle, client, &cinfo);
    if (err < 0) continue;
    for (port = 0; port < sysinfo.ports; port++) {
      err = snd_seq_get_any_port_info(handle, client, port, &pinfo);		
      if (err < 0) continue;
      cap = (SND_SEQ_PORT_CAP_SUBSCRIPTION|SND_SEQ_PORT_CAP_OUT);
      if ((pinfo.capability & cap) == cap)
	_devnum++;
    }
  }

  _devlist = (const char**) new (char*)[_devnum];
  int num = 0;
  for (client = 0; client < sysinfo.clients; client++) {
    err = snd_seq_get_any_client_info(handle, client, &cinfo);
    if (err < 0) continue;
    for (port = 0; port < sysinfo.ports; port++) {
      err = snd_seq_get_any_port_info(handle, client, port, &pinfo);		
      if (err < 0) continue;
      cap = (SND_SEQ_PORT_CAP_SUBSCRIPTION|SND_SEQ_PORT_CAP_OUT);
      if ((pinfo.capability & cap) == cap) {
	// printf("%3d:%-3d   %-30.30s    %s\n", pinfo.client, pinfo.port, cinfo.name, pinfo.name);
	_devlist[num++] = strdup(pinfo.name);
      }
    }
  }
}



AlsaManager::AlsaManager() : _devnum(0) {
  initDeviceList();
  _timebase = 384;
  _tempo = 500000;
  _delay = 1;

  int out = 0;
  int prg = 0;
  int channel = 0;
  snd_seq_event_t ev;

  ctxp = seq_create_context();
  int err = seq_connect_add(ctxp, 65, out);
  if (err!=0) cout << "err: " << err << endl;
 
  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_program(ctxp, &ev, channel, prg);

  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_init_tempo(ctxp, _timebase, 120, 1);
  seq_start_timer(ctxp);

  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_tempo(ctxp, &ev, _tempo);
}

AlsaManager::~AlsaManager() {
  seq_free_context(ctxp);
  
}

int AlsaManager::devnum() { return _devnum; }

const char * AlsaManager::device(int i) { return _devlist[i]; }




void AlsaManager::hit(int out, int channel, int pitch, int vol) {

  //
  // TODO: out has to be mapped to different ctxp's somehow...
  //
  snd_seq_event_t ev;
  
  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_init_tempo(ctxp, _timebase, 120, 1);
  seq_start_timer(ctxp);

  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_note(ctxp, &ev, channel, pitch, vol, 192);

  snd_seq_flush_output((snd_seq_t*)seq_handle(ctxp));
  
  // sleep(1);
}

void AlsaManager::playStart(long off) {
  _offset = off;
  snd_seq_event_t ev;
  
  // seq_midi_event_init(ctxp, &ev, 0, 0);
  // seq_midi_tempo(ctxp, &ev, _tempo*5000);

  seq_midi_event_init(ctxp, &ev, off, 0);
  seq_init_tempo(ctxp, _timebase, 120, 1);
  seq_start_timer(ctxp);

}

void AlsaManager::playNote(int out, int channel, int pitch, int vol, long pos, long len) {
  snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, pos - _offset, channel);
  seq_midi_note(ctxp, &ev, channel, pitch, vol, len);
  snd_seq_flush_output((snd_seq_t*)seq_handle(ctxp));

}

void AlsaManager::playEnd() {
  snd_seq_flush_output((snd_seq_t*)seq_handle(ctxp));
}

void AlsaManager::prgchange(long pos, int channel, int prg) {
  snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, pos, channel);
  seq_midi_program(ctxp, &ev, channel, prg);
}

long AlsaManager::ticks() {
  snd_seq_get_queue_status(ctxp->handle, ctxp->queue, &status);
  return status.tick+_offset;
}

double AlsaManager::time() {
  snd_seq_get_queue_status(ctxp->handle, ctxp->queue, &status);
  return ((double) status.time.tv_sec) + ((double) status.time.tv_nsec*0.000000001);
}

void AlsaManager::tempo(int tempo) {
  _tempo = tempo;
  snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, 0, 0);
  seq_midi_tempo(ctxp, &ev, int(40000000.0/_tempo));
}

int AlsaManager::tempo() { return _tempo; }

void AlsaManager::test() {
  int channel = 0;
  int timebase = 384;
  int tempo = 500000;
  int delay = 1;
  int prg = 0; // atoi(argv[1]);
  char * portdesc = strdup("65:1");

  seq_context_t * ctxp;

  snd_seq_event_t ev;


  printf("prg %d\n", prg);

  // showlist();

  // ctxp = openports(portdesc);

  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_program(ctxp, &ev, channel, prg);

  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_init_tempo(ctxp, timebase, 120, 1);
  seq_start_timer(ctxp);

  // snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_tempo(ctxp, &ev, tempo);

  // snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, 0, channel);
  seq_midi_note(ctxp, &ev, channel, 68, 100, 192);

  // snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, 384, channel);
  seq_midi_note(ctxp, &ev, channel, 74, 100, 192);
  

  // end = md_sequence_end_time(seq);
  // seq_midi_echo(ctxp, end);

  snd_seq_flush_output((snd_seq_t*)seq_handle(ctxp));
  //snd_seq_event_input(seq_handle(ctxp), &ep);
  
  sleep(delay);
  seq_free_context(ctxp);

  /*
  ctxp = seq_create_context();
  int err = seq_connect_add(ctxp, 65, 1);
  cout << "err: " << err << endl;

  seq_midi_event_init(ctxp, &ev, 0, 0);
  seq_midi_program(ctxp, &ev, 0, prg);

  seq_midi_event_init(ctxp, &ev, 0, 0);
  seq_init_tempo(ctxp, _timebase, 120, 1);
  seq_start_timer(ctxp);

  seq_midi_event_init(ctxp, &ev, 0, 0);
  seq_midi_tempo(ctxp, &ev, _tempo);

  seq_midi_event_init(ctxp, &ev, 0, 0);
  seq_midi_note(ctxp, &ev, 0, 68, 100, 192);

  seq_midi_event_init(ctxp, &ev, 384, 0);
  seq_midi_note(ctxp, &ev, 0, 74, 100, 192);
  
  snd_seq_flush_output(seq_handle(ctxp));
  
  sleep(_delay);
  seq_free_context(ctxp);
  */
}

void AlsaManager::event(int code, int channel, int val1, int val2, long pos) {
  snd_seq_event_t ev;
  seq_midi_event_init(ctxp, &ev, pos - _offset, channel);

  switch (code) {
  case 14:
    seq_midi_pitchbend(ctxp, &ev, channel, val1+128*val2);
    break;
  default:
    // printf("WARNING: play: not implemented yet %d\n", code);
    break;
  }
}

#endif

#endif
