using System;
using System.Text;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;


using FactorsNamespace;

namespace CodeGenerator
{
	//!! TODO: find better name

	class ClassBuilder
	{
		#region private variables
		
		/// <summary>
		/// flag is true if there are no changes after last graph build
		/// </summary>
		private bool isBuilded;

		/// <summary>
		/// Graph of factorsList for building the right sequence of factorsList calculating 
		/// </summary>
		private FactorGraph graph;

		/// <summary>
		/// Internal list of factorsList
		/// </summary>
		private List<FutureFactor> factorsList;

		/// <summary>
		/// Source code of class, that is built there
		/// </summary>
		private StringBuilder sourceCode = new StringBuilder();
		
    #endregion

		/// <summary>
		/// Makes sourceCode code for compiler
		/// </summary>
		private void BuildClass()
		{
			// need a string to put the code into
			sourceCode = new StringBuilder();
			StringWriter sw = new StringWriter(sourceCode);

			//!! building the factorsList graph  //(FutureFactor[])
			//FutureFactor[] fs = new FutureFactor[factorsList.Count];
			//for (int i = 0; i < factorsList.Count; i++)
			//{
			//  fs[i] = (FutureFactor)factorsList[i];
			//}
			graph.Init(factorsList);

			if (!graph.MakeGraph())
			{
				//!!panic
				throw new GraphException(GraphException.GraphExceptionType.Cicles);
				// ok?
			}
			List<FutureFactor> sortedFactors = graph.GetSorted();


			//Declare your provider and generator
			CSharpCodeProvider codeProvider = new CSharpCodeProvider();
			ICodeGenerator generator = codeProvider.CreateGenerator(sw);
			CodeGeneratorOptions codeOpts = new CodeGeneratorOptions();

			CodeNamespace myNamespace = new CodeNamespace("ColumnCalculator");
			myNamespace.Imports.Add(new CodeNamespaceImport("System"));
			myNamespace.Imports.Add(new CodeNamespaceImport("System.Windows.Forms"));
			myNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
			myNamespace.Imports.Add(new CodeNamespaceImport("System.Data"));
			myNamespace.Imports.Add(new CodeNamespaceImport("FactorsNamespace"));

			//Build the class declaration and member variables			
			CodeTypeDeclaration classDeclaration = new CodeTypeDeclaration();
			classDeclaration.IsClass = true;
			classDeclaration.Name = "Calculator";
			classDeclaration.Attributes = MemberAttributes.Public;
			classDeclaration.Members.Add(FieldVariable("answer", typeof(double), MemberAttributes.Private));

			CodeMemberMethod toDoubleMeth = new CodeMemberMethod();
			toDoubleMeth.Name = "toDouble";
			toDoubleMeth.ReturnType = new CodeTypeReference(typeof(double));

			toDoubleMeth.Parameters.Add(new CodeParameterDeclarationExpression(
				typeof(String), "str"));

			toDoubleMeth.Statements.Add(new CodeSnippetExpression("/*private double toDouble(String str)/**/\n" +
		"{\n			double res;\n			double.TryParse(str, out res);\n			return res;\n		}\n"));

			classDeclaration.Members.Add(toDoubleMeth);

			//default constructor
			CodeConstructor defaultConstructor = new CodeConstructor();
			defaultConstructor.Attributes = MemberAttributes.Public;
			defaultConstructor.Comments.Add(new CodeCommentStatement("Default Constructor for class", true));
			defaultConstructor.Statements.Add(new CodeSnippetStatement("//implement default constructor"));
			//to be continued

			#region //TODO: refactor this to function

			CodeMemberField factorsArrayMember = new CodeMemberField(
				"List<FactorsNamespace.FutureFactor>", "factorsList");
			factorsArrayMember.Attributes = MemberAttributes.Public;

			classDeclaration.Members.Add(factorsArrayMember);

			String fillFactorsStr = "factorsList = new List<FactorsNamespace.FutureFactor>(new FactorsNamespace.FutureFactor[] {";

			bool isFirst = true;

			foreach (FutureFactor f in sortedFactors)
			{
				if (isFirst)
				{
					isFirst = false;
				}
				else
				{
					fillFactorsStr += " , \n";
				}

				fillFactorsStr += "new " + f.GetType().Name + "(" +
					"\"" + f.Name + "\",\n" + /*tProp.Name + /**/ "delegate( DataRow row)";

				if (f is FutureDBFactor)
				{
					//tProp.Statements.Add(
					//    (//new CodeMethodReturnStatement(
					//    new CodeSnippetExpression(
					//    ""
					//    )));
					fillFactorsStr += "{}, \"" + (f as FutureDBFactor).DBName + "\"";
				}
				if (f is FutureCalculatingFactor)
				{
					//tProp.Statements.Add(
					//(new CodeAssignStatement(
					//  new CodeSnippetExpression(
					//  "row[\"" + f.Name + "\"]"),
					//new CodeSnippetExpression(
					//(f as FutureCalculatingFactor).ComplileExpr()
					//))));
					fillFactorsStr += "{\nrow[\"" + f.Name + "\"] = " +
						(f as FutureCalculatingFactor).ComplileExpr() + ";\n},\n";
					fillFactorsStr += " \"" + (f as FutureCalculatingFactor).mExpression + "\"";
				}

				fillFactorsStr += ")\n";

				//   classDeclaration.Members.Add(tProp);
			}
			#endregion

			fillFactorsStr += " });\n";

			//продолжение объявления конструктора
			defaultConstructor.Statements.Add(new CodeSnippetExpression(fillFactorsStr));
			classDeclaration.Members.Add(defaultConstructor);

			//write code
			myNamespace.Types.Add(classDeclaration);
			generator.GenerateCodeFromNamespace(myNamespace, sw, codeOpts);
			sw.Flush();
			sw.Close();


			isBuilded = true;
		}

		#region interface for working with factorsList
		
		public void AddFactor(FutureFactor newFactor)
		{
			factorsList.Add(newFactor);

			isBuilded = false;
		}

		public void AddFactors(List<FutureFactor> newFactors)
		{
			factorsList.AddRange(newFactors);

			isBuilded = false;
		}

		public void ClearFactors()
		{
			factorsList.Clear();

			isBuilded = false;
		}

		#endregion

		/// <summary>
		/// Constructor
		/// </summary>
		public ClassBuilder()
		{
			factorsList = new List<FutureFactor>();
			graph = new FactorGraph();

			isBuilded = false;
		}


		#region some fuctions, for fast creation of members
		
		private CodeMemberField FieldVariable(string fieldName, string typeName, MemberAttributes accessLevel)
		{
			CodeMemberField field = new CodeMemberField(typeName, fieldName);
			field.Attributes = accessLevel;
			return field;
		}

		private CodeMemberField FieldVariable(string fieldName, Type type, MemberAttributes accessLevel)
		{
			CodeMemberField field = new CodeMemberField(type, fieldName);
			field.Attributes = accessLevel;
			return field;
		}

		private CodeMemberProperty MakeProperty(string propertyName, string internalName, Type type)
		{
			CodeMemberProperty myProperty = new CodeMemberProperty();
			myProperty.Name = propertyName;
			myProperty.Comments.Add(new CodeCommentStatement(String.Format("The {0} property is the returned result", propertyName)));
			myProperty.Attributes = MemberAttributes.Public;
			myProperty.Type = new CodeTypeReference(type);
			myProperty.HasGet = true;
			myProperty.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), internalName)));

			myProperty.HasSet = true;
			myProperty.SetStatements.Add(
				new CodeAssignStatement(
										new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), internalName),
					new CodePropertySetValueReferenceExpression()));

			return myProperty;
		}
		
		#endregion

		/// <summary>
		/// Returns sourceCode code for compiler
		/// </summary>
		/// <returns>String sourceCode code</returns>
		public String GetSourceCode()
		{
			if (!isBuilded)
			{
				BuildClass();
			}

			return sourceCode.ToString();
		}
	}
}
