/*
 *****************************************************************************
 *                                                                           *
 *              IMPINJ CONFIDENTIAL AND PROPRIETARY                          *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc. Reproduction or     *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2012. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Collections.Generic;

namespace Impinj {
namespace STP {

    public class APIException : System.Exception
    {
        public APIException() : base()
        {
        }

        public APIException(string message) : base(message)
        {
        }
    }

    public class DeviceException : System.Exception
    {
        protected Response toHost;
        public DeviceException() : base()
        {
        }

        public DeviceException(Reply reply, Response toHost) : base(reply.ErrorString)
        {
            this.toHost = toHost;
        }

        public Response ToHost
        {
            get { return this.toHost; }
        }
    }

    static class InternalHelpers
    {
        public static ushort PORT = 0xC0DA;
        public static byte[] FRAME_MARKER_BYTES =
            new byte[] { 0x69, 0x6d, 0x70, 0x21 };
        public static byte VERSION_BYTE = 0x01;
        public static byte FORMATTING_LENGTH = 12;
        public static int MAX_SENSIBLE_FRAME_LENGTH = 65536;

        private static ushort[] CRC16Table = new ushort[] 
        {
            0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
            0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
            0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
            0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
            0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
            0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
            0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
            0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
            0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
            0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
            0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
            0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
            0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
            0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
            0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
            0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
            0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
            0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
            0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
            0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
            0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
            0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
            0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
            0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
            0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
            0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
            0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
            0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
            0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
            0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
            0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
            0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
        };

        public static ushort CalculateCrc(byte[] data)
        {
            uint crc = 0xFFFF;
            uint tmp;

            foreach (byte b in data)
            {
                tmp = ((uint)b) ^ ((crc >> 8) & 0xFF);
                crc = ((crc << 8) ^ CRC16Table[tmp]);
            }

            return (ushort)(~crc & 0xffff);
        }

        public static byte CalculateChecksum(byte[] bytes)
        {
            uint sum = 0xff;
            foreach (byte b in bytes)
            {
                sum += b;
            }
            return (byte)(~sum & 0xff);
        }
    }

    public class TidTableSender
    {
        private Dictionary<string, IPEndPoint> m_TidEntryListeners =
            new Dictionary<string, IPEndPoint>();

        public void AddListener(string listenerNameOrIP)
        {
            IPAddress[] addresses = Dns.GetHostAddresses(listenerNameOrIP);
            IPAddress listenerIP = addresses[0];

            // Add IPstring,endpoint pair.
            IPEndPoint endPoint = new IPEndPoint(listenerIP, InternalHelpers.PORT);
            m_TidEntryListeners.Add(listenerIP.ToString(), endPoint);
        }

        public void RemoveListener(string listenerNameOrIP)
        {
            // Remove the listener from the list.
            IPAddress[] addresses = Dns.GetHostAddresses(listenerNameOrIP);
            IPAddress listenerIP = addresses[0];

            m_TidEntryListeners.Remove(listenerIP.ToString());
        }

        public void AddTidTableEntries(TidTableEntries table_entries)
        {
            if (0 == m_TidEntryListeners.Count)
            {
                return;
            }

            Packet packet = new Packet();
            packet.Update = new Update();
            packet.Update.AddTidTableEntries = new AddTidTableEntriesCommand();
            packet.Update.AddTidTableEntries.TableEntries = table_entries;

            BroadcastPacket(packet);
        }

        private void BroadcastPacket(Packet toReader)
        {
            MemoryStream payloadMemoryStream = new MemoryStream();
            ProtoBuf.Serializer.Serialize(payloadMemoryStream, toReader);
            byte[] payloadBytes = payloadMemoryStream.ToArray();
            int payloadLength = payloadBytes.Length;

            BroadcastFrame(payloadBytes, payloadLength);
        }

        private void BroadcastFrame(byte[] payloadBytes, int payloadLength)
        {
            byte[] lengthBytes = new byte[]
            {
                (byte)((payloadLength >> 24) & 0xff),
                (byte)((payloadLength >> 16) & 0xff),
                (byte)((payloadLength >>  8) & 0xff),
                (byte)((payloadLength      ) & 0xff)
            };
            byte lengthChecksumByte = InternalHelpers.CalculateChecksum(lengthBytes);
            ushort crc = InternalHelpers.CalculateCrc(payloadBytes);
            byte[] crcBytes = new byte[]
            {
                (byte)((crc >> 8) & 0xff),
                (byte)((crc     ) & 0xff),
            };

            MemoryStream frameStream = new MemoryStream(
                payloadBytes.Length + InternalHelpers.FORMATTING_LENGTH);

            frameStream.Write(InternalHelpers.FRAME_MARKER_BYTES, 0,
                InternalHelpers.FRAME_MARKER_BYTES.Length);
            frameStream.WriteByte(InternalHelpers.VERSION_BYTE);
            frameStream.Write(lengthBytes,          0, lengthBytes.Length);
            frameStream.WriteByte(lengthChecksumByte);
            frameStream.Write(payloadBytes,         0, payloadBytes.Length);
            frameStream.Write(crcBytes,             0, crcBytes.Length);

            byte[] frameBytes = frameStream.ToArray();

            UdpClient client = new UdpClient();
            foreach (string listenerIP in m_TidEntryListeners.Keys)
            {
                IPEndPoint endPoint = m_TidEntryListeners[listenerIP];
                client.Send(frameBytes, frameBytes.Length, endPoint);
            }
        }
    }

    public class Device
    {

        private Thread rxThread = null;
        private TcpClient tcpClient = null;
        private Stream stream = null;
        private RewindableStreamReader rewindableStreamReader = null;

        /** Definition of report delegate. */
        public delegate void AllReportsReceivedHandler(Report report, ReportType type);

        /** Use this event to handle all reports. */
        public event AllReportsReceivedHandler AllReportsReceived;

        // For Serial
        //private SerialPort port = null;
        
        // Received Packet
        private Response toHost = null;

        // For Sockets and Serial
        private enum ConnectionType
        {
            NONE,
            SOCKET,
            SERIAL
        };
        ConnectionType connectionType = ConnectionType.NONE;

        /// <summary>
        ///   Initiates STP API connection to the reader specified.
        /// </summary>
        /// <param name="reader">
        ///   The IP address or DNS name of the reader to connect to.
        /// </param>
        public void NetConnect(string reader)
        {
            NetConnect(reader, InternalHelpers.PORT);
        }

        /// <summary>
        ///   Initiates STP API connection to the reader specified.
        /// </summary>
        /// <param name="reader">
        ///   The IP address or DNS name of the reader to connect to.
        /// </param>
        /// <param name="port">
        ///   The port number to connect to on the specified reader.
        /// </param>        
        public void NetConnect(string reader, ushort port)
        {
            // TODO - TA - Add checking that it's not already connected
            try
            {
                this.tcpClient = new TcpClient(reader, port);
                this.stream = this.tcpClient.GetStream();
                this.rewindableStreamReader =
                    new RewindableStreamReader(this.stream);
                this.connectionType = ConnectionType.SOCKET;
                this.tcpClient.NoDelay = true;
            }
            catch
            {
                this.rewindableStreamReader = null;
                this.stream = null;
                this.tcpClient = null;
                throw;
            }

            // Start the listen thread
            this.rxThread = new Thread(new ThreadStart(RxThreadFunc));
            this.rxThread.IsBackground = true;
            this.rxThread.Start();
            
            // Finish up the connect with the handshake
            this.SendHandshake();
            
            return;
        }

        private void Disconnect()
        {
            switch (connectionType)
            {
                case ConnectionType.SOCKET:
                {
                    this.tcpClient.Client.Shutdown(SocketShutdown.Both);
                    this.tcpClient.Client.Close();
                    this.rewindableStreamReader = null;
                    this.stream = null;
                    this.tcpClient = null;
                    this.rxThread.Join();
                    this.rxThread = null;
                    break;
                }
                case ConnectionType.SERIAL:
                {
                    break;
                }
                case ConnectionType.NONE:
                {
                    throw new APIException("Unrecognized connection type: " +
                        this.connectionType.ToString());
                }
            }
            this.connectionType = ConnectionType.NONE;
        }
        
        /// <summary>
        ///   Disconnects the current STP API session with the connected reader.
        /// </summary>        
        public void NetDisconnect()
        {
            Disconnect();
        }

        private void DispatchToHost(Packet toHost)
        {
            if (!DispatchReport(toHost.Report))
            {
                this.toHost = toHost.Response;
                lock (this)
                {
                    Monitor.Pulse(this);
                }
            }
        }

        private class RewindableStreamReader
        {
            public RewindableStreamReader(Stream inputStream)
            {
                this.inputStream = inputStream;
                this.buffer = new byte[1024];
                this.readIndex = 0;
                this.writeIndex = 0;
            }

            private void CopyBytes(
                byte[] sourceBytes, int sourceIndex,
                byte[] destBytes, int destIndex,
                int count)
            {
                for (int i = 0; i < count; i++)
                {
                    destBytes[destIndex] = sourceBytes[sourceIndex];
                    destIndex++;
                    sourceIndex++;
                }
            }

            public void DiscardRollback()
            {
                // TODO - TA - Improve efficiency
                this.CopyBytes(
                    this.buffer, this.readIndex,
                    this.buffer, 0,
                    this.writeIndex - this.readIndex);
                this.writeIndex -= this.readIndex;
                this.readIndex = 0;
            }

            private void EnsureEnoughSpaceInBuffer(int numBytesMore)
            {
                int newSize = this.buffer.Length;
                int requiredSize = this.writeIndex + numBytesMore;
                while (newSize < requiredSize)
                {
                    newSize *= 2;
                }

                if (this.buffer.Length < newSize)
                {
                    byte[] oldBuffer = this.buffer;
                    this.buffer = new byte[newSize];
                    this.CopyBytes(
                        oldBuffer, 0,
                        this.buffer, 0,
                        this.writeIndex);
                }
            }

            public byte[] ReadBytes(int numBytes)
            {
                this.EnsureEnoughSpaceInBuffer(numBytes);

                while (numBytes > (this.writeIndex - this.readIndex))
                {
                    int numBytesRead = 0;
                    try
                    {
                        numBytesRead = this.inputStream.Read(
                            this.buffer,
                            this.writeIndex,
                            this.buffer.Length - this.writeIndex);
                    }
                    catch (System.IO.IOException /* sii */)
                    {
                        numBytesRead = 0;
                    }

                    if (0 == numBytesRead)
                    {
                        throw new EndOfRxStreamException();
                    }

                    this.writeIndex += numBytesRead;
                }
                byte[] retBytes = new byte[numBytes];
                this.CopyBytes(
                    this.buffer, this.readIndex,
                    retBytes, 0,
                    numBytes);
                this.readIndex += numBytes;
                return retBytes;
            }

            public byte ReadByte()
            {
                return this.ReadBytes(1)[0];
            }

            public void Rewind()
            {
                this.readIndex = 0;
            } 

            int readIndex;
            int writeIndex;
            byte[] buffer;
            Stream inputStream;
        };

        class BadBytesReceivedException : System.Exception
        {
            public BadBytesReceivedException(): base()
            {
            }

            public BadBytesReceivedException(string message): base(message)
            {
            }
        };

        class EndOfRxStreamException : System.Exception
        {
        };

        private Packet GetNextToHost(
            RewindableStreamReader rxRewindableStreamReader)
        {
            Packet toHost = null;
            try
            {
                byte[] markerBytes = rxRewindableStreamReader.ReadBytes(4);
                for (int i = 0; i < 4; i++)
                {
                    if (markerBytes[i] != InternalHelpers.FRAME_MARKER_BYTES[i])
                    {
                        throw new BadBytesReceivedException();
                    }
                }

                byte versionByte = rxRewindableStreamReader.ReadByte();
                if (versionByte != InternalHelpers.VERSION_BYTE)
                {
                    throw new BadBytesReceivedException();
                }

                byte[] lengthBytes = rxRewindableStreamReader.ReadBytes(4);
                int length = (
                    (((int)lengthBytes[0]) << 24) +
                    (((int)lengthBytes[1]) << 16) +
                    (((int)lengthBytes[2]) <<  8) +
                    (((int)lengthBytes[3])      ));
                if (length > InternalHelpers.MAX_SENSIBLE_FRAME_LENGTH)
                {
                    throw new BadBytesReceivedException();
                }

                byte lengthChecksum = rxRewindableStreamReader.ReadByte();
                if (lengthChecksum != InternalHelpers.CalculateChecksum(lengthBytes))
                {
                    throw new BadBytesReceivedException();
                }

                byte[] payloadBytes =
                    rxRewindableStreamReader.ReadBytes(length);

                byte[] crcBytes = rxRewindableStreamReader.ReadBytes(2);
                ushort crc = (ushort)(
                    (((int)crcBytes[0]) << 8) +
                    (((int)crcBytes[1]) << 0));
                if (crc != InternalHelpers.CalculateCrc(payloadBytes))
                {
                    throw new BadBytesReceivedException();
                }

                try
                {
                    toHost =
                        ProtoBuf.Serializer.Deserialize<Impinj.STP.Packet>(
                            new MemoryStream(payloadBytes));
                }
                catch (Exception e)
                {
                    throw new BadBytesReceivedException(e.Message);
                }
            }
            catch (BadBytesReceivedException /* bbre */)
            {
                rxRewindableStreamReader.Rewind();
                rxRewindableStreamReader.ReadByte();
            }

            rxRewindableStreamReader.DiscardRollback();
            return toHost;
        }

        private void RxThreadFunc()
        {
            // Cache it locally for thread safety
            RewindableStreamReader rxRewindableStreamReader =
                this.rewindableStreamReader;
            if (null == rxRewindableStreamReader)
            {
                return;
            }

            try
            {
                for (;;)
                {
                    Packet toHost =
                        this.GetNextToHost(rxRewindableStreamReader);
                    if (null != toHost)
                    {
                        this.DispatchToHost(toHost);
                    }

                }
            }
            catch (EndOfRxStreamException /* eorse */)
            {
                // This is the normal end condition
            }
        }

        private void TxToReader(Command toReader)
        {
            MemoryStream payloadMemoryStream = new MemoryStream();
            ProtoBuf.Serializer.Serialize(payloadMemoryStream,
                new Packet(){Command = toReader});
            byte[] payloadBytes = payloadMemoryStream.ToArray();

            int payloadLength = payloadBytes.Length;
            byte[] lengthBytes = new byte[]
            {
                (byte)((payloadLength >> 24) & 0xff),
                (byte)((payloadLength >> 16) & 0xff),
                (byte)((payloadLength >>  8) & 0xff),
                (byte)((payloadLength      ) & 0xff)
            };
            byte lengthChecksumByte = InternalHelpers.CalculateChecksum(lengthBytes);
            ushort crc = InternalHelpers.CalculateCrc(payloadBytes);
            byte[] crcBytes = new byte[]
            {
                (byte)((crc >> 8) & 0xff),
                (byte)((crc     ) & 0xff),
            };

            MemoryStream frameStream = new MemoryStream(
                payloadBytes.Length + InternalHelpers.FORMATTING_LENGTH);

            frameStream.Write(InternalHelpers.FRAME_MARKER_BYTES, 0,
                InternalHelpers.FRAME_MARKER_BYTES.Length);
            frameStream.WriteByte(InternalHelpers.VERSION_BYTE);
            frameStream.Write(lengthBytes,          0, lengthBytes.Length);
            frameStream.WriteByte(lengthChecksumByte);
            frameStream.Write(payloadBytes,         0, payloadBytes.Length);
            frameStream.Write(crcBytes,             0, crcBytes.Length);

            byte[] frameBytes = frameStream.ToArray();
            this.stream.Write(frameBytes, 0, frameBytes.Length);
        }

        private Version BuildVersionFromString(
            string versionString, VersionType versionType, string identData)
        {
            Version version = new Version()
            {
                Ident = identData,
                VersionId = versionType
            };
            
            string [] identParsing = versionString.Split('-');
            if (identParsing.Length == 2)
            {
                version.Ident = (identData + ";Label=" + identParsing[1]);
            }
            
            string[] versionParsing = identParsing[0].Split('.');
            
            switch(versionParsing.Length)
            {
                case 4:
                    version.Build = Convert.ToUInt32(versionParsing[3]);
                    goto case 3;
                case 3:
                    version.Maintenance = Convert.ToUInt32(versionParsing[2]);
                    goto case 2;
                case 2:
                    version.Minor = Convert.ToUInt32(versionParsing[1]);
                    goto case 1;
                case 1:
                    version.Major = Convert.ToUInt32(versionParsing[0]);
                    break;
            }  
            
            return version;
        }
        
        /// <summary>
        ///    Gets the version of the reader's STP firmware, host library and API version.
        /// </summary>
        public Versions GetVersions()
        {
            Versions versions = new Versions();

            versions.VersionList.AddRange(GetVersionsReader().VersionList);            

            versions.VersionList.Add(BuildVersionFromString(
                kdkVersion(),
                VersionType.VERSION_TYPE_KODIAK_HOST,
                "Comp=impinj_api"));
            
            versions.VersionList.Add(BuildVersionFromString(
                kdkApiVersion(),
                VersionType.VERSION_TYPE_API_HOST,
                "Comp=impinj_api"));

            return versions;                  
        }
        
        private Response SendCommandWaitForResponse(Command toReader)
        {        
            lock(this)
            {
                TxToReader(toReader);
                Monitor.Wait(this);
                Response toHost = this.toHost;
                this.toHost = null;
                return toHost;
            }
        }
      
        private void SendHandshake()
        {
            Command toReader = new Command()
            {
                Connect = new ConnectCommand()
                {
                    HandShake = new HandShake()
                    {
                        ApiName = kdkApiName(),
                        ApiVersion = Convert.ToUInt32(kdkApiVersion()),
                    }
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Connect.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Connect.Reply, toHost);
            }
        }        
        
        //
        // Generated code follows
        //
        
        public void Connect()
        {
            Command toReader = new Command()
            {
                Connect = new ConnectCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Connect.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Connect.Reply, toHost);
            }
            
        }
        public Versions GetVersionsReader()
        {
            Command toReader = new Command()
            {
                GetVersionsReader = new GetVersionsReaderCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.GetVersionsReader.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.GetVersionsReader.Reply, toHost);
            }
            
            return toHost.GetVersionsReader.Versions;
        }
        public void Start()
        {
            Command toReader = new Command()
            {
                Start = new StartCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Start.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Start.Reply, toHost);
            }
            
        }
        public void Stop()
        {
            Command toReader = new Command()
            {
                Stop = new StopCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Stop.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Stop.Reply, toHost);
            }
            
        }
        /// <summary>
        ///   Set how GPOs change during encode and inventory. 
        /// </summary>
        /// <param name="deviceConfig">
        ///   
        /// </param>
        public void SetDeviceConfig(DeviceConfig deviceConfig)
        {
            Command toReader = new Command()
            {
                SetDeviceConfig = new SetDeviceConfigCommand()
                {
                    DeviceConfig = deviceConfig,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.SetDeviceConfig.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.SetDeviceConfig.Reply, toHost);
            }
            
        }
        public DeviceConfig GetDeviceConfig()
        {
            Command toReader = new Command()
            {
                GetDeviceConfig = new GetDeviceConfigCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.GetDeviceConfig.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.GetDeviceConfig.Reply, toHost);
            }
            
            return toHost.GetDeviceConfig.DeviceConfig;
        }
        /// <param name="antennasConfig">
        ///   
        /// </param>
        public void SetAntennaConfig(AntennasConfig antennasConfig)
        {
            Command toReader = new Command()
            {
                SetAntennaConfig = new SetAntennaConfigCommand()
                {
                    AntennasConfig = antennasConfig,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.SetAntennaConfig.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.SetAntennaConfig.Reply, toHost);
            }
            
        }
        public AntennasConfig GetAntennaConfig()
        {
            Command toReader = new Command()
            {
                GetAntennaConfig = new GetAntennaConfigCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.GetAntennaConfig.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.GetAntennaConfig.Reply, toHost);
            }
            
            return toHost.GetAntennaConfig.AntennasConfig;
        }
        /// <summary>
        ///   Call before \ref start to enable GPI triggering. 
        /// </summary>
        public void Engage()
        {
            Command toReader = new Command()
            {
                Engage = new EngageCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Engage.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Engage.Reply, toHost);
            }
            
        }
        /// <summary>
        ///   Call after stopping the encode or inventory operation so the device can be reconfigured. 
        /// </summary>
        public void Disengage()
        {
            Command toReader = new Command()
            {
                Disengage = new DisengageCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.Disengage.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.Disengage.Reply, toHost);
            }
            
        }
        /// <summary>
        ///   When the GPOConfig enable_operation_result_gpo is set false, this command lets the client application set the pass/fail GPO based on its own pass/fail conditions. 
        /// </summary>
        /// <param name="operationSummaryResult">
        ///   
        /// </param>
        public void SetOperationResultGpo(OperationSummaryResult operationSummaryResult)
        {
            Command toReader = new Command()
            {
                SetOperationResultGpo = new SetOperationResultGpoCommand()
                {
                    OperationSummaryResult = operationSummaryResult,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.SetOperationResultGpo.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.SetOperationResultGpo.Reply, toHost);
            }
            
        }
        /// <param name="serializationJob">
        ///   
        /// </param>
        public void AddSerializationJob(SerializationJob serializationJob)
        {
            Command toReader = new Command()
            {
                AddSerializationJob = new AddSerializationJobCommand()
                {
                    SerializationJob = serializationJob,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.AddSerializationJob.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.AddSerializationJob.Reply, toHost);
            }
            
        }
        /// <param name="jobId">
        ///   
        /// </param>
        public void RemoveSerializationJob(uint jobId)
        {
            Command toReader = new Command()
            {
                RemoveSerializationJob = new RemoveSerializationJobCommand()
                {
                    JobId = jobId,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.RemoveSerializationJob.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.RemoveSerializationJob.Reply, toHost);
            }
            
        }
        /// <param name="tableEntries">
        ///   
        /// </param>
        public void AddTidTableEntries(TidTableEntries tableEntries)
        {
            Command toReader = new Command()
            {
                AddTidTableEntries = new AddTidTableEntriesCommand()
                {
                    TableEntries = tableEntries,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.AddTidTableEntries.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.AddTidTableEntries.Reply, toHost);
            }
            
        }
        /// <param name="orderedListEntries">
        ///   
        /// </param>
        public void AddOrderedListEntries(OrderedListEntries orderedListEntries)
        {
            Command toReader = new Command()
            {
                AddOrderedListEntries = new AddOrderedListEntriesCommand()
                {
                    OrderedListEntries = orderedListEntries,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.AddOrderedListEntries.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.AddOrderedListEntries.Reply, toHost);
            }
            
        }
        /// <param name="inventoryJob">
        ///   
        /// </param>
        public void SetInventoryJob(InventoryJob inventoryJob)
        {
            Command toReader = new Command()
            {
                SetInventoryJob = new SetInventoryJobCommand()
                {
                    InventoryJob = inventoryJob,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.SetInventoryJob.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.SetInventoryJob.Reply, toHost);
            }
            
        }
        /// <param name="cartonContentsJob">
        ///   
        /// </param>
        public void SetCartonContentsJob(CartonContentsJob cartonContentsJob)
        {
            Command toReader = new Command()
            {
                SetCartonContentsJob = new SetCartonContentsJobCommand()
                {
                    CartonContentsJob = cartonContentsJob,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.SetCartonContentsJob.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.SetCartonContentsJob.Reply, toHost);
            }
            
        }
        public OperationData GetOperationData()
        {
            Command toReader = new Command()
            {
                GetOperationData = new GetOperationDataCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.GetOperationData.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.GetOperationData.Reply, toHost);
            }
            
            return toHost.GetOperationData.OperationData;
        }
        /// <param name="licenseKey">
        ///   
        /// </param>
        public void AddLicenseKey(LicenseKey licenseKey)
        {
            Command toReader = new Command()
            {
                AddLicenseKey = new AddLicenseKeyCommand()
                {
                    LicenseKey = licenseKey,
                }
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.AddLicenseKey.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.AddLicenseKey.Reply, toHost);
            }
            
        }
        public DeviceState GetDeviceState()
        {
            Command toReader = new Command()
            {
                GetDeviceState = new GetDeviceStateCommand()
                
            };

            Response toHost = SendCommandWaitForResponse(toReader);

            if (Impinj.STP.ErrorCode.ERROR_CODE_SUCCESS != toHost.GetDeviceState.Reply.ErrorCode)
            {
                throw new DeviceException(toHost.GetDeviceState.Reply, toHost);
            }
            
            return toHost.GetDeviceState.DeviceState;
        }

        private bool DispatchReport(Report report)
        {
            if (null == report)
            {
                return false;
            }
            if (null != report.OperationSummaryReport)
            {
                if (null != this.OperationSummaryReportReceived)
                {
                    this.OperationSummaryReportReceived(report.OperationSummaryReport);
                }
                if (null != this.AllReportsReceived)
                {
                    this.AllReportsReceived(report, Impinj.STP.ReportType.REPORT_TYPE_OPERATION_SUMMARY);
                }
                return true;
            }
            else if (null != report.TagOperationReport)
            {
                if (null != this.TagOperationReportReceived)
                {
                    this.TagOperationReportReceived(report.TagOperationReport);
                }
                if (null != this.AllReportsReceived)
                {
                    this.AllReportsReceived(report, Impinj.STP.ReportType.REPORT_TYPE_TAG_OPERATION);
                }
                return true;
            }
            else if (null != report.DeviceEventReport)
            {
                if (null != this.DeviceEventReportReceived)
                {
                    this.DeviceEventReportReceived(report.DeviceEventReport);
                }
                if (null != this.AllReportsReceived)
                {
                    this.AllReportsReceived(report, Impinj.STP.ReportType.REPORT_TYPE_DEVICE_EVENT);
                }
                return true;
            }
            else if (null != report.CartonContentsReport)
            {
                if (null != this.CartonContentsReportReceived)
                {
                    this.CartonContentsReportReceived(report.CartonContentsReport);
                }
                if (null != this.AllReportsReceived)
                {
                    this.AllReportsReceived(report, Impinj.STP.ReportType.REPORT_TYPE_CARTON_CONTENTS);
                }
                return true;
            }
            
            return false;
        }
        
        /** Definition of report delegate. */
        public delegate void OperationSummaryReportReceivedHandler(OperationSummaryReport operationSummaryReport);

        /** Use this event to handle OperationSummaryReports.*/
        public event OperationSummaryReportReceivedHandler OperationSummaryReportReceived;
        
        /** Definition of report delegate. */
        public delegate void TagOperationReportReceivedHandler(TagOperationReport tagOperationReport);

        /** Use this event to handle TagOperationReports.*/
        public event TagOperationReportReceivedHandler TagOperationReportReceived;
        
        /** Definition of report delegate. */
        public delegate void DeviceEventReportReceivedHandler(DeviceEventReport deviceEventReport);

        /** Use this event to handle DeviceEventReports.*/
        public event DeviceEventReportReceivedHandler DeviceEventReportReceived;
        
        /** Definition of report delegate. */
        public delegate void CartonContentsReportReceivedHandler(CartonContentsReport cartonContentsReport);

        /** Use this event to handle CartonContentsReports.*/
        public event CartonContentsReportReceivedHandler CartonContentsReportReceived;
        

        private string kdkVersion()
        {
            return "../VERSION";
        }

        private string kdkApiVersion()
        {
            return "1";
        }

        private string kdkApiName()
        {
            return "STP";
        }
    }
}
}