// Created with MonoDevelop
//
//    Copyright (C) 2014 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.Ports;
using System.Threading;
using Hiperscan.Common;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SpectralEngines
{
    [Obsolete("Only for evaluation")]
    public class ACM : IDisposable
    {
        private SerialPort serial_port;
        public const int DefaultReadTimeout = 1000;

        public ACM(string port)
        {
            try
            {
                this.serial_port = new SerialPort(port)
                {
                    ReadTimeout = ACM.DefaultReadTimeout
                };
                this.serial_port.Open();
            }
            catch (System.IO.IOException)
            {
                // try again 1 s later
                Thread.Sleep(1000);
                this.serial_port = new SerialPort(port);//(port, 9600, Parity.Even, 7, StopBits.Two);
                this.serial_port.ReadTimeout = ACM.DefaultReadTimeout;
                this.serial_port.Open();
            }
        }

        ~ACM()
        {
            if (this.serial_port.IsOpen)
            {
                this.serial_port.Close();
            }
        }

        public void Dispose()
        {
            if (this.serial_port.IsOpen)
            {
                this.serial_port.Close();
            }
            this.serial_port.Dispose();
        }

        public void WriteLine(string s)
        {
            Console.WriteLine("ACM.WriteLine(): " + s);
            this.serial_port.WriteLine(s);
        }

        public string ReadLine()
        {
            string line = this.serial_port.ReadLine();
            Console.WriteLine("ACM.ReadLine(): " + line);
            return line;
        }

        public byte[] Read(int count)
        {
            byte[] buf = new byte[count];

            int read_count = 0;

            Console.WriteLine("Going to read {0} bytes ...", count);
            while (count - read_count > 0)
            {
                byte[] tmp = new byte[count];
                int result = this.serial_port.Read(tmp, 0, count - read_count);

                if (result == 0)
                    throw new Exception(Catalog.GetString("No data available."));

                Console.WriteLine("  bytes {0} to {1}", read_count, read_count + result);
                Array.Copy(tmp, 0, buf, read_count, result);
                read_count += result;
            }

            return buf;
        }

        public int ReadTimeout
        {
            get { return this.serial_port.ReadTimeout; }
            set { this.serial_port.ReadTimeout = value; }
        }
    }

    [Obsolete("for evaluation only")]
    public class NSeries : Hiperscan.SGS.BaseDevice
    {

        private readonly string port;
        private ACM acm;
        private IDevice control_pcb = null;


        public NSeries(DeviceInfo info, VersionInfo version) : base(info, version)
        {
            this.port = "/dev/" + info.SerialNumber;

            using (ACM acm = new ACM(this.port))
            {
                bool success = false;

                for (int ix = 0; ix < 2; ++ix)
                {
                    acm.WriteLine("!");

                    while (acm.Read(1)[0] == '!') { }
                    acm.ReadLine();

                    acm.WriteLine("y");

                    try
                    {
                        string serial = acm.ReadLine().Substring(14).Trim();
                        string min_wavelength = acm.ReadLine().Substring(7).Trim();
                        string max_wavelength = acm.ReadLine().Substring(7).Trim();
                        acm.ReadLine();
                        acm.ReadLine();
                        acm.ReadLine();
                        acm.ReadLine();

                        acm.WriteLine("i");
                        string firmware_info = acm.ReadLine().Trim();
                        string build_date = acm.ReadLine();

                        this.lut_timestamp = DateTime.MinValue;
                        this.firmware_version = firmware_info;

                        this.serial = serial;
                        this.HardwareProperties.MinWavelength = double.Parse(min_wavelength);
                        this.HardwareProperties.MaxWavelength = double.Parse(max_wavelength);
                        this.HardwareProperties.SpectralResolution = 15;
                        this.HardwareProperties.DiffractionAngle = 0;
                        this.HardwareProperties.DynamicRange = 0;
                        this.HardwareProperties.GridConstant = 0;
                        this.HardwareProperties.IncidenceAngle = 0;
                        this.HardwareProperties.MaxAverage = 2000;
                        this.HardwareProperties.Sensitivity = 0;
                        this.HardwareProperties.SignalNoiseRatio = 0;
                        this.HardwareProperties.WavelengthDrift = 0.1;

                        success = true;
                        break;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Exception while initializing device on port {0}: {1}", this.port, ex.Message);
                        Console.WriteLine("Try again ...");
                    }
                }

                if (success)
                    this.SetState(Spectrometer.State.Disconnected);
                else
                    this.SetState(Spectrometer.State.Unknown);
            }
        }

        public override void Open(bool auto_init)
        {
            this.acm = new ACM(this.port);

            if (auto_init == false)
                return;

            string control;

            this.SetState(Spectrometer.State.Init);

            int ix = 0;
            for (int wl = (int)this.HardwareProperties.MinWavelength; wl <= (int)this.HardwareProperties.MaxWavelength; ++wl)
            {
                control = string.Format("W{0},{1}.0", ix++, wl);
                this.acm.WriteLine(control);
                this.acm.ReadLine();
            }

            control = string.Format("W{0},0.0", ix);
            this.acm.WriteLine(control);
            this.acm.ReadLine();

            this.current_config.Average = 15;
            this.current_config.QuickAverage = 1;

            try
            {
                DeviceList.Init(0x9c7);
                DeviceList dev_list = DeviceList.Get();

                if (dev_list.ContainsSerial(this.Info.SerialNumber) && dev_list[this.Info.SerialNumber].Info.DeviceType != DeviceType.NSeries)
                {
                    this.control_pcb = dev_list[this.Info.SerialNumber];
                    this.control_pcb.StateChanged += delegate (IDevice device)
                    {
                        this.SetState(device.GetState());
                    };
                    this.control_pcb.Open();
                    this.is_finder = control_pcb.IsFinder;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while initializing control PCB for device {0}: {1}", this.serial, ex.ToString());
            }

            this.SetState(Spectrometer.State.Idle);
        }

        public override void Close()
        {
            try
            {
                if (this.control_pcb != null)
                    this.control_pcb.Close();
            }
            catch { }

            try
            {
                this.acm.Dispose();
            }
            catch { }

            this.SetState(Spectrometer.State.Disconnected);
        }

        internal override Spectrometer.Config ReadConfig(bool throw_if_invalid)
        {
            throw new NotImplementedException();
        }

        internal override void WriteConfig(Spectrometer.Config config)
        {
            this.SetState(Spectrometer.State.Idle);
            //            throw new System.NotImplementedException ();
        }

        public override void WriteEEPROM()
        {
            throw new System.NotImplementedException();
        }

        public override void StartSingle(bool auto_finder_light)
        {
            Thread run_single = new Thread(new ParameterizedThreadStart(this.DoRunSingle));
            run_single.Start(auto_finder_light);
        }

        public override Spectrum Single(bool auto_finder_light, IdleTaskHandler idle_task)
        {
            try
            {
                Spectrum spectrum = null;

                if (this.GetState() == Spectrometer.State.Continuous ||
                    this.GetState() == Spectrometer.State.Stream ||
                    this.GetState() == Spectrometer.State.QuickStream)
                {
                    while (spectrum == null)
                    {
                        lock (this.current_spectrum_mutex)
                            spectrum = new Spectrum(this.current_spectrum);

                        Thread.Sleep(50);
                    }
                }
                else
                {
                    if (auto_finder_light)
                        this.SetFinderLightSource(true, idle_task);

                    if (!this.SetState(Spectrometer.State.Single))
                        throw new Exception(Catalog.GetString("Device is not ready."));

                    this.ReadSpectrum(this.current_config.Average, out uint[] add_data, out double[] lambda, out double[] intensity, idle_task);

                    this.SetState(Spectrometer.State.Idle);

                    if ((lambda.Length < 2) || (lambda.Length != intensity.Length))
                    {
                        Console.WriteLine("Read spectrum is not valid");
                        this.ExceptionHandler(Catalog.GetString("Read spectrum is invalid."), null);
                        return null;
                    }

                    this.SpectrumCount = 1;

                    WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
                    spectrum = new Spectrum(new DataSet(lambda, intensity), limits)
                    {
                        AddData = add_data
                    };

                    if (auto_finder_light)
                        this.SetFinderLightSource(false, idle_task);
                }

                DateTime now = DateTime.Now;

                spectrum.Serial = this.info.SerialNumber;
                spectrum.FirmwareVersion = this.FirmwareVersion;
                spectrum.LutTimestamp = this.LutTimestamp.ToString(DateTimeFormat.ISO_8601_TIME_ZONE);
                spectrum.SpectralResolution = this.HardwareProperties.SpectralResolution;
                spectrum.AverageCount = this.CurrentConfig.Average;
                spectrum.SpectrumType = SpectrumType.Single;

                if (this.IsFinder)
                    spectrum.ProbeType = ProbeType.FinderStandard;

                spectrum.Timestamp = now;
                spectrum.Id = "1_" + now.ToString("yyyy-MM-dd_HH-mm-ss_") + this.info.SerialNumber;
                spectrum.Label = now.ToString() + " " + this.CurrentConfig.Name;
                spectrum.DefaultLabel = true;
                spectrum.Comment = this.CurrentConfig.DefaultComment;
                spectrum.IsFromFile = false;
                spectrum.Number = (long)this.SpectrumCount;
                spectrum.LimitBandwidth = this.LimitBandwith;

                spectrum.HardwareInfo = this.FinderVersion.ToArray();

                if (this.CurrentConfig.HasAdcCorrection)
                    spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;

                if (this.StateNotification)
                    this.NewSingleNotify(spectrum.Clone());

                return spectrum;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot read single spectrum:") +
                                      " " + ex.Message, ex);
                return null;
            }
        }

        public override void StartQuickStream()
        {
            Thread run_quick = new Thread(new ThreadStart(this.DoRunQuick));
            run_quick.Start();
        }

        public override void StartStream()
        {
            this.quick_stream_running = false;

            switch (this.CurrentConfig.StreamType)
            {

            case Spectrometer.StreamType.Continuous:
                Thread run_continuous = new Thread(new ThreadStart(this.DoRunContinuous));
                run_continuous.Start();
                break;

            case Spectrometer.StreamType.Timed:
                Thread run_timed = new Thread(new ThreadStart(this.DoRunTimed));
                run_timed.Start();
                break;

            default:
                throw new Exception("Unknown StreamType");

            }
        }

        public override void StopStream()
        {
            if (this.quick_stream_running)
            {
                this.SetState(Spectrometer.State.IdleRequest);
                return;
            }

            switch (this.CurrentConfig.StreamType)
            {

            case Spectrometer.StreamType.Continuous:
                throw new NotImplementedException();

            case Spectrometer.StreamType.Timed:
                this.SetState(Spectrometer.State.IdleRequest);
                break;

            default:
                throw new Exception("Unknown StreamType");

            }
        }

        public override double ReadStatus()
        {
            this.acm.WriteLine("I0");
            string temperature = this.acm.ReadLine().Substring(7).Trim();
            return double.Parse(temperature, System.Globalization.CultureInfo.InvariantCulture);
        }

        public override void SetFinderLightSource(bool on, IdleTaskHandler idle_task)
        {
            if (this.control_pcb != null)
                this.control_pcb.SetFinderLightSource(on, idle_task);
            else
                throw new NotSupportedException("Not supported by hardware.");
        }

        public override void SetFinderPosition(int position, IdleTaskHandler idle_task, bool no_wait = false)
        {
            if (this.control_pcb != null)
                this.control_pcb.SetFinderPosition(position, idle_task);
            else
                throw new NotSupportedException("Not supported by hardware.");
        }

        public override void SetFinderFanSpeed(bool on)
        {
            if (this.control_pcb != null)
                this.control_pcb.SetFinderFanSpeed(on);
            else
                throw new NotSupportedException("Not supported by hardware.");
        }

        public override byte ReadDimmerParameter()
        {
            if (this.control_pcb != null)
                return this.control_pcb.ReadDimmerParameter();
            throw new NotSupportedException("Not supported by hardware.");
        }

        public override bool FinderButtonPressed()
        {
            if (this.control_pcb != null)
                return this.control_pcb.FinderButtonPressed();
            throw new NotSupportedException("Not supported by hardware.");
        }

        public override double ReadFinderTemperature()
        {
            if (this.control_pcb != null)
                return this.control_pcb.ReadFinderTemperature();
            throw new NotSupportedException("Not supported by hardware.");
        }

        public override byte[] ReadFinderHardwareVersion()
        {
            if (this.control_pcb != null)
                return this.control_pcb.ReadFinderHardwareVersion();
            else
                throw new NotSupportedException("Not supported by hardware.");
        }

        private void DoRunSingle(object o)
        {
            bool auto_finder_light = (bool)o;

            try
            {
                Spectrum spectrum = this.Single(auto_finder_light);
                this.spectrum_queue.Enqueue(spectrum);
            }
            catch (System.Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot read single spectrum:") +
                                      " " + ex.Message, ex);
            }
        }

        private void DoRunTimed()
        {
            try
            {
                if (this.GetState() == Spectrometer.State.QuickStream)
                {
                    this.StopStream();
                    this.WaitForIdle(5 * this.CurrentConfig.TimeoutMs);
                    this.StartStream();
                    return;
                }

                this.SetFinderLightSource(true, null);

                if (!this.SetState(Spectrometer.State.Stream))
                    throw new Exception(Catalog.GetString("Device is not ready."));

                DateTime then = DateTime.Now;
                this.SpectrumCount = 0;

                while ((this.GetState() == Spectrometer.State.Stream) &&
                       ((this.current_config.TimedCount == 0) ||
                        (this.SpectrumCount < this.CurrentConfig.TimedCount)))
                {
                    try
                    {
                        this.ReadSpectrum(this.current_config.Average, out uint[] add_data, out double[] lambda, out double[] intensity, null);

                        WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
                        Spectrum spectrum = new Spectrum(new DataSet(lambda, intensity), limits)
                        {
                            AddData = add_data,
                            Timestamp = DateTime.Now,
                            Serial = this.Info.SerialNumber,
                            FirmwareVersion = this.FirmwareVersion,
                            LutTimestamp = this.LutTimestamp.ToString(DateTimeFormat.ISO_8601_TIME_ZONE),
                            SpectralResolution = this.HardwareProperties.SpectralResolution,
                            AverageCount = this.CurrentConfig.Average,
                            SpectrumType = SpectrumType.Stream
                        };

                        if (this.IsFinder)
                            spectrum.ProbeType = ProbeType.FinderStandard;

                        spectrum.Id = "5_stream" + this.info.SerialNumber;
                        spectrum.Label = Enum.GetName(typeof(Spectrometer.State), Spectrometer.State.Stream) + " " + this.CurrentConfig.Name;
                        spectrum.DefaultLabel = true;
                        spectrum.Comment = this.CurrentConfig.DefaultComment;
                        spectrum.IsFromFile = false;
                        spectrum.Number = (long)(++this.SpectrumCount);
                        spectrum.LimitBandwidth = this.LimitBandwith;

                        spectrum.HardwareInfo = this.FinderVersion.ToArray();

                        if (this.CurrentConfig.HasAdcCorrection)
                            spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;

                        if (this.StateNotification)
                            this.NewStreamNotify(spectrum.Clone());

                        this.spectrum_queue.Enqueue(spectrum);

                        lock (this.current_spectrum_mutex)
                            this.current_spectrum = spectrum;

                    }
                    catch (Exception ex)
                    {
                        this.ExceptionHandler(Catalog.GetString("Cannot read in timed mode:") +
                                              " " + ex.Message, ex);
                        break;
                    }

                    TimeSpan span;
                    ulong mseconds = 0;
                    ulong maxtime = 0;
                    bool light_is_on = true;

                    while (this.GetState() == Spectrometer.State.Stream)
                    {
                        if (this.CurrentConfig.TimedSpan == 0)
                            break;

                        span = DateTime.Now - then;
                        mseconds = (ulong)span.TotalMilliseconds % (this.CurrentConfig.TimedSpan * 1000);

                        if (mseconds < maxtime)
                            break;
                        else
                            maxtime = mseconds;

                        if ((this.CurrentConfig.TimedCount != 0) &&
                            (this.SpectrumCount >= this.CurrentConfig.TimedCount))
                            break;

                        // switch finder light off if there are more than 3.5 seconds to wait
                        if (this.IsFinder && light_is_on &&
                            this.CurrentConfig.TimedSpan * 1000 - mseconds > 3500)
                        {
                            Console.WriteLine("Next acquisition in {0} ms.",
                                              this.CurrentConfig.TimedSpan * 1000 - mseconds);
                            lock (this)
                            {
                                this.SetState(Spectrometer.State.Idle);
                                this.SetFinderLightSource(false, null);
                                this.SetState(Spectrometer.State.Stream);
                            }
                            light_is_on = false;
                        }

                        Thread.Sleep(50);
                    }

                    if (!light_is_on && this.GetState() == Spectrometer.State.Stream &&
                        (this.SpectrumCount < this.CurrentConfig.TimedCount ||
                         this.CurrentConfig.TimedCount == 0))
                    {
                        lock (this)
                        {
                            this.SetState(Spectrometer.State.Idle);
                            this.SetFinderLightSource(true, null);
                            this.SetState(Spectrometer.State.Stream);
                        }
                    }
                }
                Console.WriteLine("Queue contains still " + this.spectrum_queue.Count + " spectra.");

                Spectrometer.State state = this.GetState();

                if (state == Spectrometer.State.Unknown)
                    return;

                if (state != Spectrometer.State.Disconnected)
                    this.SetState(Spectrometer.State.Idle);

                this.SetFinderLightSource(false, null);
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot read in timed mode:") +
                                      " " + ex.Message, ex);
            }
        }

        private void DoRunQuick()
        {
            try
            {
                if (this.GetState() == Spectrometer.State.Continuous ||
                    this.GetState() == Spectrometer.State.Stream)
                {
                    this.StopStream();
                    this.WaitForIdle(this.CurrentConfig.TimeoutMs);

                    this.StartQuickStream();
                    return;
                }

                Console.WriteLine("Write QuickStream config");
                this.old_config = new Spectrometer.Config(this.CurrentConfig);
                this.CurrentConfig.Average = this.CurrentConfig.QuickAverage;
                this.CurrentConfig.Samples = this.CurrentConfig.QuickSamples;
                this.quick_stream_running = true;
                this.WriteConfig(this.CurrentConfig);

                this.SetFinderLightSource(true, null);

                if (!this.SetState(Spectrometer.State.QuickStream))
                    throw new Exception(Catalog.GetString("Device is not ready."));

                while (this.GetState() == Spectrometer.State.QuickStream)
                {
                    try
                    {
                        this.ReadSpectrum(1, out uint[] add_data, out double[] lambda, out double[] intensity, null);

                        WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
                        Spectrum spectrum = new Spectrum(new DataSet(lambda, intensity), limits)
                        {
                            AddData = add_data,
                            Timestamp = DateTime.Now,
                            Serial = this.Info.SerialNumber,
                            FirmwareVersion = this.FirmwareVersion,
                            LutTimestamp = this.LutTimestamp.ToString(DateTimeFormat.ISO_8601_TIME_ZONE),
                            SpectralResolution = this.HardwareProperties.SpectralResolution,
                            AverageCount = this.CurrentConfig.Average,
                            SpectrumType = SpectrumType.QuickStream,

                            Id = "5_stream" + this.info.SerialNumber,
                            Label = Enum.GetName(typeof(Spectrometer.State), Spectrometer.State.QuickStream) + " " + this.CurrentConfig.Name,
                            DefaultLabel = true,
                            Comment = this.CurrentConfig.DefaultComment,
                            IsFromFile = false,
                            //spectrum.Number       = ++this.SpectrumCount;
                            LimitBandwidth = this.LimitBandwith,

                            HardwareInfo = this.FinderVersion.ToArray()
                        };

                        if (this.CurrentConfig.HasAdcCorrection)
                            spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;

                        if (this.StateNotification)
                            this.NewQuickStreamNotify(spectrum.Clone());

                        this.spectrum_queue.Enqueue(spectrum);

                        lock (this.current_spectrum_mutex)
                            this.current_spectrum = spectrum;

                    }
                    catch (Exception ex)
                    {
                        this.ExceptionHandler(Catalog.GetString("Cannot read in quick-stream mode:") +
                                              " " + ex.Message, ex);
                        break;
                    }
                }

                Console.WriteLine("Queue contains still " + this.spectrum_queue.Count + " spectra.");

                Spectrometer.State state = this.GetState();

                if (state == Spectrometer.State.Unknown)
                    return;

                if (state != Spectrometer.State.Disconnected)
                    this.SetState(Spectrometer.State.Idle);

                this.SetFinderLightSource(false, null);

                this.CurrentConfig = this.old_config;
                this.old_config = null;
                this.WriteConfig(this.CurrentConfig);
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot read in quick-stream mode:") +
                                      " " + ex.ToString(), ex);
            }
        }

        private void DoRunContinuous()
        {
            throw new NotImplementedException();
        }

        private void ReadSpectrum(int average, out uint[] add_data, out double[] lambda, out double[] intensity, IdleTaskHandler idle_task)
        {
            double temperature = this.ReadStatus();

            string control = string.Format("N{0},0.1", average);
            this.acm.WriteLine(control);

            byte[] buf;

            try
            {
                this.acm.ReadTimeout = 60000;
                buf = this.acm.Read(2048);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                this.acm.ReadTimeout = ACM.DefaultReadTimeout;
            }

            this.acm.ReadLine();

            int count = buf.Length / sizeof(float);

            lambda = new double[count];
            intensity = new double[count];

            for (int ix = 0; ix < count; ++ix)
            {
                lambda[ix] = ix + this.HardwareProperties.MinWavelength;
                intensity[ix] = BitConverter.ToSingle(buf, ix * sizeof(float));
            }

            add_data = new uint[3];

            add_data[0] = 0;
            add_data[1] = (uint)(temperature * 100.0);
            add_data[2] = 0;
        }

        public override int FinderPosition
        {
            get { return this.control_pcb.FinderPosition; }
        }

        public override bool FinderLightSourceState
        {
            get { return this.control_pcb.FinderLightSourceState; }
        }
    }
}

