/*
 * Copyright (C) 2012-2019 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
 * Copyright (C) 2013-2018 John Emmas <john@creativepost.co.uk>
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include <sys/types.h>

#include "pbd/failed_constructor.h"
#include "pbd/pthread_utils.h"

#include "ardour/debug.h"
#include "ardour/dsp_filter.h"
#include "ardour/transport_master.h"
#include "ardour/session.h"
#include "ardour/audioengine.h"

#include "pbd/i18n.h"

using namespace std;
using namespace ARDOUR;
using namespace MIDI;
using namespace PBD;
using namespace Timecode;

#define ENGINE AudioEngine::instance()
#define FLYWHEEL_TIMEOUT ( 1 * ENGINE->sample_rate() )

/* XXX USE Config->get_ltc_input */

LTC_TransportMaster::LTC_TransportMaster (std::string const & name)
	: TimecodeTransportMaster (name, LTC)
	, decoder (0)
	, samples_per_ltc_frame (0)
	, fps_detected (false)
	, monotonic_cnt (0)
	, frames_since_reset (0)
	, delayedlocked (10)
	, ltc_detect_fps_cnt (0)
	, ltc_detect_fps_max (0)
	, sync_lock_broken (false)
	, samples_per_timecode_frame (0)
	, _filter_enable (false)
{
	memset (&prev_frame, 0, sizeof(LTCFrameExt));

	AudioEngine::instance()->Xrun.connect_same_thread (port_connection, std::bind (&LTC_TransportMaster::resync_xrun, this));

}

void
LTC_TransportMaster::init ()
{
	reset (true);
}

void
LTC_TransportMaster::create_port ()
{
	if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name), false, TransportMasterPort)) == 0) {
		throw failed_constructor();
	}
}

void
LTC_TransportMaster::set_session (Session *s)
{
	TransportMaster::set_session (s);

	session_connections.drop_connections();

	if (_session) {

		samples_per_ltc_frame = _session->samples_per_timecode_frame();
		timecode.drop  = _session->timecode_drop_frames();

		if (decoder) {
			ltc_decoder_free (decoder);
		}

		decoder = ltc_decoder_create((int) samples_per_ltc_frame, 128 /*queue size*/);

		DSP::Biquad bq (AudioEngine::instance()->sample_rate());
		bq.compute (DSP::Biquad::LowPass, 3500, 0.9, 0);
		bq.coefficients (_lpf.a1, _lpf.a2, _lpf.b0, _lpf.b1, _lpf.b2);
		bq.compute (DSP::Biquad::HighPass, 100, 0.707, 0);
		bq.coefficients (_hpf.a1, _hpf.a2, _hpf.b0, _hpf.b1, _hpf.b2);
		_lpf.reset ();
		_hpf.reset ();

		// TODO add a per master preference to enable filter (and set gain)
		//_filter_enable = true;

		parse_timecode_offset();
		reset (true);

		fps_detected = false;

		_session->config.ParameterChanged.connect_same_thread (session_connections, std::bind (&LTC_TransportMaster::parameter_changed, this, _1));
		_session->LatencyUpdated.connect_same_thread (session_connections, std::bind (&LTC_TransportMaster::resync_latency, this, _1));
	} else {
		_filter_enable = false;
	}
}

LTC_TransportMaster::~LTC_TransportMaster()
{
	port_connection.disconnect();
	session_connections.drop_connections();

	ltc_decoder_free(decoder);
}

void
LTC_TransportMaster::connection_handler (std::weak_ptr<ARDOUR::Port> w0, std::string n0, std::weak_ptr<ARDOUR::Port> w1, std::string n1, bool con) 
{
	TransportMaster::connection_handler(w0, n0, w1, n1, con);

	std::shared_ptr<Port> p = w1.lock ();
	if (p == _port) {
		resync_latency (false);
		fps_detected = false;
	}
}

void
LTC_TransportMaster::parse_timecode_offset()
{
	if (_session) {
		Timecode::Time offset_tc;
		Timecode::parse_timecode_format(_session->config.get_slave_timecode_offset(), offset_tc);
		offset_tc.rate = _session->timecode_frames_per_second();
		offset_tc.drop = _session->timecode_drop_frames();
		_session->timecode_to_sample(offset_tc, timecode_offset, false, false);
		timecode_negative_offset = offset_tc.negative;
	}
}

void
LTC_TransportMaster::parameter_changed (std::string const & p)
{
	if (p == "slave-timecode-offset"
			|| p == "timecode-format"
			) {
		parse_timecode_offset();
	}
}

ARDOUR::samplecnt_t
LTC_TransportMaster::update_interval() const
{
	if (timecode.rate) {
		/* Use engine rate, timecode ports are not resampled */
		return AudioEngine::instance()->sample_rate() / timecode.rate;
	}

	return AudioEngine::instance()->sample_rate(); /* useless but what other answer is there? */
}

ARDOUR::samplecnt_t
LTC_TransportMaster::resolution () const
{
	return (samplecnt_t) (ENGINE->sample_rate() / 1000);
}

bool
LTC_TransportMaster::locked () const
{
	DEBUG_TRACE (DEBUG::Slave, string_compose ("locked: fps ? %1 dlocked %2\n", fps_detected, delayedlocked));
	return fps_detected && (delayedlocked < 5);
}

bool
LTC_TransportMaster::ok() const
{
	return true;
}

void
LTC_TransportMaster::resync_xrun()
{
	DEBUG_TRACE (DEBUG::LTC, "LTC resync_xrun()\n");
	sync_lock_broken = false;
}

void
LTC_TransportMaster::set_sample_clock_synced (bool yn)
{
	sync_lock_broken = false;
	TransportMaster::set_sample_clock_synced (yn);
}

void
LTC_TransportMaster::resync_latency (bool playback)
{
	if (playback) {
		return;
	}

	uint32_t old = ltc_slave_latency.max;
	if (_port) {
		_port->get_connected_latency_range (ltc_slave_latency, false);
		DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC resync_latency: %1\n", ltc_slave_latency.max));
	}
	if (old != ltc_slave_latency.max) {
		sync_lock_broken = false;
	}
	_lpf.reset ();
	_hpf.reset ();
}

void
LTC_TransportMaster::reset (bool with_position)
{
	DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset() with pos ? %1\n", with_position));
	if (with_position) {
		current.update (current.position, 0, 0);
		_current_delta = 0;
	} else {
		current.reset ();
	}
	transport_direction = 0;
	sync_lock_broken = false;
	delayedlocked = 10;
	monotonic_cnt = 0;
	memset (&prev_frame, 0, sizeof(LTCFrameExt));
	frames_since_reset = 0;
	timecode_format_valid = false;
}

void
LTC_TransportMaster::parse_ltc (pframes_t const n_samples, Sample const* in, samplecnt_t posinfo)
{
	pframes_t remain = n_samples;

	bool  const fe   = _filter_enable;
	float const gain = 127.0; // relative to signed 8bit, 127.0 == 0dB

	while (remain > 0) {
    ltcsnd_sample_t sound[8192];
    pframes_t c = std::min<pframes_t> (remain, 8192);
		if (fe) {
			for (pframes_t i = 0; i < c; ++i) {
				/* LPF */
				float xn = *in++;
				float z  = _lpf.b0 * xn + _lpf.z1;
				_lpf.z1  = _lpf.b1 * xn - _lpf.a1 * z + _lpf.z2;
				_lpf.z2  = _lpf.b2 * xn - _lpf.a2 * z;
				/* HPF */
				xn       = z;
				z        = _hpf.b0 * xn + _hpf.z1;
				_hpf.z1  = _hpf.b1 * xn - _hpf.a1 * z + _hpf.z2;
				_hpf.z2  = _hpf.b2 * xn - _hpf.a2 * z;
				/* convert */
				sound[i] = 128 + min (127, max<int> (-127, gain * z));
			}
		} else {
			for (pframes_t i = 0; i < c; ++i) {
				sound[i] = 128 + min (127, max<int> (-127, gain * (*in++)));
			}
		}
    ltc_decoder_write (decoder, sound, c, posinfo);
    posinfo += c;
    remain -= c;
  }
}

bool
LTC_TransportMaster::equal_ltc_sample_time(LTCFrame *a, LTCFrame *b) {
	if (a->frame_units != b->frame_units ||
	    a->frame_tens  != b->frame_tens ||
	    a->dfbit       != b->dfbit ||
	    a->secs_units  != b->secs_units ||
	    a->secs_tens   != b->secs_tens ||
	    a->mins_units  != b->mins_units ||
	    a->mins_tens   != b->mins_tens ||
	    a->hours_units != b->hours_units ||
	    a->hours_tens  != b->hours_tens) {
		return false;
	}
	return true;
}

#if 0
static ostream& operator<< (ostream& ostr, LTCFrame& a)
{
	ostr
		<< a.hours_tens
		<< a.hours_units << ':'
		<< a.mins_tens
		<< a.mins_units  << ':'
		<< a.secs_tens
		<< a.secs_units  << ':'
		<< a.frame_tens
		<< a.frame_units
		<< (a.dfbit ? 'D' : ' ')
		    ;
	return ostr;
}
#endif

#if 0
static ostream& operator<< (ostream& ostr, SMPTETimecode& t)
{
	for (size_t i = 0; i < sizeof (t.timezone); ++i) {
		ostr << t.timezone[i];
	}
	ostr << ' '
	     << t.years << ' '
	     << t.months << ' '
	     << t.days << ' '
	     << t.hours << ' '
	     << t.mins << ' '
	     << t.secs << ' '
	     << t.frame
		;
	return ostr;
}
#endif

bool
LTC_TransportMaster::detect_discontinuity(LTCFrameExt *sample, int fps, bool fuzzy)
{
	bool discontinuity_detected = false;

	if (fuzzy && (
		  ( sample->reverse && prev_frame.ltc.frame_units == 0)
		||(!sample->reverse && sample->ltc.frame_units == 0)
		)) {
		memcpy(&prev_frame, sample, sizeof(LTCFrameExt));
		return false;
	}

	if (sample->reverse) {
		ltc_frame_decrement(&prev_frame.ltc, fps, LTC_TV_525_60, 0);
	} else {
		ltc_frame_increment(&prev_frame.ltc, fps, LTC_TV_525_60, 0);
	}

	if (!equal_ltc_sample_time(&prev_frame.ltc, &sample->ltc)) {
		discontinuity_detected = true;
	}

	memcpy (&prev_frame, sample, sizeof(LTCFrameExt));
	return discontinuity_detected;
}

bool
LTC_TransportMaster::detect_ltc_fps(int frameno, bool df)
{
	bool fps_changed = false;
	double detected_fps = 0;

	if (frameno > ltc_detect_fps_max) {
		ltc_detect_fps_max = frameno;
	}

	ltc_detect_fps_cnt++;

	if (ltc_detect_fps_cnt > 40) {
		if (ltc_detect_fps_cnt > ltc_detect_fps_max) {
			detected_fps = ltc_detect_fps_max + 1;
			if (df) {
				/* LTC df -> indicates fractional framerate */
				if (fr2997()) {
					detected_fps = detected_fps * 999.0 / 1000.0;
				} else {
					detected_fps = detected_fps * 1000.0 / 1001.0;
				}
			}

			if (!timecode_format_valid) {
				fps_changed = true;
			}

			if (timecode.rate != detected_fps || timecode.drop != df) {
				DEBUG_TRACE (DEBUG::LTC, string_compose ("detected FPS: %1%2\n", detected_fps, df?"df":"ndf"));
			}
		}
		ltc_detect_fps_cnt = ltc_detect_fps_max = 0;
	}

	/* when changed */
	if (detected_fps != 0 && (detected_fps != timecode.rate || df != timecode.drop)) {
		timecode.rate = detected_fps;
		timecode.drop = df;
		samples_per_ltc_frame = double(ENGINE->sample_rate()) / timecode.rate;
		DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC reset to FPS: %1%2 ; audio-samples per LTC: %3\n",
				detected_fps, df?"df":"ndf", samples_per_ltc_frame));
		fps_changed=true;
	}

	TimecodeFormat tc_format = apparent_timecode_format();
	samples_per_timecode_frame = ENGINE->sample_rate() / Timecode::timecode_to_frames_per_second (tc_format);

	return fps_changed;
}

void
LTC_TransportMaster::process_ltc(samplepos_t const now)
{
	LTCFrameExt ltc_frame;
	LTC_TV_STANDARD tv_standard = LTC_TV_625_50;

	bool keep_rolling = Config->get_transport_masters_just_roll_when_sync_lost();

	while (ltc_decoder_read (decoder, &ltc_frame)) {

		SMPTETimecode stime;

		ltc_frame_to_time (&stime, &ltc_frame.ltc, 0);
		timecode.negative  = false;
		timecode.subframes  = 0;

		/* set timecode.rate and timecode.drop: */

		const bool ltc_is_stationary = equal_ltc_sample_time (&prev_frame.ltc, &ltc_frame.ltc);

		if (detect_discontinuity (&ltc_frame, ceil(timecode.rate), !fps_detected)) {
			if (frames_since_reset > 1) {
				reset (false);
			}
		} else {
			if (fps_detected) {
				frames_since_reset++;
				DEBUG_TRACE (DEBUG::LTC, string_compose ("fsr %1\n", frames_since_reset));
			}
		}

		if (keep_rolling && fps_detected && _session && _session->actively_recording ()) {
			/* keep recording with current framerate. */
		} else if (!ltc_is_stationary && detect_ltc_fps (stime.frame, (ltc_frame.ltc.dfbit) ? true : false)) {
			reset (true);
			fps_detected = true;
			timecode_format_valid = true; /* SET FLAG */
		}

#ifndef NDEBUG
		if (DEBUG_ENABLED (DEBUG::LTC)) {
			/* use fprintf for simpler correct formatting of times
			 */

			char buf[256];

			snprintf (buf, sizeof (buf), "LTC@(%ld..%ld) rate %.3f %02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
			          now, now+ENGINE->samples_per_cycle(),
			          timecode.rate,
			          stime.hours,
			          stime.mins,
			          stime.secs,
			          (ltc_frame.ltc.dfbit) ? '.' : ':',
			          stime.frame,
			          ltc_frame.off_start,
			          ltc_frame.off_end,
			          ltc_frame.reverse ? " R" : "  "
				);
			DEBUG_TRACE (DEBUG::LTC, buf);
		}
#endif

		/* when a full LTC frame is decoded, the timecode the LTC frame
		 * is referring has just passed.
		 * So we send the _next_ timecode which
		 * is expected to start at the end of the current LTC frame
		 */
		int fps_i = ceil(timecode.rate);

		switch(fps_i) {
			case 30:
				if (timecode.drop) {
					tv_standard = LTC_TV_525_60;
				} else {
					tv_standard = LTC_TV_1125_60;
				}
				break;
			case 25:
				tv_standard = LTC_TV_625_50;
				break;
			default:
				tv_standard = LTC_TV_FILM_24; /* == LTC_TV_1125_60 == no offset, 24,30fps BGF */
				break;
		}

		if (!ltc_frame.reverse) {
			ltc_frame_increment(&ltc_frame.ltc, fps_i, tv_standard, 0);
			ltc_frame_to_time(&stime, &ltc_frame.ltc, 0);
			transport_direction = 1;
			ltc_frame.off_start -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
			ltc_frame.off_end -= ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
		} else {
			ltc_frame_decrement(&ltc_frame.ltc, fps_i, tv_standard, 0);
			int off = ltc_frame.off_end - ltc_frame.off_start;
			ltc_frame.off_start += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
			ltc_frame.off_end += off - ltc_frame_alignment(samples_per_timecode_frame, tv_standard);
			transport_direction = -1;
		}

		timecode.hours   = stime.hours;
		timecode.minutes = stime.mins;
		timecode.seconds = stime.secs;
		timecode.frames  = stime.frame;

		samplepos_t ltc_sample; // audio-sample corresponding to position of LTC frame

		if (_session) {
			Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), _session->config.get_subframes_per_frame(), timecode_negative_offset, timecode_offset);
		} else {
			Timecode::timecode_to_sample (timecode, ltc_sample, true, false, (double)ENGINE->sample_rate(), 100, timecode_negative_offset, timecode_offset);
		}

		ltc_sample += ltc_slave_latency.max;

		/* This LTC frame spans sample time between sample.off_start  .. sample.off_end
		 *
		 * NOTE: these sample times are NOT the ones that LTC is representing. They are
		 * derived our own audioengine's monotonic audio clock.
		 *
		 * So we expect the next frame to span sample.off_end+1 and ... <don't care for now>.
		 * That isn't the time we will necessarily receive the LTC frame, but the decoder
		 * should tell us that its span begins there.
		 *
		 */

		samplepos_t cur_timestamp = ltc_frame.off_end + 1;
		double ltc_speed = current.speed;

		DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC ltc-sample: %1 prev-ltc-sample %2  cur-timestamp: %3 last-timestamp: %4 frame-spans %5..%6\n", ltc_sample, current.position, cur_timestamp, current.timestamp, ltc_frame.off_start, ltc_frame.off_end));

		if (cur_timestamp <= current.timestamp || current.timestamp == 0) {
			DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: UNCHANGED: %1\n", current.speed));
		} else {
			ltc_speed = double (ltc_sample - current.position) / double (cur_timestamp - current.timestamp);

			/* provide a .1% deadzone to lock the speed */
			if (fabs (ltc_speed - 1.0) <= 0.001) {
				ltc_speed = 1.0;
			}

			if (fabs (ltc_speed) > 10.0) {
				ltc_speed = 0;
			}

			DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed: %1 (moved %2 in %3\n", ltc_speed, (ltc_sample - current.position), (cur_timestamp - current.timestamp)));
		}
		DEBUG_TRACE (DEBUG::LTC, string_compose ("update current to %1 %2 %3\n", ltc_sample, cur_timestamp, ltc_speed));
		current.update (ltc_sample, cur_timestamp, ltc_speed);

	} /* end foreach decoded LTC frame */
}

void
LTC_TransportMaster::pre_process (ARDOUR::pframes_t nframes, samplepos_t now, std::optional<samplepos_t> session_pos)
{
	if (!_port) {
		reset (true);
		return;
	}

	Sample* in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (_port->port_handle(), nframes);
	sampleoffset_t skip;

	if (current.timestamp == 0 || frames_since_reset == 0) {
		if (delayedlocked < 10) {
			++delayedlocked;
		}

		monotonic_cnt = now;
		skip = 0;

	} else {

		skip = now - (monotonic_cnt + nframes);
		monotonic_cnt = now;
	}

	DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process - TID:%1 | latency: %2 | skip %3 | session ? %4| last %5 | dir %6 | sp %7 | dl %8\n",
	                                         pthread_name(), ltc_slave_latency.max, skip, (_session ? 'y' : 'n'), current.timestamp, transport_direction, current.speed, delayedlocked));

	DEBUG_TRACE (DEBUG::LTC, string_compose ("pre-process with audio clock time: %1\n", now));

	/* if the audioengine failed to take the process lock, it won't
	   call this method, and time will appear to skip. Reset the
	   LTC decoder's state by giving it some silence.
	*/

	if (skip > 0) {
		DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples. Feeding silence to LTC parser.\n", skip));
		if (skip >= 8192) skip = 8192;
		unsigned char sound[8192];
		memset (sound, 0x80, sizeof(char) * skip);
		ltc_decoder_write (decoder, sound, skip, now);
		reset (false);
	} else if (skip != 0) {
		/* this should never happen. it may if monotonic_cnt, now overflow on 64bit */
		DEBUG_TRACE (DEBUG::LTC, string_compose("engine skipped %1 samples\n", skip));
		reset(true);
	}

	/* Now feed the incoming LTC signal into the decoder */

	DEBUG_TRACE (DEBUG::LTC, string_compose ("%1@%2 parse ltc @ %3 in %4\n", name(), this, now, nframes));
	parse_ltc (nframes, in, now);

	/* and pull out actual LTC frame data */

	DEBUG_TRACE (DEBUG::LTC, string_compose ("%1@%2 call process_ltc(%3)\n", name(), this, now));
	process_ltc (now);

	if (current.timestamp == 0) {
		DEBUG_TRACE (DEBUG::LTC, "last timestamp == 0\n");
		return;
	} else if (current.speed != 0) {
		DEBUG_TRACE (DEBUG::LTC, string_compose ("speed non-zero (%1)\n", current.speed));
		if (delayedlocked > 1) {
			delayedlocked--;
		} else if (_current_delta == 0) {
			delayedlocked = 0;
		}
	}

	bool const keep_rolling = Config->get_transport_masters_just_roll_when_sync_lost();

	if (keep_rolling && _session && _session->actively_recording ()) {
		/* keep recording */
	} else if (abs (now - current.timestamp) > FLYWHEEL_TIMEOUT) {
		DEBUG_TRACE (DEBUG::LTC, "flywheel timeout\n");
		reset(true);
		/* don't change position from last known */

		return;
	}

	if (!sync_lock_broken && current.speed != 0 && delayedlocked == 0 && fabs(current.speed) != 1.0) {
		sync_lock_broken = true;
		DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC speed not locked based on %1\n", current.speed));
	}

	if (session_pos) {
		const samplepos_t current_pos = current.position + ((now - current.timestamp) * current.speed);
		_current_delta = current_pos - *session_pos;
	} else {
		_current_delta = 0;
	}
}

Timecode::TimecodeFormat
LTC_TransportMaster::apparent_timecode_format () const
{
	if      (timecode.rate == 24 && !timecode.drop)
		return timecode_24;
	else if (timecode.rate == 25 && !timecode.drop)
		return timecode_25;
	else if (rint(timecode.rate * 100) == 2997 && !timecode.drop)
		return (fr2997() ? timecode_2997000 : timecode_2997);
	else if (rint(timecode.rate * 100) == 2997 &&  timecode.drop)
		return (fr2997() ? timecode_2997000drop : timecode_2997drop);
	else if (timecode.rate == 30 &&  timecode.drop)
		return timecode_2997drop; // timecode_30drop; // LTC counting to 30 samples w/DF *means* 29.97 df
	else if (timecode.rate == 30 && !timecode.drop)
		return timecode_30;

	/* XXX - unknown timecode format */

	if (_session) {
		return _session->config.get_timecode_format();
	} else {
		return timecode_30;
	}
}

std::string
LTC_TransportMaster::position_string() const
{
	if (!_collect || current.timestamp == 0) {
		return " --:--:--:--";
	}
	return Timecode::timecode_format_time(timecode);
}

std::string
LTC_TransportMaster::delta_string() const
{
	if (!_collect || current.timestamp == 0) {
		return X_(u8"\u2012\u2012\u2012\u2012");
	} else if ((monotonic_cnt - current.timestamp) > 2 * samples_per_ltc_frame) {
		return _("flywheel");
	} else {
		return format_delta_time (_current_delta);
	}
}
