// 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.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Hiperscan.Common.Checksum;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.Extensions.ApoIdentCommon.PerformanceQualification
{
    [DataContract(Name = "References", Namespace = "http://www.hiperscan.com/apoidentcommon/pqtool")]
    public partial class References : System.Runtime.Serialization.IExtensibleDataObject
    {
        private const int REFERENCE_VALIDITY_PERIOD_DAYS = 548;
        private const string SALT = "kasd9452jf";
        private const BaseChecksum.HashType HASH_TYPE = BaseChecksum.HashType.SHA512;
        private const int MASTER_SET_ID = 0;
        private const int NUMBER_OF_MASTER_SETS = 4;

        public References()
        {
            this.InitMasterSet();
        }

        private void InitMasterSet()
        {
            this.MasterSet = new ReferenceSet();

            this.MasterSet.Id = MASTER_SET_ID;
            this.MasterSet.AcquistionDate = DateTime.MinValue;

            this.MasterSet.SingleSets[ReferenceSet.RB].Id = "1335B-000";
            this.MasterSet.SingleSets[ReferenceSet.RB].ScannerId = "B000";

            double[] match = References.LoadCertifiedMasterReflectance(ReferenceSet.R1).FindNearest(1650.0);
            this.MasterSet.SingleSets[ReferenceSet.R1].Id = "13351-000";
            this.MasterSet.SingleSets[ReferenceSet.R1].ScannerId = "1000";
            this.MasterSet.SingleSets[ReferenceSet.R1].Reflectance = match[1];

            match = References.LoadCertifiedMasterReflectance(ReferenceSet.R2).FindNearest(1650.0);
            this.MasterSet.SingleSets[ReferenceSet.R2].Id = "13352-000";
            this.MasterSet.SingleSets[ReferenceSet.R2].ScannerId = "2000";
            this.MasterSet.SingleSets[ReferenceSet.R2].Reflectance = match[1];

            match = References.LoadCertifiedMasterReflectance(ReferenceSet.R5).FindNearest(1650.0);
            this.MasterSet.SingleSets[ReferenceSet.R5].Id = "13355-000";
            this.MasterSet.SingleSets[ReferenceSet.R5].ScannerId = "5000";
            this.MasterSet.SingleSets[ReferenceSet.R5].Reflectance = match[1];

            match = References.LoadCertifiedMasterReflectance(ReferenceSet.R8).FindNearest(1650.0);
            this.MasterSet.SingleSets[ReferenceSet.R8].Id = "13358-000";
            this.MasterSet.SingleSets[ReferenceSet.R8].ScannerId = "8000";
            this.MasterSet.SingleSets[ReferenceSet.R8].Reflectance = match[1];

            match = References.LoadCertifiedMasterReflectance(ReferenceSet.RW).FindNearest(1650.0);
            this.MasterSet.SingleSets[ReferenceSet.RW].Id = "1335W-000";
            this.MasterSet.SingleSets[ReferenceSet.RW].ScannerId = "W000";
            this.MasterSet.SingleSets[ReferenceSet.RW].Reflectance = match[1];

            this.MasterSet.SingleSets[ReferenceSet.RX].Id = "1335X-000";
            this.MasterSet.SingleSets[ReferenceSet.RX].ScannerId = "X000";

            if (this.AllKnownRefIds == null)
                this.AllKnownRefIds = new Dictionary<string, int>();
            foreach (ReferenceSet.SingleSet single_set in this.MasterSet.SingleSets.Values)
                this.AllKnownRefIds.Add(single_set.Id, References.MASTER_SET_ID);
        }

        public static DataSet LoadCertifiedMasterReflectance(string index)
        {
            List<string> resource_names = References.GetMasterResourcesNames(index);

            CultureInfo culture = CultureInfo.InvariantCulture;

            List<double> x = new List<double>();
            for (int i = 1000; i <= 1900; i++)
                x.Add(i);

            DataSet result_ds = new DataSet(x, 0);
            

            foreach (string resource in resource_names)
            {
                using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
                {
                    using (StreamReader sr = new StreamReader(s, Encoding.UTF8))
                    {
                        List<double> y = new List<double>();

                        string[] lines = sr.ReadToEnd().Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
                        bool line_is_valid = false;

                        foreach (string line in lines)
                        {
                            if (line.StartsWith("nm;%R", StringComparison.InvariantCulture) == true)
                            {
                                line_is_valid = true;
                                continue;
                            }

                            if (line_is_valid == false)
                                continue;

                            if (line.Contains(",") && culture == CultureInfo.InvariantCulture)
                                culture = new CultureInfo("de-DE");

                            string[] line_data = line.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                            int wavelength = Int32.Parse(line_data[0].Trim(), culture);
                            if (x.Contains(wavelength) == true)
                                y.Add(double.Parse(line_data[1].Trim(), culture) / 100.0);
                        }

                        DataSet tmp = new DataSet(x, y);
                        result_ds += tmp;
                    }
                }
            }

            return result_ds /= References.NUMBER_OF_MASTER_SETS;
        }

        private static List<string> GetMasterResourcesNames(string index)
        {
            string resource_name = string.Empty;

            switch (index)
            {
                case ReferenceSet.R1:
                    resource_name = "13351-000";
                    break;

                case ReferenceSet.R2:
                    resource_name = "13352-000";
                    break;

                case ReferenceSet.R5:
                    resource_name = "13355-000";
                    break;

                case ReferenceSet.R8:
                    resource_name = "13358-000";
                    break;

                case ReferenceSet.RW:
                    resource_name = "1335W-000";
                    break;
            }

            resource_name = ("._" + resource_name + ".").Replace("-", "_");

            string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
            List<string> values = new List<string>(Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(x => x.Contains(resource_name)));
            if (values.Count != References.NUMBER_OF_MASTER_SETS)
                throw new Exception(String.Format(Catalog.GetString("Cannot find all master reference files for set id {0}."),
                                                  resource_name.Trim('.', '_').Replace("_", "-")));

            return values;
        }

        public void AddNewReferenceSet(ReferenceSet ref_set)
        {
            if (ref_set.CanBeAdded == false)
                throw new Exception(Catalog.GetString("Not valid reference set"));
            
            foreach (string index in ref_set.SingleSets.Keys)
                this.AllKnownRefIds.Add(ref_set[index].Id, ref_set.Id);
            
            this.ReferenceSets.Add(ref_set.Id, ref_set);    
        }
        
        public void RemoveReferenceSet(int set_id)
        {
            if (this.ReferenceSets.ContainsKey(set_id) == false)
                return;
            
            ReferenceSet ref_set = this.ReferenceSets[set_id];
            this.ReferenceSets.Remove(set_id);
            
            foreach (string index in ref_set.SingleSets.Keys)
                this.AllKnownRefIds.Remove(ref_set.SingleSets[index].Id);
        }
        
        public void ReplaceOrAddReferenceSet(ReferenceSet ref_set)
        {
            int old_set_id = this.GetOldSetId(ref_set);
            this.RemoveReferenceSet(old_set_id);
            
            this.AddNewReferenceSet(ref_set);
        }
        
        public int GetSetId(string long_id)
        {
            int set_id = -1;
            foreach (int id in this.ReferenceSets.Keys)
            {
                ReferenceSet ref_set = this.ReferenceSets[id];
                if (ref_set.SingleSets[ReferenceSet.RW].Id == long_id)
                {
                    set_id = id;
                    break;
                }
            }
            return set_id;
        }

        public int GetOldSetId(ReferenceSet ref_set)
        {
            int set_id = -1;

            foreach (string index in ref_set.SingleSets.Keys)
            {
                if (this.AllKnownRefIds.ContainsKey(ref_set[index].Id) == false)
                    return -1;
                
                if (set_id == -1)
                {
                    set_id = this.AllKnownRefIds[ref_set[index].Id];
                }
                else
                {
                    if (set_id != this.AllKnownRefIds[ref_set[index].Id])
                        return -1;
                        
                }
            }
            return set_id;
        }


        public bool Contains(int set_id)
        {
            return this.ReferenceSets.ContainsKey(set_id);
        }

        public ReferenceSet this[int set_id]
        {
            get
            {
                if (set_id == References.MASTER_SET_ID)
                    return this.MasterSet;
                
                if (this.ReferenceSets.ContainsKey(set_id) == false)
                    return null;
                return this.ReferenceSets[set_id];
            }
        }

        private static string GetHashValue(References references)
        {
            references.HashValue = string.Empty;

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars = "\t";
            settings.NewLineOnAttributes = true;
            settings.NewLineChars = "\n";

            using (MemoryStream ms = new MemoryStream())
            {
                using (XmlWriter writer = XmlWriter.Create(ms, settings))
                {
                    DataContractSerializer serializer = new DataContractSerializer(typeof(References));
                    serializer.WriteStartObject(writer, references);
                    writer.WriteAttributeString("xmlns", "p", null, "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
                    serializer.WriteObjectContent(writer, references);
                    serializer.WriteEndObject(writer);
                }

                BufferChecksum checksum = new BufferChecksum(
                        References.HASH_TYPE,
                        ms.GetBuffer(),
                        References.SALT);

                return checksum.ComputeHash();
            }
        }

        public bool IsSingleSetLongIdUnique(string long_id)
        {
            if (this.AllKnownRefIds.ContainsKey(long_id) == true)
                return false;

            return true;
        }




        public static References ReadDir(string dir)
        {
            References references = new References();

            if (dir == null)
                return references;

            foreach (string file in Directory.GetFiles(dir, "*.xml"))
            {
                try
                {
                    References subset = null;
                    subset = References.Read(file);

                    foreach (ReferenceSet reference_set in subset.ReferenceSets.Values)
                    {
                        if (references.ReferenceSets.ContainsKey(reference_set.Id))
                            throw new InvalidDataException(string.Format(Catalog.GetString("Cannot read reference data for Reference Set with ID {0}: ID already exists."),
                                                                         reference_set.Id));
                        references.AddNewReferenceSet(reference_set);
                    }
                }
                catch(Exception ex)
                {
                    Console.WriteLine("Cannot read references from file {0}: {1}", file, ex);
                }
            }

            foreach (ReferenceSet ref_set in references.ReferenceSets.Values)
            {
                if (DateTime.Now - ref_set.AcquistionDate > TimeSpan.FromDays(REFERENCE_VALIDITY_PERIOD_DAYS))
                    throw new TimeoutException(Catalog.GetString("The validity period of a reference set has been exceeded."));
            }

            return references;
        }

        public static References Read(string file)
        {
            using (FileStream fs = File.OpenRead(file))
            {
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
                {
                    DataContractSerializer serializer = new DataContractSerializer(typeof(References));
                    References references = (References)serializer.ReadObject(reader, true);

                    string control_hash = references.HashValue;
                    string hash = References.GetHashValue(references);

                    if (hash != control_hash)
                        throw new InvalidDataException(Catalog.GetString("Checksum is not valid"));

                    references.InitMasterSet();

                    foreach (int set_id in references.ReferenceSets.Keys)
                    {
                        foreach (ReferenceSet.SingleSet single_set in references.ReferenceSets[set_id].SingleSets.Values)
                            references.AllKnownRefIds.Add(single_set.Id, set_id);
                    }

                    return references;
                }
            }
        }

        public static void Write(References references, string file, int set_id)
        {
            References tmp = new References();
            tmp.AddNewReferenceSet(references[set_id]);

            XmlWriterSettings settings = new XmlWriterSettings
            {
                Indent = true,
                IndentChars = "\t",
                NewLineOnAttributes = true,
                NewLineChars = "\n"
            };

            tmp.HashValue = References.GetHashValue(tmp);

            using (XmlWriter writer = XmlWriter.Create(file, settings))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(References));
                    serializer.WriteStartObject(writer, tmp);
                writer.WriteAttributeString("xmlns", "p", null, "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
                serializer.WriteObjectContent(writer, tmp);
                serializer.WriteEndObject(writer);              
            }
        }


        public List<int> AllKnownSetIds     
        {
            get
            {
                List<int> known_ids = new List<int>(this.ReferenceSets.Keys);
                known_ids.Add(References.MASTER_SET_ID);
                known_ids.Sort();
                return known_ids;
            }
        }

        public ExtensionDataObject ExtensionData { get; set; }

        [DataMember(Order = 0)] private ReferenceSetDictionary ReferenceSets { get; set; } = new ReferenceSetDictionary();
        [DataMember(Order = 1)] public string HashValue { get; set; } = string.Empty;

        [IgnoreDataMember] public ReferenceSet MasterSet { get; private set; }
        [IgnoreDataMember] public Dictionary<string, int> AllKnownRefIds { get; private set; } = new Dictionary<string, int>();
    }
}

