using System;
using System.IO;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Mono.Cecil.Tests {

	public static class Formatter {

		public static string FormatInstruction (Instruction instruction)
		{
			var writer = new StringWriter ();
			WriteInstruction (writer, instruction);
			return writer.ToString ();
		}

		public static string FormatMethodBody (MethodDefinition method)
		{
			var writer = new StringWriter ();
			WriteMethodBody (writer, method);
			return writer.ToString ();
		}

		public static void WriteMethodBody (TextWriter writer, MethodDefinition method)
		{
			var body = method.Body;

			WriteVariables (writer, body);

			foreach (Instruction instruction in body.Instructions) {
				var sequence_point = body.Method.DebugInformation.GetSequencePoint (instruction);
				if (sequence_point != null) {
					writer.Write ('\t');
					WriteSequencePoint (writer, sequence_point);
					writer.WriteLine ();
				}

				writer.Write ('\t');
				WriteInstruction (writer, instruction);
				writer.WriteLine ();
			}

			WriteExceptionHandlers (writer, body);
		}

		static void WriteVariables (TextWriter writer, MethodBody body)
		{
			var variables = body.Variables;

			writer.Write ('\t');
			writer.Write (".locals {0}(", body.InitLocals ? "init " : string.Empty);

			for (int i = 0; i < variables.Count; i++) {
				if (i > 0)
					writer.Write (", ");

				var variable = variables [i];

				writer.Write ("{0} {1}", variable.VariableType, GetVariableName (variable, body));
			}
			writer.WriteLine (")");
		}

		static string GetVariableName (VariableDefinition variable, MethodBody body)
		{
			string name;
			if (body.Method.DebugInformation.TryGetName (variable, out name))
				return name;

			return variable.ToString ();
		}

		static void WriteInstruction (TextWriter writer, Instruction instruction)
		{
			writer.Write (FormatLabel (instruction.Offset));
			writer.Write (": ");
			writer.Write (instruction.OpCode.Name);
			if (null != instruction.Operand) {
				writer.Write (' ');
				WriteOperand (writer, instruction.Operand);
			}
		}

		static void WriteSequencePoint (TextWriter writer, SequencePoint sequence_point)
		{
			if (sequence_point.IsHidden) {
				writer.Write (".line hidden '{0}'", sequence_point.Document.Url);
				return;
			}

			writer.Write (".line {0},{1}:{2},{3} '{4}'",
				sequence_point.StartLine,
				sequence_point.EndLine,
				sequence_point.StartColumn,
				sequence_point.EndColumn,
				sequence_point.Document.Url);
		}

		static string FormatLabel (int offset)
		{
			string label = "000" + offset.ToString ("x");
			return "IL_" + label.Substring (label.Length - 4);
		}

		static string FormatLabel (Instruction instruction)
		{
			return FormatLabel (instruction.Offset);
		}

		static void WriteOperand (TextWriter writer, object operand)
		{
			if (null == operand) throw new ArgumentNullException ("operand");

			var target = operand as Instruction;
			if (null != target) {
				writer.Write (FormatLabel (target.Offset));
				return;
			}

			var targets = operand as Instruction [];
			if (null != targets) {
				WriteLabelList (writer, targets);
				return;
			}

			string s = operand as string;
			if (null != s) {
				writer.Write ("\"" + s + "\"");
				return;
			}

			var parameter = operand as ParameterDefinition;
			if (parameter != null) {
				writer.Write (ToInvariantCultureString (parameter.Sequence));
				return;
			}

			s = ToInvariantCultureString (operand);
			writer.Write (s);
		}

		static void WriteLabelList (TextWriter writer, Instruction [] instructions)
		{
			writer.Write ("(");

			for (int i = 0; i < instructions.Length; i++) {
				if (i != 0) writer.Write (", ");
				writer.Write (FormatLabel (instructions [i].Offset));
			}

			writer.Write (")");
		}

		static void WriteExceptionHandlers (TextWriter writer, MethodBody body)
		{
			if (!body.HasExceptionHandlers)
				return;

			foreach (var handler in body.ExceptionHandlers) {
				writer.Write ("\t");
				writer.WriteLine (".try {0} to {1} {2} handler {3} to {4}",
					FormatLabel (handler.TryStart),
					FormatLabel (handler.TryEnd),
					FormatHandlerType (handler),
					FormatLabel (handler.HandlerStart),
					FormatLabel (handler.HandlerEnd));
			}
		}

		static string FormatHandlerType (ExceptionHandler handler)
		{
			var handler_type = handler.HandlerType;
			var type = handler_type.ToString ().ToLowerInvariant ();

			switch (handler_type) {
			case ExceptionHandlerType.Catch:
				return string.Format ("{0} {1}", type, handler.CatchType.FullName);
			case ExceptionHandlerType.Filter:
				throw new NotImplementedException ();
			default:
				return type;
			}
		}

		public static string ToInvariantCultureString (object value)
		{
			var convertible = value as IConvertible;
			return (null != convertible)
				? convertible.ToString (System.Globalization.CultureInfo.InvariantCulture)
				: value.ToString ();
		}
	}
}
