namespace Binge.Bits
{
	using System;
	using System.Collections;
	using System.Collections.Specialized;
	using System.Diagnostics;
	using System.IO;
	using System.Text;
	using System.Threading;
	
	public interface IMangler
	{
		void Load (string file);
		string Mangle (string sym);
		string Mangle (MethodBase method);
	}

	public class GnuMangler: Binge.Bits.Object, IMangler
	{
		public string NmPath;
		public string CppFiltPath;

		private Hashtable unmangled;

		private Process nm;
		private Process cppfilt;

		public GnuMangler (string nm, string cppfilt)
		{
			unmangled = new Hashtable ();
			NmPath = nm;
			CppFiltPath = cppfilt;
		}

		public GnuMangler (): this ("nm", "c++filt") {}

		public void Load (string file)
		{
			ProcessStartInfo psi = new ProcessStartInfo ();
			psi.FileName = NmPath;
			psi.Arguments = "--extern-only --defined-only -f posix " + file;
#if ! ROTOR
			psi.CreateNoWindow = true;
			psi.ErrorDialog = false;
#endif
			psi.RedirectStandardInput = true;
			psi.RedirectStandardOutput = true;
			//psi.RedirectStandardError = true;

			nm = new Process ();
			nm.StartInfo = psi;

			psi = new ProcessStartInfo ();
			psi.FileName = CppFiltPath;
#if ! ROTOR
			psi.CreateNoWindow = true;
			psi.ErrorDialog = false;
#endif
			psi.RedirectStandardInput = true;
			psi.RedirectStandardOutput = true;
			//psi.RedirectStandardError = true;

			cppfilt = new Process ();
			cppfilt.StartInfo = psi;

			nm.Start ();
			cppfilt.Start ();

			// FIXME This doesn't work
			if (nm.HasExited || cppfilt.HasExited)
			{
				if (nm.HasExited)
					Errl ("Warning: nm exited prematurely");
				else
					nm.Kill ();

				if (cppfilt.HasExited)
					Errl ("Warning: c++filt exited prematurely");
				else
					cppfilt.Kill ();

				nm = cppfilt = null;

				return;
			}

			StreamReader nmOut = nm.StandardOutput;
			StreamWriter cfIn = cppfilt.StandardInput;
			StreamReader cfOut = cppfilt.StandardOutput;

			for (string line = nmOut.ReadLine (); line != string.Empty; line = nmOut.ReadLine ())
			{
				string mang = line.Split (new char[] { ' ' })[0];

				if (mang.LastIndexOfAny (new char[] { '[', '.', ']' }) != -1)
					continue;

				Outl ("Mang: {0}", mang);

				cfIn.WriteLine (mang);

				string unmang = cfOut.ReadLine ();

				Outl ("Unmang: {0}", unmang);

				unmangled[unmang] = mang;
			}

			nm.Kill ();
			cppfilt.Kill ();

			nm = cppfilt = null;
		}

		public string Mangle (string sym)
		{
			return unmangled.Contains (sym) ? unmangled[sym] as string : String.Empty;
		}

		public string Mangle (MethodBase method)
		{
			StringBuilder s = new StringBuilder ();

			// FIXME Check namespace support.
			s.Append (method.Parent.NativeName + "::" + method.NativeName + "(");

			for (int i=0; i< method.Parameters.Count; i++)
			{
				Parameter param = method.Parameters[i] as Parameter;
				s.Append (param.NativeName);

				if (i != method.Parameters.Count - 1)
					s.Append (", ");
				else
					s.Append (")");
			}

			return Mangle (s.ToString ());
		}
	}
}
