using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Mono.Cecil;
using System.Reflection;

namespace NamePatcher
{
	class NamePatcher
	{
		static void DrawUsage ()
		{
			Console.WriteLine ("Usage :");
			Console.WriteLine ("NamePatcher [input file]    Patches an Assembly and creates a backup of the original file.");
		}

		static bool TryArgs (string[] args)
		{
			return args.Length == 1 && (File.Exists (args [0]));
		}

		static bool makeBackup (string input)
		{
			string backupFile_base = input + ".bck";
			string backupFile = backupFile_base;
			int backupIndex = 0;
			while (File.Exists(backupFile)) {
				backupFile = backupFile_base + "." + (++backupIndex);
				if (backupIndex > 10) {
					return false;
				}
			}
			try {
				File.Copy (input, backupFile);
			} catch (Exception e) {
				Console.WriteLine ("Unable to create backup file : ");
				Console.WriteLine (e.ToString ());
				return false;
			}
			return true;
		}

		static DefaultAssemblyResolver getAssemblyResolver (string path)
		{
			DefaultAssemblyResolver resolver = null;
			int lastSlash = path.LastIndexOf ("\\");
			if (lastSlash == -1)
				lastSlash = path.LastIndexOf ("/");
			if (lastSlash != -1) {
				string inputPath = path.Substring (0, lastSlash);
				resolver = new DefaultAssemblyResolver ();
				resolver.AddSearchDirectory (inputPath);
			}
			return resolver;
		}

		static void applyManualPatches (ModuleDefinition mainModule)
		{
			foreach (FieldDefinition fd in mainModule.GetType ("World").Fields) {
				TypeReference fdType = fd.FieldType;
				if (fdType.FullName.Equals ("System.UInt64")) {
					Console.WriteLine ("Renaming world field -> gameTime");
					NameNormalizer.setName (fd, "gameTime");
				}
			}

			foreach (FieldDefinition fd in mainModule.GetType ("GameManager").Fields) {
				TypeReference fdType = fd.FieldType;
				if (fdType.FullName.Equals ("ConnectionManager")) {
					Console.WriteLine ("Renaming and making public GameMananger field -> connectionManager");
					fd.Attributes = fd.Attributes & (~Mono.Cecil.FieldAttributes.Private) | Mono.Cecil.FieldAttributes.Public;
					NameNormalizer.setName (fd, "connectionManager");
				}
			}

			foreach (FieldDefinition fd in mainModule.GetType ("ConnectionManager").Fields) {
				TypeReference fdType = fd.FieldType;
				if (fdType.FullName.Contains ("Dictionary") && fdType.FullName.Contains ("ClientInfo")) {
					Console.WriteLine ("Renaming and making public ConnectionManager field -> connectedClients");
					fd.Attributes = fd.Attributes & (~Mono.Cecil.FieldAttributes.Private) | Mono.Cecil.FieldAttributes.Public;
					NameNormalizer.setName (fd, "connectedClients");
				}
				if (fdType.FullName.Contains ("Dictionary") && fdType.FullName.Contains ("System.Int32,System.Int32")) {
					Console.WriteLine ("Renaming and making public ConnectionManager field -> mapClientToEntity");
					fd.Attributes = fd.Attributes & (~Mono.Cecil.FieldAttributes.Private) | Mono.Cecil.FieldAttributes.Public;
					NameNormalizer.setName (fd, "mapClientToEntity");
				}
			}

			string consoleTypeName = string.Empty;
			TypeDefinition typeTelnetServer = mainModule.GetType ("NetTelnetServer");
			foreach (FieldDefinition fd in typeTelnetServer.Fields) {
				if (NameNormalizer.makeValidName (fd.FieldType.Name) != null) {
					Console.WriteLine ("Renaming console class -> ConsoleSdtd");
					consoleTypeName = fd.FieldType.Name;
					NameNormalizer.setName (fd.FieldType.Resolve (), "ConsoleSdtd");
					NameNormalizer.setName (fd, "console");
				}
			}

			if (consoleTypeName.Length > 0) {
				TypeDefinition typeConsole = mainModule.GetType (consoleTypeName);
				string consoleCommandTypeName = string.Empty;
				foreach (FieldDefinition fd in typeConsole.Fields) {
					TypeReference fdType = fd.FieldType;
					if (fdType.FullName.Contains ("Generic.List")) {
						if (fdType.IsGenericInstance) {
							GenericInstanceType genType = (GenericInstanceType)fdType;
							TypeReference genRef = genType.GenericArguments [0];
							if (genRef.Name.Length < 2) {
								Console.WriteLine ("Renaming console command class -> ConsoleCommand");
								NameNormalizer.setName (genRef.Resolve (), "ConsoleCommand");
								NameNormalizer.setName (fd, "commands");
								consoleCommandTypeName = genRef.Name;
							}
						}
					}
					if (fdType.FullName.Equals ("GameManager")) {
						Console.WriteLine ("Renaming and making public console field -> gameManager");
						fd.Attributes = fd.Attributes & (~Mono.Cecil.FieldAttributes.Private) | Mono.Cecil.FieldAttributes.Public;
						NameNormalizer.setName (fd, "gameManager");
					}
				}

				if (consoleCommandTypeName.Length > 0) {
					foreach (MethodDefinition md in typeConsole.Methods) {
						if (md.Parameters.Count == 1 && md.Parameters [0].ParameterType.Name.Equals (consoleCommandTypeName)) {
							Console.WriteLine ("Renaming console method -> AddCommand");
							NameNormalizer.setName (md, "AddCommand");
						}
					}

					TypeDefinition typeConsoleCommand = mainModule.GetType (consoleCommandTypeName);
					foreach (MethodDefinition md in typeConsoleCommand.Methods) {
						if (!md.IsConstructor) {
							if (md.Parameters.Count == 1 && md.Parameters [0].ParameterType.Name.Equals (consoleTypeName)) {
								Console.WriteLine ("Renaming console command method -> Help");
								NameNormalizer.setName (md, "Help");
							}
							if (md.Parameters.Count == 0 && md.ReturnType.Name.Equals ("Int32")) {
								Console.WriteLine ("Renaming console command method -> Timeout");
								NameNormalizer.setName (md, "Timeout");
							}
							if (md.Parameters.Count == 0 && md.ReturnType.Name.Equals ("String[]")) {
								Console.WriteLine ("Renaming console command method -> Names");
								NameNormalizer.setName (md, "Names");
							}
							if (md.Parameters.Count == 0 && md.ReturnType.Name.Equals ("String")) {
								Console.WriteLine ("Renaming console command method -> Description");
								NameNormalizer.setName (md, "Description");
							}
							if (md.Parameters.Count == 1 && md.Parameters [0].ParameterType.IsArray) {
								Console.WriteLine ("Renaming console command method -> Run");
								NameNormalizer.setName (md, "Run");
							}
						}
					}
				}
			}
		}

		static void Main (string[] args)
		{
			Console.WriteLine ("NamePatcher for 7dtd's Assembly-CSharp.dll [by DerPopo, modified by Alloc] for Dedi build 320404");
			if (!TryArgs (args)) {
				DrawUsage ();
				return;
			}
			string dllPath = args [0];

			if (!makeBackup (dllPath)) {
				Console.WriteLine ("Could not create a backup file (maybe too many old backups?)");
				return;
			}


			AssemblyDefinition input;
			try {
				input = AssemblyDefinition.ReadAssembly (dllPath, new ReaderParameters { AssemblyResolver = getAssemblyResolver(dllPath), });
			} catch (Exception e) {
				Console.WriteLine ("Unable to load the input file : ");
				Console.WriteLine (e.ToString ());
				return;
			}
			Console.WriteLine ();
			Console.WriteLine ("Patching assembly " + dllPath + " (" + input.Modules.Count + " module[s]) ...");



			applyManualPatches (input.MainModule);


			try {
				foreach (ModuleDefinition mdef in input.Modules) {
					Console.WriteLine ("Patching module " + mdef.Name + " (" + mdef.Types.Count + " type[s]) ...");
					foreach (TypeDefinition tdef in mdef.Types) {
						NameNormalizer.CheckNames (tdef);
					}
				}
			} catch (Exception e) {
				Console.WriteLine ();
				Console.WriteLine ("Unable to patch the assembly : ");
				Console.WriteLine (e.ToString ());
				return;
			}
			Console.WriteLine ("Finalizing patches...");

			try {
				NameNormalizer.FinalizeNormalizing ();
			} catch (Exception e) {
				Console.WriteLine ();
				Console.WriteLine ("Unable to finalize patching : ");
				Console.WriteLine (e.ToString ());
				return;
			}
			NameNormalizer.clnamestomod.Clear ();
			NameNormalizer.vclasses.Clear ();
			Console.WriteLine ("Saving the patched assembly ...");

			try {
				input.Write (dllPath);
			} catch (Exception e) {
				Console.WriteLine ();
				Console.WriteLine ("Unable to save the assembly : ");
				Console.WriteLine (e.ToString ());
				return;
			}

			Console.WriteLine ();
			Console.WriteLine ("Success.");
		}
	}
}
