// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2017 Thomas Klose & Torsten Schache, 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.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

using Hiperscan.Common.I18n;
using Hiperscan.SGS.Benchtop.Transformations;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IdentModule;
using Hiperscan.Spectroscopy.IdentModule.ValidationReportInfo;
using Hiperscan.Unix;


namespace Hiperscan.Extensions.ApoIdent
{
    [DataContract]
    public class ReportInfo : IExtensibleDataObject
    {
        private const string QUERY_TAG = "QueryMetaData";
        public const string USE_ZENITH = "Zenith";
        public const string USE_TI_O2 = "TiO2";
        public const string VERSION = "2.0";

        public ReportInfo()
        {

        }

        public ReportInfo(ApoIdentApplication app)
        {
            this.Company = app.Config.Profiles.Current.Company;
            this.Town = app.Config.Profiles.Current.Town;
            this.Street = app.Config.Profiles.Current.Street;
            this.ReportCountry = app.Config.Profiles.Current.ReportCountry;
            this.Culture = this.GetCulture(this.ReportCountry);
            this.ProtocolType = app.Config.Profiles.Current.ExchangeData.ProtocolType;
            this.ShowDifferencePlot = app.Config.Profiles.Current.ShowDifference;

            this.DefaultName = app.SubstanceManagement.CurrentSelectedDefaultName;
            this.LatinName = app.SubstanceManagement.CurrentSelectedLatinName;
            this.Batch = app.Control.ReportBatch;
            this.TestId = app.GetProtocolTestId();
            this.User = app.Control.ReportTestUser;
            this.Supplier = app.Control.ReportSupplier;
            this.Comment = app.Control.ReportCommon;
            this.Pzn = app.Control.ReportPpn;
            this.CorrectionFactor = app.Control.ReportWeighingCorr;
            this.AdditionalTest = app.Config.Profiles.Current.AdditionalCompletionEntries.ProtocolText;
            this.CustomerResult = app.Config.Profiles.Current.AdditionalCompletionEntries.CustomerEvaluation(app.SubstanceManagement, 
                                                                                                             app.IdentServerClient.IsTruePositive);
            this.DeadLine = BatchDeadLine.CurrentDeadLineType;
            this.IsMeasurable = app.SubstanceManagement.CurrentClassification.IsNirMeasurable;
            this.ExpireDate = app.Control.ReportExpiryDate;
            this.BAKMarking = app.SubstanceManagement.CurrentSubstance.BAKMarkingState;
            this.BAKMrkingActivated = app.Config.Profiles.Current.BAKMarkingActivated;

            this.SubstanceClass = app.SubstanceManagement.CurrentModelClass.ModelClassName;
            this.AccessDatabaseType = app.SubstanceManagement.CurrentModelClass.DatabaseInfo.Type;
            this.SelectedSubstanceId = app.SubstanceManagement.CurrentSubstance.Id;
            this.ClassificationName = app.SubstanceManagement.CurrentClassification.ClassificationName;
            this.ClassificationType = app.SubstanceManagement.CurrentClassification.ClassificationType; ;
            this.HasValidationDocument = app.SubstanceManagement.CurrentModelClass.ValidationDocument;
            this.BatchNumbers = app.SubstanceManagement.CurrentSubstance.BatchNumbers;

            this.SubstanceNamesOfClassification = new List<string>(app.SubstanceManagement.CurrentClassification.GetSubstanceNames(null).Keys);
            this.IdentServerLocation = app.SubstanceManagement.Config.IdentModuleSettings.Locale;

            string copyright = String.Format("Copyright \u00A9 Hiperscan GmbH 2010 - {0}", Plugin.copyright_year);

            if (this.IsMeasurable == true)
            {
                this.Spectrum = app.Control.CurrentProcessedSpectrum;
                this.ProbeType = app.Control.CurrentProcessedSpectrum.ProbeType;
                this.SpectrumType = app.Control.CurrentProcessedSpectrum.SpectrumType;
                this.MatchBackProjection = app.IdentServerClient.Result.MatchBackProjection;
                this.Backprojection = app.IdentServerClient.Result.SpectrumBackProjection;

                this.ScanEngineVersion = app.Finder.ScanEngineVersion;

                this.Serial = app.Control.CurrentProcessedSpectrum.Serial;
                this.DeviceName = app.Finder.DeviceName;
                this.OemSerialNumber = app.Finder.OemSerialNumber;
                if (String.IsNullOrEmpty(this.OemSerialNumber) == true)
                    this.OemSerialNumber = this.Serial;

                this.DeviceConnectionType = String.Format("{0} | {1}",
                                                          app.Finder.ProductName,
                                                          app.Finder.ConnectionType);
                if (app.Finder.Device.Info.IsRemote == true)
                    this.DeviceConnectionType += " - " + app.Finder.RemoteDeviceServerAddress;

                this.DimmerValue = app.Finder.DimmerValue;
                this.WhiteReference = ReportInfo.USE_ZENITH;

                this.Version = app.IdentServerClient.Result.Identifier + " (" + app.IdentServerClient.Result.Date.ToString("yyyy-MM-dd hh:mm") + ")";
                this.Rating = app.IdentServerClient.Result.Rating;
                this.MinRating = app.IdentServerClient.Result.MinRating;
                this.MatchId = app.IdentServerClient.Result.MatchId;
                this.MatchRating = app.IdentServerClient.Result.MatchRating;
                this.IsMatch = app.IdentServerClient.IsTruePositive;
                this.SignificanceLevel = app.IdentServerClient.Result.SignificanceLevel;

                this.PCAVersion = IdentServerClient.Info.PcaVersion;

                this.Date = app.Control.CurrentProcessedSpectrum.Timestamp;
                this.Footer = String.Format(Catalog.GetString("Created by QuickStep {0} {1}, Ident-Module {2}, SGS: {3}, White reference: {4}, PCA: {5}, {6}, http://www.hiperscan.com"),
                                                       Plugin.name,
                                                       Plugin.version,
                                                IdentServerClient.Info.Version.IdentModuleVersion,
                                                       app.Finder.Serial,
                                                       "Zenith",
                                                       IdentServerClient.Info.PcaVersion,
                                                       copyright);

                this.Creator = this.Spectrum.Creator;
                this.FinderSDTransformation = app.Finder.Current.TransformationType;

                if (app.Config.Profiles.Current.PrintValidationWhitinProtocol)
                    this.ValidationReportInfo = app.SubstanceManagement.CurrentClassification?.ReportInfo;
            }
            else
            {
                this.IsMatch = false;
                this.Date = app.Control.NotMeasurableCreationDate;
                this.Footer = String.Format(Catalog.GetString("Created by QuickStep {0} {1}, {2}, http://www.hiperscan.com"),
                                                       Plugin.name,
                                                       Plugin.version,
                                                       copyright);
            }

            this.Label = new Label(this);
            this.PdfFileFullName = FilesAndDirs.ProtocolPdfFileName(this);
            this.XmlFileFulleName = FilesAndDirs.ProtocolXmlFileName(this);
            if (this.IsMeasurable == true)
                this.CsvFilFullName = FilesAndDirs.ProtocolCsvFileName(this);
            this.SetReportInfo();
        }

        private void SetReportInfo()
        {
            if (this.Spectrum == null)
                return;

            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("[Apo-Ident::SubstanceClass]{0}\n", this.SubstanceClass);
            sb.AppendFormat("[Apo-Ident::Specimen]{0}\n", this.DefaultName);
            sb.AppendFormat("[Apo-Ident::Latin]{0}\n", this.LatinName);
            sb.AppendFormat("[Apo-Ident::Batch]{0}\n", this.Batch);
            sb.AppendFormat("[Apo-Ident::Pzn]{0}\n", this.Pzn);
            sb.AppendFormat("[Apo-Ident::Supplier]{0}\n", this.Supplier);
            sb.AppendFormat("[Apo-Ident::ExpireDate]{0}\n", this.ExpireDate.ToString("yyyy-MM-dd"));

            sb.AppendFormat("[Apo-Ident::DeviceName]{0}\n", this.DeviceName);
            sb.AppendFormat("[Apo-Ident::DimmerValue]{0}\n", this.DimmerValue.ToString());
            sb.AppendFormat("[Apo-Ident::ScanEngineVersion]{0}\n", this.ScanEngineVersion);
            sb.AppendFormat("[Apo-Ident::DeviceConnectionType]{0}\n", this.DeviceConnectionType);

            sb.AppendFormat("[Apo-Ident::TestId]{0}\n", this.TestId);
            sb.AppendFormat("[Apo-Ident::User]{0}\n", this.User);
            sb.AppendFormat("[Apo-Ident::Comment]{0}\n", this.Comment);

            sb.AppendFormat("[Apo-Ident::SelectedSubstanceId]{0}\n", this.SelectedSubstanceId);
            sb.AppendFormat("[Apo-Ident::IsMatch]{0}\n", this.IsMatch.ToString());
            sb.AppendFormat("[Apo-Ident::Confidence]{0}\n", this.Rating.ToString(Spectrum.CultureInfo));
            sb.AppendFormat("[Apo-Ident::MinRating]{0}\n", this.MinRating.ToString(Spectrum.CultureInfo));

            sb.AppendFormat("[Apo-Ident::MatchName]{0}\n", this.MatchId);
            sb.AppendFormat("[Apo-Ident::MatchRating]{0}\n", this.MatchRating.ToString(Spectrum.CultureInfo));
            sb.AppendFormat("[Apo-Ident::CustomerEval]{0}\n", this.CustomerResult.ToString());

            sb.AppendFormat("[Apo-Ident::Version]{0}\n", this.Version);
            sb.AppendFormat("[Apo-Ident::PCAVersion]{0}\n", this.PCAVersion);
            sb.AppendFormat("[Apo-Ident::WhiteReference]{0}\n", this.WhiteReference);
            sb.AppendFormat("[Apo-Ident::FinderSDTransformation]{0}", this.FinderSDTransformation.ToString());

            this.Spectrum.Comment = sb.ToString();
        }

        [DataMember] public string Company { get; set; } = String.Empty;
        [DataMember] public string Street { get; set; } = String.Empty;
        [DataMember] public string Town { get; set; } = String.Empty;
        [DataMember] public DateTime Date { get; set; } = DateTime.Now;

        [DataMember] public string SubstanceClass { get; set; } = String.Empty;
        [DataMember] public string DefaultName { get; set; } = String.Empty;
        [DataMember] public string LatinName { get; set; } = String.Empty;
        [DataMember] public string Batch { get; set; } = String.Empty;
        [DataMember] public string Pzn { get; set; } = String.Empty;
        [DataMember] public string CorrectionFactor { get; set; } = String.Empty;
        [DataMember] public string Supplier { get; set; } = String.Empty;
        [DataMember] public DateTime ExpireDate { get; set; } = DateTime.Now;
        [DataMember] public BAKMarkingState BAKMarking { get; set; } = BAKMarkingState.None;
        [DataMember] public bool BAKMrkingActivated { get; set; } = true;

        [DataMember] public string Serial { get; set; } = String.Empty;
        [DataMember] public string DeviceName { get; set; } = String.Empty;
        [DataMember] public string OemSerialNumber { get; set; } = String.Empty;
        [DataMember] public byte DimmerValue { get; set; } = 0;
        [DataMember] public string ScanEngineVersion { get; set; } = Catalog.GetString("n/a");
        [DataMember] public string DeviceConnectionType { get; set; } = String.Empty;


        [DataMember] public string TestId { get; set; } = String.Empty;
        [DataMember] public string User { get; set; } = String.Empty;
        [DataMember] public string Comment { get; set; } = String.Empty;

        [DataMember] public string SelectedSubstanceId { get; set; } = String.Empty;
        [DataMember] public bool IsMatch { get; set; } = false;
        [DataMember] public double Rating { get; set; } = 0;
        [DataMember] public double MinRating { get; set; } = 0;
        [DataMember] public string MatchId { get; set; } = String.Empty;
        [DataMember] public double MatchRating { get; set; } = 0;

        [DataMember] public string AdditionalTest { get; set; } = String.Empty;
        [DataMember] public bool CustomerResult { get; set; } = false;

        [DataMember] public string Footer { get; set; } = String.Empty;
        [DataMember] public string Version { get; set; } = String.Empty;
        [DataMember] public string PCAVersion { get; set; } = String.Empty;
        [DataMember] public string WhiteReference { get; set; } = String.Empty;

        [DataMember] public Configurations.ReportCountryType ReportCountry { get; set; } = Configurations.ReportCountryType.Germany;
        [DataMember] public BatchDeadLine.DeadLineType DeadLine { get; set; } = BatchDeadLine.DeadLineType.UseByDate;
        [DataMember] public bool IsMeasurable { get; set; } = true;
        [DataMember] public Label Label { get; set; } = null;

        // here are the memebers, which are valid with ReportInfo-Version >= 2.0 (Apo-Ident-Version >= 2.0)
        [DataMember] public string SerializedCulture { get; set; } = CultureInfo.CurrentCulture.ToString();
        public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
        [DataMember] public string Creator { get; set; } = String.Empty;

        [DataMember] public ProbeType ProbeType { get; set; } = ProbeType.Unknown;
        [DataMember] public ProtocolType ProtocolType { get; set; } = ProtocolType.ApoIdent;
        [DataMember] public SpectrumType SpectrumType { get; set; } = SpectrumType.Unknown;
        [DataMember] public AccessDatabaseType AccessDatabaseType { get; set; } = AccessDatabaseType.None;
        [DataMember] public ClassificationType ClassificationType { get; set; } = ClassificationType.None;
        [DataMember] public string ClassificationName { get; set; } = String.Empty;
        [DataMember] public bool HasValidationDocument { get; set; } = false;
        [DataMember] public string SignificanceLevel { get; set; } = String.Empty;

        [DataMember] public List<string> SubstanceNamesOfClassification { get; set; } = new List<string>();
        [DataMember] public List<string> BatchNumbers { get; set; } = new List<string>();
        [DataMember] public bool ShowDifferencePlot { get; set; } = false;
        [DataMember] public string ReportInfoVersion { get; set; } = ReportInfo.VERSION;
        [DataMember] public Locale.LocaleType IdentServerLocation { get; set; } = Locale.LocaleType.Unknown;

        [DataMember] public TransformationTypes FinderSDTransformation { get; set; } = TransformationTypes.None;
        [DataMember] public ClassificationReportInfo ValidationReportInfo { get; set; } = null;

        // this member don't have to be restored
        public Spectrum Spectrum { get; set; } = null;
        public DataSet MatchBackProjection { get; set; } = null;
        public DataSet Backprojection { get; set; } = null;
        public string PdfFileFullName { get; set; } = String.Empty;
        public string CsvFilFullName { get; set; } = String.Empty;
        public string XmlFileFulleName { get; set; } = String.Empty;

        private ExtensionDataObject extensionDataObject_value;
        public ExtensionDataObject ExtensionData
        {
            get
            {
                return extensionDataObject_value;
            }
            set
            {
                extensionDataObject_value = value;
            }
        }


        public CultureInfo GetCulture(Configurations.ReportCountryType country_type)
        {
            CultureInfo info = null;

            switch (country_type)
            {
                case Configurations.ReportCountryType.Austria:
                case Configurations.ReportCountryType.Germany:
                case Configurations.ReportCountryType.Switzerland:
                    info = new CultureInfo("de-DE");
                    break;

                case Configurations.ReportCountryType.GreatBritain:
                    info = new CultureInfo("en-GB");
                    break;

                default:
                    throw new Exception(String.Format(Catalog.GetString("Not supported ReportCountry '{0}'."), country_type));

            }

            return info;
        }

        public static void Write(ReportInfo info, string xml_filename)
        {
            try
            {
                string directory = Path.GetDirectoryName(xml_filename);
                if (Directory.Exists(directory) == false)
                {
                    Directory.CreateDirectory(directory);
                    new DirectoryInfo(directory).Attributes = FileAttributes.Hidden | FileAttributes.Directory;
                }

                info.SerializedCulture = info.Culture.ToString();
                using (MemoryStream ms = new MemoryStream())
                {
                    using (XmlTextWriter xml_tw = new XmlTextWriter(ms, Encoding.UTF8))
                    {
                        DataContractSerializer deserializer = new DataContractSerializer(typeof(ReportInfo));
                        deserializer.WriteObject(xml_tw, info);
                    }

                    File.WriteAllBytes(xml_filename, ms.ToArray());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while writing QueryMetadata; {0}", ex.Message);
            }
        }

        public static ReportInfo Read(string xml_filename)
        {
            ReportInfo info = null;

            using (XmlTextReader tr = new XmlTextReader(xml_filename))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(ReportInfo));
                info = (ReportInfo)serializer.ReadObject(tr);
                string file_name = Path.GetFileNameWithoutExtension(xml_filename);
                string parent_dir = Path.GetDirectoryName(Path.GetDirectoryName(xml_filename));
                info.XmlFileFulleName = xml_filename;
                info.PdfFileFullName = Path.Combine(parent_dir, file_name + ".pdf");
                if (info.IsMeasurable == true)
                    info.CsvFilFullName = Path.Combine(parent_dir, file_name + ".csv");
                info.Culture = new CultureInfo(info.SerializedCulture);
            }

            return info;
        }

        public static void ConvertToMetaData2_0(Configurations config)
        {
            try
            {
                List<string> directories = new List<string>();

                foreach (Configurations.ProfilePreferences profile in config.Profiles.AllProfiles.Values)
                {
                    if (Directory.Exists(profile.Archive) == false)
                        continue;

                    ReportInfo.ConvertToMetaData2_0(profile.Archive);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while converting spectra to query meta data: {0}", ex.Message);
            }
        }

        private static void ConvertToMetaData2_0(string directory)
        {
            Regex folder_pattern = new Regex(@"^20\d\d$");

            foreach (string sub_dir in Directory.GetDirectories(directory))
            {
                if (folder_pattern.Match(Path.GetFileName(sub_dir)).Success == false)
                    continue;

                string query_dir = Path.Combine(sub_dir, FilesAndDirs.QUERY_DIR);
                if (Directory.Exists(query_dir) == false)
                    continue;

                foreach (string xml_file in Directory.GetFiles(query_dir, "*.xml"))
                {
                    try
                    {
                        ReportInfo info = ReportInfo.ReadOldVersion(xml_file);
                        if (info == null)
                            continue;

                        info.ReportInfoVersion = "1.0";

                        ReportInfo.Write(info, xml_file);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error while converting report info: File = {0}, message = {1}",
                                          xml_file, ex.Message);
                    }
                }
            }
        }

        private static ReportInfo ReadOldVersion(string file_name)
        {
            ReportInfo query = new ReportInfo();

            using (StreamReader sr = new StreamReader(file_name, Encoding.UTF8))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(sr);

                bool found_customer_result = false;
                bool found_country = false;
                bool found_dead_line = false;
                bool found_is_measurable = false;


                XmlNode root = doc.GetElementsByTagName(ReportInfo.QUERY_TAG)[0];
                if (root == null)
                    return null;

                foreach (XmlElement element in root.ChildNodes)
                {
                    if (element.Name == "Specimen")
                    {
                        query.DefaultName = element.InnerText;
                        continue;
                    }

                    if (element.Name == "Latin")
                    {
                        query.LatinName = element.InnerText;
                        continue;
                    }

                    foreach (PropertyInfo property in query.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
                    {
                        if (property.Name == "Label" || property.Name == "FileName")
                            continue;

                        if (property.Name == element.Name)
                        {
                            TypeConverter converter = TypeDescriptor.GetConverter(property.PropertyType);
                            object value = converter.ConvertFromInvariantString(element.InnerText);
                            property.SetValue(query, value, null);

                            switch (property.Name)
                            {

                                case "CustomerResult":
                                    found_customer_result = true;
                                    break;

                                case "Country":
                                    found_country = true;
                                    break;

                                case "DeadLine":
                                    found_dead_line = true;
                                    break;

                                case "IsMeasurable":
                                    found_is_measurable = true;
                                    break;

                                default:
                                    break;

                            }
                        }
                    }
                }

                if (found_customer_result == false)
                    query.CustomerResult = query.IsMatch;

                if (found_country == false)
                    query.ReportCountry = Configurations.ReportCountryType.Germany;

                if (found_dead_line == false)
                    query.DeadLine = BatchDeadLine.DeadLineType.UseByDate;

                if (found_is_measurable == false)
                {
                    try
                    {
                        string spectrum_file = Path.GetFileNameWithoutExtension(file_name) + ".csv";
                        string path = new FileInfo(file_name).Directory.Parent.FullName;
                        query.IsMeasurable = File.Exists(Path.Combine(path, spectrum_file));
                    }
                    catch (Exception) { }
                }

                query.Label = new Label(query);
            }

            return query;
        }
    }
}


