using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.RTSP.Messages;

// RTSP Server Example (c) Roger Hardiman, 2016, 2018
// Released uder the MIT Open Source Licence
//
// Re-uses some code from the Multiplexer example of SharpRTSP
//
// This example simulates a live RTSP video stream, for example a CCTV Camera
// It creates a Video Source (a test card) that creates a YUV Image
// The image is then encoded as H264 data using a very basic H264 Encoder
// The H264 data (the NALs) are sent to the RTSP clients
// Video is sent in UDP Mode or TCP Mode (ie RTP over RTSP mode)

// The Tiny H264 Encoder is a 100% .NET encoder which is lossless and creates large bitstreams as
// there is no compression. It is limited to 128x96 resolution. However it makes it easy to write a quick
// demo without needing native APIs or cross compiled C libraries for H264

namespace Inspectron.HawkEye.RTSP.Server
{
    public class RtspServer : IDisposable
    {
        const int h264_width = 192;    // Tiny needs 128x96
        const int h264_height = 128;
        const int h264_fps = 25;

        const uint global_ssrc = 0x4321FADE; // 8 hex digits

        private TcpListener _RTSPServerListener;
        private ManualResetEvent _Stopping;
        private Thread _ListenTread;

        private TestCard video_source = null;
        private SimpleH264Encoder h264_encoder = null;
        //private TinyH264Encoder h264_encoder = null;

        byte[] raw_sps = null;
        byte[] raw_pps = null;

        List<RTSPConnection> rtsp_list = new List<RTSPConnection>(); // list of RTSP Listeners

        Random rnd = new Random();
        int session_handle = 1;

        Authentication auth = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="RTSPServer"/> class.
        /// </summary>
        /// <param name="aPortNumber">A numero port.</param>
        /// <param name="username">username.</param>
        /// <param name="password">password.</param>
        public RtspServer(int portNumber, String username, String password)
        {
            if (portNumber < System.Net.IPEndPoint.MinPort || portNumber > System.Net.IPEndPoint.MaxPort)
                throw new ArgumentOutOfRangeException("aPortNumber", portNumber, "Port number must be between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort");
            Contract.EndContractBlock();

            if (String.IsNullOrEmpty(username) == false
                && String.IsNullOrEmpty(password) == false) {
                String realm = "SharpRTSPServer";
                auth = new Authentication(username,password,realm,Authentication.Type.Digest);
            } else {
                auth = null;
            }

            RtspUtils.RegisterUri();
            _RTSPServerListener = new TcpListener(IPAddress.Any, portNumber);
        }

        /// <summary>
        /// Starts the listen.
        /// </summary>
        public void StartListen()
        {
            _RTSPServerListener.Start();

            _Stopping = new ManualResetEvent(false);
            _ListenTread = new Thread(new ThreadStart(AcceptConnection));
            _ListenTread.Start();

            // Initialise the H264 encoder
            h264_encoder = new SimpleH264Encoder(h264_width, h264_height, h264_fps);
            //h264_encoder = new TinyH264Encoder(); // hard coded to 192x128

            // Start the VideoSource
            video_source = new TestCard(h264_width, h264_height, h264_fps);
            video_source.ReceivedYUVFrame += video_source_ReceivedYUVFrame;
        }


        /// <summary>
        /// Accepts the connection.
        /// </summary>
        private void AcceptConnection()
        {
            try
            {
                while (!_Stopping.WaitOne(0))
                {
                    // Wait for an incoming TCP Connection
                    TcpClient oneClient = _RTSPServerListener.AcceptTcpClient();
                    Console.WriteLine("Connection from " + oneClient.Client.RemoteEndPoint.ToString());

                    // Hand the incoming TCP connection over to the RTSP classes
                    var rtsp_socket = new RtspTcpTransport(oneClient);
                    RtspListener newListener = new RtspListener(rtsp_socket);
                    newListener.MessageReceived += RTSP_Message_Received;
                    //RTSPDispatcher.Instance.AddListener(newListener);

                    // Add the RtspListener to the RTSPConnections List
                    lock (rtsp_list) {
                        RTSPConnection new_connection = new RTSPConnection();
                        new_connection.listener = newListener;
                        new_connection.client_hostname = newListener.RemoteAdress.Split(':')[0];
                        new_connection.ssrc = global_ssrc;

                        new_connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
                        new_connection.video_time_since_last_rtcp_keepalive = DateTime.UtcNow;

                        rtsp_list.Add(new_connection);
                    }

                    newListener.Start();
                }
            }
            catch (SocketException error)
            {
                // _logger.Warn("Got an error listening, I have to handle the stopping which also throw an error", error);
            }
            catch (Exception error)
            {
                // _logger.Error("Got an error listening...", error);
                throw;
            }


        }


        public void StopListen()
        {
            _RTSPServerListener.Stop();
            _Stopping.Set();
            _ListenTread.Join();
        }

        #region IDisposable Membres

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                StopListen();
                _Stopping.Dispose();
            }
        }

        #endregion

        // Process each RTSP message that is received
        private void RTSP_Message_Received(object sender, RtspChunkEventArgs e)
        {
            // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message
            RtspListener listener = sender as RtspListener;
            RtspMessage message = e.Message as RtspMessage;

            Console.WriteLine("RTSP message received " + message);

        
            // Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce)
            if (auth != null) {
                bool authorized = false;
                if (message.Headers.ContainsKey("Authorization") == true ) {
                    // The Header contained Authorization
                    // Check the message has the correct Authorization
                    // If it does not have the correct Authorization then close the RTSP connection
                    authorized = auth.IsValid(message);

                    if (authorized == false) {
                        // Send a 401 Authentication Failed reply, then close the RTSP Socket
                        RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
                        authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader());
                        authorization_response.ReturnCode = 401;
                        listener.SendMessage(authorization_response);

                        lock (rtsp_list) {
                            foreach (RTSPConnection connection in rtsp_list.ToArray()){
                                if (connection.listener == listener) {
                                    rtsp_list.Remove(connection);
                                }
                            }
                        }
                        listener.Dispose();
                        return;

                    }
                }
                if ((message.Headers.ContainsKey("Authorization") == false)){
                    // Send a 401 Authentication Failed with extra info in WWW-Authenticate
                    // to tell the Client if we are using Basic or Digest Authentication
                    RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
                    authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); // 'Basic' or 'Digest'
                    authorization_response.ReturnCode = 401;
                    listener.SendMessage(authorization_response);
                    return;
                }
            }

            // Update the RTSP Keepalive Timeout
            // We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message
            lock (rtsp_list)
            {
                foreach (RTSPConnection connection in rtsp_list)
                {
                    if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress))
                    {
                        // found the connection
                        connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
                        break;

                    }
                }
            }


            // Handle OPTIONS message
            if (message is RtspRequestOptions)
            {
                // Create the reponse to OPTIONS
                RtspResponse options_response = (e.Message as RtspRequestOptions).CreateResponse();
                listener.SendMessage(options_response);
            }

            // Handle DESCRIBE message
            if (message is RtspRequestDescribe)
            {
                String requested_url = (message as RtspRequestDescribe).RtspUri.ToString();
                Console.WriteLine("Request for " + requested_url);

                // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL

                // Make the Base64 SPS and PPS
                raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
                raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
                String sps_str = Convert.ToBase64String(raw_sps);
                String pps_str = Convert.ToBase64String(raw_pps);

                StringBuilder sdp = new StringBuilder();

                // Generate the SDP
                // The sprop-parameter-sets provide the SPS and PPS for H264 video
                // The packetization-mode defines the H264 over RTP payloads used but is Optional
                sdp.Append("v=0\n");
                sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n");
                sdp.Append("s=SharpRTSP Test Camera\n");
                sdp.Append("m=video 0 RTP/AVP 96\n");
                sdp.Append("c=IN IP4 0.0.0.0\n");
                sdp.Append("a=control:trackID=0\n");
                sdp.Append("a=rtpmap:96 H264/90000\n");
                sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n");

                byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString());

                // Create the reponse to DESCRIBE
                // This must include the Session Description Protocol (SDP)
                RtspResponse describe_response = (e.Message as RtspRequestDescribe).CreateResponse();

                describe_response.AddHeader("Content-Base: " + requested_url);
                describe_response.AddHeader("Content-Type: application/sdp");
                describe_response.Data = sdp_bytes;
                describe_response.AdjustContentLength();
                listener.SendMessage(describe_response);
            }

            // Handle SETUP message
            if (message is RtspRequestSetup)
            {

                // 
                var setupMessage = message as RtspRequestSetup;

                // Check the RTSP transport
                // If it is UDP or Multicast, create the sockets
                // If it is RTP over RTSP we send data via the RTSP Listener

                // FIXME client may send more than one possible transport.
                // very rare
                RtspTransport transport = setupMessage.GetTransports()[0];


                // Construct the Transport: reply from the Server to the client
                RtspTransport transport_reply = new RtspTransport();
                transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters

                if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP)
                {
                    // RTP over RTSP mode}
                    transport_reply.LowerTransport = RtspTransport.LowerTransportType.TCP;
                    transport_reply.Interleaved = new PortCouple(transport.Interleaved.First, transport.Interleaved.Second);
                }

                UDPSocket udp_pair = null;
                if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
                    && transport.IsMulticast == false)
                {
                    Boolean udp_supported = true;
                    if (udp_supported) {
                        // RTP over UDP mode
                        // Create a pair of UDP sockets - One is for the Video, one is for the RTCP
                        udp_pair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
                        udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => {
                            // RTCP data received
                            Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString());
                        };
                        udp_pair.Start(); // start listening for data on the UDP ports

                        // Pass the Port of the two sockets back in the reply
                        transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
                        transport_reply.IsMulticast = false;
                        transport_reply.ClientPort = new PortCouple(udp_pair.data_port,udp_pair.control_port);
                    } else {
                        transport_reply = null;
                    }
                }

                if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
                    && transport.IsMulticast == true)
                {
                    // RTP over Multicast UDP mode}
                    // Create a pair of UDP sockets in Multicast Mode
                    // Pass the Ports of the two sockets back in the reply
                    transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
                    transport_reply.IsMulticast = true;
                    transport_reply.Port = new PortCouple(7000, 7001);  // FIX

                    // for now until implemented
                    transport_reply = null;
                }


                if (transport_reply != null)
                {

                    // Update the session with transport information
                    String copy_of_session_id = "";
                    lock (rtsp_list)
                    {
                        foreach (RTSPConnection connection in rtsp_list)
                        {
                            if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) {
                                // ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream
                                // or a SETUP for an Audio Stream.
                                // In the SDP the H264 video track is TrackID 0


                                // found the connection
                                // Add the transports to the connection
                                connection.video_client_transport = transport;
                                connection.video_transport_reply = transport_reply;

                                // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname
                                connection.video_udp_pair = udp_pair;


                                connection.video_session_id = session_handle.ToString();
                                session_handle++;


                                // Copy the Session ID
                                copy_of_session_id = connection.video_session_id;
                                break;
                            }
                        }
                    }

                    RtspResponse setup_response = setupMessage.CreateResponse();
                    setup_response.Headers[RtspHeaderNames.Transport] = transport_reply.ToString();
                    setup_response.Session = copy_of_session_id;
                    listener.SendMessage(setup_response);
                }
                else
                {
                    RtspResponse setup_response = setupMessage.CreateResponse();
                    // unsuported transport
                    setup_response.ReturnCode = 461;
                    listener.SendMessage(setup_response);
                }

            }

            // Handle PLAY message (Sent with a Session ID)
            if (message is RtspRequestPlay)
            {
                lock (rtsp_list)
                {
                    // Search for the Session in the Sessions List. Change the state to "PLAY"
                    bool session_found = false;
                    foreach (RTSPConnection connection in rtsp_list)
                    {
                        if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */
                        {
                            // found the session
                            session_found = true;
                            connection.play = true;  // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO)

                            string range = "npt=0-";   // Playing the 'video' from 0 seconds until the end
                            string rtp_info = "url="+((RtspRequestPlay)message).RtspUri+";seq=" + connection.video_sequence_number; // TODO Add rtptime  +";rtptime="+session.rtp_initial_timestamp;

                            // Send the reply
                            RtspResponse play_response = (e.Message as RtspRequestPlay).CreateResponse();
                            play_response.AddHeader("Range: " + range);
                            play_response.AddHeader("RTP-Info: " + rtp_info);
                            listener.SendMessage(play_response);

                            break;
                        }
                    }

                    if (session_found == false) {
                        // Session ID was not found in the list of Sessions. Send a 454 error
                        RtspResponse play_failed_response = (e.Message as RtspRequestPlay).CreateResponse();
                        play_failed_response.ReturnCode = 454; // Session Not Found
                        listener.SendMessage(play_failed_response);
                    }

                }

            }

            // Handle PAUSE message (Sent with a Session ID)
            if (message is RtspRequestPause)
            {
                lock (rtsp_list)
                {
                    // Search for the Session in the Sessions List. Change the state of "PLAY" 
                    foreach (RTSPConnection connection in rtsp_list)
                    {
                        if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */)
                        {
                            // found the session
                            connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO
                            break;
                        }
                    }
                }

                // ToDo - only send back the OK response if the Session in the RTSP message was found
                RtspResponse pause_response = (e.Message as RtspRequestPause).CreateResponse();
                listener.SendMessage(pause_response);
            }


            // Handle GET_PARAMETER message, often used as a Keep Alive
            if (message is RtspRequestGetParameter)
            {
                // Create the reponse to GET_PARAMETER
                RtspResponse getparameter_response = (e.Message as RtspRequestGetParameter).CreateResponse();
                listener.SendMessage(getparameter_response);
            }


            // Handle TEARDOWN (sent with a Session ID)
            if (message is RtspRequestTeardown)
            {
                lock (rtsp_list)
                {
                    // Search for the Session in the Sessions List.
                    foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list
                    {
                        if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL
                        {
                            // If this is UDP, close the transport
                            // For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection)
                            if (connection.video_udp_pair != null) {
                                connection.video_udp_pair.Stop();
                                connection.video_udp_pair = null;
                            }

                            rtsp_list.Remove(connection);

                            // Close the RTSP socket
                            listener.Dispose();
                        }
                    }
                }
            }


        }

        // The 'Camera' (YUV TestCard) has generated a YUV image.
        // If there are RTSP clients connected then Compress the Video Frame (with H264) and send it to the client
        void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
        {
            DateTime now = DateTime.UtcNow;
            int current_rtp_play_count = 0;
            int current_rtp_count = 0;
            int timeout_in_seconds = 70;  // must have a RTSP message every 70 seconds or we will close the connection
            lock (rtsp_list) {
                current_rtp_count = rtsp_list.Count;
                foreach (RTSPConnection connection in rtsp_list.ToArray()) { // Convert to Array to allow us to delete from rtsp_list
                    // RTSP Timeout (clients receiving RTP video over the RTSP session
                    // do not need to send a keepalive (so we check for Socket write errors)
                    Boolean sending_rtp_via_tcp = false;
                    if ((connection.video_client_transport != null) &&
                        (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP))
                    {
                        sending_rtp_via_tcp = true;
                    }

                    if (sending_rtp_via_tcp == false && ((now - connection.time_since_last_rtsp_keepalive).TotalSeconds > timeout_in_seconds)) {

                        Console.WriteLine("Removing session " + connection.video_session_id + " due to TIMEOUT");
                        connection.play = false; // stop sending data
                        if (connection.video_udp_pair != null)
                        {
                            connection.video_udp_pair.Stop();
                            connection.video_udp_pair = null;
                        }
                        connection.listener.Dispose();

                        rtsp_list.Remove(connection);
                        continue;
                    }
                    else if (connection.play) current_rtp_play_count++;
                }
            }

            // Take the YUV image and encode it into a H264 NAL
            // This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
            Console.WriteLine(current_rtp_count + " RTSP clients connected. " + current_rtp_play_count + " RTSP clients in PLAY mode");

            if (current_rtp_play_count == 0) return;

            // Compress the video (YUV to H264)
            byte[] raw_video_nal = h264_encoder.CompressFrame(yuv_data);
            Boolean isKeyframe = true; // SimpleH264encoder and TinyH24encoder only emit keyframes


            List<byte[]> nal_array = new List<byte[]>();
        
            // We may want to add the SPS and PPS to the H264 stream as in-band data.
            // This may be of use if the client did not parse the SPS/PPS in the SDP
            // or if the H264 encoder changes properties (eg a new resolution or framerate which
            // gives a new SPS or PPS).
            // Also looking towards H265, the VPS/SPS/PPS do not need to be in the SDP so would be added here.

            Boolean add_sps_pps_to_keyframe = true;

            if (add_sps_pps_to_keyframe && isKeyframe) {
                nal_array.Add(raw_sps);
                nal_array.Add(raw_pps);
            }

            // add the rest of the NALs
            nal_array.Add(raw_video_nal);



            UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock

            // Build a list of 1 or more RTP packets
            // The last packet will have the M bit set to '1'
            List<byte[]> rtp_packets = new List<byte[]>();

            for(int x = 0; x < nal_array.Count; x++) {

                byte[] raw_nal = nal_array[x];
                Boolean last_nal = false;
                if (x == nal_array.Count - 1) {
                    last_nal = true; // last NAL in our nal_array
                }

                // The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
                // or as a Fragmented Data, split over several RTP packets with the same Timestamp.
                bool fragmenting = false;
                int packetMTU = 65500;
                if (raw_nal.Length > packetMTU) fragmenting = true;


                if (fragmenting == false)
                {
                    // Put the whole NAL into one RTP packet.
                    // Note some receivers will have maximum buffers and be unable to handle large RTP packets.
                    // Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.

                    byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
                    // Create an single RTP fragment

                    // RTP Packet Header
                    // 0 - Version, P, X, CC, M, PT and Sequence Number
                    //32 - Timestamp. H264 uses a 90kHz clock
                    //64 - SSRC
                    //96 - CSRCs (optional)
                    //nn - Extension ID and Length
                    //nn - Extension header

                    int rtp_version = 2;
                    int rtp_padding = 0;
                    int rtp_extension = 0;
                    int rtp_csrc_count = 0;
                    int rtp_marker = (last_nal == true ? 1 : 0); // set to 1 if the last NAL in the array
                    int rtp_payload_type = 96;

                    RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                    UInt32 empty_sequence_id = 0;
                    RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                    RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

                    UInt32 empty_ssrc = 0;
                    RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                    // Now append the raw NAL
                    System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);

                    rtp_packets.Add(rtp_packet);
                }
                else
                {
                    int data_remaining = raw_nal.Length;
                    int nal_pointer = 0;
                    int start_bit = 1;
                    int end_bit = 0;

                    // consume first byte of the raw_nal. It is used in the FU header
                    byte first_byte = raw_nal[0];
                    nal_pointer++;
                    data_remaining--;

                    while (data_remaining > 0)
                    {
                        int payload_size = Math.Min(packetMTU, data_remaining);
                        if (data_remaining - payload_size == 0) end_bit = 1;

                        byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload

                        // RTP Packet Header
                        // 0 - Version, P, X, CC, M, PT and Sequence Number
                        //32 - Timestamp. H264 uses a 90kHz clock
                        //64 - SSRC
                        //96 - CSRCs (optional)
                        //nn - Extension ID and Length
                        //nn - Extension header

                        int rtp_version = 2;
                        int rtp_padding = 0;
                        int rtp_extension = 0;
                        int rtp_csrc_count = 0;
                        int rtp_marker = (last_nal == true ? 1 : 0); // Marker set to 1 on last packet
                        int rtp_payload_type = 96;

                        RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                        UInt32 empty_sequence_id = 0;
                        RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                        RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

                        UInt32 empty_ssrc = 0;
                        RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                        // Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
                        byte f_bit = 0;
                        byte nri = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
                        byte type = 28; // FU-A Fragmentation

                        rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
                        rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));

                        System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
                        nal_pointer = nal_pointer + payload_size;
                        data_remaining = data_remaining - payload_size;

                        rtp_packets.Add(rtp_packet);

                        start_bit = 0;
                    }
                }
            }

            lock (rtsp_list)
            {

                // Go through each RTSP connection and output the NAL on the Video Session
                foreach (RTSPConnection connection in rtsp_list.ToArray()) // ToArray makes a temp copy of the list.
                    // This lets us delete items in the foreach
                    // eg when there is Write Error
                {
                    // Only process Sessions in Play Mode
                    if (connection.play == false) continue;

                    String connection_type = "";
                    if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP) connection_type = "TCP";
                    if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
                        && connection.video_client_transport.IsMulticast == false) connection_type = "UDP";
                    if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
                        && connection.video_client_transport.IsMulticast == true) connection_type = "Multicast";
                    Console.WriteLine("Sending video session " + connection.video_session_id + " " + connection_type + " Timestamp(ms)=" + timestamp_ms + ". RTP timestamp=" + rtp_timestamp + ". Sequence="+ connection.video_sequence_number);

                    // There could be more than 1 RTP packet (if the data is fragmented)
                    Boolean write_error = false;
                    foreach (byte[] rtp_packet in rtp_packets)
                    {
                        // Add the specific data for each transmission
                        RTPPacketUtil.WriteSequenceNumber(rtp_packet, connection.video_sequence_number);
                        connection.video_sequence_number++;

                        // Add the specific SSRC for each transmission
                        RTPPacketUtil.WriteSSRC(rtp_packet, connection.ssrc);


                        // Send as RTP over RTSP (Interleaved)
                        if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.TCP)
                        {
                            int video_channel = connection.video_transport_reply.Interleaved.First; // second is for RTCP status messages)
                            object state = new object();
                            try
                            {
                                // send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
                                //session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
                                connection.listener.SendData(video_channel, rtp_packet);
                            }
                            catch
                            {
                                Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                                write_error = true;
                                break; // exit out of foreach loop
                            }
                        }

                        // Send as RTP over UDP
                        if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.UDP && connection.video_transport_reply.IsMulticast == false)
                        {
                            try
                            {
                                // send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
                                // Send to the IP address of the Client
                                // Send to the UDP Port the Client gave us in the SETUP command
                                connection.video_udp_pair.Write_To_Data_Port(rtp_packet,connection.client_hostname,connection.video_client_transport.ClientPort.First);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine("UDP Write Exception " + e.ToString());
                                Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                                write_error = true;
                                break; // exit out of foreach loop
                            }
                        }
                    
                        // TODO. Add Multicast
                    }
                    if (write_error)
                    {
                        Console.WriteLine("Removing session " + connection.video_session_id + " due to write error");
                        connection.play = false; // stop sending data
                        if (connection.video_udp_pair != null) {
                            connection.video_udp_pair.Stop();
                            connection.video_udp_pair = null;
                        }
                        connection.listener.Dispose();
                        rtsp_list.Remove(connection); // remove the session. It is dead
                    }
                }
            }
        }

        public class RTSPConnection
        {
            public RtspListener listener = null;  // The RTSP client connection
            public bool play = false;                  // set to true when Session is in Play mode
            public DateTime time_since_last_rtsp_keepalive = DateTime.UtcNow; // Time since last RTSP message received - used to spot dead UDP clients
            public UInt32 ssrc = 0x12345678;           // SSRC value used with this client connection
            public String client_hostname = "";        // Client Hostname/IP Address

            public String video_session_id = "";             // RTSP Session ID used with this client connection
            public UInt16 video_sequence_number = 1;         // 16 bit RTP packet sequence number used with this client connection
            public RtspTransport video_client_transport; // Transport: string from the client to the server
            public RtspTransport video_transport_reply; // Transport: reply from the server to the client
            public UDPSocket video_udp_pair = null;     // Pair of UDP sockets (data and control) used when sending via UDP
            public DateTime video_time_since_last_rtcp_keepalive = DateTime.UtcNow; // Time since last RTCP message received - used to spot dead UDP clients

            // TODO - Add Audio
        }
    }
}
