// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2017 Intel Corporation. All Rights Reserved.

namespace Intel.RealSense
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Runtime.InteropServices;

    /// <summary>
    /// The pipeline simplifies the user interaction with the device and computer vision processing modules.
    /// </summary>
    /// <remarks>
    /// The class abstracts the camera configuration and streaming, and the vision modules triggering and threading.
    /// It lets the application focus on the computer vision output of the modules, or the device output data.
    /// The pipeline can manage computer vision modules, which are implemented as a processing blocks.
    /// The pipeline is the consumer of the processing block interface, while the application consumes the
    /// computer vision interface.
    /// </remarks>
    public class Pipeline : Base.Object
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private frame_callback m_callback;

        internal static IntPtr Create(Context ctx)
        {
            object error;
            return NativeMethods.rs2_create_pipeline(ctx.Handle, out error);
        }

        internal static IntPtr Create()
        {
            using (var ctx = new Context())
            {
                return Create(ctx);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pipeline"/> class.
        /// </summary>
        /// <param name="ctx">context</param>
        public Pipeline(Context ctx)
            : base(Create(ctx), NativeMethods.rs2_delete_pipeline)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pipeline"/> class.
        /// </summary>
        public Pipeline()
            : base(Create(), NativeMethods.rs2_delete_pipeline)
        {
        }

        /// <summary>Start the pipeline streaming with its default configuration.</summary>
        /// <remarks>
        /// Starting the pipeline is possible only when it is not started. If the pipeline was started, an exception is raised.
        /// </remarks>
        /// <returns>The actual pipeline device and streams profile, which was successfully configured to the streaming device.</returns>
        public PipelineProfile Start()
        {
            object error;
            var res = NativeMethods.rs2_pipeline_start(Handle, out error);
            var prof = new PipelineProfile(res);
            return prof;
        }

        /// <summary>
        /// Start the pipeline streaming according to the configuraion.
        /// </summary>
        /// <param name="cfg">A <see cref="Config"/> with requested filters on the pipeline configuration. By default no filters are applied.</param>
        /// <returns>The actual pipeline device and streams profile, which was successfully configured to the streaming device.</returns>
        public PipelineProfile Start(Config cfg)
        {
            object error;
            var res = NativeMethods.rs2_pipeline_start_with_config(Handle, cfg.Handle, out error);
            var prof = new PipelineProfile(res);
            return prof;
        }

        /// <summary>
        /// Start the pipeline streaming with its default configuration.
        /// <para>
        /// The pipeline captures samples from the device, and delivers them to the through the provided frame callback.
        /// </para>
        /// </summary>
        /// <remarks>
        /// Starting the pipeline is possible only when it is not started. If the pipeline was started, an exception is raised.
        /// When starting the pipeline with a callback both <see cref="WaitForFrames"/> or <see cref="PollForFrames"/> will throw exception.
        /// </remarks>
        /// <param name="cb">Delegate to register as per-frame callback</param>
        /// <returns>The actual pipeline device and streams profile, which was successfully configured to the streaming device.</returns>
        // TODO: overload with state object and Action<Frame, object> callback to avoid allocations
        public PipelineProfile Start(FrameCallback cb)
        {
            object error;
            frame_callback cb2 = (IntPtr f, IntPtr u) =>
            {
                using (var frame = Frame.Create(f))
                {
                    cb(frame);
                }
            };
            m_callback = cb2;
            var res = NativeMethods.rs2_pipeline_start_with_callback(Handle, cb2, IntPtr.Zero, out error);
            var prof = new PipelineProfile(res);
            return prof;
        }

        // TODO: overload with state object and Action<Frame, object> callback to avoid allocations
        public PipelineProfile Start(Config cfg, FrameCallback cb)
        {
            object error;
            frame_callback cb2 = (IntPtr f, IntPtr u) =>
            {
                using (var frame = Frame.Create(f))
                {
                    cb(frame);
                }
            };
            m_callback = cb2;
            var res = NativeMethods.rs2_pipeline_start_with_config_and_callback(Handle, cfg.Handle, cb2, IntPtr.Zero, out error);
            var prof = new PipelineProfile(res);
            return prof;
        }

        /// <summary>
        /// Stop the pipeline streaming.
        /// </summary>
        /// <remarks>
        /// The pipeline stops delivering samples to the attached computer vision modules and processing blocks, stops the device streaming
        /// and releases the device resources used by the pipeline. It is the application's responsibility to release any frame reference it owns.
        /// The method takes effect only after <see cref="Start"/> was called, otherwise an exception is raised.
        /// </remarks>
        public void Stop()
        {
            object error;
            NativeMethods.rs2_pipeline_stop(Handle, out error);
        }

        public FrameSet WaitForFrames(uint timeout_ms = 5000u)
        {
            object error;
            var ptr = NativeMethods.rs2_pipeline_wait_for_frames(Handle, timeout_ms, out error);
            return FrameSet.Create(ptr);
        }

        public bool TryWaitForFrames(out FrameSet frames, uint timeout_ms = 5000u)
        {
            object error;
            IntPtr ptr;
            bool res = NativeMethods.rs2_pipeline_try_wait_for_frames(Handle, out ptr, timeout_ms, out error) > 0;
            frames = res ? FrameSet.Create(ptr) : null;
            return res;
        }

        public bool PollForFrames(out FrameSet result)
        {
            object error;
            IntPtr fs;
            if (NativeMethods.rs2_pipeline_poll_for_frames(Handle, out fs, out error) > 0)
            {
                result = FrameSet.Create(fs);
                return true;
            }

            result = null;
            return false;
        }

        /// <summary>Gets the active device and streams profiles, used by the pipeline.</summary>
        /// <remarks>The method returns a valid result only when the pipeline is active</remarks>
        /// <value>The actual pipeline device and streams profile, which was successfully configured to the streaming device on start.</value>
        public PipelineProfile ActiveProfile
        {
            get
            {
                object error;
                var ptr = NativeMethods.rs2_pipeline_get_active_profile(Handle, out error);
                return new PipelineProfile(ptr);
            }
        }
    }
}