// ##   Publics   ##

        public static Dictionary <string, object> ExecuteRule(string assemblyName, Dictionary <string, object> inputs)
        {
            PrintMemoryUsage();

            // Create assembly sandbox
            var id        = Guid.NewGuid();
            var stopwatch = Stopwatch.StartNew();

            var sandbox = AssemblySandboxBuilder.Create(id);

            PrintMemoryUsage(sandbox.CurrentDomain, "CREATED");

            // Load assembly into sandbox
            var assemblyBytes = GetAssemblyBytes(assemblyName);

            sandbox.LoadAssembly(assemblyName, assemblyBytes);
            PrintMemoryUsage(sandbox.CurrentDomain, "LOADED");

            // Execute assembly specified method
            byte[] serializedResult = sandbox.ExecuteMethod(
                assemblyName, $"{assemblyName}.{RULE_IMPLEMENT_TYPENAME}", RULE_EXECUTE_METHOD,
                ConstructorTypes: null, ConstructorParameters: null,
                MethodTypes: new string[] { typeof(Dictionary <string, object>).AssemblyQualifiedName }, MethodParameters: new object[] { inputs });
            PrintMemoryUsage(sandbox.CurrentDomain, "EXECUTED");

            var result          = Deserialize(serializedResult);
            var executionResult = result as Dictionary <string, object>;

            AppDomain.Unload(sandbox.CurrentDomain);
            sandbox = null; GC.Collect();
            PrintMemoryUsage(id, "UNLOADED");

            PrintTotalTime(stopwatch);
            return(executionResult);
        }
        // ##   User Cases  ##

        public static void AppDomainCreationMemoryCase(int instanceNum = 100)
        {
            var sandboxCreationTimes = new Dictionary <int, double>();
            var sandboxMemoryTraces  = new Dictionary <int, double>();

            var parentDirectory = "AppDomainCreationMemoryCase";
            var exportDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, parentDirectory);

            if (Directory.Exists(exportDirectory))
            {
                Directory.Delete(exportDirectory, true);
            }

            for (int i = 0; i < instanceNum; i++)
            {
                Console.WriteLine($"Creating sandbox: #{i} ...");
                var id        = Guid.NewGuid();
                var stopwatch = Stopwatch.StartNew();

                var sandbox = AssemblySandboxBuilder.Create(id, id.ToString(), parentDirectory);

                sandboxCreationTimes[i] = MillisecondToSeconds(stopwatch.ElapsedMilliseconds);
                sandboxMemoryTraces[i]  = GetProcessMemorySize();

                Console.WriteLine($"Sandbox #{i} -> {sandboxCreationTimes[i]} (s)");
                Console.WriteLine($"Sandbox #{i} -> {sandboxMemoryTraces[i]} (MB)");
            }

            var creationTimesReportFile = GetReportFilePath("appdomain_creation_time.csv");

            using (var sw = new StreamWriter(creationTimesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "time" }));
                foreach (var item in sandboxCreationTimes)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }

            var memoryTracesReportFile = GetReportFilePath("appdomain_creation_memory.csv");

            using (var sw = new StreamWriter(memoryTracesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "memory" }));
                foreach (var item in sandboxMemoryTraces)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }
        }
        public static void AppDomainSingleExecutionMemoryCase(RuleType ruleType, Dictionary <string, object> inputs, int taskNum = 100)
        {
            var sandboxExecuteTimes        = new Dictionary <int, double>();
            var sandboxExecuteMemoryTraces = new Dictionary <int, double>();

            var parentDirectory = "AppDomainSingleExecutionMemoryCase";
            var exportDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, parentDirectory);

            if (Directory.Exists(exportDirectory))
            {
                Directory.Delete(exportDirectory, true);
            }

            var assemblyName = RuleTypeAssemblyMap[ruleType];

            for (int i = 0; i < taskNum; i++)
            {
                Console.WriteLine($"Creating task: #{i} ...");
                var id        = Guid.NewGuid();
                var stopwatch = Stopwatch.StartNew();
                var sandbox   = AssemblySandboxBuilder.Create(id, id.ToString(), parentDirectory);

                // Load assembly into sandbox if not exits
                var assemblyBytes = GetAssemblyBytes(assemblyName);
                sandbox.LoadAssembly(assemblyName, assemblyBytes);

                // Execute specified method in assembly
                byte[] serializedResult = sandbox.ExecuteMethod(
                    assemblyName, $"{assemblyName}.{RULE_IMPLEMENT_TYPENAME}", RULE_EXECUTE_METHOD,
                    ConstructorTypes: null, ConstructorParameters: null,
                    MethodTypes: new string[] { typeof(Dictionary <string, object>).AssemblyQualifiedName }, MethodParameters: new object[] { inputs });

                Console.WriteLine($"Killing task #{i} ...");
                AppDomain.Unload(sandbox.CurrentDomain);
                sandbox = null; GC.Collect();

                sandboxExecuteTimes[i]        = MillisecondToSeconds(stopwatch.ElapsedMilliseconds);
                sandboxExecuteMemoryTraces[i] = GetProcessMemorySize();

                Console.WriteLine($"Task #{i} -> {sandboxExecuteTimes[i]} (s)");
                Console.WriteLine($"Task #{i} -> {sandboxExecuteMemoryTraces[i]} (MB)");
            }

            var executionTimesReportFile = GetReportFilePath($"appdomain_{assemblyName.ToLower()}_single_execution_time.csv");

            using (var sw = new StreamWriter(executionTimesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "time" }));
                foreach (var item in sandboxExecuteTimes)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }

            var executionMemoryTracesReportFile = GetReportFilePath($"appdomain_{assemblyName.ToLower()}_single_execution_memory.csv");

            using (var sw = new StreamWriter(executionMemoryTracesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "memory" }));
                foreach (var item in sandboxExecuteMemoryTraces)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }
        }
        public static void AppDomainSharedExecutionMemoryCase(int instanceNum, RuleType ruleType, Dictionary <string, object> inputs, int taskNum = 100)
        {
            var sandboxExecuteTimes        = new Dictionary <int, double>();
            var sandboxExecuteMemoryTraces = new Dictionary <int, double>();
            var taskSandboxTraces          = new Dictionary <int, string>();

            var parentDirectory = "AppDomainSharedExecutionMemoryCase";
            var exportDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, parentDirectory);

            if (Directory.Exists(exportDirectory))
            {
                Directory.Delete(exportDirectory, true);
            }

            Console.WriteLine($"Preparing sandboxes: <{instanceNum}> ...");
            var sharedSandboxes = new List <IAssemblySandbox>();

            for (int i = 0; i < instanceNum; i++)
            {
                var id      = Guid.NewGuid();
                var sandbox = AssemblySandboxBuilder.Create(id, id.ToString(), parentDirectory);
                sharedSandboxes.Add(sandbox);
            }

            var rand         = new Random(1024);
            var assemblyName = RuleTypeAssemblyMap[ruleType];

            Console.WriteLine($"Runing tasks on sandboxes ...");
            for (int i = 0; i < taskNum; i++)
            {
                var stopwatch = Stopwatch.StartNew();
                var sandbox   = sharedSandboxes[rand.Next(instanceNum)];

                taskSandboxTraces[i] = sandbox.CurrentDomain.FriendlyName;
                Console.WriteLine($"Using sandbox: <{taskSandboxTraces[i]}> for task: #{i} ...");

                // Load assembly into sandbox
                //var loadedAssemblies = sandbox.LoadedAssemblies;
                if (!sandbox.ExistsAssembly(assemblyName))
                {
                    var assemblyBytes = GetAssemblyBytes(assemblyName);
                    sandbox.LoadAssembly(assemblyName, assemblyBytes);
                    //TEST
                    //loadedAssemblies = sandbox.LoadedAssemblies;
                }

                // Execute assembly specified method
                byte[] serializedResult = sandbox.ExecuteMethod(
                    assemblyName, $"{assemblyName}.{RULE_IMPLEMENT_TYPENAME}", RULE_EXECUTE_METHOD,
                    ConstructorTypes: null, ConstructorParameters: null,
                    MethodTypes: new string[] { typeof(Dictionary <string, object>).AssemblyQualifiedName }, MethodParameters: new object[] { inputs });

                sandboxExecuteTimes[i]        = MillisecondToSeconds(stopwatch.ElapsedMilliseconds);
                sandboxExecuteMemoryTraces[i] = GetProcessMemorySize();

                Console.WriteLine($"Task #{i} -> {sandboxExecuteTimes[i]} (s)");
                Console.WriteLine($"Task #{i} -> {sandboxExecuteMemoryTraces[i]} (MB)");
            }

            foreach (var sandbox in sharedSandboxes)
            {
                AppDomain.Unload(sandbox.CurrentDomain);
            }

            GC.Collect(); //Forcing GC
            PrintMemoryUsage();

            var executionTimesReportFile = GetReportFilePath($"appdomain_{assemblyName.ToLower()}_shared_execution_time.csv");

            using (var sw = new StreamWriter(executionTimesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "time" }));
                foreach (var item in sandboxExecuteTimes)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }

            var executionMemoryTracesReportFile = GetReportFilePath($"appdomain_{assemblyName.ToLower()}_shared_execution_memory.csv");

            using (var sw = new StreamWriter(executionMemoryTracesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "memory" }));
                foreach (var item in sandboxExecuteMemoryTraces)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }

            var taskSandboxTracesReportFile = GetReportFilePath($"appdomain_{assemblyName.ToLower()}_shared_execution_task.csv");

            using (var sw = new StreamWriter(taskSandboxTracesReportFile, false))
            {
                sw.WriteLine(string.Join(",", new string[] { "id", "sandbox" }));
                foreach (var item in taskSandboxTraces)
                {
                    sw.WriteLine(string.Join(",", new string[] { item.Key.ToString(), item.Value.ToString() }));
                }

                sw.Flush();
            }
        }