// // 
// // (c) 2010 eedv holger@eiboeck.de
// // 
using System;
using System.Threading;
using System.Timers;
using Modbus.Utility;
using Timer = System.Timers.Timer;

namespace KaltgasTool
{
    public class Controller : IDisposable
    {
        public const float Mintemp = -196F;
        public const float Maxtemp = 200;
        public const int MinJet = 0;
        public const int MaxJet = 100;
        public const int MainTimerInterval = 500;
        public const float ParameterSphMax = 120;

        private static readonly Timer TimerDataAquire = new Timer();
        private bool _alarm;

        private float? _analogeingang1;
        private float? _analogeingang2;
        private ushort? _binaerausgaenge110;
        private bool _computerJet;
        private int? _cpr;
        private int? _currentJet;
        private ushort[] _disuDisl;
        private ushort[] _inp12;
        private ushort[] _splsph;
        private bool _isChamber;
        private ushort _out12;
        private bool _parameterAlreadyRead;

        private bool _power;
        private float? _sollwertW1;
        private ushort? _sphMax;
        private ushort? _grenzwertAlarm;

        private Device _mbus;

        public Controller(string controllerPort, int controllerBaudRate, int controllerDataBits, int controllerParity,
                          int controllerStopBits)
        {
            TimerDataAquire.Elapsed += timerHandler;
            TimerDataAquire.Interval = MainTimerInterval;

            try
            {
                _mbus =
                    new Device(DeviceFactory.Create(controllerPort, controllerBaudRate, controllerDataBits,
                                                                controllerParity,
                                                                controllerStopBits));
            }
            catch (ArgumentNullException)
            {
            }
            updateValues();
            TimerDataAquire.Enabled = true;
        }

        public bool IsControllingChamber
        {
            get
            {
                using (new TimerLock(TimerDataAquire))
                {
                    _isChamber = _mbus.ReadInt((ushort) JumoDTron.Register.ParametersatzUmschaltung) == 2;
                }
                return _isChamber;
            }
            set
            {
                CPr = value ? 2 : 1;

                if (value == _isChamber)
                    return;

                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteInt((ushort) JumoDTron.Register.ParametersatzUmschaltung,
                                   (ushort) (value ? 2 : 1));
                }

                _isChamber = value;
            }
        }

        public bool IsFound { get; private set; }

        public bool IsHeaterOn
        {
            get { return bitTest(_binaerausgaenge110, JumoDTron.Binaerausgang1Mask); }
        }

        public int CPr
        {
            get
            {
                using (new TimerLock(TimerDataAquire))
                {
                    using (new Device.ParameterPage(_mbus))
                    {
                        _cpr = _mbus.ReadInt((ushort) JumoDTron.Register.ParameterCprAnalogSelector);
                    }
                    return getLByte(_cpr);
                }
            }

            set
            {
                if (_cpr == null)
                {
#pragma warning disable 168
                    var initializeGlobalBaseValue = CPr;
#pragma warning restore 168
                }

                if (getLByte(_cpr) == value)
                    return;

                ushort writeVal = (ushort) (value*256 + getUByte(_cpr));
                using (new TimerLock(TimerDataAquire))
                {
                    using (new Device.ParameterPage(_mbus))
                    {
                        _mbus.WriteInt((ushort) JumoDTron.Register.ParameterCprAnalogSelector, writeVal);
                    }
                }
                _cpr = writeVal;
            }
        }

        public float? Sollwert
        {
            get { return _sollwertW1; }
            set
            {
                if (_mbus == null)
                    return;

                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteFloat((ushort) JumoDTron.Register.SollwertW1, value.GetValueOrDefault());
                }
                _sollwertW1 = value;
            }
        }

        public float? Analogeingang1
        {
            get { return _analogeingang1; }
        }

        public float? Analogeingang2
        {
            get { return _analogeingang2; }
        }

        public bool IsComputerJet
        {
            get { return _computerJet; }
            set
            {
                _computerJet = value;
                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteInt((ushort) Leistungssteller.Register.ComputerJet,
                                   (value ? (ushort) 1 : (ushort) 0),
                                   Leistungssteller.ModBusAddress);
                }

                Thread.Sleep(Device.WaitControllerMs);
            }
        }

        public int? Jet
        {
            get { return _currentJet; }
            set
            {
                if (value == _currentJet || value < 0) return;

                _currentJet = value;
                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteInt((ushort) Leistungssteller.Register.Jet, (ushort) value.GetValueOrDefault(),
                                   Leistungssteller.ModBusAddress);
                }
            }
        }

        public bool IsPower
        {
            get { return _power; }
            set
            {
                _power = value;
                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteInt((ushort) Leistungssteller.Register.Masterrelais,
                                   (ushort) ((value) ? 1 : 0),
                                   Leistungssteller.ModBusAddress);
                }
            }
        }

        public bool IsAlarm
        {
            get { return _alarm; }
            set
            {
                _alarm = value;
                using (new TimerLock(TimerDataAquire))
                {
                    _mbus.WriteInt((ushort) Leistungssteller.Register.Hilfsrelais, (ushort) (value ? 1 : 0),
                                   Leistungssteller.ModBusAddress);
                }
            }
        }

        public int? ParameterDisl
        {
            get
            {
                readParameter();
                return getUByte(_disuDisl[0]);
            }
            set
            {
                if (_disuDisl == null)
                    return;

                int oldDisl = getUByte(_disuDisl[0]);

                if (value != oldDisl)
                    writeDualValue(JumoDTron.Register.ParameterDisuDisl, value, ref _disuDisl[0], 0);
            }
        }


        public int? ParameterDisu
        {
            get
            {
                readParameter();
                return getLByte(_disuDisl[0]);
            }
            set
            {
                if (_disuDisl == null)
                    return;

                int oldDisl = getLByte(_disuDisl[0]);

                if (value != oldDisl)
                    writeDualValue(JumoDTron.Register.ParameterDisuDisl, value, ref _disuDisl[0], 1);
            }
        }

        public int? ParameterDecP
        {
            get
            {
                readParameter();
                return getLByte(_disuDisl[1]);
            }
            set
            {
                if (value == null)
                    return;

                var bytes = BitConverter.GetBytes(_disuDisl[1]);
                bytes[1] = (byte) value;
                _mbus.ModifyParameterInt(ref _disuDisl, 1, BitConverter.ToUInt16(bytes, 0));
                using (new TimerLock(TimerDataAquire))
                {
                    using (new Device.ParameterPage(_mbus))
                    {
                        _mbus.WriteBlock((ushort) JumoDTron.Register.ParameterDisuDisl, _disuDisl);
                    }
                }
            }
        }

        public int ParameterOut1
        {
            get
            {
                readParameter();

                var out12 = BitConverter.GetBytes(_out12);
                return out12[1];
            }

            set { writeDualValue(JumoDTron.Register.ParameterOut12, value, ref _out12, 1); }
        }

        public int ParameterOut2
        {
            get
            {
                readParameter();

                var out12 = BitConverter.GetBytes(_out12);
                return out12[0];
            }
            set { writeDualValue(JumoDTron.Register.ParameterOut12, value, ref _out12, 0); }
        }

        public float ParameterSpl
        {
            get
            {
                Logger.Log.DebugFormat("Controller ParameterSpl get Started");
                readParameter();

                Logger.Log.DebugFormat("Controller ParameterSpl get ParameterRead");

                float value;
                using (new TimerLock(TimerDataAquire))
                {
                    value = _mbus.ParseParameterFloat(_splsph[0], _splsph[1]);
                }
                Logger.Log.DebugFormat("Controller ParameterSpl get Value decoded");
                return scaleTemp(value);
            }
            set { setParamFloatValue(scaleTemp(value), 0, (ushort) JumoDTron.Register.ParameterSplSph, ref _splsph); }
        }

        public float ParameterSph
        {
            get
            {
                readParameter();
                float value = _mbus.ParseParameterFloat(_splsph[2], _splsph[3]);
                if (value > ParameterSphMax)
                    value = ParameterSphMax;
                return scaleTemp(value);
            }
            set
            {
                if (value > ParameterSphMax)
                    value = ParameterSphMax;

                setParamFloatValue(scaleTemp(value), 2, (ushort) JumoDTron.Register.ParameterSplSph, ref _splsph);
            }
        }


        public int? ParameterUnit
        {
            get
            {
                readParameter();
                return getLByte(_inp12[0]);
            }
            set
            {
                if (value == null)
                    return;

                if (_inp12 == null)
                    readParameter();

                _mbus.ModifyParameterByte(ref _inp12, 0, true, (byte) value);
                using (new TimerLock(TimerDataAquire))
                {
                    using (new Device.ParameterPage(_mbus))
                    {
                        _mbus.WriteBlock((ushort) JumoDTron.Register.ParameterInp12, _inp12);
                    }
                }
            }
        }

        public int? ParameterLinearization1
        {
            get
            {
                readParameter();
                return getUByte(_inp12[2]);
            }

            set { writeInp12ParamByte(value, 2, false); }
        }

        public int? ParameterSensor1
        {
            get
            {
                readParameter();
                return getLByte(_inp12[3]);
            }
            set { writeInp12ParamByte(value, 3, true); }
        }

        public float ParameterOffset1
        {
            get
            {
                readParameter();
                return _mbus.ParseParameterFloat(_inp12[8], _inp12[9]);
            }
            set { setParamFloatValue(value, 8, (ushort) JumoDTron.Register.ParameterInp12, ref _inp12); }
        }

        public int? ParameterLinearization2
        {
            get
            {
                readParameter();
                return getUByte(_inp12[14]);
            }
            set { writeInp12ParamByte(value, 14, false); }
        }

        public int? ParameterSensor2
        {
            get
            {
                readParameter();
                return getLByte(_inp12[14]);
            }
            set { writeInp12ParamByte(value, 14, true); }
        }

        public float? ParameterOffset2
        {
            get
            {
                readParameter();
                if (_inp12 == null)
                    return null;

                var a = BitConverter.GetBytes(_inp12[19]);
                var b = BitConverter.GetBytes(_inp12[20]);
                var c = BitConverter.GetBytes(_inp12[21]);

                return ModbusUtility.GetSingle((ushort) (c[1]*256 + b[0]), (ushort) (b[1]*256 + a[0]));
            }
            set
            {
                if (_inp12 == null)
                    readParameter();

                if (_inp12 == null)
                    return;
                var registers = _mbus.GetFloatBytes(value.GetValueOrDefault());

                var a = BitConverter.GetBytes(registers[0]);
                var b = BitConverter.GetBytes(registers[1]);

// ReSharper disable PossibleNullReferenceException
                var byteA = BitConverter.GetBytes(_inp12[19]);
// ReSharper restore PossibleNullReferenceException
                var byteC = BitConverter.GetBytes(_inp12[21]);

                _inp12[19] = (ushort) (byteA[1]*256 + a[0]);
                _inp12[20] = (ushort) (a[1]*256 + b[0]);
                _inp12[21] = (ushort) (b[1]*256 + byteC[0]);

                using (new TimerLock(TimerDataAquire))
                {
                    using (new Device.ParameterPage(_mbus))
                    {
                        _mbus.WriteBlock((ushort) JumoDTron.Register.ParameterInp12, _inp12);
                    }
                }
            }
        }

        public bool IsGrenzwertalarmHeater
        {
            get { return _grenzwertAlarm == 2 || _grenzwertAlarm == 3; }
        }

        public bool IsGrenzwertalarmJet
        {
            get { return _grenzwertAlarm == 1 || _grenzwertAlarm == 3; }
        }

        public bool IsAutotune
        {
            get
            {
                using (new TimerLock(TimerDataAquire))
                {
                    return _mbus.ReadInt((ushort) JumoDTron.Register.ReglerStatus) == 0x8000;
                }
            }
            set
            {
                using (new TimerLock(TimerDataAquire))
                {
                    var registerValue = _mbus.ReadInt((ushort) JumoDTron.Register.BinaerfunktionenRegler);
                    if (registerValue.HasValue)
                    {
                        ushort oldValue = registerValue.Value;

                        registerValue = (ushort) (oldValue | (value ? 0x0080 : 0x2000));
                        // selbstoptimierung start oder abbruch
                        _mbus.WriteInt((ushort) JumoDTron.Register.BinaerfunktionenRegler, registerValue.Value);
                        Thread.Sleep(Device.WaitControllerMs);

                        _mbus.WriteInt((ushort) JumoDTron.Register.BinaerfunktionenRegler, oldValue);
                    }
                }
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            TimerDataAquire.Enabled = false;
            TimerDataAquire.Stop();
            TimerDataAquire.Dispose();
        }

        #endregion

        private void timerHandler(object sender, ElapsedEventArgs e)
        {
            updateValues();
        }

        private void updateValues()
        {
            try
            {
                using (new TimerLock(TimerDataAquire))
                {
                    Logger.Log.InfoFormat("Controller: updating input Values");
                    
                    _analogeingang1 = _mbus.ReadFloat((ushort) JumoDTron.Register.Analogeingang1Temp);
                    _analogeingang2 = _mbus.ReadFloat((ushort) JumoDTron.Register.Analogeingang2Temp);
                    _sollwertW1 = _mbus.ReadFloat((ushort) JumoDTron.Register.SollwertW1);
                    _binaerausgaenge110 = _mbus.ReadInt((ushort) JumoDTron.Register.Binaerausgaenge1Bis10);
                    _currentJet = _mbus.ReadInt((ushort) Leistungssteller.Register.Jet,
                                                Leistungssteller.ModBusAddress);
                    _computerJet =
                        (_mbus.ReadInt((ushort) Leistungssteller.Register.ComputerJet,
                                       Leistungssteller.ModBusAddress) ==
                         1);
                    _power =
                        _mbus.ReadInt((ushort) Leistungssteller.Register.Masterrelais,
                                      Leistungssteller.ModBusAddress) ==
                        1;

                    _alarm =
                        _mbus.ReadInt((ushort) Leistungssteller.Register.Hilfsrelais,
                                      Leistungssteller.ModBusAddress) ==
                        1;
                    _grenzwertAlarm = _mbus.ReadInt((ushort) Leistungssteller.Register.Grenzwertalarm,
                                                    Leistungssteller.ModBusAddress);
                }
                IsFound = true;
            }
            catch
            {
                Logger.Log.DebugFormat("Controller: updateValues Exception");
                //throw new ControllerTimeoutException();
            }
        }

        public void Init(string controllerPort, int controllerBaudRate, int controllerDataBits, int controllerParity,
                         int controllerStopBits)
        {
            using (new TimerLock(TimerDataAquire))
            {
                try
                {
                    _mbus =
                        new Device(DeviceFactory.Create(controllerPort, controllerBaudRate,
                                                                    controllerDataBits, controllerParity,
                                                                    controllerStopBits));

                    _sphMax = _mbus.ReadInt((ushort) Leistungssteller.Register.Eeprom,
                                            Leistungssteller.ModBusAddress);
                }
                catch
                {
                    IsFound = false;
                    return;
                }

                IsFound = true;

                // lese spl/sph
                using (new Device.ParameterPage(_mbus))
                {
                    _splsph = _mbus.ReadBlock((ushort) JumoDTron.Register.ParameterSplSph, 4);
                }
            }
        }

        private static bool bitTest(ushort? value, ushort bit)
        {
            if (value == null)
                return false;
            return ((value & bit) == bit);
        }

        private void writeDualValue(JumoDTron.Register address, int? value, ref ushort previousValue, int offset)
        {
            if (value == null)
                return;

            var bytes = BitConverter.GetBytes(previousValue);
            bytes[offset] = (byte) value.Value;

            ushort newValue = BitConverter.ToUInt16(bytes, 0);
            using (new TimerLock(TimerDataAquire))
            {
                using (new Device.ParameterPage(_mbus))
                {
                    _mbus.WriteInt((ushort) address, newValue);
                }
            }
            previousValue = newValue;
        }

        public void SendDefaultParameters()
        {
            // Set Standard Params
            ParameterDecP = 1; // DECP 0.1
            ParameterDisu = 1; // DISU Analog Input 1
            ParameterDisl = 2; // DISL Analog Input 2
            ParameterSpl = Mintemp; // SPL = Mintemp
            ParameterSph = 120; // SPH
            ParameterUnit = 0; // UNIT Celsius
            ParameterOffset1 = 0; // OFFS1 = 0
            ParameterOffset2 = 0; // OFFS2 = 0
            ParameterLinearization1 = 1; // Lin1 = Pt100
            ParameterSensor1 = 1; // SENS1 = 3 wire
            ParameterLinearization2 = 1; // Lin2 = Pt100
            ParameterSensor2 = 1; // SENS2 = 3 wire
            ParameterOut1 = 1;
            ParameterOut2 = 0;
            Logger.Log.InfoFormat("Controller Default Parameters sent!");
        }

        private void writeInp12ParamByte(int? value, int offset, bool byteZero)
        {
            if (value == null)
                return;

            if (_inp12 == null)
                readParameter();

            _mbus.ModifyParameterByte(ref _inp12, offset, byteZero, (byte) value);
            using (new TimerLock(TimerDataAquire))
            {
                using (new Device.ParameterPage(_mbus))
                {
                    _mbus.WriteBlock((ushort) JumoDTron.Register.ParameterInp12, _inp12);
                }
            }
        }

        private void setParamFloatValue(float value, int offset, ushort address, ref ushort[] paramBlock)
        {
            _mbus.ModifyParameterFloat(ref paramBlock, offset, value);
            using (new TimerLock(TimerDataAquire))
            {
                using (new Device.ParameterPage(_mbus))
                {
                    _mbus.WriteBlock(address, paramBlock);
                }
            }
        }

        private static int getLByte(int? value)
        {
            var bytes = BitConverter.GetBytes(value.GetValueOrDefault());
            return bytes[1];
        }

        private static int getUByte(int? value)
        {
            var bytes = BitConverter.GetBytes(value.GetValueOrDefault());
            return bytes[0];
        }

        private void readParameter()
        {
            if (_parameterAlreadyRead) return;

            using (new TimerLock(TimerDataAquire))
            {
                using (new Device.ParameterPage(_mbus))
                {
                    _disuDisl = _mbus.ReadBlock((ushort) JumoDTron.Register.ParameterDisuDisl, 2);
                    if (_disuDisl == null)
                        return;

                    _out12 = _mbus.ReadInt((ushort) JumoDTron.Register.ParameterOut12).GetValueOrDefault();
                    _splsph = _mbus.ReadBlock((ushort) JumoDTron.Register.ParameterSplSph, 4);
                    _inp12 = _mbus.ReadBlock((ushort) JumoDTron.Register.ParameterInp12, 26);
                }
                _sphMax = _mbus.ReadInt((ushort) Leistungssteller.Register.Eeprom,
                                        Leistungssteller.ModBusAddress);
            }
            _parameterAlreadyRead = true;
        }

        private static float scaleTemp(float value)
        {
            if (value > Maxtemp)
                value = Maxtemp;
            if (value < Mintemp)
                value = Mintemp;
            return value;
        }

        public static bool IsValidTemp(string text)
        {
            float theValue;
            return float.TryParse(text, out theValue) &&
                   (theValue >= Mintemp && theValue <= Maxtemp);
        }

        public void UpdateParameterValues()
        {
            _parameterAlreadyRead = false;
        }

        public JumoDTron.AutotuneParameters ReadAutotuneParameterSet()
        {
            using (new TimerLock(TimerDataAquire))
            {
                return new JumoDTron.AutotuneParameters
                           {
                               ParameterSet1 = readParameterSet((int) JumoDTron.Register.RegelparameterSet1),
                               ParameterSet2 = readParameterSet((int) JumoDTron.Register.RegelparameterSet2)
                           };
            }
        }

        private JumoDTron.ParameterSet readParameterSet(int address)
        {
            var s = new SequentialParameter(_mbus, address);
            return new JumoDTron.ParameterSet
                       {
                           Pb1 = s.ReadNextFloat(),
                           Pb2 = s.ReadNextFloat(),
                           Dt = s.ReadNextInt(),
                           Rt = s.ReadNextInt(),
                           Cy1 = s.ReadNextFloat(),
                           Cy2 = s.ReadNextFloat(),
                           Db = s.ReadNextFloat(),
                           HyS1 = s.ReadNextFloat(),
                           HyS2 = s.ReadNextFloat(),
                           Tt = s.ReadNextInt(),
                           Y0 = s.ReadNextInt(),
                           Y1 = s.ReadNextInt(),
                           Y2 = s.ReadNextInt(),
                       };
        }

        public void WriteAutotuneParameterSet(JumoDTron.AutotuneParameters values)
        {
            using (new TimerLock(TimerDataAquire))
            {
                writeParameterSet((int) JumoDTron.Register.RegelparameterSet1, values.ParameterSet1);
                writeParameterSet((int) JumoDTron.Register.RegelparameterSet2, values.ParameterSet2);
            }
        }

        private void writeParameterSet(int address, JumoDTron.ParameterSet value)
        {
            var s = new SequentialParameter(_mbus, address);
            s.WriteNext(value.Pb1);
            s.WriteNext(value.Pb2);
            s.WriteNext(value.Dt);
            s.WriteNext(value.Rt);
            s.WriteNext(value.Cy1);
            s.WriteNext(value.Cy2);
            s.WriteNext(value.Db);
            s.WriteNext(value.HyS1);
            s.WriteNext(value.HyS2);
            s.WriteNext(value.Tt);
            s.WriteNext(value.Y0);
            s.WriteNext(value.Y1);
            s.WriteNext(value.Y2);
        }

        #region SequentialParameter Helper Class

        private class SequentialParameter
        {
            private int _address;
            private readonly IDevice _mbus;

            public SequentialParameter(IDevice mbus, int baseAddress)
            {
                _address = baseAddress;
                _mbus = mbus;
            }

            public float ReadNextFloat()
            {
                float value = _mbus.ReadFloat((ushort)_address).GetValueOrDefault();
                _address += 2;
                return value;
            }

            public int ReadNextInt()
            {
                ushort value = _mbus.ReadInt((ushort)_address).GetValueOrDefault();
                _address += 1;
                return value;
            }

            public void WriteNext(float value)
            {
                _mbus.WriteFloat((ushort)_address, value);
                _address += 2;
            }

            public void WriteNext(int value)
            {
                _mbus.WriteInt((ushort)_address, (ushort)value);
                _address += 1;
            }
        }

        #endregion
    }
}