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: refactor this class to only GUI and complier classes

	/// <summary>
	/// 
	/// </summary>
	/// 

	public partial class CodedomCalculator : Form
	{

		private BindingSource bindFactors;
		private List<FutureFactor> factors;
		private FutureFactor currentFactor;
		private int lastSelectedFactor = -1;

		private StringBuilder sourceCode = new StringBuilder();

		private int lastFactorIdx = 10;
		private bool mIsAdding = false;

		//!! is used in compliler region
		private ClassBuilder _builder = new ClassBuilder();
		
		//!! is used in complier.math region
		private ArrayList _mathMembers = new ArrayList();
		private Hashtable _mathMembersMap = new Hashtable();


		//constructor
		public CodedomCalculator()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			GetMathMemberNames();  // track all members of the math namespace

			
			factors = new List<FutureFactor>();
			bindFactors = new BindingSource();

			_builder.ClearFactors();
			String s = FactorsInfo.FactorTypes[0].pubName;

			factors.Add(new FutureDBFactor("p1", "p1"));//"x_m506.p1"));
			factors.Add(new FutureCalculatingFactor("p2", "3 + p1 * p1"));
			factors.Add(new FutureCalculatingFactor("p3", "p1 - 1"));


			bindFactors.DataSource = factors;

			listOfFactors.SelectedValueChanged += new EventHandler(listOfFactors_SelectedValueChanged);
			listOfFactors.DataSource = bindFactors;
			listOfFactors.DisplayMember = "Name";


			comboTypeOfFactor.SelectedValueChanged += new EventHandler(comboTypeOfFactor_SelectedValueChanged);
			comboTypeOfFactor.DataSource = FactorsInfo.FactorTypes;
			//comboTypeOfFactor.DisplayMember = "pubName";
		}

		#region GUI event handlers
		void comboTypeOfFactor_SelectedValueChanged(object sender, EventArgs e)
		{
			int idx = comboTypeOfFactor.SelectedIndex;
			if (idx == -1)
			{//nothing selected 
				return;
			}

			if (mIsAdding)
			{
				System.Drawing.Size s = this.CurrentFactorControl.Size;
				Point p = this.CurrentFactorControl.Location;
				this.SuspendLayout();
				this.Controls.Remove(this.CurrentFactorControl);

				this.CurrentFactorControl = (FutureFactorControl)FactorsInfo.FactorTypes[idx].typeOfControl.InvokeMember(
					FactorsInfo.FactorTypes[idx].typeOfControl.Name, BindingFlags.CreateInstance,
					null, null, new object[] { });

				this.CurrentFactorControl.Size = s;
				this.CurrentFactorControl.Location = p;

				this.Controls.Add(this.CurrentFactorControl);
				this.ResumeLayout();



				currentFactor = (FutureFactor)FactorsInfo.FactorTypes[idx].typeOfFutureClass.InvokeMember(
					FactorsInfo.FactorTypes[idx].typeOfFutureClass.Name, BindingFlags.CreateInstance,
					null, null, new object[] { "p" + lastFactorIdx, "" });
				CurrentFactorControl.LoadFactor(currentFactor);
			}
			else
			{
				//! если можно будет менять тип уже созданного показателя,
				//  то это надо дописать тут
			}
		}
		/// <summary>
		/// SelectedValueChanged  for listOfFactors handler
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void listOfFactors_SelectedValueChanged(object sender, EventArgs e)
		{

			if (mIsAdding)
			{
				return;
			}


			if (listOfFactors.SelectedValue != null)
			{
				currentFactor = (FutureFactor)listOfFactors.SelectedValue;
			}
			if (currentFactor != null)
			{

				#region  //!! todo make funcntion

				int idx = 0;
				foreach (FactorType ft in FactorsInfo.FactorTypes)
				{
					if (ft.typeOfFutureClass == currentFactor.GetType())
					{
						break;
					}
					idx++;
				}
				#endregion


				System.Drawing.Size s = this.CurrentFactorControl.Size;
				Point p = this.CurrentFactorControl.Location;
				this.SuspendLayout();

				this.Controls.Remove(this.CurrentFactorControl);
				this.CurrentFactorControl = (FutureFactorControl)FactorsInfo.FactorTypes[idx].typeOfControl.InvokeMember(
					FactorsInfo.FactorTypes[idx].typeOfControl.Name, BindingFlags.CreateInstance,
					null, null, new object[] { });

				this.CurrentFactorControl.Size = s;
				this.CurrentFactorControl.Location = p;

				this.Controls.Add(this.CurrentFactorControl);

				this.ResumeLayout();

				CurrentFactorControl.LoadFactor(currentFactor);
				try
				{
					comboTypeOfFactor.SelectedIndex = idx;
				}
				catch (Exception someEx)
				{
					System.Diagnostics.Debug.WriteLine("Catched some exception. may be this is bad symbol");
					System.Diagnostics.Debug.WriteLine(someEx);
				}
				comboTypeOfFactor.Enabled = false;
			}

			lastSelectedFactor = listOfFactors.SelectedIndex;
		}

		/// <summary>
		/// Saving or adding of new factor
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void butSaveAdd_Click(object sender, EventArgs e)
		{
			//!! todo: save 
			#region  saving of currentFactor
			if (currentFactor != null)
			{
				CurrentFactorControl.StoreFactor(currentFactor);
			}

			if (mIsAdding)
			{
				if (comboTypeOfFactor.SelectedIndex != -1)
				{
					mIsAdding = false;
					factors.Add(currentFactor);

					listOfFactors.Enabled = true;
					listOfFactors.SelectedIndex = factors.Count - 1;
				}
				return;
			}
			bindFactors.ResetBindings(true);
			#endregion
		}

		#region button clicks

		/// <summary>
		/// Adding of new factor - part 1
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void butAddFactor_Click(object sender, EventArgs e)
		{
      mIsAdding = true;

      lastFactorIdx++;
 
      
      System.Drawing.Size s = this.CurrentFactorControl.Size;
      Point p = this.CurrentFactorControl.Location;
      this.SuspendLayout();

      this.Controls.Remove(this.CurrentFactorControl);
      this.CurrentFactorControl = new FutureFactorControl();

      this.CurrentFactorControl.Size = s;
      this.CurrentFactorControl.Location = p;
      this.CurrentFactorControl.Enabled = false;

      this.Controls.Add(this.CurrentFactorControl);

      this.ResumeLayout();

			comboTypeOfFactor.Enabled = true;
			comboTypeOfFactor.SelectedIndex = -1;
			listOfFactors.Enabled = false;
			listOfFactors.SelectedIndex = -1;
		}

		private void butDelFactor_Click(object sender, EventArgs e)
		{
			if (listOfFactors.SelectedValue == null)
			{//nothing to delete
				return;
			}
			factors.Remove((FutureFactor)listOfFactors.SelectedValue);
			bindFactors.ResetBindings(true);
		}

		private void btnCompile_Click(object sender, System.EventArgs e)
		{
			// Blank out result fields and compile result fields
			InitializeFields();

			// change evaluation string to pick up Math class members
			//!!			string expression = RefineEvaluationString(txtCalculate.Text);

			_builder.ClearFactors();
			_builder.AddFactors(factors);

			try
			{
				sourceCode = new StringBuilder(_builder.GetSourceCode());

				// compile the class into an in-memory assembly.
				// if it doesn't compile, show errors in the window
				CompilerResults results = CompileAssembly();

				Console.WriteLine("...........................\r\n");
				Console.WriteLine(sourceCode.ToString());

			}
			catch (GraphException exc)
			{
				//
				WriteLine(exc.Message, new object[0]);
			}
		}
		#endregion

		#endregion

		#region Complier

		ICodeCompiler CreateCompiler()
		{
			//Create an instance of the C# compiler   
			CodeDomProvider codeProvider = null;
			codeProvider = new CSharpCodeProvider();
			ICodeCompiler compiler = codeProvider.CreateCompiler();
			return compiler;
		}

		/// <summary>
		/// Creawte parameters for compiling
		/// </summary>
		/// <returns></returns>
		CompilerParameters CreateCompilerParameters()
		{
			//add compiler parameters and assembly references
			CompilerParameters compilerParams = new CompilerParameters();
			compilerParams.CompilerOptions = "/target:library /optimize /out:my.dll";
			/** /
				compilerParams.GenerateExecutable = false;
				compilerParams.GenerateInMemory = true;
				/*/
			compilerParams.GenerateExecutable = true;
			compilerParams.GenerateInMemory = false;
			/**/
			compilerParams.IncludeDebugInformation = false;
			compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
			compilerParams.ReferencedAssemblies.Add("System.dll");
			compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
      compilerParams.ReferencedAssemblies.Add("baseclasses.dll");
      compilerParams.ReferencedAssemblies.Add("System.Data.dll");

			//add any aditional references needed
			//            foreach (string refAssembly in code.References)
			//              compilerParams.ReferencedAssemblies.Add(refAssembly);

			return compilerParams;
		}


		/// <summary>
		/// Compiles the code from the code string
		/// </summary>
		/// <param name="compiler"></param>
		/// <param name="parms"></param>
		/// <param name="sourceCode"></param>
		/// <returns></returns>
		private CompilerResults CompileCode(ICodeCompiler compiler, CompilerParameters parms, string source)
		{
			//actually compile the code
			CompilerResults results = compiler.CompileAssemblyFromSource(
																	parms, source);

			//Do we have any compiler errors?
			if (results.Errors.Count > 0)
			{
				foreach (CompilerError error in results.Errors)
					WriteLine("Compile Error:" + error.ErrorText);
				return null;
			}

			return results;
		}


		/// <summary>
		/// Compiles the c# into an assembly if there are no syntax errors
		/// </summary>
		/// <returns></returns>
		private CompilerResults CompileAssembly()
		{
			// create a compiler
			ICodeCompiler compiler = CreateCompiler();
			// get all the compiler parameters
			CompilerParameters parms = CreateCompilerParameters();
			// compile the code into an assembly
			CompilerResults results = CompileCode(compiler, parms, sourceCode.ToString());
			return results;

		}

		#region working with "math" namespace
		/// <summary>
		/// Need to change eval string to use .NET Math library
		/// </summary>
		/// <param name="eval">evaluation expression</param>
		/// <returns></returns>
		string RefineEvaluationString(string eval)
		{
			// look for regular expressions with only letters
			Regex regularExpression = new Regex("[a-zA-Z_]+");

			// track all functions and constants in the evaluation expression we already replaced
			ArrayList replacelist = new ArrayList();

			// find all alpha words inside the evaluation function that are possible functions
			MatchCollection matches = regularExpression.Matches(eval);
			foreach (Match m in matches)
			{
				// if the word is found in the math member map, add a Math prefix to it
				bool isContainedInMathLibrary = _mathMembersMap[m.Value.ToUpper()] != null;
				if (replacelist.Contains(m.Value) == false && isContainedInMathLibrary)
				{
					eval = eval.Replace(m.Value, "Math." + _mathMembersMap[m.Value.ToUpper()]);
				}

				// we matched it already, so don't allow us to replace it again
				replacelist.Add(m.Value);
			}

			// return the modified evaluation string
			return eval;
		}

		void GetMathMemberNames()
		{
			// get a reflected assembly of the System assembly
			Assembly systemAssembly = Assembly.GetAssembly(typeof(System.Math));
			try
			{
				//cant call the entry method if the assembly is null
				if (systemAssembly != null)
				{
					//Use reflection to get a reference to the Math class

					Module[] modules = systemAssembly.GetModules(false);
					Type[] types = modules[0].GetTypes();

					//loop through each class that was defined and look for the first occurrance of the Math class
					foreach (Type type in types)
					{
						if (type.Name == "Math")
						{
							// get all of the members of the math class and map them to the same member
							// name in uppercase
							MemberInfo[] mis = type.GetMembers();
							foreach (MemberInfo mi in mis)
							{
								_mathMembers.Add(mi.Name);
								_mathMembersMap[mi.Name.ToUpper()] = mi.Name;
							}
						}
						//if the entry point method does return in Int32, then capture it and return it
					}


					//if it got here, then there was no entry point method defined.  Tell user about it
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Error:  An exception occurred while executing the script", ex);
			}
		}
		#endregion


		#endregion

		void InitializeFields()
		{
			//			txtResult.Text = "";
			txtCompile.Text = "";
		}

		/// <summary>
		/// Writes the output to the text box on the win form
		/// </summary>
		/// <param name="txt"></param>
		/// <param name="args"></param>
		private void WriteLine(string txt, params object[] args)
		{
			txtCompile.Text += string.Format(txt, args) + "\r\n";
		}


	}
	
	
}

