//
// 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 MatrixMultiplyFixed.
	/// </summary>
	public class MatrixMultiplyFixed : MatrixMultiply
	{
		private IMatrixMultiply matrixMultiplyInterface;
		public MatrixMultiplyFixed(double [,] A, double [,] B) : base(A, B)
		{
			GenerateCode();
		}
		private void GenerateCode()
		{
			string filename = "mmf";
			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");
			cg.GenerateCodeFromStatement(comment, t, op);

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

			// Utworzenie funkcji Multiply
			CodeMemberMethod multiplyMethod = new CodeMemberMethod();
			multiplyMethod.Name = "Multiply";
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression("double [,]", "A"));
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression("double [,]", "B"));
			multiplyMethod.Parameters.Add(new CodeParameterDeclarationExpression("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;

			StringBuilder str = new StringBuilder();

			str.Append("{");
			// Pętla dla wierszy A
			for(int i = 0; i < A.GetLength(0); i++)
			{
				// Pętla dla kolumn A i wierszy B
				for(int j = 0; j < A.GetLength(1); j++)
				{
					// Pętla dla kolumn B
					for(int k = 0; k < B.GetLength(1); k++)
						C[i,k] += A[i,j] * B[j,k];
				}
				str.Append("{");
				for(int j = 0; j < B.GetLength(1); j++)
				{
					if(j < A.GetLength(0) - 1)
						str.Append(string.Format("{0},\r\n",C[i,j]));
					else
						str.Append(string.Format("{0}",C[i,j]));
				}
				if(i < A.GetLength(0) - 1)
					str.Append("},\r\n");
				else
					str.Append("}");
			}
			str.Append("}");

			CodeSnippetTypeMember variable = new CodeSnippetTypeMember(string.Format("static readonly double [,] result = new double[{0},{1}]\r\n{2};", A.GetLength(0), B.GetLength(1), str.ToString()));
			matrixClass.Members.Add(variable);

			CodeAssignStatement assignment = new CodeAssignStatement();
			assignment.Left = new CodeVariableReferenceExpression("C");
			assignment.Right = new CodeVariableReferenceExpression("result");
			multiplyMethod.Statements.Add(assignment);

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

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

			// 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ę.");
			matrixMultiplyInterface.Multiply(A, B, C);
			return C;
		}
	}
}
