// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2023. 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.Linq;
using System.Collections.Generic;

using Hiperscan.Unix;
using Hiperscan.Common.IO;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IO;
using Hiperscan.Extensions.ReferenceCheck.Control.Enums;
using Hiperscan.Extensions.ReferenceCheck.Control.Evaluation;
using Hiperscan.Extensions.ReferenceCheck.Control.Acquisition;

namespace Hiperscan.Extensions.ReferenceCheck.Control
{
    public partial class CoreApp
    {
        private void Save(string zip_file)
        {
            if (this.SessionManager.IsRestored)
            {
                if (false == this.RestoreSuccess || string.IsNullOrEmpty(this.RestoredZipFile))
                    throw new Exception(Catalog.GetString("Cannot copy zip file beacause restore failed."));
                
                zip_file = zip_file.Replace(Path.GetExtension(zip_file), Path.GetExtension(this.RestoredZipFile));
                File.Copy(this.RestoredZipFile, zip_file);
                return;
            }

            System.Threading.Thread.Sleep(1500);

            this.LastSelectedDir = Path.GetDirectoryName(zip_file);

            FileIO.RecreateDirectory(this.OutputDir);

            this.Finder.AutoSaveDirectory = this.OutputDir;
            this.Finder.AutoSaveReferences = true;

            foreach (AcquisitionType acquisition_type in Enum.GetValues(typeof(AcquisitionType)))
                this.SaveReferences(acquisition_type);

            this.SaveCalibrationSpectra();
            this.SessionManager.Result.Save(this.OutputDir);
            this.SessionManager.OldOperator = this.SessionManager.Operator;

            Zip zip = new Zip(zip_file, true);
            zip.Add(this.OutputDir);
            zip.Finish();

            FileIO.DeleteDirectory(this.output_dir, true);
        }

        private void SaveReferences(AcquisitionType type)
        {
            SpectrumType Spectrum_type = SpectrumType.Preview;
            AcquisitionSet acuisition_set = null;

            switch(type)
            {
                case AcquisitionType.Black:
                    Spectrum_type = SpectrumType.ExternalEmpty;
                    acuisition_set = this.BlackReferenceSet;
                    break;

                case AcquisitionType.White:
                    Spectrum_type = SpectrumType.ExternalWhite;
                    acuisition_set = this.WhiteReferenceSet;
                    break;

                case AcquisitionType.Transflectance:
                    Spectrum_type = SpectrumType.ExternalFluidWhite;
                    acuisition_set = this.TransflectanceReferenceSet;
                    break;

                case AcquisitionType.WhiteInsert:
                    Spectrum_type = SpectrumType.ExternalWhiteInsert;
                    acuisition_set = this.InsertWhiteReferenceSet;
                    break;

                case AcquisitionType.Empty:
                    Spectrum_type = SpectrumType.SolidSpecimen;
                    acuisition_set = this.EmptyReferenceSet;
                    break;

                default:
                    break;
            }

            foreach (AcquisitionSession session in acuisition_set.Sessions.SessionQuue)
            {
                foreach (string serial in session.Spectra.Keys)
                {
                    Spectrum spectrum = session.Spectra[serial];
                    this.AppendAcuisitionInofos(spectrum, session.Id, serial);
                    this.Finder.SaveReferenceSpectrum(spectrum, Spectrum_type);
                }
            }
        }

        private void SaveCalibrationSpectra()
        {
            foreach (CalibrationSession session in this.CalibrationSet.SessionQuue)
            {
                string serial = String.Format("{0}-{1:D2}", SpectrumType.InternalDark, session.Id);
                this.AppendAcuisitionInofos(session.DarkSpectrum, session.Id, serial);
                this.Finder.SaveReferenceSpectrum(session.DarkSpectrum,
                                                  SpectrumType.InternalDark);

                serial = String.Format("{0}-{1:D2}", SpectrumType.InternalWhite, session.Id);
                this.AppendAcuisitionInofos(session.WhiteSpectrum, session.Id, serial);
                this.Finder.SaveReferenceSpectrum(session.WhiteSpectrum,
                                                  SpectrumType.InternalWhite);

                serial = String.Format("{0}-{1:D2}", SpectrumType.InternalOxid, session.Id);
                this.AppendAcuisitionInofos(session.OxidSpectrum, session.Id, serial);
                this.Finder.SaveReferenceSpectrum(session.OxidSpectrum,
                                                  SpectrumType.InternalOxid);
            }
        }

        private void AppendAcuisitionInofos(Spectrum spectrum, int session_id, string serial)
        {
            string comment = $"{SpectrumExtension.REFERENCE_CHECK_OPERATOR}{this.SessionManager.Operator}\n";
            comment += $"{SpectrumExtension.REFERENCE_CHECK_SESSION}{session_id:D2}\n";
            comment += $"{SpectrumExtension.REFERENCE_CHECK_SERIAL}{serial}\n";
            spectrum.Comment = comment;
        }

        private void RestoreSessonManager(string file_name)
        {
            System.Threading.Thread.Sleep(2000);

            FileIO.RecreateDirectory(this.TmpPath);

            this.LastSelectedDir = Path.GetDirectoryName(file_name);

            Zip.Decompress(file_name, this.TmpPath);
            this.RestoreRawData(this.TmpPath);
            this.RestoreReferenceSpectra();
            this.RestoreCalibrationSpectra();
            this.RestoreResultData();

            FileIO.DeleteDirectory(this.TmpPath, true);
        }

        private void RestoreResultData()
        {
            string[] result_files = FileIO.GetFiles(this.TmpPath, "*.xml", SearchOption.AllDirectories);
            this.SessionManager.Result = ResultData.Load(result_files);
        }

        private void RestoreReferenceSpectra()
        {
            Dictionary<AcquisitionType, Dictionary<string, Spectrum>> all_specra = new Dictionary<AcquisitionType, Dictionary<string, Spectrum>>();

            all_specra.Add(AcquisitionType.Black, this.SelectSpectraBy(SpectrumType.ExternalEmpty));
            all_specra.Add(AcquisitionType.White, this.SelectSpectraBy(SpectrumType.ExternalWhite));
            all_specra.Add(AcquisitionType.Transflectance, this.SelectSpectraBy(SpectrumType.ExternalFluidWhite));
            all_specra.Add(AcquisitionType.WhiteInsert, this.SelectSpectraBy(SpectrumType.ExternalWhiteInsert));
            all_specra.Add(AcquisitionType.Empty, this.SelectSpectraBy(SpectrumType.SolidSpecimen));

            this.SessionManager = new SessionManager(all_specra[AcquisitionType.Black].Count,
                                                     all_specra[AcquisitionType.White].Count,
                                                     all_specra[AcquisitionType.Transflectance].Count,
                                                     all_specra[AcquisitionType.WhiteInsert].Count);

            this.SessionManager.Operator = this.RestoreOperatorName();
            this.SessionManager.IsRestored = true;

            foreach (AcquisitionType acquisition_type in Enum.GetValues(typeof(AcquisitionType)))
            {
                Dictionary<string, Spectrum> spectra = all_specra[acquisition_type];
                foreach (AcquisitionSession session in this.SessionManager.AllSesstions[acquisition_type].SessionQuue)
                {
                    Dictionary<string, Spectrum> session_spectra = this.SelectSpectraBy(spectra, session.Id);
                    foreach (string serial in session_spectra.Keys)
                        session.AddSpectrum(serial, session_spectra[serial]);

                    if (session.Spectra.Count != session.NumberOfAcquisitions)
                    {
                        throw new Exception(String.Format(Catalog.GetString("Invalid spectra count (Type={0}, Session={1}, Expected={2}, Is={3})"),
                                                              acquisition_type,
                                                              session.Id,
                                                              session.NumberOfAcquisitions,
                                                              session.Spectra.Count));
                    }                       
                }
            }
        }

        private void RestoreCalibrationSpectra()
        {
            Dictionary<string, Spectrum> dark = this.SelectSpectraBy(SpectrumType.InternalDark);
            Dictionary<string, Spectrum> white = this.SelectSpectraBy(SpectrumType.InternalWhite);
            Dictionary<string, Spectrum> oxid = this.SelectSpectraBy(SpectrumType.InternalOxid);

            foreach (CalibrationSession session in this.SessionManager.CalibrationSessions.SessionQuue)
            {
                session.DarkSpectrum = this.SelectSpectraBy(dark, session.Id).Select(x => x.Value).First();
                session.WhiteSpectrum = this.SelectSpectraBy(white, session.Id).Select(x => x.Value).First();
                session.OxidSpectrum = this.SelectSpectraBy(oxid, session.Id).Select(x => x.Value).First();
            }
        }


        private void RestoreRawData(string path)
        {
            this.RestoredRawData = FileIO.GetFiles(path, "*.csv", SearchOption.AllDirectories).
                Select(x => CSV.Read(x)).
                Where(x => x.xIsValidReferenceCheckSpectrum()).
                ToDictionary(x => x.xSerial(), x => x);
        }

        private Dictionary<string, Spectrum> SelectSpectraBy(SpectrumType spectrum_type)
        {
            return this.RestoredRawData.Where(x => x.Value.SpectrumType == spectrum_type).
                       ToDictionary(x => x.Key, x => x.Value);
        }

        private string RestoreOperatorName()
        {
            return this.RestoredRawData.Select(x => x.Value).First().xOperator();
        }

        private Dictionary<string, Spectrum> SelectSpectraBy(Dictionary<string, Spectrum> spectra, int session_id)
        {
            return spectra.Where(x => x.Value.xSessionId() == session_id).
                          OrderBy(x => x.Value.Timestamp).
                          ToDictionary(x => x.Key, x => x.Value);
        }

        private string _tmp_path;
        private string TmpPath
        {
            get
            {
                if (string.IsNullOrEmpty(_tmp_path))
                {
                    string path = Path.GetTempPath();
                    string sub_directory = String.Format("ReferenceCheck_{0}", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
                    _tmp_path = Path.Combine(path, sub_directory);
                }

                return _tmp_path;
            }
        }


        private string output_dir = null;
        private string OutputDir
        {
            get
            {
                if (String.IsNullOrEmpty(this.output_dir))
                    this.output_dir = this.TmpPath;

                return this.output_dir;
            }
        }

        private string LastSelectedDir { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
        private Dictionary<string, Spectrum> RestoredRawData { get; set; } = null;
    }
}
