// UserDefinedPlotTypes.cs created with MonoDevelop
// User: klose at 11:24 10.01.2011
// CVS release: $Id: UserDefinedPlotType.cs,v 1.1 2011-04-07 09:41:44 klose Exp $
//

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Xml.Serialization;
using System.IO;
using System.Linq;
using Microsoft.CSharp;

using Hiperscan.Unix;
using Hiperscan.Spectroscopy;
using Hiperscan.Extensions;


namespace Hiperscan.Extensions.UserDefinedPlotType
{
	
	public interface IComputeDataSetProvider
	{
		DataSet ComputeDataSet(UserDefinedPlotTypeInfo info, Spectrum[] spectra, Spectrum spectrum, DataSet.XConstraints x_constraints);
	}
	
	public enum DefinitionType : int
	{
		Simple,
		Advanced
	}
	
	public enum SpectrumType : int
	{
		Intensity,
		Absorbance,
		Reflectivity,
		Background,
		RawIntensity
	}
	
	public enum Spectrum0Type : int
	{
		First,
		Last,
		LeastValue,
		GreatestValue,
		LeastMean,
		GreatestMean,
		LeastMedian,
		GreatestMedian,
		MeanSpectrum
	}
	
	[Serializable()]
	public class UserDefinedPlotData
	{
		public string Name { get; set; }
		public string XLabel { get; set; }
		public string YLabel { get; set; }
		public double LowerLimit { get; set; }
		public double UpperLimit { get; set; }
		public double StandardMin { get; set; }
		public double StandardMax { get; set; }
		public bool Absorbance { get; set; }
		public DefinitionType DefinitionType { get; set; }
		public SpectrumType SpectrumType { get; set; }
		public Spectrum0Type Spectrum0Type { get; set; }
		public string Function { get; set; }
		public string Code { get; set; }
		public bool Active { get; set; }
	}
	
	[Serializable()]
	public class UserDefinedPlotDataList : List<UserDefinedPlotData>
	{
		public static UserDefinedPlotDataList ReadFromFile(string fname)
		{
			using (Stream stream = new FileStream(fname, FileMode.Open))
			{
				using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8))
				{
					XmlSerializer serializer = new XmlSerializer(typeof(UserDefinedPlotDataList));
					UserDefinedPlotDataList list = (UserDefinedPlotDataList)serializer.Deserialize(reader);

					if (list == null)
						throw new Exception("Error while deserialization.");
				
					return list;
				}
			}
		}

		public void WriteToFile(string fname)
		{
			using (Stream stream = new FileStream(fname, FileMode.Create))
			{
				using (StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.UTF8)){
					XmlSerializer serializer = new XmlSerializer(typeof(UserDefinedPlotDataList));
					serializer.Serialize(writer, this);
				}
			}
		}
	}
	
	public class UserDefinedPlotTypeInfo : PlotTypeInfo
	{
		private string code;
		private string function;
		private DefinitionType definition_type;
		private SpectrumType   spectrum_type;
		private Spectrum0Type  spectrum0_type;
		
		private IComputeDataSetProvider provider_cache  = null;

		private static Dictionary<int,DataSet> spectrum0_cache = new Dictionary<int,DataSet>();
		private static List<Spectrum> spectra = null;
		private static int spectra_hash = 0;

		public UserDefinedPlotTypeInfo(string name) : base(name)
		{
			this.XLabel = Catalog.GetString("Wavelength / nm");
			this.YLabel = Catalog.GetString("Intensity / a.u.");
			this.LowerLimit = -10.0;
			this.UpperLimit = 10.0;
			this.StandardMin = -5.0;
			this.StandardMax = 5.0;
			this.NeedAbsorbance = false;
			this.definition_type = DefinitionType.Simple;
			this.spectrum_type   = SpectrumType.Intensity;
			this.spectrum0_type  = Spectrum0Type.First;
			this.code = "return spectrum.Intensity;";
			this.function = "spectrum";
			this.Active = true;
		}
		
		public UserDefinedPlotTypeInfo(UserDefinedPlotData data) : base(data.Name)
		{
			this.XLabel = data.XLabel;
			this.YLabel = data.YLabel;
			this.LowerLimit = data.LowerLimit;
			this.UpperLimit = data.UpperLimit;
			this.StandardMin = data.StandardMin;
			this.StandardMax = data.StandardMax;
			this.NeedAbsorbance = data.Absorbance;
			this.definition_type = data.DefinitionType;
			this.spectrum_type   = data.SpectrumType;
			this.spectrum0_type  = data.Spectrum0Type;
			this.code = data.Code;
			this.function = data.Function;
			this.Active = data.Active;
		}
		
		public override DataSet ComputeDataSet(Spectrum spectrum, DataSet.XConstraints x_constraints)
		{
			IComputeDataSetProvider provider = this.GetProvider();

			if (provider == null)
				return null;

			int tmp = 0;
			if (spectra == null || spectrum == Plugin.host.CurrentSpectra.Values.First())
			{
				spectra = new List<Spectrum>();
				spectra_hash = tmp;
				foreach (Spectrum s in Plugin.host.CurrentSpectra.Values)
				{
					spectra.Add(s.Clone());
				}
			}
			return provider.ComputeDataSet(this, spectra.ToArray(), spectrum.Clone(), x_constraints);
		}
		
		public override void Reset()
		{
		}
		
		public override int GetPlotTypeId ()
		{
			return base.GetPlotTypeId() ^ this.Name.GetHashCode();
		}
		
		private IComputeDataSetProvider GetProvider()
		{
			if (this.provider_cache == null)
			{
				CompilerResults results = this.Compile();

				if (results.Errors.Count > 0)
				{
					bool fatal = false;
					foreach (CompilerError error in results.Errors)
					{
						if (!error.IsWarning)
						{
							Console.WriteLine("Compiler error: " + error.ErrorText);
							fatal = true;
						}
					}
					if (fatal)
						return null;
				}
			
				Assembly assembly = results.CompiledAssembly;
			
				foreach (Type type in assembly.GetTypes())
				{
					if (!type.IsPublic || (type.Attributes & TypeAttributes.Abstract) != 0)
						continue;
					
					Type iprovider = type.GetInterface("Hiperscan.Extensions.UserDefinedPlotType.IComputeDataSetProvider");
					
					if (iprovider == null)
						continue;
					
					this.provider_cache = (IComputeDataSetProvider)assembly.CreateInstance(type.FullName);
					break;			
				}
			}
			
			return this.provider_cache;
		}

		public CompilerResults Compile()
		{
			switch (this.DefinitionType)
			{
				
			case DefinitionType.Simple:
				if (this.Function.Contains("spectrum0"))
					this.Interdependent = true;
				return this.CompileFunction();
				
			case DefinitionType.Advanced:
				return this.CompileCode();
				
			default:
				throw new Exception("Unknown plot definition type: " + this.DefinitionType.ToString());
				
			}
		}
		
		private CompilerResults CompileFunction()
		{
			StringBuilder sb = new StringBuilder();
			
			sb.Append(@"
using System;
using Hiperscan.Spectroscopy;
using Hiperscan.SGS;


namespace Hiperscan.Extensions.UserDefinedPlotType
{
			
   public class Provider : IComputeDataSetProvider 
   {
			
      public double Min(DataSet ds)  { return ds.YMin(); }
      public double Max(DataSet ds)  { return ds.YMax(); }
      public double Mean(DataSet ds) { return ds.YMean(); }
      public double Median(DataSet ds) { return ds.YMedian(); }
      public double Value(DataSet ds, double xvalue) { return ds.Interpolate(xvalue); }
      public double Sigma(DataSet ds) { return ds.YSigma(); }
      public DataSet Snv(DataSet ds) { return ds.SNV(); }
      public DataSet Log(DataSet ds) { return DataSet.Log(ds); }
      public DataSet Ln(DataSet ds) { return DataSet.Ln(ds); }
      public DataSet Diff(DataSet ds) { return ds.Derivative(3, 21, 1).Interpolate(ds.X); }
      public DataSet Diff2(DataSet ds) { return ds.Derivative(3, 21, 2).Interpolate(ds.X); }
      public DataSet Smooth(DataSet ds) { return ds.Derivative(3, 21, 0).Interpolate(ds.X); }
      public DataSet Pec(DataSet ds) { return ProbeErrorCharacteristic.CorrectDataSet(ds); }
      public DataSet Linreg(DataSet ds) { return ds.LinearRegressionCurve(ds.LinearRegression()); }
      public DataSet Range(DataSet ds, double min, double max) { return ds.GetRange(min, max); }
			
      public DataSet ComputeDataSet(UserDefinedPlotTypeInfo info, Spectrum[] __spectra, Spectrum __spectrum, DataSet.XConstraints x_constraints)
      {

         DataSet spectrum = null, spectrum0 = null;
         info.DefineDataSets(__spectra, __spectrum, out spectrum0, out spectrum, x_constraints);

         if (spectrum == null || spectrum0 == null)
            return null;
");
			
			sb.AppendLine("object result = " + this.function + ";");

			sb.Append(@"
         if (result.GetType() == typeof(double) || result.GetType() == typeof(int))
         {
           return new DataSet(spectrum.X, (double)result);
         }
         else if (result.GetType() == typeof(DataSet))
         {
           return (DataSet)result;
         }
         else
           throw new Exception();
      }
   }
}
");

			using (CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"))
			{
				CompilerParameters options = new CompilerParameters();
				options.CompilerOptions = "/target:library /optimize";
				options.GenerateExecutable = false;
				options.GenerateInMemory = true;			
				options.IncludeDebugInformation = false;
				options.ReferencedAssemblies.Add("mscorlib.dll");
				options.ReferencedAssemblies.Add("System.dll");
				if (Directory.Exists("HCL"))
				{
					options.ReferencedAssemblies.Add(Path.Combine("HCL", "Spectroscopy.dll"));
					options.ReferencedAssemblies.Add(Path.Combine("HCL", "SGS.dll"));
					options.ReferencedAssemblies.Add(Path.Combine("HCL", "Extensions.dll"));
				}
				else
				{
					options.ReferencedAssemblies.Add("Spectroscopy.dll");
					options.ReferencedAssemblies.Add("SGS.dll");
					options.ReferencedAssemblies.Add("Extensions.dll");
				}
				options.ReferencedAssemblies.Add("Plugins/UserDefinedPlotType/UserDefinedPlotType.dll");

				Console.WriteLine("Compile assembly:\n" + sb.ToString());
			
				return provider.CompileAssemblyFromSource(options, sb.ToString());
			}
		}

		private CompilerResults CompileCode()
		{
			StringBuilder sb = new StringBuilder();
			
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Collections.Generic;");
			sb.AppendLine("using Hiperscan.Spectroscopy;");
			sb.AppendLine("using Hiperscan.SGS;");
			//sb.AppendLine("using Hiperscan.Extensions;");
			
			sb.AppendLine("namespace Hiperscan.Extensions.UserDefinedPlotType {");
			
			sb.AppendLine("public class Provider : IComputeDataSetProvider {");
			
			sb.AppendLine("public DataSet ComputeDataSet(UserDefinedPlotTypeInfo info, Spectrum[] spectra, Spectrum spectrum, DataSet.XConstraints xconstraints) {");
			sb.AppendLine(this.code);
			sb.AppendLine("}");
			
			sb.AppendLine("}");
			
			sb.AppendLine("}");
			
			using (CodeDomProvider provider = new CSharpCodeProvider())
			{
			
				CompilerParameters options = new CompilerParameters();
				options.CompilerOptions = "/target:library /optimize";
				options.GenerateExecutable = false;
				options.GenerateInMemory = true;			
				options.IncludeDebugInformation = false;
				options.ReferencedAssemblies.Add("mscorlib.dll");
				options.ReferencedAssemblies.Add("System.dll");

				string hcl_path = Environment.GetEnvironmentVariable("HCL_PATH");
				if (string.IsNullOrEmpty(hcl_path))
					hcl_path = ".";
				string base_path = Environment.GetEnvironmentVariable("QS_BASE_PATH");
				if (string.IsNullOrEmpty(base_path))
					base_path = ".";
				
				options.ReferencedAssemblies.Add(Path.Combine(hcl_path, "Spectroscopy.dll"));
				options.ReferencedAssemblies.Add(Path.Combine(hcl_path, "Extensions.dll"));
				options.ReferencedAssemblies.Add(Path.Combine(hcl_path, "SGS.dll"));
				options.ReferencedAssemblies.Add(Path.Combine(base_path, "Plugins", "UserDefinedPlotType", "UserDefinedPlotType.dll"));
			
				return provider.CompileAssemblyFromSource(options, sb.ToString());
			}
		}
		
		public void DefineDataSets(Spectrum[] __spectra, Spectrum __spectrum, out DataSet spectrum0, out DataSet spectrum, DataSet.XConstraints x_constraints)
		{
			List<DataSet> ds_spectra = new List<DataSet>();
			
			spectrum = null;
			spectrum0 = null;

			switch (this.SpectrumType)
			{
			
			case SpectrumType.Intensity:
				spectrum = x_constraints.Apply(__spectrum.Intensity);
				foreach (Spectrum s in __spectra) 
				{
					DataSet ds = x_constraints.Apply(s.Intensity);
					ds.Id = s.Id;
					ds_spectra.Add(ds);
				}
				break;
				
			case SpectrumType.Absorbance:
				if (__spectrum.HasAbsorbance) spectrum = x_constraints.Apply(__spectrum.Absorbance);
				foreach (Spectrum s in __spectra) 
				{
					if (!s.HasAbsorbance)
						continue;
					DataSet ds = x_constraints.Apply(s.Absorbance);
					ds.Id = s.Id;
					ds_spectra.Add(ds);
				}
				break;
				
			case SpectrumType.Reflectivity:
				if (__spectrum.HasAbsorbance) spectrum = x_constraints.Apply(__spectrum.Intensity / __spectrum.Background.Intensity);
				foreach (Spectrum s in __spectra) 
				{
					if (!s.HasAbsorbance)
						continue;
					DataSet ds = x_constraints.Apply(s.Intensity / s.Background.Intensity);
					ds.Id = s.Id;
					ds_spectra.Add(ds);
				}
				break;
				
			case SpectrumType.Background:
				if (__spectrum.HasBackground) spectrum = x_constraints.Apply(__spectrum.Background.Intensity);
				foreach (Spectrum s in __spectra) 
				{
					if (!s.HasBackground)
						continue;
					DataSet ds = x_constraints.Apply(s.Background.Intensity);
					ds.Id = s.Id;
					ds_spectra.Add(ds);
				}
				break;
				
			case SpectrumType.RawIntensity:
				spectrum = x_constraints.Apply(__spectrum.RawIntensity);
				foreach (Spectrum s in __spectra) 
				{
					DataSet ds = x_constraints.Apply(s.RawIntensity);
					ds.Id = s.Id;
					ds_spectra.Add(ds);
				}
				break;

			default:
				throw new Exception("Unknown spectrum type: " + this.SpectrumType.ToString());
			}
			
			if (this.Spectrum0Type == Spectrum0Type.MeanSpectrum && spectrum != null)
			{
				int hash = this.GetSpectrum0Hash(ds_spectra, spectrum.X);
				if (UserDefinedPlotTypeInfo.spectrum0_cache.ContainsKey(hash))
				{
					spectrum0 = UserDefinedPlotTypeInfo.spectrum0_cache[hash];
				}
				else
				{
					spectrum0 = new DataSet(spectrum.X, 0.0);
					foreach (DataSet ds in ds_spectra)
					{
						spectrum0 += ds.Interpolate(spectrum.X);
					}
					spectrum0 /= (double)ds_spectra.Count;
					spectrum0 = x_constraints.Apply(spectrum0);

					if (UserDefinedPlotTypeInfo.spectrum0_cache.Count > 1000)
						UserDefinedPlotTypeInfo.spectrum0_cache.Clear();
					UserDefinedPlotTypeInfo.spectrum0_cache[hash] = spectrum0;
				}
				return;
			}
			
			
			int found = -1; 
			int first = 0;
			double tmp = 0.0;
			double new_tmp = 0f;
			for (int ix=0; ix < ds_spectra.Count; ++ix) 
			{
				if (ds_spectra[ix].IsPreview && ds_spectra.Count > 1)
				{
					++first;
					continue;
				}
				
				switch (this.Spectrum0Type)
				{
				
				case Spectrum0Type.First:
					if (ix == first)
					{ 
						found = ix;
						break; 
					}
					break;
				
				case Spectrum0Type.Last:
					if (ix == ds_spectra.Count-1) 
					{
						found = ix;
						break;
					}
					break;
				
				case Spectrum0Type.LeastValue:
					new_tmp = ds_spectra[ix].YMin();
					if (ix == 0) 
					{ 
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp < tmp)
					{
						tmp = new_tmp; 
						found = ix;
					}
					break;
				
				case Spectrum0Type.GreatestValue:
					new_tmp = ds_spectra[ix].YMax();
					if (ix == 0)
					{
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp > tmp)
					{
						tmp = new_tmp;
						found = ix;
					}
					break;
				
				case Spectrum0Type.LeastMean:
					new_tmp = ds_spectra[ix].YMean();
					if (ix == 0)
					{ 
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp < tmp) 
					{ 
						tmp = new_tmp;
						found = ix;
					}
					break;
				
				case Spectrum0Type.GreatestMean:
					new_tmp = ds_spectra[ix].YMean();
					if (ix == 0)
					{
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp > tmp)
					{ 
						tmp = new_tmp;
						found = ix; 
					}
					break;
				
				case Spectrum0Type.LeastMedian:
					new_tmp = ds_spectra[ix].YMedian();
					if (ix == 0)
					{ 
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp < tmp) 
					{ 
						tmp = new_tmp;
						found = ix;
					}
					break;
				
				case Spectrum0Type.GreatestMedian:
					new_tmp = ds_spectra[ix].YMedian();
					if (ix == 0)
					{
						tmp = new_tmp; 
						found = ix; 
						continue;
					}
					if (new_tmp > tmp)
					{ 
						tmp = new_tmp;
						found = ix; 
					}
					break;
				
				default:
					throw new Exception("Unknown spectrum0 type: " + this.Spectrum0Type.ToString());
				}
			}
			
			if (found > -1 && found < ds_spectra.Count)
				spectrum0 = ds_spectra[found];
			
			if (spectrum != null && spectrum0 != null)
				spectrum0 = spectrum0.Interpolate(spectrum.X);
		}

		private int GetSpectrum0Hash(List<DataSet> dss, List<double> xs)
		{
			int hash = 0;
			foreach (DataSet ds in dss)
			{
				hash ^= ds.Id.GetHashCode();
			}
			foreach (double x in xs)
			{
				hash ^= x.GetHashCode();
			}
			return hash ^ this.Spectrum0Type.GetHashCode();
		}

//		private int GetSpectraHash()
//		{
//			int hash = 0;
//			foreach (Spectrum s in Plugin.host.CurrentSpectra.Values)
//			{
//				hash ^= s.Id.GetHashCode() ^ s.LimitBandwidth.GetHashCode();
//			}
//			return hash;
//		}

		public override string Name
		{
			get { return "*" + base.Name; }
			set { base.Name = value;      }
		}
		
		public DefinitionType DefinitionType
		{
			get { return this.definition_type; }
			set 
			{
				this.definition_type = value; 
				this.provider_cache = null;
			}
		}
		
		public SpectrumType SpectrumType
		{
			get { return this.spectrum_type;  }
			set
			{
				this.spectrum_type = value; 
				this.provider_cache = null;
			}
		}

		public Spectrum0Type Spectrum0Type
		{
			get { return this.spectrum0_type;  }
			set
			{
				this.spectrum0_type = value; 
				this.provider_cache = null;
			}
		}
		
		public string Code
		{
			get { return this.code;  }
			set 
			{ 
				this.code = value; 
				this.provider_cache = null;
			}
		}
		
		public string Function
		{
			get { return this.function;  }
			set 
			{
				this.function = value; 
				this.provider_cache = null;
			}
		}
		
		public UserDefinedPlotData UserDefinedPlotData
		{
			get
			{
				UserDefinedPlotData data = new UserDefinedPlotData();
				data.Name = this.Name.Remove(0, 1);
				data.XLabel = this.XLabel;
				data.YLabel = this.YLabel;
				data.LowerLimit = this.LowerLimit;
				data.UpperLimit = this.UpperLimit;
				data.StandardMin = this.StandardMin;
				data.StandardMax = this.StandardMax;
				data.Absorbance = this.NeedAbsorbance;
				data.DefinitionType = this.DefinitionType;
				data.SpectrumType = this.SpectrumType;
				data.Spectrum0Type = this.Spectrum0Type;
				data.Code = this.Code;
				data.Function = this.Function;
				data.Active = this.Active;
				return data;
			}
		}
	}
}