using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using Gtk;
using Mono.Posix;


namespace Tomboy
{
	// ------------ Shared Data
	public class NetworkData : MarshalByRefObject
	{
		[Serializable]
		public class SerNote {
			public string title;
			public string xml;
			public DateTime date;

			public SerNote (string title, string xml, DateTime date)
			{
				this.title = title;
				this.xml = xml;
				this.date = date;
			}
		}

		private NoteManager manager;

		public NetworkData (NoteManager manager)
		{
			this.manager = manager;
		}

		public bool Connected ()
		{
			return true;
		}

		public ArrayList Notes ()
		{
			ArrayList tmp = new ArrayList ();
			foreach (Note note in manager.Notes)
				tmp.Add (new SerNote (note.Title, note.XmlContent, note.ChangeDate));
			return tmp;
		}

		// unused
		public ArrayList Titles ()
		{
			ArrayList tmp = new ArrayList ();
			foreach (Note note in manager.Notes)
				tmp.Add (note.Title);
			return tmp;
		}

		// unused
		public bool PutNote (SerNote note, bool overwrite)
		{
			// create or overwrite
			Note local_note = manager.Find (note.title);
			if (local_note != null && !overwrite)
				return false;

			if (local_note !=null)
			{
				local_note.XmlContent = note.xml;
				return true;
			}

			manager.Create (note.title, note.xml);
			return true;
		}

		// unused
		public SerNote GetNote (string title)
		{
			SerNote note = null;
			Note local_note = manager.Find (title);
			if (local_note == null)
				return note;

			note = new SerNote (local_note.Title, local_note.XmlContent, 
					local_note.ChangeDate);
			return note;
		}

		// Fixes crash on exit...
		public override System.Object InitializeLifetimeService()
		{
			return null;
		}
	}

	// ------------ Client
	public class NetworkConnectDialog : Dialog		
	{
		Button connectButton;
		Entry remoteHost, remotePort;
		static Gdk.Pixbuf tintin;

		NoteManager manager;
		NetworkData data;
		TcpChannel channel;

		static NetworkConnectDialog  ()
		{
			tintin = new Gdk.Pixbuf (null, "tintin.png");
		}

		public NetworkConnectDialog  (NoteManager manager) : base ()
		{
			Icon = tintin;
			Title = Catalog.GetString ("Tomboy Network");
			Modal = true;

			this.manager = manager;

			Preferences.SettingChanged += OnNetworkSettingChanged;

			MakeLayout ();

			remoteHost.Text = GetHost();
			remotePort.Text = GetPort();
		}

		public void MakeLayout ()
		{
			Gtk.Label label;

			VBox.Spacing = 5;
			ActionArea.Layout = Gtk.ButtonBoxStyle.End;

			// Settings Table
			Gtk.Table table = new Gtk.Table (2, 2, false);
			table.ColumnSpacing = 6;
			table.RowSpacing = 6;

			label = new Label (Catalog.GetString ("Remote Host"));
			label.Show ();
			table.Attach (label, 0, 1, 0, 1);

			// FIXME as a ComboBox, filled by avahi
			remoteHost = new Entry ();
			remoteHost.Show ();
			table.Attach (remoteHost, 1, 2, 0, 1);

			label = new Label (Catalog.GetString ("Remote Port"));
			label.Show ();
			table.Attach (label, 0, 1, 1, 2);

			remotePort = new Entry ();
			remotePort.Show ();
			table.Attach (remotePort, 1, 2, 1, 2);

			table.Show ();
			VBox.Add (table);

			// Connect&Sync Button
			connectButton = new Button ();
			connectButton.Label = Catalog.GetString("Connect");
                        connectButton.CanDefault = true;
			connectButton.Show ();
			connectButton.Clicked += OnConnectRemoteClicked;
			VBox.Add (connectButton);

                        // Ok button...
                        Gtk.Button button = new Gtk.Button (Gtk.Stock.Close);
                        button.CanDefault = true;
                        button.Show ();

                        Gtk.AccelGroup accel_group = new Gtk.AccelGroup ();
                        AddAccelGroup (accel_group);

                        button.AddAccelerator ("activate",
                                               accel_group,
                                               (uint) Gdk.Key.Escape,
                                               0,
                                               0);

                        AddActionWidget (button, Gtk.ResponseType.Close);
                        DefaultResponse = Gtk.ResponseType.Close;
		}

		void OnNetworkSettingChanged (object sender, GConf.NotifyEventArgs args)
		{
			switch (args.Key) {
			case Preferences.NETWORK_REMOTE_HOST:
				remoteHost.Text = GetHost();
				break;

			case Preferences.NETWORK_REMOTE_PORT:
				remotePort.Text = GetPort ();
				break;
			}
		}

		private string GetHost ()
		{
			string host = (string) Preferences.Get (Preferences.NETWORK_REMOTE_HOST);
			if (host == null)
				host = (string) Preferences.GetDefault ("NETWORK_REMOTE_HOST");
			return host;
		}

		private string GetPort ()
		{
			string port_str = (string) Preferences.Get (Preferences.NETWORK_REMOTE_PORT);
			if (port_str == null || Int32.Parse (port_str) < 1024)
				port_str = (string) Preferences.GetDefault ("NETWORK_REMOTE_PORT");
			return port_str;
		}

		public void OnConnectRemoteClicked (object obj, System.EventArgs args)
		{
			string hostname = remoteHost.Text;
			string port = remotePort.Text;

			if (hostname == "" || port == "")
				return;

			Console.WriteLine ("Connecting {0}:{1}", 
					hostname, port);

                        IDictionary props = new Hashtable();
                        props["name"] = "tomboy.rem";
                        props["port"] = 1236;
                        channel = new TcpChannel(props, null, null);
			try {
                        	ChannelServices.RegisterChannel (channel);
			} catch {
				Console.WriteLine ("Channel registration error.");
				return;
			}
                        object remOb = Activator.GetObject (
					typeof(NetworkData),
					String.Format("tcp://{0}:{1}/tomboy.rem", 
						hostname, port)
					);
                        data = (NetworkData)remOb;
			
			try {
				bool test = data.Connected ();
			} catch {
				// FIXME show error dialog
	                        ChannelServices.UnregisterChannel (channel);
				Console.WriteLine ("Connection failed.");
				return;
			}

			Preferences.Set (Preferences.NETWORK_REMOTE_HOST, remoteHost.Text);
			Preferences.Set (Preferences.NETWORK_REMOTE_PORT, remotePort.Text);
			ShowSyncWindow ();
			Destroy();
		}

		public void ShowSyncWindow ()
		{
			Gtk.Window sync_window = new NetworkSyncWindow (manager, data);
			sync_window.Destroyed += OnSyncWindowClosed;
			sync_window.Show();
		}

		public void OnSyncWindowClosed (object obj, System.EventArgs args)
		{
			Console.WriteLine ("Connection closed.");
                        ChannelServices.UnregisterChannel (channel);
		}

	}

	// ------------ Server
	public class NetworkState : EventArgs 
	{ 
		private bool listen;
		public bool Listen { get { return listen; } }
		public NetworkState (bool listen) : base ()
		{
			this.listen = listen;
		}
	}

	public class NetworkServer
	{
		private int listenPort;
		private TcpChannel channel;

		NoteManager manager;
	
		private NetworkData data;
		public NetworkData Data { 
			get { return data; } 
			set { data = value; }
		}

		public delegate void NetworkEventHandler (object sender, NetworkState args);
		public event NetworkEventHandler ListenStateChanged;

		public NetworkServer (NoteManager manager)
		{
			this.manager = manager;
			listenPort = GetPort ();

			data = new NetworkData (manager);
			RemotingServices.Marshal (data, "tomboy.rem");

		}

		public void Setup ()
		{
			if ((bool) Preferences.Get (Preferences.ENABLE_NETWORK_SERVER)) 
				Listen ();

			Preferences.SettingChanged += OnNetworkSettingChanged;
		}

		private int GetPort ()
		{
			string port_str = (string) Preferences.Get (Preferences.NETWORK_LISTEN_PORT);
			if (port_str == null || Int32.Parse (port_str) < 1024)
				port_str = (string) Preferences.GetDefault ("NETWORK_LISTEN_PORT");
			return Int32.Parse (port_str);
		}

		public void Listen ()
		{
			if (channel != null)
				return;

			try {
				channel = new TcpChannel(listenPort);
				ChannelServices.RegisterChannel (channel);
			} catch {
				Console.WriteLine ("Channel already in use.");
				channel = null;
				return;
			}

			if (ListenStateChanged != null)
				ListenStateChanged (this, new NetworkState (true));

			Console.WriteLine ("Tomboy tcp network enabled, " + 
					"listening for incoming connections on port {0}", 
					listenPort.ToString());
		}

		public void UnListen ()
		{
			if (channel == null)
				return;

			channel.StopListening (null);
			ChannelServices.UnregisterChannel(channel);
			channel = null;

			if (ListenStateChanged != null)
				ListenStateChanged (this, new NetworkState (false));
			Console.WriteLine ("Tomboy tcp network disabled");
		}

		public void Shutdown ()
		{
			RemotingServices.Disconnect(data);
			if (channel != null)
				ChannelServices.UnregisterChannel(channel);
			Console.WriteLine("Network server shutdown.");
		}

		void OnNetworkSettingChanged (object sender, GConf.NotifyEventArgs args)
		{
			switch (args.Key) {
			case Preferences.ENABLE_NETWORK_SERVER:
				if ((bool) Preferences.Get (Preferences.ENABLE_NETWORK_SERVER)) 
					Listen ();
				else
					UnListen ();
				break;

			case Preferences.NETWORK_LISTEN_PORT:
				listenPort = GetPort ();
				if ((bool) Preferences.Get (Preferences.ENABLE_NETWORK_SERVER)) 
				{
					UnListen ();
					Listen ();
				}
				break;
			}
		}
	}

#if ENABLE_AVAHI		
	// ------------ Avahi Announcer
	public class AvahiServer
	{
		Avahi.Client client;
		private Avahi.EntryGroup eg;

		public AvahiServer (Avahi.Client client)
		{
			this.client = client;

			Preferences.SettingChanged += OnAvahiSettingChanged;
		}

		public void Start ()
		{
			if ((bool) Preferences.Get (Preferences.ENABLE_AVAHI_SERVER) && 
					(bool) Preferences.Get (Preferences.ENABLE_AVAHI_SERVER)) 
				RegisterService ();
		}

		public void Stop ()
		{
			UnregisterService ();
		}

		private int GetPort ()
		{
			string port_str = (string) Preferences.Get (Preferences.NETWORK_LISTEN_PORT);
			if (port_str == null || Int32.Parse (port_str) < 1024)
				port_str = (string) Preferences.GetDefault ("NETWORK_LISTEN_PORT");
			return Int32.Parse (port_str);
		}

		private void RegisterService () 
		{
			if (eg != null)
				eg.Reset ();
			else
				eg = new Avahi.EntryGroup (client);

			try {
				eg.AddService (
						String.Format ("Tomboy Notes from {0}", 
							System.Environment.UserName),
						"_tomboy._tcp", "", 
						(ushort) GetPort (),
						new string[] { 
							"Machine Name=Tomboy Note Share", 
							"Password=false", 
							"txtvers=1" 
						});
				eg.Commit ();
				Console.WriteLine ("Started Avahi announcer.");
			} catch (Exception e) {
				// already registered similiar mdns
				Console.WriteLine ("Avahi Exception: {0}", e.ToString());
			}
		}

		private void UnregisterService () 
		{
			if (eg == null)
				return;

			eg.Reset ();
			eg.Dispose ();
			eg = null;
			Console.WriteLine ("Stopped Avahi announcer.");
		}

		void OnAvahiSettingChanged (object sender, GConf.NotifyEventArgs args)
		{
			if (args.Key == Preferences.ENABLE_AVAHI_SERVER)
				if ((bool) Preferences.Get (Preferences.ENABLE_AVAHI_SERVER)) 
					RegisterService ();
				else
					UnregisterService ();
		}
	}
#endif // ENABLE_AVAHI		

#if ENABLE_AVAHI		
	// ------------ Avahi Service Browser
	public class AvahiBrowser
	{
		Avahi.Client client;
		Avahi.ServiceBrowser browser;
		Avahi.ServiceResolver resolver;

		public event Avahi.ServiceInfoHandler ServiceFound;

		public AvahiBrowser (Avahi.Client client)
		{
			this.client = client;
			this.browser = new Avahi.ServiceBrowser (client, "_tomboy._tcp");
			browser.ServiceAdded += OnServiceAdded;
		}

		public void OnServiceAdded (object o, Avahi.ServiceInfoArgs args)
		{
			if (args.Service.Protocol != Avahi.Protocol.IPv4)
				return;
			// FIXME still receiving own anouncement
			if (args.Service.Flags != Avahi.LookupResultFlags.OurOwn)
			{
				resolver = new Avahi.ServiceResolver (client, args.Service);
				resolver.Found += OnFound;
			}
		}

		public void OnFound (object o, Avahi.ServiceInfoArgs args)
		{
			ServiceFound(this, args);
			Avahi.ServiceResolver r = o as Avahi.ServiceResolver;
			r.Dispose();
		}
	}
#endif // ENABLE_AVAHI		
}
