//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using

using System.Collections;
using System.Threading;
using System.Net;
using OSC;

namespace Framefield.Core.IDb3adfc0a_5efe_4127_b2e3_038028022e3a
{
    public class Class_OSCReceiver : OperatorPart.Function, Framefield.Core.OperatorPartTraits.ITimeAccessor
    {
        //>>> _inputids
        private enum InputId
        {
            UDPPort = 0,
            OSCPathPrefix = 1,
            ShowLogInfo = 2
        }
        //<<< _inputids
        
        public override void Dispose()
		{
		    //Logger.Debug(this, "Class_OSCInput.Dispose");
			StopListeningOSC();
			
			base.Dispose();
		}
        
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) 
        {
            if (Changed)
            {
                //>>> _params
                var UDPPort = inputs[(int)InputId.UDPPort].Eval(context).Text;
                var OSCPathPrefix = inputs[(int)InputId.OSCPathPrefix].Eval(context).Text;
                var ShowLogInfo = (int) inputs[(int)InputId.ShowLogInfo].Eval(context).Value;
                //<<< _params
                
                // Update UDP port
                int newUDPPort;
                bool res = int.TryParse(UDPPort, out newUDPPort);
            	if (res == false)
            	{
            	    Logger.Error(this, "Eval: Wrong port string: " + UDPPort);
            	}
            	else if (_UDPPort != newUDPPort)
            	{
            	    _UDPPort = newUDPPort;
             
                	// Reinitialize the network part
    				StopListeningOSC();
    				StartListeningOSC();
    			}
    			
    			_OSCPathPrefix = OSCPathPrefix;
    			_showLogInfo = Convert.ToBoolean(ShowLogInfo); // Enum: 0 is Off, 1 is On
            
                Changed = false;
            }
            
            // Process the message queue
            lock (_messageQueue)
			{
			    //if (_debug)
			    //    Logger.Debug(this, "Eval: Processing messages: " + _messageQueue.Count);
			        
			    if (_messageQueue.Count > 0)
			    {
				    foreach (var message in _messageQueue)
					    ProcessOSCMessage(message, context);
				    _messageQueue.Clear();
				}
			}
			
            // Should always set a new value for the context, even if no messages were processed since last frame
            context.Dynamic = _frameMessages;
            return context;
        }
        
        private void StartListeningOSC()
		{
		    if (_listening == true || _OSCReceiver != null)
		        return;
            
            _OSCReceiver = new OSCReceiver(_UDPPort);
            int errorCode;
            bool success = _OSCReceiver.Connect(out errorCode);
            if (success == false)
            {
                Logger.Error(this, "StartListeningOSC: Connect to port {0} failed, error code: {1}", _UDPPort, errorCode);
                return;
            }
            
            if (_debug)
                Logger.Debug(this, "StartListeningOSC: Connect to port {0} succeded, starting listener thread", _UDPPort);
    		
    		_listening = true;
			_receiverThread = new Thread(new ThreadStart(ListenToOSC));
			_receiverThread.Start();
		}
		
		private void StopListeningOSC()
		{
			if (_receiverThread != null && _receiverThread.IsAlive)
			{
				_listening = false;
				// _OSCReceiver is blocking the thread
				// so waiting would freeze
				// shouldn't be necessary here anyway...
				//_receiverThread.Join();
			}

			if (_OSCReceiver != null)
			{
				_OSCReceiver.Close();
				if (_debug)
                    Logger.Debug(this, "StopListeningOSC: Closed UDP port");
			}
			_OSCReceiver = null;
		}
		
		private void ListenToOSC()
		{
			while (_listening)
			{
				try
				{
					OSCPacket packet = _OSCReceiver.Receive();
					if (packet != null)
					{
					    if (_debug)
					       Logger.Debug(this, "ListenToOSC: received OSC packet");
					       
						if (packet.IsBundle())
						{
							ArrayList messages = packet.Values;
							lock(_messageQueue)
							{
								for (int i = 0; i < messages.Count; ++i)
									_messageQueue.Add((OSCMessage)messages[i]);
							}
						}
						else
						{
							lock(_messageQueue)
							{
								_messageQueue.Add((OSCMessage)packet);
							}
						}
						
						if (_debug)
						    Logger.Debug(this, "ListenToOSC: number of messages in queue: " + _messageQueue.Count);
					}
				}
				catch (Exception ex)
				{
					Logger.Error(this, "ListenToOSC: exception caught: " + ex.Message);
				}
			}
		}
		
		private string messageToString(OSCMessage message)
		{
		  return Convert.ToString(message);
		}
		
		private void ProcessOSCMessage(OSCMessage message, OperatorPartContext context)
		{
            var path = message.Address.Split('/');
            
            // Ignore messages which don't have a user-defined path prefix
            if (_OSCPathPrefix != String.Empty && path[0] != _OSCPathPrefix)
            {
                if (_showLogInfo)
                    Logger.Info(this, "Message ignored: {0}", messageToString(message));
                return;
            }
            
            if (_showLogInfo)
                Logger.Info(this, "Message accepted: {0}", messageToString(message));
            
            
            // Trim the prefix from the path: let "tooll" be the prefix, then 
            // /tooll/someparam becomes /someparam
            if (_OSCPathPrefix != String.Empty)
            {
                string trimedAddress = "";
                for (int i = 1; i < path.Length; ++i)
                {
                    trimedAddress += path[i] + '/';
                }
                message.Address = trimedAddress;
            }
 
            _frameMessages[message.Address] = message;
		}
		
		// Enable/disable printing some additional very verbose debug info to Logger.Debug
		private bool _debug = true;
		
		// Input parameters
		private int _UDPPort;
		private string _OSCPathPrefix;
		private bool _showLogInfo;
        
        // OSCReceiver instance
        private OSCReceiver _OSCReceiver;
        // Whether OSCReceiver is listening to a specified port
        private bool _listening;
        // A thread where listening function lives
        private Thread _receiverThread;
        // A list of messages which haven't been processed yet
        private List<OSCMessage> _messageQueue = new List<OSCMessage>();        
        // This associative array of messages is being filled with messages from _messageQueue on each frame
        // and passed to OSCMessage* operators as Dynamic value
        private IDictionary<string, OSCMessage> _frameMessages = new Dictionary<string, OSCMessage>();
    }
}

