// ----------------------------------------------------------------------------
//
//  Copyright (C) 2024 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 <math.h>
#include <stdio.h>
#include <string.h>
#include "jfwplay.h"


Jfwplay::Jfwplay (const char *client_name,
		  const char *server_name,
		  int nchan) :
    _nchan (nchan),
    _fchan (0),
    _fsize (0),
    _fbuff (0),
    _p0 (0),
    _p1 (0),
    _pc (0)
{
    if (nchan > MAXCHAN) nchan = MAXCHAN;
    else if (nchan < 0) nchan = 0;
    if (open_jack (client_name, server_name, 0, nchan + 1) || init ())
    {
        _state = FAILED;
        return;
    }
}


Jfwplay::~Jfwplay (void)
{
    fini ();
}


int Jfwplay::init (void)
{
    int  i;
    char s [16];

    for (i = 0; i < _nchan; i++)
    {
	sprintf (s, "out_%d", i);
        if (create_out_port (i, s)) return 1;
    }	
    if (create_out_port (_nchan, "_sync")) return 1;
    _state = SILENCE;
    return 0;
}


void Jfwplay::fini (void)
{
    _state = INITIAL;
    jack_set_freewheel (_client, 0);
    close_file ();
    close_jack ();
}


int Jfwplay::close_file (void)
{    
    if (_state > SILENCE) return 1;
    _afile.close ();
    delete _fbuff;
    _fbuff = 0;
    _fsize = 0;
    _fchan = 0;
    return 0;
}


int Jfwplay::load_file (const char *fname)
{
    if (_state != SILENCE) return 1;
    close_file ();
    if (! fname) return 0;

    _afile.open_read (fname);
    if (_afile.mode () == Audiofile::MODE_READ)
    {
	_fsize = _afile.size ();
	_fchan = _afile.chan ();
        _fbuff = new float [_fchan * jack_size ()];
	_p0 = 0;
	_p1 = _fsize;
	return 0;
    }
    return 1;
}


int Jfwplay::set_range (int64_t p0, int64_t p1)
{
    if ((p0 < 0) || (p1 < p0)) return 1;
    _p0 = p0;
    _p1 = p1;
    return 0;
}


int Jfwplay::set_state (int run)
{
    // The real state change happens in
    // the jack_freewheel() callback.
    jack_set_freewheel (_client, run);
    return 0;
}


void Jfwplay::jack_freewheel (int run)
{
    if (run && _state == SILENCE)
    {
        if (_p0 < _fsize) _afile.seek (_p0);
        _pc = _p0;
        _state = PROCESS;
    }
    else _state = SILENCE;
}


int Jfwplay::jack_process (int nframes)
{
    int      c, i, n;
    int64_t  ns, nr;
    char     *sync;
    float    *outp, *p;

    if (_state < SILENCE) return 0;
    ns = nr = 0;
    if (_state == PROCESS)
    {
	// Number of samples to send.
	ns = _p1 - _pc;
	if (ns > 0)
	{
	    if (ns > nframes) ns = nframes;
	    // Number of samples to read rom file.
	    nr = _fsize - _pc;
	    if (nr > ns) nr = ns;
	    if (nr > 0) nr = _afile.read (_fbuff, nr);
	    else nr = 0;
	}
	else 
	{
	    // Done.
	    ns = nr = 0;
	    _state = SILENCE;
	    // Jack doesn't like us calling jack_set_freewheel() here,
	    // so this is done by process() or stop() in the Python class.
	}
    }

    // Create message on the sync output.
    sync = (char *) jack_port_get_buffer (_out_ports [_nchan], nframes);
    memset (sync, 0, nframes * sizeof (float));
    if (ns)
    {
	// These are equivalent to very small FP values.
	// Magic code.
	sync [0] = 'F';       
	sync [1] = 'W';
	sync [3] = 1; 
	// Number of valid samples.
	sync [4] = ns & 255;
	sync [5] = (ns >> 8) & 255;
	sync [7] = 1;
        _pc += ns;
    }    

    // Write audio data to ports.
    n = nr;
    for (c = 0; c < _nchan; c++)
    {
        outp = (float *) jack_port_get_buffer (_out_ports [c], nframes);
	if ((c < _fchan) && (n > 0))
	{
	    // Deinterleave samples.
	    for (i = 0, p = _fbuff + c; i < n; i++, p += _fchan) outp [i] = *p;
	    // Clear rest of output buffer.
	    memset (outp + n, 0, (nframes - n) * sizeof (float));
	}
	else
	{
	    // Clear entire output buffer.
	    memset (outp, 0, nframes * sizeof (float));
	}
    }

    return 0;
}

