// GtkSharp.Generation.Signal.cs - The Signal Generatable.
//
// Author: Mike Kestner <mkestner@speakeasy.net>
//
// Copyright (c) 2001-2003 Mike Kestner
// Copyright (c) 2003-2005 Novell, Inc.
// Copyright (c) 2007 Novell, Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.


namespace GtkSharp.Generation {

	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.IO;
	using System.Xml;

	public class Signal {

		bool marshaled;
		string name;
		XmlElement elem;
		ReturnValue retval;
		Parameters parms;
		ObjectBase container_type;

		public Signal (XmlElement elem, ObjectBase container_type)
		{
			this.elem = elem;
			name = elem.GetAttribute ("name");
			marshaled = elem.GetAttribute ("manual") == "true";
			retval = new ReturnValue (elem ["return-type"]);
			parms = new Parameters (elem["parameters"], container_type.ParserVersion == 1 ? true : false);
			this.container_type = container_type;
		}

		bool Marshaled {
			get { return marshaled; }
		}

		public string Name {
			get {
				return name;
			}
			set {
				name = value;
			}
		}

		public bool Validate (LogWriter log)
		{
			log.Member = Name;
			if (Name == "") {
				log.Warn ("Nameless signal found. Add name attribute with fixup.");
				Statistics.ThrottledCount++;
				return false;
			} else if (!parms.Validate (log) || !retval.Validate (log)) {
				Statistics.ThrottledCount++;
				return false;
			}
			return true;
		}

 		public void GenerateDecl (StreamWriter sw)
 		{
			if (elem.GetAttributeAsBoolean ("new_flag") || (container_type != null && container_type.GetSignalRecursively (Name) != null))
				sw.Write("new ");

 			sw.WriteLine ("\t\tevent " + EventHandlerQualifiedName + " " + Name + ";");
		}

		public string CName {
			get {
				return "\"" + elem.GetAttribute("cname") + "\"";
			}
		}

		string CallbackSig {
			get {
				string result = "";
				for (int i = 0; i < parms.Count; i++) {
					if (i > 0)
						result += ", ";

					Parameter p = parms [i];
					if (p.PassAs != "" && !(p.Generatable is StructBase))
						result += p.PassAs + " ";
					result += (p.MarshalType + " arg" + i);
				}

				return result;
			}
		}

		string CallbackName {
			get { return Name + "SignalCallback"; }
		}

		string DelegateName {
			get { return Name + "SignalDelegate"; }
		}

		private string EventArgsName {
			get {
				if (IsEventHandler)
					return "EventArgs";
				else
					return Name + "Args";
			}
		}

		private string EventArgsQualifiedName {
			get {
				if (IsEventHandler)
					return "System.EventArgs";
				else
					return container_type.NS + "." + Name + "Args";
			}
		}

		private string EventHandlerName {
			get {
				if (IsEventHandler)
					return "EventHandler";
				else if (SymbolTable.Table [container_type.NS + Name + "Handler"] != null)
					return Name + "EventHandler";
		else
					return Name + "Handler";
			}
		}

		private string EventHandlerQualifiedName {
			get {
				if (IsEventHandler)
					return "System.EventHandler";
				else
					return container_type.NS + "." + EventHandlerName;
			}
		}

		private bool IsEventHandler {
			get {
				return retval.CSType == "void" && parms.Count == 0;
			}
		}

		private string GenArgsInitialization (StreamWriter sw, IList<Parameter> dispose_params)
		{
			if (parms.Count > 0)
				sw.WriteLine("\t\t\t\targs.Args = new object[" + parms.Count + "];");
			string finish = "";
			for (int idx = 0; idx < parms.Count; idx++) {
				Parameter p = parms [idx];
				IGeneratable igen = p.Generatable;
				if (p.PassAs != "out") {
					if (igen is ManualGen) {
						sw.WriteLine("\t\t\t\tif (arg{0} == IntPtr.Zero)", idx);
						sw.WriteLine("\t\t\t\t\targs.Args[{0}] = null;", idx);
						sw.WriteLine("\t\t\t\telse {");
						sw.WriteLine("\t\t\t\t\targs.Args[" + idx + "] = " + p.FromNative ("arg" + idx)  + ";");
						sw.WriteLine("\t\t\t\t}");
					} else if (dispose_params.Contains (p)) {
						sw.WriteLine("\t\t\t\t" + p.Name + " = " + p.FromNative ("arg" + idx)  + ";");
						sw.WriteLine("\t\t\t\targs.Args[" + idx + "] = " + p.Name  + ";");
					} else {
						sw.WriteLine("\t\t\t\targs.Args[" + idx + "] = " + p.FromNative ("arg" + idx)  + ";");
					}
				}
				if ((igen is StructBase || igen is ByRefGen) && p.PassAs != "")
					finish += "\t\t\t\tif (arg" + idx + " != IntPtr.Zero) System.Runtime.InteropServices.Marshal.StructureToPtr (args.Args[" + idx + "], arg" + idx + ", false);\n";
				else if (igen is IManualMarshaler && p.PassAs != "")
					finish += String.Format ("\t\t\t\targ{0} = {1};\n", idx, (igen as IManualMarshaler).AllocNative ("args.Args[" + idx + "]"));
				else if (p.PassAs != "")
					finish += "\t\t\t\targ" + idx + " = " + igen.CallByName ("((" + p.CSType + ")args.Args[" + idx + "])") + ";\n";
			}
			return finish;
		}

		private void GenArgsCleanup (StreamWriter sw, string finish)
		{
			if (retval.IsVoid && finish.Length == 0)
				return;

			sw.WriteLine("\n\t\t\ttry {");
			sw.Write (finish);
			if (!retval.IsVoid) {
				if (retval.CSType == "bool") {
					sw.WriteLine ("\t\t\t\tif (args.RetVal == null)");
					sw.WriteLine ("\t\t\t\t\treturn false;");
				}
				sw.WriteLine ("\t\t\t\treturn {0};", retval.ToNative (String.Format ("(({0}) args.RetVal)", retval.CSType)));
			}
			sw.WriteLine("\t\t\t} catch (Exception) {");
			sw.WriteLine ("\t\t\t\tException ex = new Exception (\"args.RetVal or 'out' property unset or set to incorrect type in " + EventHandlerQualifiedName + " callback\");");
			sw.WriteLine("\t\t\t\tGLib.ExceptionManager.RaiseUnhandledException (ex, true);");
			
			sw.WriteLine ("\t\t\t\t// NOTREACHED: above call doesn't return.");
			sw.WriteLine ("\t\t\t\tthrow ex;");
			sw.WriteLine("\t\t\t}");
		}

		public void GenCallback (StreamWriter sw)
		{
			if (IsEventHandler)
				return;

			IList<Parameter> dispose_params = new List<Parameter> ();
			foreach (Parameter p in parms) {
				if (p.IsOwnable) {
					dispose_params.Add (p);
				}
			}

			string native_signature = "IntPtr inst";
			if (parms.Count > 0)
				native_signature += ", " + CallbackSig;
			native_signature += ", IntPtr gch";

			sw.WriteLine ("\t\t[UnmanagedFunctionPointer (CallingConvention.Cdecl)]");
			sw.WriteLine ("\t\tdelegate {0} {1} ({2});", retval.ToNativeType, DelegateName, native_signature);
			sw.WriteLine ();
			sw.WriteLine ("\t\tstatic {0} {1} ({2})", retval.ToNativeType, CallbackName, native_signature);
			sw.WriteLine("\t\t{");
			sw.WriteLine("\t\t\t{0} args = new {0} ();", EventArgsQualifiedName);
			foreach (Parameter p in dispose_params) {
				sw.WriteLine("\t\t\t{0} {1} = null;", p.CSType, p.Name);
			}
			sw.WriteLine("\t\t\ttry {");
			sw.WriteLine("\t\t\t\tGLib.Signal sig = ((GCHandle) gch).Target as GLib.Signal;");
			sw.WriteLine("\t\t\t\tif (sig == null)");
			sw.WriteLine("\t\t\t\t\tthrow new Exception(\"Unknown signal GC handle received \" + gch);");
			sw.WriteLine();
			string finish = GenArgsInitialization (sw, dispose_params);
			sw.WriteLine("\t\t\t\t{0} handler = ({0}) sig.Handler;", EventHandlerQualifiedName);
			sw.WriteLine("\t\t\t\thandler (GLib.Object.GetObject (inst), args);");
			sw.WriteLine("\t\t\t} catch (Exception e) {");
			sw.WriteLine("\t\t\t\tGLib.ExceptionManager.RaiseUnhandledException (e, false);");
			if (dispose_params.Count > 0) {
				sw.WriteLine ("\t\t\t} finally {");
				foreach (Parameter p in dispose_params) {
					string disp_name = "disposable_" + p.Name;

					sw.WriteLine ("\t\t\t\tvar " + disp_name + " = " + p.Name + " as IDisposable;");
					sw.WriteLine ("\t\t\t\tif (" + disp_name + " != null)");
					sw.WriteLine ("\t\t\t\t\t" + disp_name + ".Dispose ();");
				}
			}
			sw.WriteLine ("\t\t\t}");
			GenArgsCleanup (sw, finish);
			sw.WriteLine("\t\t}");
			sw.WriteLine();
		}

		private bool NeedNew (ObjectBase implementor)
		{
			return elem.GetAttributeAsBoolean ("new_flag") ||
				(container_type != null && container_type.GetSignalRecursively (Name) != null) ||
				(implementor != null && implementor.GetSignalRecursively (Name) != null);
		}

		public void GenEventHandler (GenerationInfo gen_info)
		{
			if (IsEventHandler)
				return;

			string ns = container_type.NS;

			StreamWriter sw = gen_info.OpenStream (EventHandlerName, container_type.NS);
			
			sw.WriteLine ("namespace " + ns + " {");
			sw.WriteLine ();
			sw.WriteLine ("\tusing System;");

			sw.WriteLine ();
			sw.WriteLine ("\tpublic delegate void " + EventHandlerName + "(object o, " + EventArgsName + " args);");
			sw.WriteLine ();
			sw.WriteLine ("\tpublic class " + EventArgsName + " : GLib.SignalArgs {");
			for (int i = 0; i < parms.Count; i++) {
				sw.WriteLine ("\t\tpublic " + parms[i].CSType + " " + parms[i].StudlyName + "{");
				if (parms[i].PassAs != "out") {
					sw.WriteLine ("\t\t\tget {");
					if (SymbolTable.Table.IsInterface (parms [i].CType)) {
						var igen = SymbolTable.Table.GetInterfaceGen (parms [i].CType);
						sw.WriteLine ("\t\t\t\treturn {0}.GetObject (Args [{1}] as GLib.Object);", igen.QualifiedAdapterName, i);
					} else {
						sw.WriteLine ("\t\t\t\treturn ({0}) Args [{1}];", parms [i].CSType, i);
					}
					sw.WriteLine ("\t\t\t}");
				}
				if (parms[i].PassAs != "") {
					sw.WriteLine ("\t\t\tset {");
					if (SymbolTable.Table.IsInterface (parms [i].CType)) {
						var igen = SymbolTable.Table.GetInterfaceGen (parms [i].CType);
						sw.WriteLine ("\t\t\t\tArgs [{0}] = value is {1} ? (value as {1}).Implementor : value;", i, igen.AdapterName);
					} else {
						sw.WriteLine ("\t\t\t\tArgs[" + i + "] = (" + parms [i].CSType + ")value;");
					}
					sw.WriteLine ("\t\t\t}");
				}
				sw.WriteLine ("\t\t}");
				sw.WriteLine ();
			}
			sw.WriteLine ("\t}");
			sw.WriteLine ("}");
			sw.Close ();
		}

		public void GenEvent (StreamWriter sw, ObjectBase implementor, string target)
		{
			string args_type = IsEventHandler ? "" : ", typeof (" + EventArgsQualifiedName + ")";
			
			if (Marshaled) {
				GenCallback (sw);
				args_type = ", new " + DelegateName + "(" + CallbackName + ")";
			}

			sw.WriteLine("\t\t[GLib.Signal("+ CName + ")]");
			sw.Write("\t\tpublic ");
			if (NeedNew (implementor))
				sw.Write("new ");
			sw.WriteLine("event " + EventHandlerQualifiedName + " " + Name + " {");
			sw.WriteLine("\t\t\tadd {");
			sw.WriteLine("\t\t\t\t{0}.AddSignalHandler ({1}, value{2});", target, CName, args_type);
			sw.WriteLine("\t\t\t}");
			sw.WriteLine("\t\t\tremove {");
			sw.WriteLine("\t\t\t\t{0}.RemoveSignalHandler ({1}, value);", target, CName);
			sw.WriteLine("\t\t\t}");
			sw.WriteLine("\t\t}");
			sw.WriteLine();
		}

		public void Generate (GenerationInfo gen_info, ObjectBase implementor)
		{
			StreamWriter sw = gen_info.Writer;

			if (implementor == null)
				GenEventHandler (gen_info);

			GenEvent (sw, implementor, "this");
			
			Statistics.SignalCount++;
		}
	}
}