/* Copyright (c) 2003, Miller Puckette and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/*  Audio back-end for connecting with the JACK audio interconnect system.
*/

#ifdef USEAPI_JACK

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "m_pd.h"
#include "s_stuff.h"
#ifdef __APPLE__
#include <jack/weakjack.h>
#endif
#include <jack/jack.h>
#include <regex.h>

#define MAX_CLIENTS 100
#define MAX_JACK_PORTS 128  /* higher values seem to give bad xrun problems */
#define BUF_JACK 4096
#define JACK_OUT_MAX  64

static jack_nframes_t jack_out_max;
static jack_nframes_t jack_filled = 0;
static t_sample *jack_outbuf;
static t_sample *jack_inbuf;
static int jack_started = 0;
static jack_port_t *input_port[MAX_JACK_PORTS];
static jack_port_t *output_port[MAX_JACK_PORTS];
static int outport_count = 0;
static jack_client_t *jack_client = NULL;
static char * desired_client_name = NULL;
char *jack_client_names[MAX_CLIENTS];
static int jack_dio_error;
static t_audiocallback jack_callback;
static int jack_should_autoconnect = 1;
pthread_mutex_t jack_mutex;
pthread_cond_t jack_sem;

static int pollprocess(jack_nframes_t nframes, void *arg)
{
    int j;
    jack_default_audio_sample_t *out, *in;

    pthread_mutex_lock(&jack_mutex);
    if (nframes > JACK_OUT_MAX) jack_out_max = nframes;
    else jack_out_max = JACK_OUT_MAX;
    if (jack_filled >= nframes)
    {
        if (jack_filled != nframes)
            fprintf(stderr,"Partial read\n");
        /* hmm, how to find out whether 't_sample' and
            'jack_default_audio_sample_t' are actually the same type??? */
        if (sizeof(t_sample)==sizeof(jack_default_audio_sample_t))
        {
            for (j = 0; j < STUFF->st_outchannels;  j++)
            {
                if (out = jack_port_get_buffer(output_port[j], nframes))
                    memcpy(out, jack_outbuf + (j * BUF_JACK),
                        sizeof (jack_default_audio_sample_t) * nframes);
            }
            for (j = 0; j < STUFF->st_inchannels; j++)
            {
                if (in = jack_port_get_buffer(input_port[j], nframes))
                    memcpy(jack_inbuf + (j * BUF_JACK), in,
                        sizeof (jack_default_audio_sample_t) * nframes);
            }
        }
        else
        {
            unsigned int frame=0;
            t_sample*data;
            for (j = 0; j < STUFF->st_outchannels;  j++)
            {
                if (out = jack_port_get_buffer (output_port[j], nframes))
                {
                    data = jack_outbuf + (j * BUF_JACK);
                    for (frame=0; frame<nframes; frame++)
                        *out++ = *data++;
                }
            }
            for (j = 0; j < STUFF->st_inchannels; j++)
            {
                if (in = jack_port_get_buffer( input_port[j], nframes))
                {
                    data = jack_inbuf + (j * BUF_JACK);
                    for (frame=0; frame<nframes; frame++)
                        *data++ = *in++;
                }
            }
        }
        jack_filled -= nframes;
    }
    else
    {           /* PD could not keep up ! */
        if (jack_started) jack_dio_error = 1;
        for (j = 0; j < outport_count;  j++)
        {
            if (out = jack_port_get_buffer (output_port[j], nframes))
                memset(out, 0, sizeof (float) * nframes);
            memset(jack_outbuf + j * BUF_JACK, 0, BUF_JACK * sizeof(t_sample));
        }
        jack_filled = 0;
    }
    pthread_cond_broadcast(&jack_sem);
    pthread_mutex_unlock(&jack_mutex);
    return 0;
}

static int callbackprocess(jack_nframes_t nframes, void *arg)
{
    int chan, j, k;
    unsigned int n;
    jack_default_audio_sample_t *out[MAX_JACK_PORTS], *in[MAX_JACK_PORTS], *jp;

    if (nframes % DEFDACBLKSIZE)
    {
        fprintf(stderr, "jack: nframes %d not a multiple of blocksize %d\n",
            nframes, DEFDACBLKSIZE);
        nframes -= (nframes % DEFDACBLKSIZE);
    }
    for (chan = 0; chan < STUFF->st_inchannels; chan++)
        in[chan] = jack_port_get_buffer(input_port[chan], nframes);
    for (chan = 0; chan < STUFF->st_outchannels; chan++)
        out[chan] = jack_port_get_buffer(output_port[chan], nframes);
    for (n = 0; n < nframes; n += DEFDACBLKSIZE)
    {
        t_sample *fp;
        for (chan = 0; chan < STUFF->st_inchannels; chan++)
            if (in[chan])
        {
            for (fp = STUFF->st_soundin + chan*DEFDACBLKSIZE,
                jp = in[chan] + n, j=0; j < DEFDACBLKSIZE; j++)
                    *fp++ = *jp++;
        }
        for (chan = 0; chan < STUFF->st_outchannels; chan++)
        {
            for (fp = STUFF->st_soundout + chan*DEFDACBLKSIZE,
                j = 0; j < DEFDACBLKSIZE; j++)
                    *fp++ = 0;
        }
        (*jack_callback)();
        for (chan = 0; chan < STUFF->st_outchannels; chan++)
            if (out[chan])
        {
            for (fp = STUFF->st_soundout + chan*DEFDACBLKSIZE, jp = out[chan] + n,
                j=0; j < DEFDACBLKSIZE; j++)
                    *jp++ = *fp++;
        }
    }
    return 0;
}

static int
jack_srate (jack_nframes_t srate, void *arg)
{
        STUFF->st_dacsr = srate;
        return 0;
}


void glob_audio_setapi(void *dummy, t_floatarg f);

static void
jack_shutdown (void *arg)
{
  error("JACK: server shut down");

  jack_deactivate (jack_client);
  //jack_client_close(jack_client); /* likely to hang if the server shut down */
  jack_client = NULL;

  glob_audio_setapi(NULL, API_NONE); // set pd_whichapi 0
}

static int jack_xrun(void* arg) {
  jack_dio_error = 1;
  return 0;
}


static char** jack_get_clients(void)
{
    const char **jack_ports;
    int i,j;
    int num_clients = 0;
    regex_t port_regex;
    jack_ports = jack_get_ports( jack_client, "", "", 0 );
    regcomp( &port_regex, "^[^:]*", REG_EXTENDED );

    jack_client_names[0] = NULL;

    /* Build a list of clients from the list of ports */
    for( i = 0; jack_ports[i] != NULL; i++ )
    {
        int client_seen;
        regmatch_t match_info;
        char tmp_client_name[100];

        if(num_clients>=MAX_CLIENTS)break;


        /* extract the client name from the port name, using a regex
         * that parses the clientname:portname syntax */
        regexec( &port_regex, jack_ports[i], 1, &match_info, 0 );
        memcpy( tmp_client_name, &jack_ports[i][match_info.rm_so],
                match_info.rm_eo - match_info.rm_so );
        tmp_client_name[ match_info.rm_eo - match_info.rm_so ] = '\0';

        /* do we know about this port's client yet? */
        client_seen = 0;

        for( j = 0; j < num_clients; j++ )
            if( strcmp( tmp_client_name, jack_client_names[j] ) == 0 )
                client_seen = 1;

        if( client_seen == 0 )
        {
            jack_client_names[num_clients] = (char*)getbytes(strlen(tmp_client_name) + 1);

            /* The alsa_pcm client should go in spot 0.  If this
             * is the alsa_pcm client AND we are NOT about to put
             * it in spot 0 put it in spot 0 and move whatever
             * was already in spot 0 to the end. */

            if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && num_clients > 0 )
            {
              char* tmp;
                /* alsa_pcm goes in spot 0 */
              tmp = jack_client_names[ num_clients ];
              jack_client_names[ num_clients ] = jack_client_names[0];
              jack_client_names[0] = tmp;
              strcpy( jack_client_names[0], tmp_client_name);
            }
            else
            {
                /* put the new client at the end of the client list */
                strcpy( jack_client_names[ num_clients ], tmp_client_name );
            }
            num_clients++;
        }
    }

    /*    for (i=0;i<num_clients;i++) post("client: %s",jack_client_names[i]); */

    free( jack_ports );
    return jack_client_names;
}

/*
 *   Wire up all the ports of one client.
 *
 */

static int jack_connect_ports(char* client)
{
    char  regex_pattern[100]; /* its always the same, ... */
    int i;
    const char **jack_ports;

    if (strlen(client) > 96)  return -1;

    sprintf( regex_pattern, "%s:.*", client );

    jack_ports = jack_get_ports( jack_client, regex_pattern,
                                 NULL, JackPortIsOutput);
    if (jack_ports)
    {
        for (i=0;jack_ports[i] != NULL && i < STUFF->st_inchannels;i++)
            if (jack_connect (jack_client, jack_ports[i],
               jack_port_name (input_port[i])))
                  error ("JACK: cannot connect input ports %s -> %s",
                      jack_ports[i],jack_port_name (input_port[i]));
        free(jack_ports);
    }
    jack_ports = jack_get_ports( jack_client, regex_pattern,
                                 NULL, JackPortIsInput);
    if (jack_ports)
    {
        for (i=0;jack_ports[i] != NULL && i < STUFF->st_outchannels;i++)
          if (jack_connect (jack_client, jack_port_name (output_port[i]),
            jack_ports[i]))
              error( "JACK: cannot connect output ports %s -> %s",
                jack_port_name (output_port[i]),jack_ports[i]);

        free(jack_ports);
    }
    return 0;
}


static void pd_jack_error_callback(const char *desc) {
  error("JACKerror: %s", desc);
  return;
}

int
jack_open_audio(int inchans, int outchans, int rate, t_audiocallback callback)
{
    int j;
    char port_name[80] = "";
    char client_name[80] = "";

    int client_iterator = 0;
    int new_jack = 0;
    int srate;
    jack_status_t status;

    if (!jack_client_open)
    {
        error("Can't open Jack (it seems not to be installed)");
        return 1;
    }

    jack_dio_error = 0;

    if ((inchans == 0) && (outchans == 0)) return 0;

    if (outchans > MAX_JACK_PORTS) {
        error("JACK: %d output ports not supported, setting to %d",
            outchans, MAX_JACK_PORTS);
        outchans = MAX_JACK_PORTS;
    }

    if (inchans > MAX_JACK_PORTS) {
        error("JACK: %d input ports not supported, setting to %d",
            inchans, MAX_JACK_PORTS);
        inchans = MAX_JACK_PORTS;
    }

    /* try to become a client of the JACK server.  (If no JACK server exists,
        jack_client_open() will start uone up by default.  It's not clear
        whether or not this is desirable; see long Pd list thread started by
        yvan volochine, June 2013) */
    if (!jack_client) {
        if (!desired_client_name || !strlen(desired_client_name))
            jack_client_name("pure_data");
        jack_client = jack_client_open (desired_client_name, JackNoStartServer,
          &status, NULL);
        if (status & JackFailure) {
            error("JACK: Failure.  Is JACK running?");
            verbose(1, "JACK: Returned status is: %d", status);
            jack_client=NULL;
            /* jack spits out enough messages already, do not warn */
            STUFF->st_inchannels = STUFF->st_outchannels = 0;
            return 1;
        }
        if (status & JackNameNotUnique)
            jack_client_name(jack_get_client_name(jack_client));
        verbose(1, "JACK: registered as '%s'", desired_client_name);

        STUFF->st_inchannels = inchans;
        STUFF->st_outchannels = outchans;
        if (jack_inbuf)
            free(jack_inbuf);
        if (STUFF->st_inchannels)
            jack_inbuf = calloc(sizeof(t_sample), STUFF->st_inchannels * BUF_JACK);
        if (jack_outbuf)
            free(jack_outbuf);
        if (STUFF->st_outchannels)
            jack_outbuf = calloc(sizeof(t_sample), STUFF->st_outchannels * BUF_JACK);

        jack_get_clients();

        /* set JACK callback functions */

        jack_callback = callback;
        jack_set_process_callback(jack_client,
            (callback? callbackprocess : pollprocess), 0);

        jack_set_error_function (pd_jack_error_callback);

#ifdef JACK_XRUN
        jack_set_xrun_callback (jack_client, jack_xrun, NULL);
#endif

        /* tell the JACK server to call `srate()' whenever
           the sample rate of the system changes.
        */

        jack_set_sample_rate_callback (jack_client, jack_srate, 0);


        /* tell the JACK server to call `jack_shutdown()' if
           it ever shuts down, either entirely, or if it
           just decides to stop calling us.
        */

        jack_on_shutdown (jack_client, jack_shutdown, 0);

        for (j=0; j<STUFF->st_inchannels; j++)
             input_port[j]=NULL;
        for (j=0; j<STUFF->st_outchannels; j++)
             output_port[j] = NULL;

        new_jack = 1;
    }

    /* display the current sample rate. once the client is activated
       (see below), you should rely on your own sample rate
       callback (see above) for this value.
    */

    srate = jack_get_sample_rate (jack_client);
    STUFF->st_dacsr = srate;

    /* create the ports */

    for (j = 0; j < inchans; j++)
    {
        sprintf(port_name, "input%d", j);
        if (!input_port[j]) input_port[j] = jack_port_register (jack_client,
            port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
        if (!input_port[j])
        {
          error("JACK: can only register %d input ports (of %d requested)",
            j, inchans);
          STUFF->st_inchannels = inchans = j;
          break;
        }
    }

    for (j = 0; j < outchans; j++)
    {
        sprintf(port_name, "output%d", j);
        if (!output_port[j]) output_port[j] = jack_port_register (jack_client,
            port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
        if (!output_port[j])
        {
          error("JACK: can only register %d output ports (of %d requested)",
            j, outchans);
          STUFF->st_outchannels = outchans = j;
          break;
        }
    }
    outport_count = outchans;

    /* tell the JACK server that we are ready to roll */

    if (new_jack)
    {
        if (jack_activate (jack_client)) {
            error("cannot activate client");
            STUFF->st_inchannels = STUFF->st_outchannels = 0;
            return 1;
        }

        for (j = 0; j < outchans; j++)
            memset(jack_outbuf + j * BUF_JACK, 0,
                BUF_JACK * sizeof(t_sample));

        if (jack_client_names[0] && jack_should_autoconnect)
            jack_connect_ports(jack_client_names[0]);

        pthread_mutex_init(&jack_mutex, NULL);
        pthread_cond_init(&jack_sem, NULL);
    }
    return 0;
}

void jack_close_audio(void)
{
    if (jack_client){
        jack_deactivate (jack_client);
        jack_client_close(jack_client);
    }

    jack_client=NULL;
    jack_started = 0;

    pthread_cond_broadcast(&jack_sem);

    pthread_cond_destroy(&jack_sem);
    pthread_mutex_destroy(&jack_mutex);
    if (jack_inbuf)
        free(jack_inbuf), jack_inbuf = 0;
    if (jack_outbuf)
        free(jack_outbuf), jack_outbuf = 0;

}

int jack_send_dacs(void)
{
    t_sample * fp;
    int j;
    int rtnval =  SENDDACS_YES;
    int timenow;
    int timeref = sys_getrealtime();
    if (!jack_client) return SENDDACS_NO;
    if (!STUFF->st_inchannels && !STUFF->st_outchannels) return (SENDDACS_NO);
    if (jack_dio_error)
    {
        sys_log_error(ERR_RESYNC);
        jack_dio_error = 0;
    }
    pthread_mutex_lock(&jack_mutex);
    if (jack_filled >= jack_out_max)
        pthread_cond_wait(&jack_sem,&jack_mutex);

    if (!jack_client)
    {
        pthread_mutex_unlock(&jack_mutex);
        return SENDDACS_NO;
    }
    jack_started = 1;

    fp = STUFF->st_soundout;
    for (j = 0; j < STUFF->st_outchannels; j++)
    {
        memcpy(jack_outbuf + (j * BUF_JACK) + jack_filled, fp,
            DEFDACBLKSIZE*sizeof(t_sample));
        fp += DEFDACBLKSIZE;
    }
    fp = STUFF->st_soundin;
    for (j = 0; j < STUFF->st_inchannels; j++)
    {
        memcpy(fp, jack_inbuf + (j * BUF_JACK) + jack_filled,
            DEFDACBLKSIZE*sizeof(t_sample));
        fp += DEFDACBLKSIZE;
    }
    jack_filled += DEFDACBLKSIZE;
    pthread_mutex_unlock(&jack_mutex);

    if ((timenow = sys_getrealtime()) - timeref > 0.002)
    {
        rtnval = SENDDACS_SLEPT;
    }
    memset(STUFF->st_soundout, 0, DEFDACBLKSIZE*sizeof(t_sample)*STUFF->st_outchannels);
    return rtnval;
}

void jack_getdevs(char *indevlist, int *nindevs,
    char *outdevlist, int *noutdevs, int *canmulti,
        int maxndev, int devdescsize)
{
    int i, ndev;
    *canmulti = 0;  /* supports multiple devices */
    ndev = 1;
    for (i = 0; i < ndev; i++)
    {
        sprintf(indevlist + i * devdescsize, "JACK");
        sprintf(outdevlist + i * devdescsize, "JACK");
    }
    *nindevs = *noutdevs = ndev;
}

void jack_listdevs(void)
{
    post("device listing not implemented for jack yet\n");
}

void jack_autoconnect(int v)
{
    jack_should_autoconnect = v;
}

void jack_client_name(char *name)
{
    if (desired_client_name) {
      free(desired_client_name);
      desired_client_name = NULL;
    }
    if (name) {
      desired_client_name = (char*)getbytes(strlen(name) + 1);
      strcpy(desired_client_name, name);
    }
}

#endif /* JACK */
