// ----------------------------------------------------------------------------
//
//  Copyright (C) 2010-2015 Fons Adriaensen <fons@linuxaudio.org>
//    
//  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; either version 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <string.h>
#include "jclient.h"


Jclient::Jclient (const char *jname, const char *jserv) :
    A_thread ("Jclient"),
    _jack_client (0),
    _active (false),
    _jname (0)
{
    init_jack (jname, jserv);   
}


Jclient::~Jclient (void)
{
    if (_jack_client) close_jack ();
}


void Jclient::init_jack (const char *jname, const char *jserv)
{
    int            i, j;
    char           s [16];
    jack_status_t  stat;
    int            opts;

    opts = JackNoStartServer;
    if (jserv) opts |= JackServerName;
    if ((_jack_client = jack_client_open (jname, (jack_options_t) opts, &stat, jserv)) == 0)
    {
        fprintf (stderr, "Can't connect to JACK\n");
        exit (1);
    }
    jack_set_process_callback (_jack_client, jack_static_process, (void *) this);
    jack_on_shutdown (_jack_client, jack_static_shutdown, (void *) this);
    if (jack_activate (_jack_client))
    {
        fprintf(stderr, "Can't activate JACK.\n");
        exit (1);
    }
    _jname = jack_get_client_name (_jack_client);
    _fsamp = jack_get_sample_rate (_jack_client);
    _bsize = jack_get_buffer_size (_jack_client);

    for (i = j = 0; i < 4; i++)
    {
	sprintf (s, "in_%d.L", i + 1);
        _moninpport [j++] = jack_port_register (_jack_client, s, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
	sprintf (s, "in_%d.R", i + 1);
        _moninpport [j++] = jack_port_register (_jack_client, s, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
	_ipg [i] = 0;
    }
    _seloutport [0] = jack_port_register (_jack_client, "sel_out.L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _seloutport [1] = jack_port_register (_jack_client, "sel_out.R", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _monoutport [0] = jack_port_register (_jack_client, "mon_out1.L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _monoutport [1] = jack_port_register (_jack_client, "mon_out1.R", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _monoutport [2] = jack_port_register (_jack_client, "mon_out2.L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _monoutport [3] = jack_port_register (_jack_client, "mon_out2.R", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _tbinpport[0] = jack_port_register (_jack_client, "TB_in_1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
    _tbinpport[1] = jack_port_register (_jack_client, "TB_in_2", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
    _tboutport [0] = jack_port_register (_jack_client, "TB_out_1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _tboutport [1] = jack_port_register (_jack_client, "TB_out_2", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);

    Kmeterdsp::init (_fsamp, _bsize, 0.5f, 15.0f);
    Cmeterdsp::init (_fsamp, 1e3f, 0.15f);

    _fragm = _fsamp / 50;
    _frcnt = 0;
    _inpsel = 1;
    _monmod = 0;
    _outsel = 1;
    _talkbk = 0;
    _out1gain = 0;
    _out2gain = 0;
    _out2hdph = 0;
    _tb1gain = 0;
    _tb2gain = 0;
    _opg [0] = _opg [1] = 0;
    _dog [0] = _dog [1] = 0;
    _tbg [0] = _tbg [1] = 0;
    _ww = 2500.0f / _fsamp;
    _zs = _zd = 0;
    _active = true;
}


void Jclient::close_jack ()
{
    jack_deactivate (_jack_client);
    jack_client_close (_jack_client);
}


void Jclient::jack_static_shutdown (void *arg)
{
    ((Jclient *) arg)->jack_shutdown ();
}


int Jclient::jack_static_process (jack_nframes_t nframes, void *arg)
{
    return ((Jclient *) arg)->jack_process (nframes);
}


void Jclient::jack_shutdown (void)
{
    send_event (EV_EXIT, 1);
}


int Jclient::prepare (int frames)
{
    int   i, k;
    float g [2];

    if (_frcnt == 0)
    {
	_frcnt = _fragm;
        _tbg [0] = 0;
        _tbg [1] = 0;
	g [0] = (_outsel & 1) ? _out1gain : 0;
	g [1] = (_outsel & 2) ? _out2gain : 0;

	k = _dimstat.state ((_monmod & 16) | _talkbk);
	if (k == DelayAct::UP || k == DelayAct::ON)
	{
            g [0] *= 0.05f;
	    if (! _out2hdph) g [1] *= 0.05f;
	}
        if (k == DelayAct::ON)
	{
	    if (_talkbk & 1) _tbg [0] = _tb1gain;
            if (_talkbk & 2) _tbg [1] = _tb2gain;
	}
	for (i = 0, k = _inpsel; i < 4; i++, k >>= 1)
	{
            _inpstat [i].state (k & 1);
	}
	for (i = 0; i < 2; i++)
	{
	    _dog [i] = (g [i] - _opg [i]) / _fragm;
	    if (fabsf (_dog [i]) < 1e-30f)
	    {
                 _dog [i] = 0; 
		 _opg [i] = g [i];
	    }
	}
    }	

    return (_frcnt < frames) ? _frcnt : frames;
}


int Jclient::jack_process (int frames)
{
    int   i, j, k, m;
    float *moninp [8];
    float *monout [4];
    float *selout [2];
    float *tbinp [2];
    float *tbout [2];
    float *p0, *p1, *q0, *q1;
    float d, g, s, t, zs, zd;

    if (!_active) return 0;

    for (i = 0; i < 8; i++)
    {
        moninp [i] = (float *) jack_port_get_buffer (_moninpport [i], frames);
    }
    for (i = 0; i < 4; i++)
    {
        monout [i] = (float *) jack_port_get_buffer (_monoutport [i], frames);
    }
    for (i = 0; i < 2; i++)
    {
        selout [i] = (float *) jack_port_get_buffer (_seloutport [i], frames);
        tbinp [i] = (float *) jack_port_get_buffer (_tbinpport [i], frames);
        tbout [i] = (float *) jack_port_get_buffer (_tboutport [i], frames);
	memset (selout [i], 0, frames * sizeof (float));
    }

    while (frames)
    {
	k = prepare (frames);

	q0 = selout [0];
	q1 = selout [1];
	for (i = 0; i < 4; i++)
	{	
	    m = _inpstat [i].state ();
	    if (m)
	    {
	        p0 = moninp [2 * i];
	        p1 = moninp [2 * i + 1];
	        if (m == DelayAct::ON)
		{
		    for (j = 0; j < k; j++)
		    {
			q0 [j] += p0 [j];
			q1 [j] += p1 [j];
		    }
		}
		else
		{
		    g = _ipg [i];
		    t = (m == DelayAct::UP) ? 1.0f : 0.0f;
		    d = (t - g) / _frcnt;
		    for (j = 0; j < k; j++)
		    {
			g += d;
			q0 [j] += g * p0 [j];
			q1 [j] += g * p1 [j];
		    }
		    _ipg [i] = g;
		}
	    }
	}

	_kmdsp [0].process (q0, k);
	_kmdsp [1].process (q1, k);
	_cmdsp.process (q0, q1, k);

	for (i = 0; i < 2; i++)
	{
	    p0 = monout [2 * i];
	    p1 = monout [2 * i + 1];
	    g = _opg [i];
	    d = _dog [i];
	    switch (_monmod & 3)
	    {
	    case 0:
 	        for (j = 0; j < k; j++)
	        {
	            g += d;
	            p0 [j] = g * q0 [j];
		    p1 [j] = g * q1 [j];
		}
		break;
	    case 1:
		for (j = 0; j < k; j++)
		{
		    g += d;
		    p0 [j] = g * q0 [j];
		    p1 [j] = 0;
		}
		break;
	    case 2:
		for (j = 0; j < k; j++)
		{
		    g += d;
		    p0 [j] = 0;
		    p1 [j] = g * q1 [j];
		}
		break;
	    case 3:
		for (j = 0; j < k; j++)
		{
		    g += d;
		    t = 0.7f  * g * (q0 [j] + q1 [j]);
		    p0 [j] = t;
		    p1 [j] = t;
		}
		break;
	    }
	    _opg [i] = g;
	}	

	if (_outsel & 16)
	{
	    zs = _zs;
	    zd = _zd;
	    p0 = monout [2];
	    p1 = monout [3];
            for (j = 0; j < k; j++)
	    {
	        s = p0 [j] + p1 [j];
	        d = p0 [j] - p1 [j];
		zs += _ww * (s - zs) + 1e-20f;
		s += 0.4f * zs;
		zd += _ww * (d - zd) + 1e-20f;
		d -= zd;
		p0 [j] = 0.5f * (s + d);
		p1 [j] = 0.5f * (s - d);
	    }
	    _zs = zs;
	    _zd = zd;
	}
	else _zs = _zd = 0;

	for (i = 0; i < 2; i++)
	{
            p0 = tbinp [i];
            p1 = tbout [i];
   	    g = _tbg [i];
	    for (j = 0; j < k; j++) p1 [j] = g * p0 [j];
	}


	for (i = 0; i < 8; i++)
	{
            moninp [i] += k;
	}
	for (i = 0; i < 4; i++)
	{
            monout [i] += k;
	}
	for (i = 0; i < 2; i++)
	{
	    selout [i] += k;
	    tbinp [i] += k;
	    tbout [i] += k;
	}
	frames -= k;
        _frcnt -= k;
    }

    return 0;
}

