// 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.Xml;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.Extensions.ApoIdentCommon.PerformanceQualification
{
	public class References
	{
		private const string REFERENCE_LIST_TAG = "ReferenceList";
		private const string REFERENCE_SETS_TAG = "ReferenceSets";
		private const string DATE_ATTRIBUTE		= "Date";
		private const string VERSION_ATTRIBUTE	= "Version";

		private const int REFERENCE_VALIDITY_PERIOD_DAYS = 548;

		private ReferenceSet master_set;


		public References()
		{
			this.RefSets = new Dictionary<int, ReferenceSet>();
			this.AllKnownRefIds = new Dictionary<string, int>();
			this.InitMasterSet();
		}

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

			this.master_set.Id = 0;
			this.master_set.AcquistionDate = DateTime.MinValue;

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

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

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

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

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

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

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

        public static DataSet LoadCertifiedMasterReflectance(string index)
        {
            string resource_name = String.Empty;

            switch(index)
            {
                case ReferenceSet.R1:
                    resource_name = Path.Combine(resource_name, "Ref10.csv");
                    break;

                case ReferenceSet.R2:
                    resource_name = Path.Combine(resource_name, "Ref25.csv");
                    break;

                case ReferenceSet.R5:
                    resource_name = Path.Combine(resource_name, "Ref50.csv");
                    break;

                case ReferenceSet.R8:
                    resource_name = Path.Combine(resource_name, "Ref80.csv");
                    break;

                case ReferenceSet.RW:
                    resource_name = Path.Combine(resource_name, "Ref99.csv");
                    break;
            }

            CultureInfo culture = CultureInfo.InvariantCulture;

            List<double> x = new List<double>();
            List<double> y = new List<double>();

            for (int i = 1000; i <= 1900; i++)
                x.Add(i);

            using (Stream s =  Assembly.GetExecutingAssembly().GetManifestResourceStream(resource_name))
            {
                using (StreamReader sr = new StreamReader(s, Encoding.UTF8))
                {
                    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") == 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);

                    }
                }
            }

            return new DataSet(x, y);
        }
		
		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.RefSets.Add(ref_set.Id, ref_set);	
		}
		
		public void RemoveReferenceSet(int set_id)
		{
			if (this.RefSets.ContainsKey(set_id) == false)
				return;
			
			ReferenceSet ref_set = this.RefSets[set_id];
			this.RefSets.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.RefSets.Keys)
			{
				ReferenceSet ref_set = this.RefSets[id];
				if (ref_set.SingleSets[ReferenceSet.RW].Id == long_id)
				{
					set_id = id;
					break;
				}
			}
			return set_id;
		}
		
		private void ReadFromXml(string file)
		{
			XmlDocument  doc = new XmlDocument();
			using (StreamReader sr = new StreamReader(file, System.Text.Encoding.UTF8))
			{
				doc.Load(sr);
			}
			
			XmlNode root = doc.DocumentElement;
			XmlNode ref_sets_node = root.SelectNodes(References.REFERENCE_SETS_TAG)[0];
			
			foreach (XmlNode child in ref_sets_node.ChildNodes)
			{
				ReferenceSet ref_set = new ReferenceSet();
				ref_set.ReadFromXml(child);
				this.AddNewReferenceSet(ref_set);
			}
		}

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

			if (dir == null)
				return references;

			foreach (string file in Directory.GetFiles(dir, "*.xml"))
			{
				References subset = new References();
				subset.ReadFromXml(file);

				foreach (ReferenceSet reference_set in subset.RefSets.Values)
				{
					if (references.RefSets.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);
				}
			}

			foreach (ReferenceSet ref_set in references.RefSets.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 ReadFile(string fname)
		{
			References references = new References();

			if (fname == null)
				return references;

			references.ReadFromXml(fname);

			foreach (ReferenceSet ref_set in references.RefSets.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;
		}

		private void WriteToXml(string file, int set_id)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(String.Format("<{0}></{0}>", References.REFERENCE_LIST_TAG));
			XmlNode root = doc.DocumentElement;

			XmlAttribute version_attribute = doc.CreateAttribute(References.VERSION_ATTRIBUTE);
			version_attribute.Value = Assembly.GetExecutingAssembly().GetName().Version.ToString();
			root.Attributes.Append(version_attribute);

			XmlAttribute creation_date_attribute = doc.CreateAttribute(References.DATE_ATTRIBUTE);
			creation_date_attribute.Value = DateTime.Now.ToString(CultureInfo.InvariantCulture);
			root.Attributes.Append(creation_date_attribute);
			
			XmlElement refs_element = doc.CreateElement(References.REFERENCE_SETS_TAG);
			root.AppendChild(refs_element);

			bool success = false;
			foreach (ReferenceSet ref_set in this.RefSets.Values)
			{
				if (ref_set.Id != set_id)
					continue;
				ref_set.WriteToXml(refs_element);
				success = true;
			}

			if (success == false)
				throw new Exception(string.Format(Catalog.GetString("Cannot write Reference Set data: Cannot find specified set ID {0}."), set_id));

			using (StreamWriter sw = new StreamWriter(file, false, System.Text.Encoding.UTF8))
			{
				doc.Save(sw);
			}
		}

		public static void Write(References references, string file, int set_id)
		{
			references.WriteToXml(file, 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.RefSets.ContainsKey(set_id);
		}

		public ReferenceSet this[int set_id]
		{
			get
			{
				if (this.RefSets.ContainsKey(set_id) == false)
					return null;
				return this.RefSets[set_id];
			}
		}
		
		public ReferenceSet MasterSet
		{
			get
			{
				return this.master_set;
			}
		}
		
		public List<int> AllKnownSetIds 	
		{
			get
			{
				return new List<int>(this.RefSets.Keys);
			}
		}
		
		public  bool IsSingleSetLongIdUnique(string long_id)
		{
			if (this.AllKnownRefIds.ContainsKey(long_id) == true)
				return false;	
			
			return true;
		}
		
		public Dictionary<string, int> AllKnownRefIds 	{get; private set;}
		private Dictionary<int, ReferenceSet> RefSets	{get; set;}
	}
}

