//
// file:	ictool.cs
// author:	Dan Lewis (dihlewis@yahoo.co.uk)
// 		(C) 2002
//
// description:
//
// Tool for generating C prototypes and structures suitable for use by the runtime
// from a list of supplied assemblies. See ictool-config.xml for configuration details.
//

using System;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Collections;

public class ICTool {
	public static void Main (string[] args) {
		string filename = "ictool-config.xml";
		if (args.Length == 1) {
			filename = args[0];
		}
		else if (args.Length > 1) {
			Console.Error.WriteLine ("Usage: ictool.exe [config.xml]");
			Environment.Exit (-1);
		}

		try {
			Stream config = File.OpenRead (filename);
			Configure (config);
		}
		catch (Exception e) {
			Console.Error.WriteLine ("Error: could not read configuration file.");
			Console.Error.WriteLine (e);
			Environment.Exit (-1);
		}

		EmitPrototypes ();
		EmitStructures ();
	}

	// private

	private static void EmitPrototypes () {
		StreamWriter methods_file = GetOutputFile ("methods");
		StreamWriter map_file = GetOutputFile ("map");

		// includes

		methods_file.WriteLine ("#include \"{0}\"\n", output_files["types"]);
		map_file.WriteLine ("#include \"{0}\"\n", output_files["methods"]);

		map_file.Write (
			"static gpointer icall_map [] = {\n\t"
		);

		ArrayList map_lines = new ArrayList ();

		BindingFlags binding =
			BindingFlags.DeclaredOnly |
			BindingFlags.Instance |
			BindingFlags.Static |
			BindingFlags.Public |
			BindingFlags.NonPublic;

		foreach (Type type in types.Values) {
			bool has_icall = false;
			MethodInfo[] methods = type.GetMethods (binding);

			foreach (MethodInfo method in methods) {
				if (IsInternalCall (method)) {
					has_icall = true;
					break;
				}
			}

			if (!has_icall)
				continue;

			methods_file.WriteLine ("\n/* {0} */\n", type.FullName);
			//map_lines.Add (String.Format ("\n/* {0} */\n", type.FullName));
			
			foreach (MethodInfo method in methods) {
				if (!IsInternalCall (method))
					continue;

				// function name

				string func_name = String.Format ("ves_icall_{0}_{1}",
					
					type.FullName,
					method.Name
				);

				func_name = func_name.Replace ('.', '_');

				// map file

				map_lines.Add (String.Format (
					"\"{0}::{1}\", {2}",

					type.FullName.Replace ('.', '_'),
					method.Name,
					func_name
				));

				// methods file

				ArrayList args = new ArrayList ();

				// FIXME: return types that are structs need to be inserted
				// into the argument list as a destination pointer

				// object/value instance pointer
				
				if (IsInstanceMethod (method)) {
					args.Add (String.Format (
						"{0}{1}",

						peer_map.GetPeer (method.DeclaringType).GetTypedef (1),
						"this"
					));
				}

				// arguments

				foreach (ParameterInfo param in method.GetParameters ()) {
					Type arg_type = param.ParameterType;

					int refs = 0;
					if (arg_type.IsByRef) {
						arg_type = arg_type.GetElementType ();
						++ refs;
					}

					Peer arg_peer = peer_map.GetPeer (arg_type);
					if (!arg_peer.IsValueType)
						++ refs;

					args.Add (String.Format ("{0}{1}", arg_peer.GetTypedef (refs), param.Name));
				}

				Peer ret = peer_map.GetPeer (method.ReturnType);
				methods_file.WriteLine ("static {0}", ret.GetTypedef (ret.IsValueType ? 0 : 1));
				methods_file.WriteLine ("{0} ({1});",
					
					func_name,
					Join (", ", args)
				);
				methods_file.WriteLine ();
			}

		}

		methods_file.Close ();

		// write map file and close it

		map_file.Write (
			"{0}\n}};\n", Join (",\n\t", map_lines)
		);

		map_file.Close ();
	}

	private static bool IsInternalCall (MethodInfo meth) {
		return (meth.GetMethodImplementationFlags () & MethodImplAttributes.InternalCall) != 0;
	}

	private static bool IsInstanceMethod (MethodInfo meth) {
		return (meth.CallingConvention & CallingConventions.HasThis) != 0;
	}

	private static void EmitStructures () {
		StreamWriter file = GetOutputFile ("types");

		// build dependency graph
		
		DependencyGraph dg = new DependencyGraph ();
		foreach (Peer peer in peer_map.Peers) {
			dg.AddNode (peer);

			// peer depends on nearest base

			if (peer.NearestBase != null)
				dg.AddEdge (peer.NearestBase, peer);

			// peer depends on any value types used for fields

			foreach (PeerField field in peer.Fields) {
				if (field.Peer.IsValueType)
					dg.AddEdge (field.Peer, peer);
			}
		}

		// write structures in order

		foreach (Peer peer in dg.TopologicalSort ()) {
			if (peer.IsOpaque)
				continue;

			if (peer.IsEnum) {
				file.WriteLine ("typedef {0} {1};", peer.UnderlyingPeer.Name, peer.Name);
				file.WriteLine ("enum _{0} {{", peer.Name);

				ArrayList enum_lines = new ArrayList ();
				foreach (string name in peer.EnumConstants.Keys) {
					enum_lines.Add (String.Format ("\t{0}_{1} = {2}",
						peer.Name,
						name,
						peer.EnumConstants[name]
					));
				}
				
				file.WriteLine ("{0}\n}};\n", Join (",\n", enum_lines));
			}
			else {
				file.WriteLine ("typedef struct _{0} {{", peer.Name);

				// base type
				
				if (peer.NearestBase != null) {
					file.WriteLine ("\t{0} __base;", peer.NearestBase.Name);
					file.WriteLine ();
				}

				// fields
				
				foreach (PeerField field in peer.Fields) {
					bool use_struct = true;
					if (field.Peer.IsValueType || field.Peer.IsOpaque)
						use_struct = false;
				
					file.WriteLine ("\t{0}{1}{2};",
						use_struct ? "struct _" : "",
						field.Peer.GetTypedef (field.Peer.IsValueType ? 0 : 1),
						field.Name
					);
				}

				file.WriteLine ("}} {0};\n", peer.Name);
			}
		}
	}

	private static void LoadAssemblies () {
		types = new Hashtable ();

		foreach (string filename in assemblies) {
			Assembly assembly;

			// find assembly

			FileInfo info = null;
			foreach (string path in assembly_paths) {
				info = new FileInfo (Path.Combine (path, filename));
				if (info.Exists)
					break;
			}

			if (!info.Exists) {
				Console.Error.WriteLine ("Error: assembly {0} not found.", filename);
				Environment.Exit (-1);
			}

			// load assembly

			assembly = Assembly.LoadFrom (info.FullName);

			// load types

			ArrayList loaded_types;
			
			try {
				loaded_types = new ArrayList (assembly.GetTypes ());
			}
			catch (ReflectionTypeLoadException e) {
				loaded_types = new ArrayList ();
				foreach (Type type in e.Types) {
					if (type != null)
						loaded_types.Add (type);
				}

				foreach (Exception f in e.LoaderExceptions) {
					if (f is TypeLoadException) {
						Console.Error.WriteLine ("Warning: {0} could not be loaded from assembly {1}.",
							((TypeLoadException)f).TypeName,
							filename
						);
					}
					else
						Console.Error.WriteLine (f);
				}
			}

			// add to type dictionary

			foreach (Type type in loaded_types) {
				if (!types.Contains (type.FullName))
					types.Add (type.FullName, type);
			}
		}
	}

	private static void Configure (Stream input) {
		XmlDocument doc = new XmlDocument ();
		doc.Load (input);

		// assemblies

		assembly_paths = new ArrayList ();
		assembly_paths.Add (".");

		foreach (XmlNode node in doc.SelectNodes ("config/assemblypath")) {
			assembly_paths.Add (node.Attributes["path"].Value);
		}

		assemblies = new ArrayList ();
		foreach (XmlNode node in doc.SelectNodes ("config/assembly")) {
			assemblies.Add (node.Attributes["file"].Value);
		}

		LoadAssemblies ();

		// outputfiles

		output_path = ".";
		XmlNode path_node = doc.SelectSingleNode ("config/outputpath");
		if (path_node != null)
			output_path = path_node.Attributes["path"].Value;

		output_files = new Hashtable ();
		output_includes = new Hashtable ();
		foreach (XmlNode node in doc.SelectNodes ("config/outputfile")) {
			string name = node.Attributes["name"].Value;
			output_files.Add (name, node.Attributes["file"].Value);

			foreach (XmlNode child in node.ChildNodes) {
				if (child.Name == "include")
					output_includes[name] = child.InnerText;
			}
		}

		// typemap

		peer_map = new PeerMap ();
		foreach (XmlNode node in doc.SelectNodes ("config/typemap/namespace")) {
			string ns = node.Attributes["name"].Value;

			foreach (XmlNode child in node.ChildNodes) {
				if (child.Name == "type") {
					string name = child.Attributes["name"].Value;
					string peer_name = child.Attributes["peer"].Value;

					bool opaque = false;
					if (child.Attributes["opaque"] != null && child.Attributes["opaque"].Value == "true")
						opaque = true;

					String fullname = String.Format ("{0}.{1}", ns, name);
					
					Type type;
					if (child.Attributes["default"] != null && child.Attributes["default"].Value == "true")
						type = Type.GetType (fullname);
					else
						type = (Type)types [fullname];

					if (type != null)
						peer_map.Add (new Peer (type, peer_name, opaque));
				}
			}
		}

		peer_map.ResolvePeers ();
	}

	private static StreamWriter GetOutputFile (string name) {
		string filename = Path.Combine (output_path, (string)output_files[name]);
		StreamWriter file = File.CreateText (filename);
		file.AutoFlush = true;

		file.Write (

// (verbatim string)
		
@"/**
 *  {0}
 *
 *  This file was automatically generated on {1} by ictool.exe from
 *  the following assemblies:
 *    {2}
 */
 
",

			output_files[name],
			DateTime.Now.ToString ("d"),
			Join (", ", assemblies)
		);

		if (output_includes.Contains (name)) {
			file.WriteLine (output_includes [name]);
			file.WriteLine ();
		}

		return file;
	}

	private static string Join (string separator, ICollection values) {
		// note to microsoft: please implement this in String :)

		string[] strs = new string[values.Count];

		int i = 0;
		foreach (object value in values)
			strs[i ++] = value.ToString ();

		return String.Join (separator, strs);
	}
	
	private static ArrayList assembly_paths;
	private static ArrayList assemblies;
	private static string output_path;
	private static Hashtable output_files;
	private static Hashtable output_includes;
	private static PeerMap peer_map;
	private static Hashtable types;
}
