using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace DMX2
{
	public static partial class AlsaSeqLib
	{
		static SeqHandleWrapper seq_handle = null;
		static int clientId;
		static int inport;
		static int outport;


		public class Client
		{
			int id;
			snd_seq_client_type_t type;
			string name;
			List<Port> ports = new List<Port> ();

			public int Id {
				get {
					return id;
				}
			}

			public snd_seq_client_type_t Type {
				get {
					return type;
				}
			}

			public string Name {
				get {
					return name;
				}
			}

			public ReadOnlyCollection<Port> Ports {
				get {
					return ports.AsReadOnly ();
				}
			}

			internal Client (PointerWrapper clientInfo)
			{
				id = Invoke.snd_seq_client_info_get_client (clientInfo.Pointer);
				IntPtr nameptr = Invoke.snd_seq_client_info_get_name (clientInfo.Pointer);
				name = nameptr.PtrToString ();
				type = (snd_seq_client_type_t)Invoke.snd_seq_client_info_get_type (clientInfo.Pointer);

				using (PointerWrapper portInfo = new PointerWrapper(GetPortInfoSize ())) {
					Invoke.snd_seq_port_info_set_client (portInfo.Pointer, id);
					Invoke.snd_seq_port_info_set_port (portInfo.Pointer, -1);
					while (Invoke.snd_seq_query_next_port(seq_handle.Handle, portInfo.Pointer) >= 0) {
						ports.Add (new Port (portInfo));
					}
				}
			}
		}

		public static int ClientId {
			get {
				return clientId;
			}
		}
		public class Port
		{
			int portId;
			int clientId;
			string name;
			uint caps;
			uint type;

			public int ClientId {
				get {
					return clientId;
				}
			}

			public int PortId {
				get {
					return portId;
				}
			}

			public string Name {
				get {
					return name;
				}
			}

			public uint Caps {
				get {
					return caps;
				}
			}

			public uint Type {
				get {
					return type;
				}
			}

			internal Port (PointerWrapper portInfo)
			{
				clientId = Invoke.snd_seq_port_info_get_client (portInfo.Pointer);
				portId = Invoke.snd_seq_port_info_get_port (portInfo.Pointer);
				IntPtr namePtr = Invoke.snd_seq_port_info_get_name (portInfo.Pointer);
				caps = Invoke.snd_seq_port_info_get_capability (portInfo.Pointer);
				type = Invoke.snd_seq_port_info_get_type (portInfo.Pointer);
				name = namePtr.PtrToString ();
			}
		}

		public static void Init ()
		{
			if (seq_handle != null)
				return;

			seq_handle = new SeqHandleWrapper ();

			clientId = Invoke.snd_seq_client_id (seq_handle.Handle);

			outport = inport = Invoke.snd_seq_create_simple_port (seq_handle.Handle, 
			                                                      "dmx_ctrl",
			                                                      SND_SEQ_PORT_CAP_WRITE 
			                                                      //+ SND_SEQ_PORT_CAP_SUBS_WRITE 
			                                                      + SND_SEQ_PORT_CAP_READ
			                                                      //+ SND_SEQ_PORT_CAP_SUBS_READ
			                                                      , 
			                                                      SND_SEQ_PORT_TYPE_APPLICATION
			                                                      );

		}

		public static void Close ()
		{
			if (seq_handle == null)
				return;
			seq_handle.Dispose ();
			seq_handle = null;
		}

		public static bool GetEvent (out snd_seq_event_t ev)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			ev = new snd_seq_event_t ();
			if (Invoke.snd_seq_event_input_pending (seq_handle.Handle, 1) <= 0)
				return false;

			IntPtr evPtr;

			// Recup du pointeur vers l'ev
			Invoke.snd_seq_event_input (seq_handle.Handle, out evPtr);	

			// Copie de la zone mémoire dans une structure managee
			ev = (snd_seq_event_t)Marshal.PtrToStructure (evPtr, typeof(snd_seq_event_t)); 

			// liberation du pointeur
			Invoke.snd_seq_free_event (evPtr); 

			// TODO : Manage System Events here

			return true;

		}

		static PointerWrapper evOutPtr = new PointerWrapper (32);

		public static void SendEvent (snd_seq_event_t ev)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			ev.queue = SND_SEQ_QUEUE_DIRECT;
			ev.source.client = (byte)clientId;
			ev.source.port = (byte)outport;
			Marshal.StructureToPtr (ev, evOutPtr.Pointer, false);
			Invoke.snd_seq_event_output (seq_handle.Handle, evOutPtr.Pointer);
			Invoke.snd_seq_drain_output (seq_handle.Handle);
		}

		public static void SendEventToSubscribers (snd_seq_event_t ev)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; 
			ev.dest.port = SND_SEQ_ADDRESS_UNKNOWN;
			SendEvent (ev);
		}

		public static IEnumerable<Client> EnumClients ()
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			using (PointerWrapper clientInfo = new PointerWrapper(GetClientInfoSize())) {
				Invoke.snd_seq_client_info_set_client (clientInfo.Pointer, -1);
				while (Invoke.snd_seq_query_next_client(seq_handle.Handle,clientInfo.Pointer)>=0) {
					yield return new Client (clientInfo);			
				}
			}
		}

		public static Client GetClientByID (int client)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			using (PointerWrapper clientInfo = new PointerWrapper(GetClientInfoSize())) {
				if (Invoke.snd_seq_get_any_client_info (seq_handle.Handle, client, clientInfo.Pointer) >= 0) {
					return new Client (clientInfo);
				} else 
					return null;
			}
		}

		public static Port GetPortByIDs (int client, int port)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			using (PointerWrapper portInfo = new PointerWrapper(GetPortInfoSize ())) {
				if (Invoke.snd_seq_get_any_port_info (seq_handle.Handle, client, port, portInfo.Pointer) >= 0) {
					return new Port (portInfo);
				} else 
					return null;
			}
		}

		public static bool Connect (Port port)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			bool isInput = (port.Caps & SND_SEQ_PORT_CAP_WRITE) == SND_SEQ_PORT_CAP_WRITE;
			bool isOutput = (port.Caps & SND_SEQ_PORT_CAP_READ) == SND_SEQ_PORT_CAP_READ;

			if (isInput)
			if (!ConnectTo (port.ClientId, port.PortId))
				return false;
				   
			if (isOutput)
			if (!ConnectFrom (port.ClientId, port.PortId))
				return false;

			return true;
		}

		public static bool ConnectTo (int client, int port)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			return Invoke.snd_seq_connect_to (seq_handle.Handle, outport, client, port) == 0;
		}

		public static bool ConnectFrom (int client, int port)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			return Invoke.snd_seq_connect_from (seq_handle.Handle, inport, client, port) == 0;
		}

		public static void Deconnecte (int client, int port)
		{
			if (seq_handle == null)
				throw new InvalidOperationException ();
			snd_seq_addr_t local = new snd_seq_addr_t ();
			local.client = (byte)clientId;
			local.port = (byte)inport;

			snd_seq_addr_t addr;
			using (PointerWrapper subqueryInfo = new PointerWrapper(GetQuerySubscribeInfoSize())) 
			using (PointerWrapper localAddr =new PointerWrapper(local.SndSeqAddrToPtr())) {
				Invoke.snd_seq_query_subscribe_set_root (subqueryInfo.Pointer, localAddr.Pointer);
				Invoke.snd_seq_query_subscribe_set_type (subqueryInfo.Pointer, SND_SEQ_QUERY_SUBS_WRITE);

				for (Invoke.snd_seq_query_subscribe_set_index(subqueryInfo.Pointer,0);
				     Invoke.snd_seq_query_port_subscribers (seq_handle.Handle,subqueryInfo.Pointer)>=0;
				     Invoke.snd_seq_query_subscribe_set_index(subqueryInfo.Pointer,
						Invoke.snd_seq_query_subscribe_get_index(subqueryInfo.Pointer) +1)
				     ) {

					//root = Invoke.snd_seq_query_subscribe_get_root(subqueryInfo.Pointer).PtrToSndSeqAddr();
					addr = Invoke.snd_seq_query_subscribe_get_addr (subqueryInfo.Pointer).PtrToSndSeqAddr ();

					if (addr.client != client || addr.port != port)	
						continue;

					Invoke.snd_seq_disconnect_from (seq_handle.Handle, inport, client, port);


				}

				Invoke.snd_seq_query_subscribe_set_type (subqueryInfo.Pointer, SND_SEQ_QUERY_SUBS_READ);

				for (Invoke.snd_seq_query_subscribe_set_index(subqueryInfo.Pointer,0);
				     Invoke.snd_seq_query_port_subscribers (seq_handle.Handle,subqueryInfo.Pointer)>=0;
				     Invoke.snd_seq_query_subscribe_set_index(subqueryInfo.Pointer,
						Invoke.snd_seq_query_subscribe_get_index(subqueryInfo.Pointer) +1)
				     ) {

					//root = Invoke.snd_seq_query_subscribe_get_root(subqueryInfo.Pointer).PtrToSndSeqAddr();
					addr = Invoke.snd_seq_query_subscribe_get_addr (subqueryInfo.Pointer).PtrToSndSeqAddr ();

					if (addr.client != client || addr.port != port)	
						continue;

					Invoke.snd_seq_disconnect_to (seq_handle.Handle, outport, client, port);
				}
			}

		}


		/*
		public static void GetConnections()
		{
			using(PointerWrapper subscribeInfo = new PointerWrapper(GetQuerySubscribeInfoSize())){
				int index=0;
				Invoke.snd_seq_query_subscribe_set_client (subscribeInfo.Pointer,clientId);
				Invoke.snd_seq_query_subscribe_set_port (subscribeInfo.Pointer,inport);
				Invoke.snd_seq_query_subscribe_set_index(subscribeInfo.Pointer,index++);
				Invoke.snd_seq_query_subscribe_set_type(subscribeInfo.Pointer,SND_SEQ_QUERY_SUBS_WRITE);

				while (Invoke.snd_seq_query_port_subscribers(seq_handle.Handle,subscribeInfo.Pointer) ==0){

					//Console.WriteLine("Remote => {0}:{1}",rclient,rport);
					Invoke.snd_seq_query_subscribe_set_index(subscribeInfo.Pointer,index++);
				}
			}
		}*/
	}
}

