using Gst;

public class Receiver : GLib.Object {

	public signal void stream_timed_out (bool stream_ok);

	private Pipeline pipeline;
	private dynamic Element rtpbin;
	private Element sink;
	private int stream_count = 0;

	public uint port { get; construct; }

	public Receiver (uint port) {
		this.port = port;
	}

	construct {

		pipeline = new Pipeline ("Receiver");

		dynamic Element udpsrc = ElementFactory.make ("udpsrc", null);
		udpsrc.port = port;
		udpsrc.caps = Caps.from_string ("application/x-rtp," +
										"media=(string)video," +
										"clock-rate=(int)90000," +
										"payload=(int)96," +
										"encoding-name=(string)H263-1998");

		rtpbin = ElementFactory.make ("gstrtpbin", null);
		rtpbin.pad_added += on_pad_added;
		rtpbin.on_timeout += on_timeout;
		rtpbin.on_new_ssrc += on_new_ssrc;

		pipeline.add (udpsrc);
		pipeline.add (rtpbin);

		udpsrc.link (rtpbin);
	}

	public void start () {pipeline.set_state (State.PLAYING);}
	public void stop  () {pipeline.set_state (State.NULL);}

	private void setup_playback_of_stream (Pad recv_rtp_src_pad)
	requires (sink == null) {
		stdout.printf ("  Setting up playback of incoming stream\n");

		// Don't bother with showing the video for testing, just use a fakesink
		sink = ElementFactory.make ("fakesink", null);
		sink.set_state (pipeline.current_state);
		pipeline.add (sink);
		recv_rtp_src_pad.link (sink.get_static_pad ("sink"));
	}

	private void teardown_playback_of_stream ()
	requires (sink != null) {
		stdout.printf ("  Tearing down playback of stream\n");

		var sink_pad = sink.get_static_pad ("sink");
		var recv_rtp_src_pad = sink_pad.get_peer ();
		recv_rtp_src_pad.unlink (sink_pad);
		pipeline.remove (sink);
		sink.set_state (State.NULL);
		sink = null;

		rtpbin.release_request_pad (recv_rtp_src_pad);
	}

	private void on_pad_added (Element element, Pad new_pad) {
		stdout.printf ("  pad_added (%s)\n", new_pad.name);
		if (new_pad.name.has_prefix ("recv_rtp_src_"))
			setup_playback_of_stream (new_pad);
	}

	private void on_timeout (Element rtpbin, uint session_id, uint ssrc) {
		stdout.printf ("  on_timeout (%u, %u)\n", session_id, ssrc);
		bool stream_ok = false;
		if (sink != null) {
			debug_bin_to_dot_file (pipeline, DebugGraphDetails.ALL,
								   "before-teardown-%d".printf (stream_count));
			teardown_playback_of_stream ();
			debug_bin_to_dot_file (pipeline, DebugGraphDetails.ALL,
								   "after-teardown-%d".printf (stream_count));
			stream_ok = true;
		}
		stream_timed_out (stream_ok);
	}

	private void on_new_ssrc (Element rtpbin, uint session_id, uint ssrc) {
		stdout.printf ("  on_new_stream (%u, %u)\n", session_id, ssrc);
		stream_count++;
	}
}


public class Sender : GLib.Object{

	public signal void stopped_sending ();
	private Pipeline pipeline;

	public uint port { get; construct; }

	public Sender (uint port) {
		this.port = port;
	}

	construct {
		pipeline = new Pipeline ("Sender");

		dynamic Element src = ElementFactory.make ("videotestsrc", null);
		src.num_buffers = 100;
		src.is_live = true;

		var encoder = ElementFactory.make ("ffenc_h263p", null);

		dynamic Element payloader = ElementFactory.make ("rtph263ppay", null);
		payloader.pt = 96;
		payloader.ssrc = 1000000000;

		dynamic Element udpsink = ElementFactory.make ("udpsink", null);
		udpsink.port = port;

		pipeline.add (src);
		pipeline.add (encoder);
		pipeline.add (payloader);
		pipeline.add (udpsink);

		src.link_many (encoder, payloader, udpsink);

		var bus = pipeline.get_bus ();
		bus.add_signal_watch ();
		bus.enable_sync_message_emission ();
		bus.message += (bus, msg) => {
			if (msg.type == MessageType.EOS)
				stopped_sending ();
		};
	}

	public void start () {pipeline.set_state (State.PLAYING);}
	public void stop  () {pipeline.set_state (State.NULL);}
}


public class TestRepeatedSsrc : GLib.Object {
	private Receiver receiver;
	private Sender sender;
	private MainLoop mainloop;
	private int count = 0;

	public uint port { get; construct; }

	public TestRepeatedSsrc (uint port) {
		this.port = port;
	}

	construct {
		receiver = new Receiver (port);
		receiver.start ();
		receiver.stream_timed_out += on_stream_timed_out;
	}

	public void run () {
		start_new_sender (receiver);
		mainloop = new MainLoop (null, false);
		mainloop.run ();
	}

	private void start_new_sender (Receiver r) {
		stdout.printf ("Sending stream #%d\n", ++count);
		if (sender == null) {
			sender = new Sender (port);
			sender.stopped_sending += (s) => {
				stdout.printf ("  Stopped sending. Waiting for timeout...\n");
			};
		}
		else {
			sender.stop ();
		}
		sender.start ();
	}

	private void on_stream_timed_out (Receiver r, bool stream_ok) {
		if (stream_ok)
			stdout.printf ("SUCCESS on stream #%d\n", count);
		else
			stdout.printf ("FAILURE on stream #%d\n", count);
		start_new_sender (r);
	}
}


static int main (string[] args) {
	Gst.init (ref args);

	uint port = 12345;
	var test = new TestRepeatedSsrc (port);
	test.run ();

	return 0;
}