// 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.IO;
using System.Threading;
using System.Xml;


namespace Hiperscan.Extensions.ApoIdent
{
    public partial class Configurations
    {
        public event EventHandler ConnectionSettingsChanged = null;
        public event EventHandler ProfileChanged = null;
        public event EventHandler GuiSettingsChanged = null;
        public event EventHandler LabelPrinterSettingsChanged = null;
        public event EventHandler ProfileSettingsChanged = null;
        public event EventHandler ScannerSettingsChanged = null;

        public const string BACKUP_FILE_EXTENSION = "_backup";

        private const string APO_IDENT_TAG = "ApoIdentConfig";
        private const string CONFIG_FILE_NAME = "ApoIdent.config.xml";

        private Configurations()
        {
            this.Profiles = new ProfileCollection();
            this.IdentModuleSettings = new IdentModulePreferences();
            this.DeviceSettings = new DevicePreferences();
            this.LabelPrinterSettings = new LabelPrinterPreferences();
            this.GuiSettings = new GuiPreferences();
            this.ScannerSettings = new ScannerPreferences();
            this.SoftwareUpgradeSettings = new SoftwareUpgradePreferences();
        }

        public Configurations(string path) : this()
        {
            this.ConfigFile = Path.Combine(path, Configurations.CONFIG_FILE_NAME);
            this.BackupConfigFile = this.ConfigFile + Configurations.BACKUP_FILE_EXTENSION;

            bool can_read_from_xml = File.Exists(this.ConfigFile) || File.Exists(this.BackupConfigFile);

            if (can_read_from_xml == true)
            {
                for (int i = 0; i < 5; i++)
                {
                    try
                    {
                        this.ReadFromXmlFile(this.ConfigFile);
                        break;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("An error occurred while first readeing apo-ident-config-file: {0}", ex);
                        System.Threading.Thread.Sleep(50);
                    }

                    try
                    {
                        this.ReadFromXmlFile(this.BackupConfigFile);
                        break;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("An error occurred while second readeing apo-ident-config-file: {0}", ex);
                        System.Threading.Thread.Sleep(50);
                        if (i == 4)
                        {
                            this.Profiles = new ProfileCollection();
                            this.IdentModuleSettings = new IdentModulePreferences();
                            this.DeviceSettings = new DevicePreferences();
                            this.LabelPrinterSettings = new LabelPrinterPreferences();
                            this.GuiSettings = new GuiPreferences();
                            this.ScannerSettings = new ScannerPreferences();
                            this.SoftwareUpgradeSettings = new SoftwareUpgradePreferences();
                        }
                    }
                }
            }


            this.OldHashCode = this.GetXmlDoc().InnerXml.GetHashCode();
        }

        private void ReadFromXmlFile(string file)
        {
            XmlDocument doc = new XmlDocument();
            using (StreamReader sr = new StreamReader(file, System.Text.Encoding.UTF8))
            {
                doc.Load(sr);
            }

            XmlNode root_node = doc.DocumentElement;

            try
            {
                XmlNode profiles_node = root_node.SelectNodes(ProfileCollection.PROFILES_TAG)[0];
                this.Profiles = new ProfileCollection(profiles_node);

            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while reading profiles: {0}", ex);
                this.Profiles = new ProfileCollection();
            }

            try
            {
                XmlNode ident_module_node = root_node.SelectNodes(IdentModulePreferences.ACQUISITION_SETTINGS_TAG)[0];
                this.IdentModuleSettings = new IdentModulePreferences(ident_module_node);
            }
            catch (Exception)
            {
                this.IdentModuleSettings = new IdentModulePreferences();
            }

            try
            {
                XmlNode device_search_node = root_node.SelectNodes(DevicePreferences.DEVICE_SETTINGS_TAG)[0];
                this.DeviceSettings = new DevicePreferences(device_search_node);
            }
            catch (Exception)
            {
                this.DeviceSettings = new DevicePreferences();
            }

            try
            {
                XmlNode printer_node = root_node.SelectNodes(LabelPrinterPreferences.PRINTER_SETTINGS_TAG)[0];
                this.LabelPrinterSettings = new LabelPrinterPreferences(printer_node);
            }
            catch (Exception)
            {
                this.LabelPrinterSettings = new LabelPrinterPreferences();
            }

            try
            {
                XmlNode gui_node = root_node.SelectNodes(GuiPreferences.GUI_SETTINGS_TAG)[0];
                this.GuiSettings = new GuiPreferences(gui_node);
            }
            catch (Exception)
            {
                this.GuiSettings = new GuiPreferences();
            }

            try
            {
                XmlNode scanner_node = root_node.SelectNodes(ScannerPreferences.SCANNER_SETTINGS_TAG)[0];
                this.ScannerSettings = new ScannerPreferences(scanner_node);
            }
            catch (Exception)
            {
                this.ScannerSettings = new ScannerPreferences();
            }

            try
            {
                XmlNode software_node = root_node.SelectNodes(SoftwareUpgradePreferences.SOFTWARE_UPGRADE_SETTINGS_TAG)[0];
                this.SoftwareUpgradeSettings = new SoftwareUpgradePreferences(software_node);
            }
            catch (Exception)
            {
                this.SoftwareUpgradeSettings = new SoftwareUpgradePreferences();
            }
        }

        public void WriteToXmlFile()
        {
            XmlDocument doc = GetXmlDoc();

            if (this.ForceWriting == false)
            {
                if (this.OldHashCode == doc.InnerXml.GetHashCode())
                    return;
            }

            for (int i = 0; i < 5; i++)
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(this.ConfigFile, false, System.Text.Encoding.UTF8))
                    {
                        doc.Save(sw);
                    }

                    Configurations tmp = new Configurations();
                    tmp.ReadFromXmlFile(this.ConfigFile);
                    break;
                }
                catch (Exception ex)
                {
                    if (i == 4)
                    {
                        Console.WriteLine("Error while saving substance extension: {0}", ex);
                        throw;
                    }
                    Thread.Sleep(50);
                }
            }

            try
            {
                File.Copy(this.ConfigFile, this.BackupConfigFile, true);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while saving apo-ident-config (second try): {0}", ex);
            }

            this.OldHashCode = doc.InnerXml.GetHashCode();
            this.ForceWriting = false;
        }

        private XmlDocument GetXmlDoc()
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(String.Format("<{0}></{0}>", Configurations.APO_IDENT_TAG));
            XmlNode root = doc.DocumentElement;

            this.Profiles.WriteToXml(doc, root);
            this.DeviceSettings.WriteToXml(doc, root);
            this.IdentModuleSettings.WriteToXml(doc, root);
            this.LabelPrinterSettings.WriteToXml(doc, root);
            this.GuiSettings.WriteToXml(doc, root);
            this.ScannerSettings.WriteToXml(doc, root);
            this.SoftwareUpgradeSettings.WriteToXml(doc, root);

            return doc;
        }

        public void StashAll()
        {
            this.Profiles.Stash();
            this.DeviceSettings.Stash();
            this.IdentModuleSettings.Stash();
            this.LabelPrinterSettings.Stash();
            this.GuiSettings.Stash();
            this.ScannerSettings.Stash();
            this.SoftwareUpgradeSettings.Stash();
        }

        public void UnstashAll()
        {
            this.Profiles.Unstash();
            this.DeviceSettings.Unstash();
            this.IdentModuleSettings.Unstash();
            this.LabelPrinterSettings.Unstash();
            this.GuiSettings.Unstash();
            this.ScannerSettings.Unstash();
            this.SoftwareUpgradeSettings.Unstash();

            this.RaiseEvents();
        }

        private void RaiseEvents()
        {
            foreach (ProfilePreferences item in this.Profiles.AllProfiles.Values)
            {
                if (item.HasChanges)
                {
                    this.ProfileSettingsChanged?.Invoke(item, EventArgs.Empty);
                }
            }

            if (this.Profiles.HasChanges)
            {
                this.ProfileChanged?.Invoke(this.Profiles, EventArgs.Empty);
            }
            else
            {
                if (this.Profiles.Current != null && this.Profiles.Current.HasChanges)
                    this.ProfileChanged?.Invoke(this.Profiles, EventArgs.Empty);
            }

            if (this.LabelPrinterSettings.HasChanges)
                this.LabelPrinterSettingsChanged?.Invoke(this.LabelPrinterSettings, EventArgs.Empty);

            if (GuiSettings.HasChanges)
                this.GuiSettingsChanged?.Invoke(this.GuiSettings, EventArgs.Empty);

            if (this.IdentModuleSettings.HasChanges || this.DeviceSettings.HasChanges)
                this.ConnectionSettingsChanged?.Invoke(this, EventArgs.Empty);

            if (this.ScannerSettings.HasChanges)
                this.ScannerSettingsChanged?.Invoke(this, EventArgs.Empty);
        }

        public bool ForceWriting
        {
            get
            {
                if (this.Profiles.ForceWriting)
                    return true;

                if (this.IdentModuleSettings.ForceWriting)
                    return true;

                if (this.DeviceSettings.ForceWriting)
                    return true;

                if (this.LabelPrinterSettings.ForceWriting)
                    return true;

                if (GuiSettings.ForceWriting)
                    return true;

                if (ScannerSettings.ForceWriting)
                    return true;

                if (this.SoftwareUpgradeSettings.ForceWriting)
                    return true;

                return false;
            }

            set
            {
                this.Profiles.ForceWriting = value;
                this.IdentModuleSettings.ForceWriting = value;
                this.DeviceSettings.ForceWriting = value;
                this.LabelPrinterSettings.ForceWriting = value;
                this.GuiSettings.ForceWriting = value;
                this.ScannerSettings.ForceWriting = value;
                this.SoftwareUpgradeSettings.ForceWriting = value;
            }
        }

        public ProfileCollection Profiles { get; private set; }
        public IdentModulePreferences IdentModuleSettings { get; private set; }
        public DevicePreferences DeviceSettings { get; private set; }
        public LabelPrinterPreferences LabelPrinterSettings { get; private set; }
        public GuiPreferences GuiSettings { get; private set; }
        public ScannerPreferences ScannerSettings { get; private set; }
        public SoftwareUpgradePreferences SoftwareUpgradeSettings { get; set; }

        public string ConfigFile { get; private set; }
        public string BackupConfigFile { get; private set; }

        private int OldHashCode { get; set; } = 0;
    }
}

