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

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

namespace GstreamerSharp
{
	class Playback
	{
		// playbin flags
		static uint Video = (1 << 0); // We want video output
		static uint Audio = (1 << 1); // We want audio output
		static uint Text = (1 << 2);  // We want subtitle output

		static Element Playbin;
		static int NAudio, NVideo, NText;
		static int CurrentVideo, CurrentAudio, CurrentText;
		static GLib.MainLoop MainLoop;

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

			// Create the elements
			Playbin = ElementFactory.Make ("playbin", "playbin");

			if (Playbin == null) {
				Console.WriteLine ("Not all elements could be created.");
				return;
			}

			// Set the URI to play
			Playbin ["uri"] = "http://freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm";

			// Set flags to show Audio and Video but ignore Subtitles
			var flags = (uint)Playbin ["flags"];
			flags |= Audio | Video;
			flags &= ~Text;
			Playbin ["flags"] = flags;

			// Set connection speed. This will affect some internal decisions of playbin2
			Playbin ["connection-speed"] = 56;

			// Add a bus watch, so we get notified when a message arrives
			var bus = Playbin.Bus;
			bus.AddSignalWatch ();
			bus.Message += HandleMessage;

			// Start playing
			var ret = Playbin.SetState (State.Playing);
			if (ret == StateChangeReturn.Failure) {
				Console.WriteLine ("Unable to set the pipeline to the playing state.");
				return;
			}

			// Add a keyboard watch so we get notified of keystrokes
			GLib.Idle.Add (HandleKeyboard);
			MainLoop = new GLib.MainLoop ();
			MainLoop.Run ();

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

		static void HandleMessage (object o, MessageArgs args)
		{
			var msg = args.Message;
			switch (msg.Type) {
			case MessageType.Error:
				GLib.GException err;
				string debug;
				msg.ParseError (out err, out debug);
				Console.WriteLine ("Error received from element {0}: {1}", msg.Src, err.Message);
				Console.WriteLine ("Debugging information: {0}", debug != null ? debug : "none");
				MainLoop.Quit ();
				break;
			case MessageType.Eos:
				Console.WriteLine ("End-Of-Stream reached.");
				MainLoop.Quit ();
				break;
			case MessageType.StateChanged: {
					State oldState, newState, pendingState;
					msg.ParseStateChanged (out oldState, out newState, out pendingState);
					if (msg.Src == Playbin) {
						if (newState == State.Playing) {
							// Once we are in the playing state, analyze the streams
							AnalyzeStreams ();
						}
					}
				} break;
			default:
				break;
			}

			// We want to keep receiving messages
			args.RetVal = true;
		}

		// Extract some metadata from the streams and print it on the screen
		static void AnalyzeStreams () {
			// Read some properties
			NVideo = (int)Playbin ["n-video"];
			NAudio = (int)Playbin ["n-audio"];
			NText = (int)Playbin ["n-text"];

			Console.WriteLine ("{0} video stream(s), {1} audio stream(s), {2} text stream(s)", NVideo, NAudio, NText);

			Console.WriteLine ();
			for (int i = 0; i < NVideo; i++) {
				// Retrieve the stream's video tags
				var tags = (TagList)Playbin.Emit ("get-video-tags", new object [] { i });
				if (tags != null) {
					Console.WriteLine ("video stream {0}", i);
					string str;
					tags.GetString (Constants.TAG_VIDEO_CODEC, out str);
					Console.WriteLine ("  codec: {0}", str != null ? str : "unknown");
				}
			}

			Console.WriteLine ();
			for (int i = 0; i < NAudio; i++) {
				// Retrieve the stream's audio tags
				var tags = (TagList)Playbin.Emit ("get-audio-tags", new object [] { i });
				if (tags != null) {
					Console.WriteLine ("audio stream {0}", i);
					string str;
					if (tags.GetString (Constants.TAG_AUDIO_CODEC, out str)) {
						Console.WriteLine ("  codec: {0}", str);
					}
					if (tags.GetString (Constants.TAG_LANGUAGE_CODE, out str)) {
						Console.WriteLine ("  language: {0}", str);
					}
					uint rate;
					if (tags.GetUint (Constants.TAG_BITRATE, out rate)) {
						Console.WriteLine ("  bitrate: {0}", rate);
					}
				}
			}

			Console.WriteLine ();
			for (int i = 0; i < NText; i++) {
				// Retrieve the stream's subtitle tags
				var tags = (TagList)Playbin.Emit ("get-text-tags", new object [] { i });
				if (tags != null) {
					Console.WriteLine ("subtitle stream {0}", i);
					string str;
					if (tags.GetString (Constants.TAG_LANGUAGE_CODE, out str)) {
						Console.WriteLine ("  language: {0}", str);
					}
				}
			}

			CurrentAudio = (int)Playbin ["current-audio"];
			CurrentVideo = (int)Playbin ["current-video"];
			CurrentText = (int)Playbin ["current-text"];

			Console.WriteLine ();
			Console.WriteLine ("Currently playing video stream {0}, audio stream {1} and text stream {2}", CurrentVideo, CurrentAudio, CurrentText);
			Console.WriteLine ("Type any number to select a different audio stream");
		}

		// Process keyboard input
		static bool HandleKeyboard () {
			if (Console.KeyAvailable) {
				var key = Console.ReadKey (false);

				if (char.IsDigit (key.KeyChar)) {
					var digit = int.Parse (key.KeyChar.ToString ());

					if (digit < 0 || digit > NAudio) {
						Console.WriteLine ("Index out of bounds");
					} else {
						// If the input was a valid audio stream index, set the current audio stream
						Console.WriteLine ("Setting current audio stream to {0}", digit);
						Playbin ["current-audio"] = digit;

					}
				}
			}
			return true;
		}
	}
}