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

namespace TestGst
{
    public static class Utils
    {
        public static int PrintErr(this string format, params object[] args)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            var s = String.Format(format, args);
            Console.WriteLine(s);
            Console.ResetColor();
            return -1;
        }
    }

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

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

            if (sourceFactory == null || sinkFactory == null)
            {
                "Not all element factories could be created".PrintErr();
                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)
            {
                "Not all elements could be created".PrintErr();
                return;
            }
            pipeline.Add(source, sink);

            // Build the pipeline 
            if (!source.Link(sink))
            {
                "Elements could not be linked".PrintErr();
                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)
            {
                "Unable to set the pipeline to the playing state (check the bus for error messages).".PrintErr();
            }

            // Wait until error, EOS or State Change
            var bus = pipeline.Bus;
            bool terminate = false;
            do
            {
                var msg = bus.TimedPopFiltered(Constants.CLOCK_TIME_NONE, MessageType.Error | MessageType.Eos | MessageType.StateChanged);
                if (msg == null)
                    continue;
                // Parse message
                switch (msg.Type)
                {
                    case MessageType.Eos:
                        Console.WriteLine("End of stream reached");
                        terminate = true;
                        break;
                    case MessageType.Error:
                        msg.ParseError(out GLib.GException error, out string debug);
                        $"Error received from element {msg.Src.Name}: {error.Message}".PrintErr();
                        String.Format("Debugging information {0}", debug ?? "(none)").PrintErr();
                        terminate = true;
                        break;
                    case MessageType.StateChanged:
                        // We are only interested in state-changed messages from the pipeline
                        if (msg.Src != pipeline)
                            continue;
                        msg.ParseStateChanged(out State oldState, out State newState, out State pending);
                        Console.WriteLine($"Pipeline state changed from {Element.StateGetName(oldState)} to {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
                        "Unexpected message received".PrintErr();
                        break;
                }
            } while (!terminate);

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

        #region Functions below print the Capabilities in a human-friendly format
        static bool PrintField(uint field_id, GLib.Value value)
        {
            string quark=String.Empty;
            try
            {
                var ptr = g_quark_to_string(field_id);
                quark = GLib.Marshaller.Utf8PtrToString(ptr);
                Console.WriteLine($"{quark}: {value.Val}");
            }
            catch (Exception ex)
            {
                $"{quark} : {ex.Message}".PrintErr();
            }
            return true;
        }

        static void PrintCaps(Caps caps, string pfx)
        {
            if (caps == null)
                return;
            if (caps.IsAny)
            {
                Console.WriteLine($"{pfx}ANY");
                return;
            }
            if (caps.IsEmpty)
            {
                Console.WriteLine($"{pfx}EMPTY");
                return;
            }
            for (uint i = 0; i < caps.Size; i++)
            {

                var structure = caps[i];
                Console.WriteLine($"{pfx}{structure.Name}");
                structure.Foreach(PrintField);
            }
        }

        /// <summary>
        /// Prints information about a Pad Template, including its Capabilities
        /// </summary>
        static void PrintPadTemplateInformation(ElementFactory factory)
        {
            Console.WriteLine($"Pad templates for {factory.Name}");
            if (factory.NumPadTemplates == 0)
            {
                Console.WriteLine(" none");
                return;
            }

            var pads = factory.StaticPadTemplates;
            foreach (var pad in pads)
            {
                Console.WriteLine($"    {pad.Direction} template: '{pad.NameTemplate}'");
                Console.WriteLine($"    Availability {pad.Presence}");
                if (pad.StaticCaps.String != null)
                {
                    Console.WriteLine("      Capabilities:");
                    var caps = pad.StaticCaps.Caps;
                    PrintCaps(caps, "      ");
                }
            }
        }

        /// <summary>
        /// Shows the CURRENT capabilities of the requested pad in the given element 
        /// </summary>
        static void PrintPadCapabilities(Element element, string padName)
        {
            var pad = element.GetStaticPad(padName);
            if (pad == null)
            {
                $"Cound not retrieve pad {padName}".PrintErr();
                return;
            }
            /* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */
            var caps = pad.CurrentCaps ?? pad.QueryCaps();

            /* Print and free */
            Console.WriteLine($"Pads for the {padName} pad");
            PrintCaps(caps, "      ");
        }
        #endregion

        [DllImport("libglib-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern IntPtr g_quark_to_string(uint quark);
    }
}
