using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq;
using System.Threading;

namespace HelloWorld1
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                System.Console.Error.WriteLine("usage: HelloWorld1 filename.exe");
                System.Environment.Exit(1);
            }

            string exeName = args[0];

            // AssemblyBuilder#save(path)がディレクトリまたぎを受け付けないので拒否
            if (System.IO.Path.GetDirectoryName(exeName).Length > 0)
            {
                System.Console.Error.WriteLine("Specifing path is not supported.");
                System.Environment.Exit(1);
            }

            // アプリケーションドメインの取得とアセンブリ・モジュールの作成
            AppDomain appDomain = Thread.GetDomain();
            AssemblyName assemblyName = new AssemblyName() { Name = exeName };
            AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("HelloWorld", exeName, true);

            // クラスの作成
            TypeBuilder helloClassBuilder = moduleBuilder.DefineType("Hello", TypeAttributes.Class);
            ConstructorBuilder ctorBuilder = helloClassBuilder.DefineDefaultConstructor(MethodAttributes.Public);

            // インスタンスメソッドvoid Say(string value)の作成
            MethodBuilder sayMethodBuilder = helloClassBuilder.DefineMethod("Say", 
                MethodAttributes.Public, 
                typeof(void), 
                new Type[] { typeof(string) });
            ILGenerator ilSay = sayMethodBuilder.GetILGenerator();

            // System.Console.WriteLine()にSayの第一引数を渡してコール
            ilSay.Emit(OpCodes.Ldarg_1);
            ilSay.EmitCall(OpCodes.Call,
                typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
                null);

            // Sayを抜ける
            ilSay.Emit(OpCodes.Ret);

            // エントリポイントとなる静的メソッドの作成
            MethodInfo thisMainMethod = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint;
            MethodBuilder mainMethodBuilder = helloClassBuilder.DefineMethod(
                thisMainMethod.Name,
                thisMainMethod.Attributes,
                thisMainMethod.ReturnType,
                (from p in thisMainMethod.GetParameters() select p.ParameterType).ToArray<Type>());

            ILGenerator ilMain = mainMethodBuilder.GetILGenerator();

            // ローカル変数を宣言してメッセージを格納
            LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));
            ilMain.Emit(OpCodes.Ldstr, "Hello, World");
            ilMain.Emit(OpCodes.Stloc, msgLocal);

            // ローカル変数を宣言してHelloオブジェクトを生成・格納
            LocalBuilder helloLocal = ilMain.DeclareLocal(helloClassBuilder);
            ilMain.Emit(OpCodes.Newobj, ctorBuilder);
            ilMain.Emit(OpCodes.Stloc, helloLocal);

            // 作成したオブジェクトに対して、Sayメソッドを実行
            ilMain.Emit(OpCodes.Ldloc, helloLocal);
            ilMain.Emit(OpCodes.Ldloc, msgLocal);
            ilMain.EmitCall(OpCodes.Call, sayMethodBuilder, null);

            // メイン終了
            ilMain.Emit(OpCodes.Ret);

            // Helloクラスの作成
            helloClassBuilder.CreateType();

            // エントリポイントの設定
            assemblyBuilder.SetEntryPoint(mainMethodBuilder);

            // 保存
            assemblyBuilder.Save(exeName);
        }
    }
}