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

/*
 * Example client program
 *
 * On Linux, build with "make" using the Makefile in this directory.
 *
 * On Windows, use the Visual Studio project file: client_example.sln .
 */

#ifdef _WIN32
#include "stdafx.h"
#include <windows.h>
#else
#include <unistd.h>
#endif

#include <stdint.h>

#include <iomanip>
#include <iostream>
#include <tuple>

#include "stp.h"

class TagReportHandler : public Impinj::STP::ITagOperationReportHandler
{
public:
	TagReportHandler() : nNumberOfTagReports(0) {}

    void HandleTagOperationReport(
        const Impinj::STP::TagOperationReport& report);

    unsigned int nNumberOfTagReports;
};

class SummaryReportHandler : public Impinj::STP::IOperationSummaryReportHandler
{
public:
    void HandleOperationSummaryReport(
        const Impinj::STP::OperationSummaryReport& report);
};

void TagReportHandler::HandleTagOperationReport(
    const Impinj::STP::TagOperationReport& report)
{
    this->nNumberOfTagReports++;
    std::cout << "App HandleTagOperationReport (reports received="
              << this->nNumberOfTagReports << ")" << std::endl;

    std::cout << " EPC=" << Impinj::BinToHexString(report.epc()) << std::endl;
    std::cout << " TID=" << Impinj::BinToHexString(report.tid()) << std::endl;

    if (Impinj::STP::TAG_OPERATION_REPORT_TYPE_ENCODE ==
        report.tag_operation_report_type())
    {
        std::cout << " EPC_Memory_Result=" << report.epc_memory_result()
                  << std::endl;

        std::cout << " EPC_Attempted="
                  << Impinj::BinToHexString(report.epc_attempted())
                  << std::endl;
    }

    std::cout << " Operation result=" << report.tag_operation_result()
              << std::endl;
    std::cout << "==============================" << std::endl;
}

void SummaryReportHandler::HandleOperationSummaryReport(
    const Impinj::STP::OperationSummaryReport& report)
{
    std::cout << "Summary result:" << report.operation_result() << std::endl;
}

static std::tuple<std::string, bool, bool, bool> ParseCmdLineArgs(int argc,
                                                                  char* argv[])
{
    std::string readerName = "localhost";
    bool bEncode = false;
    bool bSingleMode = false;
    bool bSerialReporting = false;

    int i;
    for(i = 1; i < argc; i++)
    {
        /* Assume non dashed param is the reader name */
        if (argv[i][0] != '-')
        {
            readerName = argv[i];
            continue;
        }

        if (!strcmp("-e", argv[i]) || !strcmp("--encode", argv[i]))
        {
            bEncode = true;
        }
        if (!strcmp("-s", argv[i]) || !strcmp("--single", argv[i]))
        {
            bSingleMode = true;
            bSerialReporting = true;
        }
    }

    return std::make_tuple(readerName, bEncode, bSingleMode, bSerialReporting);
}

static void PrintVersion(const Impinj::STP::Version& version)
{
    switch(version.version_id())
    {
        case Impinj::STP::VERSION_TYPE_KODIAK_HOST:
            std::cout << "KODIAK_HOST = ";
            break;
        case Impinj::STP::VERSION_TYPE_KODIAK_READER:
            std::cout << "KODIAK_READER = ";
            break;
        case Impinj::STP::VERSION_TYPE_FRAME_READER:
            std::cout << "FRAME_READER = ";
            break;
        case Impinj::STP::VERSION_TYPE_API_HOST:
            std::cout << "API_HOST = ";
            break;
        case Impinj::STP::VERSION_TYPE_API_READER:
            std::cout << "API_READER = ";
            break;
        case Impinj::STP::VERSION_TYPE_FIRMWARE:
            std::cout << "FIRMWARE = ";
            break;
        default:
            break;
    }
    std::cout << version.major() << "." << version.minor() << "."
              << version.maintenance() << "." << version.build()
              << " ident = " << version.ident() << std::endl;
}

int main(int argc, char* argv[])
{
    std::string readerName;
    bool bGpiTrigger = false;
    bool bEncode = false;
    bool bSingleMode = false;
    bool bSerialReporting = false;
    TagReportHandler tagReportHandler;
    SummaryReportHandler summaryReportHandler;

    std::tie(readerName, bEncode, bSingleMode, bSerialReporting) =
        ParseCmdLineArgs(argc, argv);

    std::cout << "Client starting" << std::endl;

    /* Connect to the reader specified on the command line */
    Impinj::STP::Device device;
    Impinj::STP::Reply reply;

    std::cout << "Connecting to " << readerName << std::endl;
    reply = device.NetConnect(readerName);
    if (Impinj::STP::ERROR_CODE_SUCCESS != reply.error_code())
    {
        std::cout << "Connection failed with code " << reply.error_code()
                  << " : " << reply.error_string();
        exit(1);
    }

    /* Register for reports */
    device.RegisterTagOperationReportHandler(&tagReportHandler);
    device.RegisterOperationSummaryReportHandler(&summaryReportHandler);

    /* Show versions */
    Impinj::STP::Versions versions;
    reply = device.GetVersions(versions);

    std::cout << "Versions: " << std::endl;
    for (const Impinj::STP::Version& version : versions.version_list())
    {
        PrintVersion(version);
    }

    /* Antenna config. */
    Impinj::STP::AntennasConfig antennasConfig;
    Impinj::STP::AntennaConfig* pAntennaConfig =
        antennasConfig.add_antenna_config();
    pAntennaConfig->set_antenna_id(1);
    pAntennaConfig->set_transmit_power(2900);
    pAntennaConfig->set_tag_population_estimate(2);
    pAntennaConfig->set_gen2_mode(Impinj::STP::GEN2_MODE_MAX_THROUGHPUT);
    pAntennaConfig->set_gen2_session(Impinj::STP::GEN2_SESSION_1);
    pAntennaConfig->set_inventory_search_mode(
        Impinj::STP::INVENTORY_SEARCH_MODE_DUAL_TARGET);
    reply = device.SetAntennaConfig(antennasConfig);

    /* Setup device config */
    Impinj::STP::DeviceConfig deviceConfig;
    deviceConfig.mutable_trigger_config()
        ->mutable_start_trigger_config()
        ->set_enable_gpi_start_trigger(bGpiTrigger);
    deviceConfig.mutable_trigger_config()
        ->mutable_stop_trigger_config()
        ->set_enable_gpi_stop_trigger(bGpiTrigger);

    /* Set encode or inventory mode */
    if (bEncode)
    {
        deviceConfig.set_device_mode(Impinj::STP::DEVICE_MODE_ENCODE);

        if (bSingleMode)
        {
            deviceConfig.set_operation_mode(Impinj::STP::OPERATION_MODE_SINGLE);

            /* Enable serial reporting */
            if (bSerialReporting)
            {
                deviceConfig.mutable_report_config()->
                    mutable_tag_operation_report_config()->
                    set_enable_tag_operation_reports(true);
                deviceConfig.mutable_report_config()->
                    mutable_tag_operation_report_config()->
                    set_enable_reporting_over_serial_port(true);
            }
        }
        else
        {
            deviceConfig.set_operation_mode(
                Impinj::STP::OPERATION_MODE_CONTINUOUS);
        }
    }
    else
    {
        deviceConfig.set_device_mode(Impinj::STP::DEVICE_MODE_INVENTORY);
        deviceConfig.set_operation_mode(Impinj::STP::OPERATION_MODE_CONTINUOUS);
    }

    if (bGpiTrigger)
    {
        /* GPI, trigger config. */
        deviceConfig.mutable_gpi_config()->set_signaling_mode(
            Impinj::STP::GPIO_SIGNAL_MODE_SEPARATE);
        deviceConfig.mutable_gpi_config()->set_gpi1_polarity(
            Impinj::STP::GPIO_POLARITY_ACTIVE_LOW);
        deviceConfig.mutable_gpi_config()->set_gpi2_polarity(
            Impinj::STP::GPIO_POLARITY_ACTIVE_LOW);
        deviceConfig.mutable_gpi_config()->set_gpi3_polarity(
            Impinj::STP::GPIO_POLARITY_ACTIVE_LOW);
    }

    /* GPO config */
    deviceConfig.mutable_gpo_config()->set_signaling_mode(
        Impinj::STP::GPIO_SIGNAL_MODE_SEPARATE);

    reply = device.SetDeviceConfig(deviceConfig);
    if (Impinj::STP::ERROR_CODE_SUCCESS != reply.error_code())
    {
        std::cout << "Device config failed: " << reply.error_string()
                  << std::endl;
        exit(1);
    }

    if (bEncode)
    {
        Impinj::STP::SerializationJob serializationJob =
            Impinj::STP::SerializationJob();

        serializationJob.set_job_id(1);
        serializationJob.set_job_type(
            Impinj::STP::SERIALIZATION_JOB_TYPE_STATIC);

        std::string epcData = "3008 33B2 DDD9 0140 0000 0000";
        serializationJob.mutable_epc_memory()->set_data(
            Impinj::HexStringToBin(epcData));

        reply = device.AddSerializationJob(serializationJob);
    }

    device.Engage();
    device.Start();

    /* Wait for tag reports to arrive. */
    std::cout << "Waiting for tag reports..." << std::endl;
#ifdef __gnu_linux__
    sleep(3);
#elif _WIN32
    Sleep(3000);
#endif

    /* Stop */
    device.Stop();
    device.Disengage();
    device.NetDisconnect();
    std::cout << "Done." << std::endl;

    return 0;
}

#if defined(_WIN32) && defined(UNICODE)
#error Todo: convert argv strings and call main.
int _tmain(int argc, _TCHAR* argv[]) { return main(argc, argv); }
#endif
