// Authors
//   Copyright (C) 2014 Stephan Sundermann <stephansundermann@gmail.com>

using System;
using Gst;
using System.Runtime.InteropServices;

namespace GstreamerSharp
{
	class Playback
	{
		// Functions below print the capabilities in a human-friendly format
		static void PrintCaps (Caps caps, string pfx) {

			if (caps == null)
				return;

			if (caps.IsAny) {
				Console.WriteLine ("{0}ANY", pfx);
				return;
			}

			if (caps.IsEmpty) {
				Console.WriteLine ("{0}EMPTY", pfx);
				return;
			}

			for (uint i = 0; i < caps.Size; i++) {
				var structure = caps.GetStructure (i);

				Console.WriteLine ("{0}{1}", pfx, structure.Name);
				structure.Foreach ((field_id, value) => {
					var ptr = g_quark_to_string (field_id);
					var quark = GLib.Marshaller.Utf8PtrToString (ptr);
					Console.WriteLine ("{0}   {1}: {2}", pfx, quark, value.Val);
					return true;
				});
			}
		}

		// Prints information about a Pad Template, including its Capabilities*/
		static void PrintPadTemplateInformation (ElementFactory factory) {

			Console.WriteLine ("Pad Templates for {0}:", factory.Name);
			if (factory.NumPadTemplates == 0) {
				Console.WriteLine ("  none");
				return;
			}

			var pads = factory.StaticPadTemplates;
			foreach (var p in pads) {
				var pad = (StaticPadTemplate) p;

				if (pad.Direction == PadDirection.Src)
					Console.WriteLine ("  SRC template: '{0}'", pad.NameTemplate);
				else if (pad.Direction == PadDirection.Sink)
					Console.WriteLine ("  SINK template: '{0}'", pad.NameTemplate);
				else
					Console.WriteLine ("  UNKNOWN!!! template: '{0}'", pad.NameTemplate);

				if (pad.Presence == PadPresence.Always)
					Console.WriteLine ("    Availability: Always");
				else if (pad.Presence == PadPresence.Sometimes)
					Console.WriteLine ("    Availability: Sometimes");
				else if (pad.Presence == PadPresence.Request) {
					Console.WriteLine ("    Availability: On request");
				} else
					Console.WriteLine ("    Availability: UNKNOWN!!!");

				if (pad.StaticCaps.String != null) {
					Console.WriteLine ("    Capabilities:");
					PrintCaps (pad.StaticCaps.Get (), "      ");
				}

				Console.WriteLine ();
			}
		}

		// Shows the CURRENT capabilities of the requested pad in the given element */
		static void PrintPadCapabilities (Element element, string padName) {

			// Retrieve pad
			var pad = element.GetStaticPad (padName);
			if (pad == null) {
				Console.WriteLine ("Could not retrieve pad '{0}'", padName);
				return;
			}

			// Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet)
			var caps = pad.CurrentCaps;
			if (caps == null)
				caps = pad.Caps;

			/* Print and free */
			Console.WriteLine ("Caps for the {0} pad:", padName);
			PrintCaps (caps, "      ");
		}

		public static void Main (string[] args)
		{
			// Initialize Gstreamer
			Gst.Application.Init(ref args);

			// Create the element factories
			var sourceFactory = ElementFactory.Find ("audiotestsrc");
			var sinkFactory = ElementFactory.Find ("autoaudiosink");



			if (sourceFactory == null || sinkFactory == null) {
				Console.WriteLine ("Not all element factories could be created.");
				return;
			}

			// Print information about the pad templates of these factories
			PrintPadTemplateInformation (sourceFactory);
			PrintPadTemplateInformation (sinkFactory);

			// Ask the factories to instantiate actual elements
			var source = sourceFactory.Create ("source");
			var sink = sinkFactory.Create ("sink");

			// Create the empty pipeline
			var pipeline = new Pipeline ("test-pipeline");

			if (pipeline == null || source == null || sink == null) {
				Console.WriteLine ("Not all elements could be created.");
				return;
			}

			// Build the pipeline
			pipeline.Add (source, sink);
			if (!source.Link (sink)) {
				Console.WriteLine ("Elements could not be linked.");
				return;
			}

			// Print initial negotiated caps (in NULL state)
			Console.WriteLine ("In NULL state:");
			PrintPadCapabilities (sink, "sink");

			// Start playing
			var ret = pipeline.SetState (State.Playing);
			if (ret == StateChangeReturn.Failure) {
				Console.WriteLine ("Unable to set the pipeline to the playing state (check the bus for error messages).");
			}

			// Wait until error, EOS or State Change
			var bus = pipeline.Bus;
			var terminate = false;

			do {
				var msg = bus.TimedPopFiltered (Constants.CLOCK_TIME_NONE, MessageType.Error | MessageType.Eos | MessageType.StateChanged);

				// Parse message
				if (msg != null) {
					switch (msg.Type) {
					case MessageType.Error:
						string debug;
						GLib.GException exc;
						msg.ParseError (out exc, out debug);
						Console.WriteLine ("Error received from element {0}: {1}", msg.Src.Name, exc.Message);
						Console.WriteLine ("Debugging information: {0}", debug != null ? debug : "none");
						terminate = true;
						break;
					case MessageType.Eos:
						Console.WriteLine ("End-Of-Stream reached.\n");
						terminate = true;
						break;
					case MessageType.StateChanged:
						// We are only interested in state-changed messages from the pipeline
						if (msg.Src == pipeline) {
							State oldState, newState, pendingState;
							msg.ParseStateChanged (out oldState, out newState, out pendingState);
							Console.WriteLine ("Pipeline state changed from {0} to {1}:",
								Element.StateGetName (oldState), Element.StateGetName (newState));
							// Print the current capabilities of the sink element
							PrintPadCapabilities (sink, "sink");
						}
						break;
					default:
						// We should not reach here because we only asked for ERRORs, EOS and STATE_CHANGED
						Console.WriteLine ("Unexpected message received.");
						break;
					}
				}
			} while (!terminate);

			// Free resources
			pipeline.SetState (State.Null);
		}

		[DllImport ("libglib-2.0.dll")]
		static extern IntPtr g_quark_to_string (uint quark);
	}
}