/*
 *****************************************************************************
 *                                                                           *
 *              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. 2015. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Impinj.STP;

namespace Impinj
{
    class Program
    {
        // Private data member declarations
        private static Device ItemEncodeDevice = new Device();
        private static string SpeedwayRHostname = string.Empty;
        private static bool serial_number_range_empty = false;

        // Main routine
        static void Main(string[] args)
        {
            // Parameters for defining the Serial Number Range SerializationJob
            ulong SerialNumberStartValue = 0;
            ulong SerialNumberStopValue = 1000000;
            uint SizeOfSerializationBitField = 38;
            uint BitOffsetToStartSerialNumber = 58;

            // Initialize console window
            InitializeConsoleWindow();

            // Determine whether Speedway Revolution hostname has been provided
            // on the command line, or not
            switch (args.Length)
            {
                case 0:
                    // Use the Speedway Revolution hostname defined in the
                    // SolutionsConstants class of the SolutionItems project
                    SpeedwayRHostname = SolutionConstants.SpeedwayRHostname;
                    break;
                case 1:
                    // If the input parameter is a valid string, use it
                    // as the hostname
                    if (false == string.IsNullOrEmpty(args[0]))
                    {
                        SpeedwayRHostname = args[0];
                    }
                    // Otherwise print a usage message and exit
                    else
                    {
                        PrintUsage();
                    }
                    break;
                default:
                    // For all other cases, print a usage message and exit
                    PrintUsage();
                    break;
            }

            // Variables used to monitor current ItemEncode device status
            bool connected = false;
            bool operating = false;

            // Main body of example code
            try
            {
                // Print status message
                Console.Write("Connecting to device {0} ...", SpeedwayRHostname);

                // Connect to ItemEncode device
                ItemEncodeDevice.NetConnect(SpeedwayRHostname);
                // If we have reached here, the connection was successful,
                // so assert the connected flag.
                // The ItemEncode device is now in the Configuration state.
                connected = true;
            }
            catch
            {
                Console.WriteLine(
                    "{0}{0}WARNING - connection exception! Verify hostname is " +
                    "correct.{0}",
                    Environment.NewLine
                    );

                // Print a usage message and exit
                PrintUsage();
            }

            try
            {
                // Bind report handlers to the ItemEncodeDevice report events
                ItemEncodeDevice.DeviceEventReportReceived += OnDeviceEventReport;
                ItemEncodeDevice.OperationSummaryReportReceived += OnOperationSummaryReport;
                ItemEncodeDevice.TagOperationReportReceived += OnTagOperationReport;

                // Get the OperationData from the ItemEncodeVerify device
                OperationData operationData = ItemEncodeDevice.GetOperationData();

                // Output the ItemEncode firmware version
                Console.WriteLine(" Connected. Running ItemEncode {1}{2}",
                    SpeedwayRHostname,
                    operationData.OperationDetails.FirmwareVersion,
                    Environment.NewLine
                    );

                // Define an ItemEncode Device Configuration
                DeviceConfig deviceConfig = new DeviceConfig();

                // For Speedway Revolution readers that support fixed
                // frequencies, this is an example of how to configure the
                // device to use a fixed frequency configuration
                //
                //deviceConfig.Region = new Region();
                //deviceConfig.Region.RegionId = RegulatoryRegion.REGULATORY_REGION_ETSI_EN_302_208_V1_2_1;
                //deviceConfig.Region.FixedFrequencyList.Add(865700);
                //deviceConfig.Region.FixedFrequencyList.Add(866300);
                //deviceConfig.Region.FixedFrequencyList.Add(866900);
                //deviceConfig.Region.FixedFrequencyList.Add(867500);

                // Configure the ItemEncode device to operate in single tag
                // encode mode
                deviceConfig.DeviceMode = DeviceMode.DEVICE_MODE_ENCODE;
                deviceConfig.OperationMode = OperationMode.OPERATION_MODE_SINGLE;

                // Enable both a start and stop trigger for the ItemEncode device
                deviceConfig.TriggerConfig = new TriggerConfig();

                deviceConfig.TriggerConfig.StartTriggerConfig = new StartTriggerConfig();
                deviceConfig.TriggerConfig.StartTriggerConfig.EnableGpiStartTrigger = true;

                deviceConfig.TriggerConfig.StopTriggerConfig = new StopTriggerConfig();
                deviceConfig.TriggerConfig.StopTriggerConfig.EnableGpiStopTrigger = true;
                deviceConfig.TriggerConfig.StopTriggerConfig.Timeout = 35;                          // need to tune to aperture of antenna and web speed

                // Configure how the device will use the General Purpose Inputs
                deviceConfig.GpiConfig = new GpiConfig();
                deviceConfig.GpiConfig.SignalingMode = GpioSignalMode.GPIO_SIGNAL_MODE_COMBINED;
                deviceConfig.GpiConfig.Gpi1Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // System Input
                deviceConfig.GpiConfig.Gpi2Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // Operation Start and Stop (Combined)
                deviceConfig.GpiConfig.Gpi3Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // Reserved for Future Use

                // Configure how the device will use the General Purpose Outputs
                deviceConfig.GpoConfig = new GpoConfig();
                deviceConfig.GpoConfig.SignalingMode = GpioSignalMode.GPIO_SIGNAL_MODE_SEPARATE;
                deviceConfig.GpoConfig.Gpo1Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // Operation State
                deviceConfig.GpoConfig.Gpo2Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // Operation Pass
                deviceConfig.GpoConfig.Gpo3Polarity = GpioPolarity.GPIO_POLARITY_ACTIVE_HIGH;       // Operation Fail
                deviceConfig.GpoConfig.EnableOperationResultGpo = true;
                deviceConfig.GpoConfig.GpoPulseDuration = 1;                                        // GPO Pulse Duration in ms

                // Configure which reports the device will produce
                deviceConfig.ReportConfig = new ReportConfig();
                // Enable GPI status events
                deviceConfig.ReportConfig.DeviceEventConfig = new DeviceEventConfig();
                deviceConfig.ReportConfig.DeviceEventConfig.EnableGpiEvents = true;
                // Enable the Operation summary report
                deviceConfig.ReportConfig.OperationSummaryReportConfig = new OperationSummaryReportConfig();
                deviceConfig.ReportConfig.OperationSummaryReportConfig.EnableOperationSummaryReports = true;
                // Enable the Tag Operation report
                deviceConfig.ReportConfig.TagOperationReportConfig = new TagOperationReportConfig();
                deviceConfig.ReportConfig.TagOperationReportConfig.TagOperationReportTrigger = TagOperationReportTrigger.TAG_OPERATION_REPORT_TRIGGER_IMMEDIATE;
                deviceConfig.ReportConfig.TagOperationReportConfig.EnableTagOperationReports = true;
                // Report options that are to be disabled
                deviceConfig.ReportConfig.TagOperationReportConfig.EnableTagOperationRetryReports = false;
                deviceConfig.ReportConfig.TagOperationReportConfig.EnableReportingOverSerialPort = false;       // Disable serial report if you don't need it to save time
                deviceConfig.ReportConfig.TagOperationReportConfig.EnableEpcInSerialReport = false;
                deviceConfig.ReportConfig.TagOperationReportConfig.SendSerialReportAfterGpoDeassert = false;    // Send report after GPO assert

                // Upload the device configuration details to the reader
                ItemEncodeDevice.SetDeviceConfig(deviceConfig);

                // Define an ItemEncode device antenna configurations list.
                // The order that each individual antenna is added to the list
                // is the order that the antennas will be used.
                // Each antenna can be added more than once.
                AntennasConfig antennaConfigs = new AntennasConfig();

                // Define RFID UHF Gen 2 configuration specifics for Antenna 1
                AntennaConfig antennaConfig = new AntennaConfig();
                antennaConfig.AntennaId = 1;
                antennaConfig.Gen2Mode = Gen2Mode.GEN2_MODE_MAX_THROUGHPUT;
                antennaConfig.InventorySearchMode =
                    InventorySearchMode.INVENTORY_SEARCH_MODE_SINGLE_TARGET_A_TO_B;
                antennaConfig.Gen2Session = Gen2Session.GEN2_SESSION_0;
                antennaConfig.TransmitPower = 2000;
                // The following options instruct the ItemEncode device to use
                // a custom intelligent approach to Gen 2 tag population (or Q)
                // management
                antennaConfig.TagPopulationEstimate = 0;
                antennaConfig.EnableQueryForQIncrease = true;
                antennaConfig.EnableQueryForQDecrease = true;
                antennaConfig.EnableQueryAtUCycleEnd = true;

                // Add Antenna 1 configuration to antenna configurations list
                antennaConfigs.AntennaConfig.Add(antennaConfig);

                // Upload antenna configurations list to the ItemEncode device
                ItemEncodeDevice.SetAntennaConfig(antennaConfigs);

                // Define the ItemEncode serialization job that defines
                // what encoding operations the ItemEncode device is to perform.
                // A Static serialization job is defined here, which means that
                // the device will encode very tag chip using the details
                // defined in the SerialazionJob object.
                SerializationJob serializationJob = new SerializationJob();
                serializationJob.JobId = 1;
                serializationJob.JobType = SerializationJobType.SERIALIZATION_JOB_TYPE_STATIC;

                // Define the paramers of the Serial Number Range. The serial
                // number is defined as a bit field that can be placed anywhere
                // within the EPC memory bank. There are several main
                // configuration parameters for specifying the serial number
                // bit field:
                //  - Data to place in the non-serialized EPC bits
                //  - Starting serial number value
                //  - Ending serial number value
                //  - Number of bits to include in the serial number bit field
                //  - The bit location to place the serial number bit field

                serializationJob.EpcMemory = new EPCMemory();

                // Data to place in the non-serialized EPC bits
                serializationJob.EpcMemory.Data = new ByteArray("3074257BF7194E4000000000");

                // Define serialization specifics
                serializationJob.EpcMemory.Serialization = new Serialization();
                // Number of bits to assign to the serial number bit field
                serializationJob.EpcMemory.Serialization.SerializationBits = SizeOfSerializationBitField;
                // Define the parameters for the Serial Number range
                serializationJob.EpcMemory.Serialization.SerialNumberRange = new SerialNumberRange();
                // Choose Serial Number format;
                // either Binary Coded Hexadecimal or Binary Coded Decimal
                serializationJob.EpcMemory.Serialization.SerialNumberRange.SerialNumberRangeFormat =
                    SerialNumberRangeFormat.SERIAL_NUMBER_RANGE_FORMAT_HEX;
                // The bit offset within EPC memory to locate the serialization
                // bit field. For this parameter, the bit count goes from left
                // to right, so for a 96-bit EPC bit offset 0 is the most
                // significant bit, and bit 95 is the least significant bit.
                serializationJob.EpcMemory.Serialization.SerialNumberRange.BitOffset = BitOffsetToStartSerialNumber;
                // Starting serial number bit field value
                serializationJob.EpcMemory.Serialization.SerialNumberRange.SerialNumberRangeStart = SerialNumberStartValue;
                // Ending serial number bit field value
                serializationJob.EpcMemory.Serialization.SerialNumberRange.SerialNumberRangeEnd = SerialNumberStopValue;

                // Add the serialization job with the serial number range
                // configuration to the ItemEncode device
                ItemEncodeDevice.AddSerializationJob(serializationJob);

                // Engage the ItemEncode device to transition from
                // Configuration state to Engaged state
                ItemEncodeDevice.Engage();

                // Encoding can now be triggered either by using the user
                // interaction defined below to start and stop the device,
                // or by using the ItemEncode device GPI start and stop triggers.
                // If a start GPI trigger is received, the ItemEncode device
                // transitions from the Engaged state to the Operating state.

                // Output operation instructions to the user
                Console.WriteLine("Press {0} to Start and {1} to Stop\r\n",
                    ((char)'\u25B2').ToString(),  // Up arrow
                    ((char)'\u25BC').ToString()); // Down arrow

                // Assert the operating flag; the encoding loop will continue
                // while this flag is asserted. it is de-asserted when the
                // user presses the down arrow key.
                operating = true;

                // User interaction control loop
                while (operating)
                {
                    if (Console.KeyAvailable)
                    {
                        ConsoleKeyInfo key = Console.ReadKey(true);
                        switch (key.Key)
                        {
                            case ConsoleKey.UpArrow:
                                // An up arrow keypress has been detected,
                                // so start an encode operation. This will
                                // encode a single tag at a time.
                                // The ItemEncode device transitions from
                                // the Engaged state to the Operating state.
                                if (false == serial_number_range_empty)
                                {
                                    ItemEncodeDevice.Start();
                                    operating = true;
                                }
                                else
                                {
                                    // If the serial number range is empty,
                                    // then the reader has already been stopped
                                    // and disengaged.
                                    operating = false;
                                    Console.WriteLine(
                                        "Nothing to do. All of the numbers " +
                                        "in the serial number range have " +
                                        "been consumed."
                                        );
                                }
                                break;
                            case ConsoleKey.DownArrow:
                            case ConsoleKey.Escape:
                                // A down arrow has been detected, so stop the
                                // ItemEncode device and exit the user 
                                // interaction control loop.
                                // This causes the ItemEncode device to
                                // transition from the Operating State back to
                                // the Engaged state.
                                ItemEncodeDevice.Stop();
                                operating = false;
                                break;
                            default:
                                break;
                        }
                    }
                }

                // We have reached here because the user has requested a stop,
                // so tear down connection with ItemEncode device. If the serial
                // number range has not been consumed, then first disengage
                // the ItemEncode device. This causes the ItemEncode
                // device to transition from the Engaged state back to the
                // Configuration State.
                if (false == serial_number_range_empty)
                {
                    ItemEncodeDevice.Disengage();
                }

                // Remove the report event handlers
                ItemEncodeDevice.TagOperationReportReceived -= OnTagOperationReport;
                ItemEncodeDevice.OperationSummaryReportReceived -= OnOperationSummaryReport;
                ItemEncodeDevice.DeviceEventReportReceived -= OnDeviceEventReport;

                // Disconnect from device
                ItemEncodeDevice.NetDisconnect();
                connected = false;

                // Pause before exiting the application to help when
                // debugging within Visual Studio
                Console.WriteLine("\r\nPress any key to exit . . .");
                Console.ReadKey(true);
            }
            // Exception handlers
            catch (DeviceException dex)
            {
                // A DeviceException is thrown when the command/response
                // communication with the ItemEncode device occurs

                if (ErrorCode.ERROR_CODE_COMMAND_REFUSED == dex.ToHost.AddSerializationJob.Reply.ErrorCode &&
                    "Not enough serialization bits for requested serial number range" ==
                    dex.ToHost.AddSerializationJob.Reply.ErrorString)
                {
                    Console.WriteLine("Exception!{0}", Environment.NewLine);
                    Console.WriteLine("Serial number range:{0}\tStart: {1}{0}\tStop: {2}{0}Does not " +
                        "fit within:{0}\tSerializationBits: {3}{0}",
                        Environment.NewLine,
                        SerialNumberStartValue,
                        SerialNumberStopValue,
                        SizeOfSerializationBitField
                        );
                    Console.WriteLine(
                        "Increase SerializationBits to fit max serial " +
                        "number value.{0}" +
                        "(e.g. serial numbers from 0 to 15 " +
                        "will require a SerializationBits value of 4)",
                        Environment.NewLine
                        );
                }
                else
                {
                    // Output the details of the command for which a communication
                    // exception was detected
                    Console.WriteLine("Device Exception sending command: {0}",
                        dex.ToHost.ToString()
                        );
                    Console.WriteLine("{0}", dex.StackTrace.ToString());
                }

                // Pause before exiting the application to help when
                // debugging within Visual Studio
                Console.WriteLine("\r\nPress any key to exit . . .");
                Console.ReadKey(true);
            }
            catch (Exception ex)
            {
                // Print out the exception details
                Utilities.printExceptionDetails(ex);

                // Pause before exiting the application to help when
                // debugging within Visual Studio
                Console.WriteLine("\r\nPress any key to exit . . .");
                Console.ReadKey(true);
            }
            // Ensure that the application disconnects from the ItemEncode
            // device when an exception occurs.
            finally
            {
                if (connected)
                {
                    ItemEncodeDevice.NetDisconnect();
                }
            }
        }

        // Print a brief usage statement and then exit the application
        private static void PrintUsage()
        {
            Console.WriteLine("Usage: DataManagementSerialNumberRange <deviceName>");
            Console.WriteLine("\r\nPress any key to exit . . .");
            Console.ReadKey(true);
            Environment.Exit(-1);
        }

        // Handler that is called whenever a DeviceEventReport event is received
        private static void OnDeviceEventReport(DeviceEventReport report)
        {
            // Look to see whether the report is a GPI event
            if (report.GpiEvent != null)
            {
                // Copy GPI Event object
                GpiEvent thisGpiEvent = report.GpiEvent;

                // Print details of GPI event
                Console.WriteLine(
                    "GPI Event: {0}, GPI State: {1}",
                    Utilities.GetDateTime(thisGpiEvent.Timestamp).ToString("yyyy-MM-dd HH:mm:ss.ffff"),
                    report.GpiEvent.GpiState
                    );
            }
            // Do nothing if it is any other type of DeviceEvent report
        }

        // Handler that is called whenever an OperationSummaryReport event
        // is received
        private static void OnOperationSummaryReport(OperationSummaryReport report)
        {
            try
            {
                // Print the details of the OperationSummary report
                Console.WriteLine(
                    "{0}Operation Summary - Total: {1}, Pass: {2}, Fail: {3}, Elapsed Time: {4} ms{0}",
                    Environment.NewLine,
                    report.TagOperationTotalCount,
                    report.TagOperationPassCount,
                    report.TagOperationFailCount,
                    report.OperationElapsedTime
                    );
            }
            catch (Exception ex)
            {
                // Print out the exception details
                Utilities.printExceptionDetails(ex);
            }
        }

        // Handler that is called whenever a TagOperationReport event
        // is received
        private static void OnTagOperationReport(TagOperationReport report)
        {
            string timestamp = string.Empty;
            try
            {
                // Convert the report timestamp to the .NET DateTime format
                timestamp =
                    Utilities.GetDateTime(report.Timestamp).ToString("yyyy-MM-dd HH:mm:ss.ffff");
            }
            catch
            {
                // If an exception occurs during conversion, set a default
                // timestamp value
                timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff");
            }

            // Print out the contents of any TagOperation Encode reports in the
            // correct format for the report result
            if (report.TagOperationReportType == TagOperationReportType.TAG_OPERATION_REPORT_TYPE_ENCODE)
            {
                switch (report.TagOperationResult)
                {
                    case TagOperationResult.TAG_OPERATION_RESULT_PASS:
                        Console.WriteLine(
                            "{0},{1},{2},{3},{4},{5},{6},{7},{8}",
                            timestamp,
                            report.TagOperationResult,
                            report.Tid.ToHexString(),
                            report.Epc.ToHexString(),
                            report.EpcAttempted,
                            report.EpcMemoryResult.ToString(),
                            report.AntennaId.ToString(),
                            report.Frequency,
                            report.Rssi
                            );
                        break;
                    case TagOperationResult.TAG_OPERATION_RESULT_FAIL_TAG_NOT_INVENTORIED:
                        Console.WriteLine(
                            "{0},{1},{2},{3},{4}",
                            timestamp,
                            report.TagOperationResult,
                            report.AntennaId.ToString(),
                            report.Frequency,
                            report.Rssi
                            );
                        break;
                    case TagOperationResult.TAG_OPERATION_RESULT_FAIL_SERIAL_NUMBER_RANGE_EMPTY:
                        Console.WriteLine("{0},{1},{2},{3},{4},{5},{6}",
                            timestamp,
                            report.TagOperationResult,
                            report.Tid.ToHexString(),
                            report.Epc.ToHexString(),
                            report.AntennaId.ToString(),
                            report.Frequency,
                            report.Rssi
                            );
                        serial_number_range_empty = true;
                        break;
                    default:
                        Console.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},{8}",
                            timestamp,
                            report.TagOperationResult,
                            report.Tid.ToHexString(),
                            report.Epc.ToHexString(),
                            report.EpcAttempted,
                            report.EpcMemoryResult.ToString(),
                            report.AntennaId.ToString(),
                            report.Frequency,
                            report.Rssi
                            );
                        break;
                }
                // Only handle Encode reports
            }
        }

        // Utility procedure for changing console window configuration,
        // including the window title
        private static void InitializeConsoleWindow()
        {
            Console.Clear();
            Console.Title = "Impinj ItemEncode 2.x - Data Management Serial Number Range Example";
            Console.SetWindowSize(150, 50);
            Console.BufferHeight = 1000;
            Console.TreatControlCAsInput = true;
        }

    }
}

