// Created with MonoDevelop
//
//    Copyright (C) 2018 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.Threading;
using System.Collections.Generic;

using Hiperscan.Common;
using Hiperscan.Unix;


namespace Hiperscan.SGS.Benchtop.FinderSetup
{

    public static class SampleSpinner
    {
        private enum SampleSpinnerPositionType
        {
            Unknown,
            BlackReference,
            WhiteReference
        }
        
        public static readonly sbyte  SAMPLE_SPINNER_VMAX = sbyte.Parse(Environment.GetEnvironmentVariable(Env.Finder.SAMPLE_SPINNER_VMAX) ?? "16");
        public static readonly sbyte  SAMPLE_SPINNER_VREF = sbyte.Parse(Environment.GetEnvironmentVariable(Env.Finder.SAMPLE_SPINNER_VREF) ?? "12");
        public static readonly sbyte  SAMPLE_SPINNER_VMIN = sbyte.Parse(Environment.GetEnvironmentVariable(Env.Finder.SAMPLE_SPINNER_VMIN) ?? "1");
        public static readonly uint   SAMPLE_SPINNER_INIT_TIME = uint.Parse(Environment.GetEnvironmentVariable(Env.Finder.SAMPLE_SPINNER_INIT_TIME) ?? "4000");
        public static readonly ushort SAMPLE_SPINNER_REF_ANGLE = ushort.Parse(Environment.GetEnvironmentVariable(Env.Finder.SAMPLE_SPINNER_REF_ANGLE) ?? "180");

        private static Dictionary<string,SampleSpinnerPositionType> _sample_spinner_positions = new Dictionary<string,SampleSpinnerPositionType>();


        public static bool MoveToBlackReference(IDevice dev)
        {
            if (dev.FinderVersion.Peripheral != FinderPeripheralType.SampleSpinner ||
                Environment.GetEnvironmentVariable(Env.Finder.NO_SAMPLE_SPINNER) == "1")
            {
                return false;
            }

            dev.WaitForIdle(10000);
            dev.StartSampleSpinner(SAMPLE_SPINNER_VMAX);
            Thread.Sleep((int)SAMPLE_SPINNER_INIT_TIME);
            dev.StopSampleSpinner();

            SampleSpinner.SetSampleSpinnerPosition(dev, SampleSpinnerPositionType.BlackReference);

            return true;
        }

        public static bool MoveToWhiteReference(IDevice dev)
        {
            if (dev.FinderVersion.Peripheral != FinderPeripheralType.SampleSpinner || Environment.GetEnvironmentVariable(Env.Finder.NO_SAMPLE_SPINNER) == "1")
                return false;

            if (SampleSpinner.GetSampleSpinnerPosition(dev) == SampleSpinnerPositionType.WhiteReference)
                return true;

            if (SampleSpinner.GetSampleSpinnerPosition(dev) != SampleSpinnerPositionType.BlackReference)
                throw new Exception(Catalog.GetString("Cannot move to white reference from unknown position."));

            dev.WaitForIdle(10000);
            dev.RotateSampleSpinner((sbyte)(-SAMPLE_SPINNER_VREF), SAMPLE_SPINNER_REF_ANGLE);

            SampleSpinner.SetSampleSpinnerPosition(dev, SampleSpinnerPositionType.WhiteReference);

            return true;
        }

        public static bool Start(IDevice dev, ushort average)
        {
            if (dev.FinderVersion.Peripheral != FinderPeripheralType.SampleSpinner || Environment.GetEnvironmentVariable(Env.Finder.NO_SAMPLE_SPINNER) == "1")
                return false;
            
            // measured times vs. velocity/average vs. time
            // t      V       A     t
            // 3,01   -16     50    1
            // 4,04   -15     100   1,3
            // 5,5    -14     200   2
            // 6,5    -13     300   2,7
            // 7,5    -12     400   3,4
            // 8,2    -11     500   4,06
            // 9      -10     800   6,07
            // 9,8    -9      1000  7,5
            // 10,8   -8      1500  10,9
            // 12,15  -7      2000  14,26
            // 13,04  -6          
            // 14     -5          
            // 15,1   -4          
            // 17,8   -3          
            // 22     -2          
            // 30     -1          
            // angular velocity parameter for a scan frequency of 150Hz (oscillation frequency of MEMS)
            // velocity over time for one rotation can be approximated by       v = 1.02*t-19.3
            // time over average can be approximated by                         t = 0.0068*a+0.648
            // to be on the safe side (MEMS timing), we multiply time with 0.9: v = 1.02*0.9*(0.0068*a+0.648)-19.3
            sbyte angular_velocity = (sbyte)Math.Round(1.02*0.95*(0.0068*average+0.648)-19.3);

            if (Math.Abs(angular_velocity) < SAMPLE_SPINNER_VMIN) angular_velocity = (sbyte)(SAMPLE_SPINNER_VMIN * Math.Sign(angular_velocity));
            if (Math.Abs(angular_velocity) > SAMPLE_SPINNER_VMAX) angular_velocity = (sbyte)(SAMPLE_SPINNER_VMAX * Math.Sign(angular_velocity));

            dev.StartSampleSpinner(angular_velocity);
            SampleSpinner.SetSampleSpinnerPosition(dev, SampleSpinnerPositionType.Unknown);

            return true;
        }

        public static bool Stop(IDevice dev)
        {
            if (dev.FinderVersion.Peripheral != FinderPeripheralType.SampleSpinner || Environment.GetEnvironmentVariable(Env.Finder.NO_SAMPLE_SPINNER) == "1")
                return false;

            dev.StopSampleSpinner();

            return true;
        }

        private static SampleSpinnerPositionType GetSampleSpinnerPosition(IDevice dev)
        {
            if (SampleSpinner._sample_spinner_positions.ContainsKey(dev.Info.SerialNumber) == false)
                return SampleSpinnerPositionType.Unknown;

            return SampleSpinner._sample_spinner_positions[dev.Info.SerialNumber];
        }

        private static void SetSampleSpinnerPosition(IDevice dev, SampleSpinnerPositionType position)
        {
            if (SampleSpinner._sample_spinner_positions.ContainsKey(dev.Info.SerialNumber))
                SampleSpinner._sample_spinner_positions[dev.Info.SerialNumber] = position;
            else
                SampleSpinner._sample_spinner_positions.Add(dev.Info.SerialNumber, position);
        }
    }
}
