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

#ifndef _IPJ_BASE_H_
#define _IPJ_BASE_H_

#include <stdint.h>

#ifdef UNDER_CE
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
#endif
#endif


/*
 * The DLL export macro def must preceed the generated protobuf
 * header which contains symbols decorated with IPJ_EXPORT.
 */
#ifdef __gnu_linux__
#define IPJ_EXPORT
#endif

#ifdef UNDER_CE
// Since we're never going to make a dll in CE, get rid of the declspec stuff.
#define IPJ_EXPORT
#else
#ifdef _WIN32
#ifdef IPJ_EXPORT_MODULE
#define IPJ_EXPORT __declspec(dllexport)
#else
    #ifdef IPJ_DLL_MODULE
    #define IPJ_EXPORT __declspec(dllimport)
    #else
    #define IPJ_EXPORT
    #endif
#endif
#endif
#endif

#include "stp_api.pb.h"

#define IPJ_PORT 49370

#include <map>

enum CONNECTION_TYPE
{
    CONNECTION_TYPE_NONE,
    CONNECTION_TYPE_SOCKET,
    CONNECTION_TYPE_SERIAL
};

namespace Impinj {
/* Helper functions */
IPJ_EXPORT
std::string BinToHexString(const std::string& InStr);

IPJ_EXPORT
std::string HexStringToBin(const std::string& InStr);
}

namespace Impinj {
namespace STP {

class Device;

/* Report handler interfaces and declarations. */
class IPJ_EXPORT IAllReportsHandler
{
    public:
        virtual void HandleAllReports(const Impinj::STP::Report& report,
                                      const Impinj::STP::ReportType type) = 0;
};

class IPJ_EXPORT IOperationSummaryReportHandler
{
    public:
    virtual void HandleOperationSummaryReport(
        const Impinj::STP::OperationSummaryReport& report) = 0;
};

class IPJ_EXPORT ITagOperationReportHandler
{
    public:
    virtual void HandleTagOperationReport(
        const Impinj::STP::TagOperationReport& report) = 0;
};

class IPJ_EXPORT IDeviceEventReportHandler
{
    public:
    virtual void HandleDeviceEventReport(
        const Impinj::STP::DeviceEventReport& report) = 0;
};

class IPJ_EXPORT ICartonContentsReportHandler
{
    public:
    virtual void HandleCartonContentsReport(
        const Impinj::STP::CartonContentsReport& report) = 0;
};

class ReportHandler : public IAllReportsHandler,
    public IOperationSummaryReportHandler,
    public ITagOperationReportHandler,
    public IDeviceEventReportHandler,
    public ICartonContentsReportHandler
{
    public:
    void HandleAllReports(const Impinj::STP::Report& report,
                          const Impinj::STP::ReportType type);
    void HandleOperationSummaryReport(
        const Impinj::STP::OperationSummaryReport& report);
    void HandleTagOperationReport(
        const Impinj::STP::TagOperationReport& report);
    void HandleDeviceEventReport(
        const Impinj::STP::DeviceEventReport& report);
    void HandleCartonContentsReport(
        const Impinj::STP::CartonContentsReport& report);
};

class IPJ_EXPORT TidTableSender
{
    public:
    TidTableSender(uint32_t nInitialFrameSize = 1500);
    const Reply AddListener(const std::string& listenerNameOrIp);
    const Reply RemoveListener(const std::string& listenerNameOrIp);
    const Reply AddTidTableEntries(
        const Impinj::STP::TIDTableEntries& table_entries);

    /* MutableTidTableEntries() and SendTidTableEntries() can be used to 
       accomplish the same action as AddTidTableEntries() with less copying. It
       is not thread-safe. */
    Impinj::STP::TIDTableEntries* MutableTidTableEntries();
    void SendTidTableEntries();

    protected:
    /* Readers listening for TID table entries. Map from IP addr to socket */
    std::map<std::string, int> m_TidEntryListeners;
    Impinj::STP::Packet m_Packet;
    std::vector<uint8_t> m_Frame;

    void GenerateFrame(const Impinj::STP::Packet& packet);
    void BroadcastPacket(const Impinj::STP::Packet& packet);
    void BroadcastFrame();
    int NetConnect(std::string& reader, uint16_t port = IPJ_PORT);
};

class IPJ_EXPORT Device
{
    protected:
    std::vector<uint8_t> m_TxFrame;
    Impinj::STP::Packet m_RxPacket;
    IAllReportsHandler*  m_pAllReportHandler;
    ReportHandler   m_DefaultReportHandler;
    #ifdef __gnu_linux__
    int             m_socket;
    pthread_t       m_rxThread;
    pthread_mutex_t m_read_mutex;
    pthread_cond_t  m_read_cond;
    bool            m_bResponseReceived;
    #endif

    #ifdef _WIN32
    SOCKET              m_socket;
    HANDLE              m_rxThread;
    DWORD               m_thread_id;
    CRITICAL_SECTION    m_read_mutex;
    HANDLE              m_read_cond;
    #endif

    CONNECTION_TYPE m_connectionType;
    uint32_t        m_u32ResponseTimeout_ms;

    void            DispatchPacket(const uint8_t* pBuffer, int32_t length);
    static void*    RXThreadFunc(void* pThreadParam);
    int32_t         Rx(uint8_t* pBuffer, std::size_t nSize);
    int32_t         Tx(const uint8_t* pBuffer, int32_t length);
    void            GenerateFrame(const Impinj::STP::Packet& packet);
    int32_t         TxPacket(const Impinj::STP::Packet& packet);
    void            TxFrame();
    void BuildVersionFromString(Impinj::STP::Version& versionData,
                                const std::string versionString,
                                Impinj::STP::VersionType& versionType,
                                const std::string identData);
    bool SendCommandWaitForResponse(const Impinj::STP::Packet& packet);
    const Impinj::STP::Reply SendHandshake();
    std::string     ApiToolVersion();
    uint32_t        ApiVersion();
    std::string     ApiName();
    bool            HandleReport(const Impinj::STP::Report& rxReport);

    public:
    Device(void);
    ~Device(void);
    const Impinj::STP::Reply GetVersions(Impinj::STP::Versions& versions);
    const Impinj::STP::Reply NetConnect(const std::string reader,
                                        uint16_t port = IPJ_PORT);
    void Disconnect(void);
    void NetDisconnect(void);
    void RegisterAllReportsHandler(IAllReportsHandler* pHandler);
    void UnregisterAllReportsHandler(IAllReportsHandler* pHandler);
    void SetResponseTimeout(uint32_t uResponseTimeout_s);
    void ResetResponseTimeout(void);
    
    const Impinj::STP::Reply Connect();
    const Impinj::STP::Reply GetVersionsReader(
        Impinj::STP::Versions* versions);
    const Impinj::STP::Reply Start();
    const Impinj::STP::Reply Stop();
    const Impinj::STP::Reply SetDeviceConfig(
        const Impinj::STP::DeviceConfig& device_config);
    const Impinj::STP::Reply GetDeviceConfig(
        Impinj::STP::DeviceConfig* device_config);
    const Impinj::STP::Reply SetAntennaConfig(
        const Impinj::STP::AntennasConfig& antennas_config);
    const Impinj::STP::Reply GetAntennaConfig(
        Impinj::STP::AntennasConfig* antennas_config);
    const Impinj::STP::Reply Engage();
    const Impinj::STP::Reply Disengage();
    const Impinj::STP::Reply SetOperationResultGpo(
        const Impinj::STP::OperationSummaryResult& operation_summary_result);
    const Impinj::STP::Reply AddSerializationJob(
        const Impinj::STP::SerializationJob& serialization_job);
    const Impinj::STP::Reply RemoveSerializationJob(uint32_t job_id);
    const Impinj::STP::Reply AddTidTableEntries(
        const Impinj::STP::TIDTableEntries& table_entries);
    const Impinj::STP::Reply AddOrderedListEntries(
        const Impinj::STP::OrderedListEntries& ordered_list_entries);
    const Impinj::STP::Reply SetInventoryJob(
        const Impinj::STP::InventoryJob& inventory_job);
    const Impinj::STP::Reply SetCartonContentsJob(
        const Impinj::STP::CartonContentsJob& carton_contents_job);
    const Impinj::STP::Reply GetOperationData(
        Impinj::STP::OperationData* operation_data);
    const Impinj::STP::Reply AddLicenseKey(
        const Impinj::STP::LicenseKey& license_key);
    const Impinj::STP::Reply GetDeviceState(
        Impinj::STP::DeviceState* device_state);

    private:
    void HandleOperationSummaryReport(const Impinj::STP::Report& rxReport);
    void HandleTagOperationReport(const Impinj::STP::Report& rxReport);
    void HandleDeviceEventReport(const Impinj::STP::Report& rxReport);
    void HandleCartonContentsReport(const Impinj::STP::Report& rxReport);

    protected:
    IOperationSummaryReportHandler* m_pOperationSummaryReportHandler;
    ITagOperationReportHandler* m_pTagOperationReportHandler;
    IDeviceEventReportHandler* m_pDeviceEventReportHandler;
    ICartonContentsReportHandler* m_pCartonContentsReportHandler;

    public:
    void RegisterOperationSummaryReportHandler(
        IOperationSummaryReportHandler * pIOperationSummaryReport);
    void UnregisterOperationSummaryReportHandler(
        IOperationSummaryReportHandler * pIOperationSummaryReport);
    void RegisterTagOperationReportHandler(
        ITagOperationReportHandler * pITagOperationReport);
    void UnregisterTagOperationReportHandler(
        ITagOperationReportHandler * pITagOperationReport);
    void RegisterDeviceEventReportHandler(
        IDeviceEventReportHandler * pIDeviceEventReport);
    void UnregisterDeviceEventReportHandler(
        IDeviceEventReportHandler * pIDeviceEventReport);
    void RegisterCartonContentsReportHandler(
        ICartonContentsReportHandler * pICartonContentsReport);
    void UnregisterCartonContentsReportHandler(
        ICartonContentsReportHandler * pICartonContentsReport);
};

}
}

#endif