// UserDefinedPlotType.cs created with MonoDevelop
// User: klose at 15:27 10.01.2011
// CVS release: $Id: UserDefinedPlotDialog.cs,v 1.2 2011-04-19 12:49:21 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2010  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

using Microsoft.CSharp;


namespace Hiperscan.Extensions.UserDefinedPlotType
{

    public class UserDefinedPlotType : Hiperscan.Extensions.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 UserDefinedPlotType(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 UserDefinedPlotType(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 == false)
                        {
                            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;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Benchtop.FinderSetup;


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(UserDefinedPlotType 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
                {
                    CompilerOptions = "/target:library /optimize",
                    GenerateExecutable = false,
                    GenerateInMemory = true,
                    IncludeDebugInformation = false
                };

                options.ReferencedAssemblies.Add("mscorlib.dll");
                options.ReferencedAssemblies.Add("System.dll");
                options.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");

                if (FileIO.ExistsDirectory("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("namespace Hiperscan.Extensions.UserDefinedPlotType {");
            
            sb.AppendLine("public class Provider : IComputeDataSetProvider {");
            
            sb.AppendLine("public DataSet ComputeDataSet(UserDefinedPlotType 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
                {
                    CompilerOptions = "/target:library /optimize",
                    GenerateExecutable = false,
                    GenerateInMemory = true,
                    IncludeDebugInformation = false
                };
                options.ReferencedAssemblies.Add("mscorlib.dll");
                options.ReferencedAssemblies.Add("System.dll");
                options.ReferencedAssemblies.Add("System.Runtime.Serialization.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 == false)
                        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.Reference.Intensity);
                foreach (Spectrum s in __spectra) 
                {
                    if (s.HasAbsorbance == false)
                        continue;
                    DataSet ds = x_constraints.Apply(s.Intensity / s.Reference.Intensity);
                    ds.Id = s.Id;
                    ds_spectra.Add(ds);
                }
                break;
                
            case SpectrumType.Background:
                if (__spectrum.HasReference) spectrum = x_constraints.Apply(__spectrum.Reference.Intensity);
                foreach (Spectrum s in __spectra) 
                {
                    if (s.HasReference == false)
                        continue;
                    DataSet ds = x_constraints.Apply(s.Reference.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 (UserDefinedPlotType.spectrum0_cache.ContainsKey(hash))
                {
                    spectrum0 = UserDefinedPlotType.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 (UserDefinedPlotType.spectrum0_cache.Count > 1000)
                        UserDefinedPlotType.spectrum0_cache.Clear();
                    UserDefinedPlotType.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();
        }

        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
            {
                return new UserDefinedPlotData
                {
                    Name           = this.Name.Remove(0, 1),
                    XLabel         = this.XLabel,
                    YLabel         = this.YLabel,
                    LowerLimit     = this.LowerLimit,
                    UpperLimit     = this.UpperLimit,
                    StandardMin    = this.StandardMin,
                    StandardMax    = this.StandardMax,
                    Absorbance     = this.NeedAbsorbance,
                    DefinitionType = this.DefinitionType,
                    SpectrumType   = this.SpectrumType,
                    Spectrum0Type  = this.Spectrum0Type,
                    Code           = this.Code,
                    Function       = this.Function,
                    Active         = this.Active
                };
            }
        }
    }
}
