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

namespace KaltgasTool
{
    ///<summary>
    /// LowLevel ModBus helper library
    ///</summary>
    public class Device : IDevice
    {
        private static IModbusSerialMaster _device;
        private static byte _slaveId;
        private static byte _lastSlaveId;

        public const int WaitControllerMs = 50;

        public Device(IModbusSerialMaster device, byte slaveId = (byte) 1)
        {
            if (device == null) throw new ArgumentNullException("device");
            _device = device;
            _slaveId = slaveId;
        }

        private static void guard(byte slaveId)
        {
            if (_lastSlaveId == slaveId) return;
            Thread.Sleep(WaitControllerMs);
            _lastSlaveId = slaveId;
        }

        /// <summary>
        /// Reads a floating point value from a ModBus device
        /// </summary>
        /// <param name="address">Address of the Parameter</param>
        /// <returns>IEEE 32 floating point value</returns>
        public float? ReadFloat(ushort address)
        {
            if (_device == null)
                return null;

            guard(_slaveId);

            const ushort numRegisters = 2;
            var registers = _device.ReadInputRegisters(_slaveId, address, numRegisters);
            return ModbusUtility.GetSingle(registers[1], registers[0]);
        }

        ///<summary>
        /// Reads a floating point value from a ModBus device
        ///</summary>
        ///<param name="address"></param>
        ///<returns>IEEE 32 floating point value</returns>
        public float? ReadParameterFloat(ushort address)
        {
            if (_device == null)
                return null;

            guard(_slaveId);

            const ushort numRegisters = 2;
            var registers = _device.ReadInputRegisters(_slaveId, address, numRegisters);
            var a = BitConverter.GetBytes(registers[0]);
            var b = BitConverter.GetBytes(registers[1]);

            return ModbusUtility.GetSingle((ushort) (b[0]*256 + b[1]), (ushort) (a[0]*256 + a[1]));
        }

        ///<summary>
        /// Converts the two values to a IEEE 32 floating point value
        ///</summary>
        ///<param name="p1">value1</param>
        ///<param name="p2">value2</param>
        ///<returns>IEEE 32 floating point value</returns>
        public float ParseParameterFloat(ushort p1, ushort p2)
        {
            var a = BitConverter.GetBytes(p1);
            var b = BitConverter.GetBytes(p2);

            return ModbusUtility.GetSingle((ushort) (b[0]*256 + b[1]), (ushort) (a[0]*256 + a[1]));
        }


        ///<summary>
        /// Reads a byte block from a ModBus device
        ///</summary>
        ///<param name="address"></param>
        ///<param name="numRegisters"></param>
        ///<returns></returns>
        public ushort[] ReadBlock(ushort address, ushort numRegisters)
        {
            if (_device == null)
                return null;

            guard(_slaveId);
            return _device.ReadInputRegisters(_slaveId, address, numRegisters);
        }

        ///<summary>
        /// Writes a byte block from a ModBus device
        ///</summary>
        ///<param name="startAddress"></param>
        ///<param name="values"></param>
        public void WriteBlock(ushort startAddress, ushort[] values)
        {
            if (_device == null)
                return;

            guard(_slaveId);
            _device.WriteMultipleRegisters(_slaveId, startAddress, values);
        }

        /// <summary>
        /// Reads a integer value from a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <returns>Integer Value</returns>
        public ushort? ReadInt(ushort startAddress)
        {
            return ReadInt(startAddress, _slaveId);
        }

        /// <summary>
        /// Reads a integer value from a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <param name="slaveId">Slave Id if Device to read from</param>
        /// <returns>Integer Value</returns>
        public ushort? ReadInt(ushort startAddress, byte slaveId)
        {
            if (_device == null)
                return null;

            guard(slaveId);

            const ushort numRegisters = 1;
            var registers = _device.ReadInputRegisters(slaveId, startAddress, numRegisters);
            return registers[0];
        }

        /// <summary>
        /// Write to a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <param name="value">value to be written</param>
        public void WriteInt(ushort startAddress, ushort value)
        {
            WriteInt(startAddress, value, _slaveId);
        }

        /// <summary>
        /// Write to a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <param name="value">value to be written</param>
        /// <param name="slaveId">Slave Id if Device to write to</param>
        public void WriteInt(ushort startAddress, ushort value, byte slaveId)
        {
            if (_device == null)
                return;

            guard(slaveId);
            _device.WriteSingleRegister(slaveId, startAddress, value);
        }

        /// <summary>
        /// Write to a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <param name="value">float to be written</param>
        public void WriteFloat(ushort startAddress, float value)
        {
            if (_device == null)
                return;

            guard(_slaveId);
            _device.WriteMultipleRegisters(_slaveId, startAddress, GetFloatBytes(value));
        }

        ///<summary>
        ///
        ///</summary>
        ///<param name="startAddress">Address of the Parameter</param>
        ///<param name="value">value to be written</param>
        public void WriteParameterFloat(ushort startAddress, float value)
        {
            if (_device == null)
                return;

            guard(_slaveId);
            var registers = GetFloatBytes(value);

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

            ushort[] reg2 = {(ushort) (a[0]*256 + a[1]), (ushort) (b[0]*256 + b[1])};
            _device.WriteMultipleRegisters(_slaveId, startAddress, reg2);
        }

        ///<summary>
        ///
        ///</summary>
        ///<param name="field"></param>
        ///<param name="offset"></param>
        ///<param name="value"></param>
        public void ModifyParameterFloat(ref ushort[] field, int offset, float value)
        {
            var registers = GetFloatBytes(value);

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

            field[offset] = (ushort) (a[0]*256 + a[1]);
            field[offset + 1] = (ushort) (b[0]*256 + b[1]);
        }

        ///<summary>
        ///
        ///</summary>
        ///<param name="field"></param>
        ///<param name="offset"></param>
        ///<param name="byteZero"></param>
        ///<param name="value"></param>
        public void ModifyParameterByte(ref ushort[] field, int offset, bool byteZero, byte value)
        {
            var bytes = BitConverter.GetBytes(field[offset]);

            if (byteZero)
                field[offset] = (ushort) (value*256 + bytes[0]);
            else
                field[offset] = (ushort) (bytes[1]*256 + value);
        }

        ///<summary>
        ///
        ///</summary>
        ///<param name="field"></param>
        ///<param name="offset"></param>
        ///<param name="value"></param>
        public void ModifyParameterInt(ref ushort[] field, int offset, int value)
        {
            field[offset] = (ushort) value;
        }

        /// <summary>
        /// Write to a ModBus device
        /// </summary>
        /// <param name="startAddress">Address of the Parameter</param>
        /// <param name="valueString">string containing a float value to be written</param>
        /// <exception cref="ArgumentOutOfRangeException"><c>valueString</c> is out of range.</exception>
        public void WriteFloat(ushort startAddress, string valueString)
        {
            if (_device == null)
                return;

            guard(_slaveId);
            float value;
            if (!float.TryParse(valueString, out value))
                throw new ArgumentOutOfRangeException(@"valueString");
            _device.WriteMultipleRegisters(_slaveId, startAddress, GetFloatBytes(value));
        }


        /// <summary>
        /// Converts a IEEE 32 floating point format into two UInt16 values
        /// </summary>
        /// <param name="f">IEEE 32 floating point value</param>
        /// <returns>Array of 2 shorts</returns>
        public ushort[] GetFloatBytes(float f)
        {
            guard(_slaveId);
            var bytes = BitConverter.GetBytes(f);

            ushort lowOrderValue = (ushort) BitConverter.ToInt16(bytes, 0);
            ushort highOrderValue = (ushort) BitConverter.ToInt16(bytes, 2);

            ushort[] registers = {lowOrderValue, highOrderValue};

            return registers;
        }

        /// <summary>
        /// Switches to Parameter Page
        /// </summary>
        public void SwitchParameterPage(bool on)
        {
            WriteInt((ushort) JumoDTron.Register.ParamaterPage, (ushort) (on ? 2 : 0));
            // returns 0xFF060013
        }

        public class ParameterPage : IDisposable
        {
            private readonly Device _mbus;

            public ParameterPage(Device mbus)
            {
                if (mbus == null)
                    return;

                _mbus = mbus;
                _mbus.SwitchParameterPage(true);
            }

            public void Dispose()
            {
                if (_mbus != null)
                    _mbus.SwitchParameterPage(false);
            }
        }
    }
}