//
// Copyright (c) 2002
// Ronald Kevin Burton
//
// Z poniższym kodem nie jest związana żadna gwarancja poprawności działania.
// Program został dołączony do książki ".NET CLR. Księga eksperta" w celu
// ilustracji koncepcji i zasad przedstawionych w tej książce. Program może być 
// używany na własne ryzyko.
//
// Przyznaje się prawo do użycia lub kopiowania tego oprogramowania do dowolnego celu
// bez konieczności ponoszenia żadnych opłat pod warunkiem, że powyższe uwagi zostaną 
// zachowane we wszystkich kopiach. Przyznaje się także prawo do modyfikacji kodu
// i dystrybucji zmodyfikowanego kodu pod warunkiem zachowania powyższych uwag
// oraz dołączenia informacji mówiącej o modyfikacji kodu.
//
//
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

namespace MatrixMultiplication
{
	/// <summary>
	/// Podsumowanie dla MatrixMultiplyLoop.
	/// </summary>
	public class MatrixMultiplyLoop : MatrixMultiply
	{
		private IMatrixMultiply matrixMultiplyInterface;
		public MatrixMultiplyLoop(double [,] A, double [,] B) : base(A,B)
		{
			GenerateCode();
		}
		private void GenerateCode()
		{
			string filename = "mml";
			Stream s = File.Open(filename + ".cs", FileMode.Create);
			StreamWriter t = new StreamWriter(s);

			// Generowanie kodu w C#
			CSharpCodeProvider provider = new CSharpCodeProvider();
			ICodeGenerator cg = provider.CreateGenerator(t);
			CodeGeneratorOptions op = new CodeGeneratorOptions();

			// Generowanie komentarzy na początku funkcji
			CodeCommentStatement comment = new CodeCommentStatement("Mnożenie macierzy (pętla)");
			cg.GenerateCodeFromStatement(comment, t, op);

			// Klasa otrzymuje unikalną nazwę
			string className = "__MatrixMultiplyLoop";
			CodeTypeDeclaration matrixClass = new CodeTypeDeclaration(className);
			// Klasa implementuje IPolynomial
			matrixClass.BaseTypes.Add("MatrixMultiplication.IMatrixMultiply");

			// Utworzenie funkcji Multiply
			CodeMemberMethod multiplyHelperMethod = new CodeMemberMethod();
			multiplyHelperMethod.Name = "_Multiply";
			multiplyHelperMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double [,]), "A"));
			multiplyHelperMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double [,]), "B"));
			multiplyHelperMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "i"));
			multiplyHelperMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "j"));
			multiplyHelperMethod.ReturnType = new CodeTypeReference(typeof(double));
			{
				CodeBinaryOperatorExpression plus;
				CodeBinaryOperatorExpression lastplus;
				plus = new CodeBinaryOperatorExpression();
				plus.Operator = CodeBinaryOperatorType.Add;
				CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(plus);

				CodeBinaryOperatorExpression multiply;
				multiply = new CodeBinaryOperatorExpression();
				multiply.Operator = CodeBinaryOperatorType.Multiply;
				multiply.Right = new CodeSnippetExpression("A[i,0]");
				multiply.Left = new CodeSnippetExpression("B[0,j]");

				plus.Left = multiply;
				lastplus = plus;
				for(int k = 1; k < A.GetLength(1); k++)
				{
					multiply = new CodeBinaryOperatorExpression();
					multiply.Operator = CodeBinaryOperatorType.Multiply;
					multiply.Right = new CodeSnippetExpression(string.Format("A[i,{0}]",k));
					multiply.Left = new CodeSnippetExpression(string.Format("B[{0},j]",k));
					if(k < A.GetLength(1) - 1)
					{
						plus = new CodeBinaryOperatorExpression();
						plus.Operator = CodeBinaryOperatorType.Add;
						lastplus.Right = plus;
						plus.Left = multiply;
					}
					else
					{
						lastplus.Right = multiply;
					}
					lastplus = plus;
				}
				multiplyHelperMethod.Statements.Add(returnStatement);
			}

			matrixClass.Members.Add(multiplyHelperMethod);

			// Utworzenie funkcji Multiply
			CodeMemberMethod multiplyMethod = new CodeMemberMethod();
			multiplyMethod.Name = "Multiply";
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double [,]), "A"));
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double [,]), "B"));
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double [,]), "C"));
			// Obejście poniższego błędu...
			multiplyMethod.ReturnType = new CodeTypeReference("public void");
			// BŁĄD: Poniższe polecenie nie generuje "public", ale pozostawia
			// atrybut elementu składowego...
			multiplyMethod.Attributes |= MemberAttributes.Public;

			// Pętla dla wierszy A
			for(int i = 0; i < A.GetLength(0); i++)
			{
				// Pętla dla kolumn B
				for(int j = 0; j < B.GetLength(1); j++)
				{
					CodeAssignStatement assignment = new CodeAssignStatement();
					assignment.Left = new CodeArrayIndexerExpression(new CodeVariableReferenceExpression("C"), 
						                                             new CodePrimitiveExpression(i), new CodePrimitiveExpression(j));
					assignment.Right = new CodeMethodInvokeExpression(null,
                                                                      "_Multiply",
						                                              new CodeVariableReferenceExpression("A"),
						                                              new CodeVariableReferenceExpression("B"),
						                                              new CodePrimitiveExpression(i), new CodePrimitiveExpression(j));
					multiplyMethod.Statements.Add(assignment);
				}
			}

			matrixClass.Members.Add(multiplyMethod);
			cg.GenerateCodeFromType(matrixClass, t, op);

			t.Close();
			s.Close();

			string target = "mml.dll";
			// Użycie interfejsu CodeProvider
			CSharpCodeProvider cscp = new CSharpCodeProvider();
			CompilerParameters param = new CompilerParameters(null, null, true);
			param.GenerateInMemory = true;
			param.IncludeDebugInformation = false;
			param.CompilerOptions = "/optimize+";
			param.ReferencedAssemblies.Add("MatrixMultiplication.exe");
			ICodeCompiler cc = cscp.CreateCompiler();
			CompilerResults cr = cc.CompileAssemblyFromFile(param, filename + ".cs");
			System.Collections.Specialized.StringCollection output = cr.Output;
			foreach(string outputSting in output)
			{
				Console.WriteLine(outputSting);
			}

			if(cr.Errors.Count != 0)
			{
				CompilerErrorCollection es = cr.Errors;
				foreach(System.CodeDom.Compiler.CompilerError e in es)
				{
					Console.WriteLine(e.ToString());
				}
			}
			else
			{
				matrixMultiplyInterface = (IMatrixMultiply)cr.CompiledAssembly.CreateInstance(className);
			}
		}
		public override double [,] Multiply()
		{
			if (matrixMultiplyInterface == null)
				throw new ArgumentOutOfRangeException("Kompilacja kodu nie powiodła się.");
			try
			{
				matrixMultiplyInterface.Multiply(A, B, C);
			}
			catch(Exception e)
			{
				Console.WriteLine(e.ToString());
				throw;
			}
			return C;
		}
	}
}

