// SPDX-License-Identifier: MIT
/** @file
   Copyright (c) 2015 - 2022 Beckhoff Automation GmbH & Co. KG
 */

#pragma once

#include <cstdint>
#include <string>

#define ADS_TCP_SERVER_PORT 0xBF02

////////////////////////////////////////////////////////////////////////////////
// AMS Ports
enum AMSPORT : uint16_t {
	AMSPORT_LOGGER = 100,
	AMSPORT_R0_RTIME = 200,
	AMSPORT_R0_TRACE = (AMSPORT_R0_RTIME + 90),
	AMSPORT_R0_IO = 300,
	AMSPORT_R0_SPS = 400,
	AMSPORT_R0_NC = 500,
	AMSPORT_R0_ISG = 550,
	AMSPORT_R0_PCS = 600,
	AMSPORT_R0_PLC = 801,
	AMSPORT_R0_PLC_RTS1 = 801,
	AMSPORT_R0_PLC_RTS2 = 811,
	AMSPORT_R0_PLC_RTS3 = 821,
	AMSPORT_R0_PLC_RTS4 = 831,
	AMSPORT_R0_PLC_TC3 = 851
};

////////////////////////////////////////////////////////////////////////////////
// ADS Cmd Ids
#define ADSSRVID_INVALID 0x00
#define ADSSRVID_READDEVICEINFO 0x01
#define ADSSRVID_READ 0x02
#define ADSSRVID_WRITE 0x03
#define ADSSRVID_READSTATE 0x04
#define ADSSRVID_WRITECTRL 0x05
#define ADSSRVID_ADDDEVICENOTE 0x06
#define ADSSRVID_DELDEVICENOTE 0x07
#define ADSSRVID_DEVICENOTE 0x08
#define ADSSRVID_READWRITE 0x09

////////////////////////////////////////////////////////////////////////////////
// ADS reserved index groups
#define ADSIGRP_SYMTAB 0xF000
#define ADSIGRP_SYMNAME 0xF001
#define ADSIGRP_SYMVAL 0xF002

#define ADSIGRP_SYM_HNDBYNAME 0xF003
#define ADSIGRP_SYM_VALBYNAME 0xF004
#define ADSIGRP_SYM_VALBYHND 0xF005
#define ADSIGRP_SYM_RELEASEHND 0xF006
#define ADSIGRP_SYM_INFOBYNAME 0xF007
#define ADSIGRP_SYM_VERSION 0xF008
#define ADSIGRP_SYM_INFOBYNAMEEX 0xF009

#define ADSIGRP_SYM_DOWNLOAD 0xF00A
#define ADSIGRP_SYM_UPLOAD 0xF00B
#define ADSIGRP_SYM_UPLOADINFO 0xF00C
#define ADSIGRP_SYM_DOWNLOAD2 0xF00D
#define ADSIGRP_SYM_DT_UPLOAD 0xF00E
#define ADSIGRP_SYM_UPLOADINFO2 0xF00F

#define ADSIGRP_SYMNOTE 0xF010 /**< notification of named handle */

/**
 * AdsRW  IOffs list size or 0 (=0 -> list size == WLength/3*sizeof(ULONG))
 * @param W: {list of IGrp, IOffs, Length}
 * @param R: if IOffs != 0 then {list of results} and {list of data}
 * @param R: if IOffs == 0 then only data (sum result)
 */
#define ADSIGRP_SUMUP_READ 0xF080

/**
 * AdsRW  IOffs list size
 * @param W: {list of IGrp, IOffs, Length} followed by {list of data}
 * @param R: list of results
 */
#define ADSIGRP_SUMUP_WRITE 0xF081

/**
 * AdsRW  IOffs list size
 * @param W: {list of IGrp, IOffs, RLength, WLength} followed by {list of data}
 * @param R: {list of results, RLength} followed by {list of data}
 */
#define ADSIGRP_SUMUP_READWRITE 0xF082

/**
 * AdsRW  IOffs list size
 * @param W: {list of IGrp, IOffs, Length}
 */
#define ADSIGRP_SUMUP_READEX 0xF083

/**
 * AdsRW  IOffs list size
 * @param W: {list of IGrp, IOffs, Length}
 * @param R: {list of results, Length} followed by {list of data (returned lengths)}
 */
#define ADSIGRP_SUMUP_READEX2 0xF084

/**
 * AdsRW  IOffs list size
 * @param W: {list of IGrp, IOffs, Attrib}
 * @param R: {list of results, handles}
 */
#define ADSIGRP_SUMUP_ADDDEVNOTE 0xF085

/**
 * AdsRW  IOffs list size
 * @param W: {list of handles}
 * @param R: {list of results, Length} followed by {list of data}
 */
#define ADSIGRP_SUMUP_DELDEVNOTE 0xF086

#define ADSIGRP_IOIMAGE_RWIB 0xF020 /**< read/write input byte(s) */
#define ADSIGRP_IOIMAGE_RWIX 0xF021 /**< read/write input bit */
#define ADSIGRP_IOIMAGE_RISIZE 0xF025 /**< read input size (in byte) */
#define ADSIGRP_IOIMAGE_RWOB 0xF030 /**< read/write output byte(s) */
#define ADSIGRP_IOIMAGE_RWOX 0xF031 /**< read/write output bit */
#define ADSIGRP_IOIMAGE_ROSIZE 0xF035 /**< read output size (in byte) */
#define ADSIGRP_IOIMAGE_CLEARI 0xF040 /**< write inputs to null */
#define ADSIGRP_IOIMAGE_CLEARO 0xF050 /**< write outputs to null */
#define ADSIGRP_IOIMAGE_RWIOB 0xF060 /**< read input and write output byte(s) */

#define ADSIGRP_DEVICE_DATA 0xF100 /**< state, name, etc... */
#define ADSIOFFS_DEVDATA_ADSSTATE 0x0000 /**< ads state of device */
#define ADSIOFFS_DEVDATA_DEVSTATE 0x0002 /**< device state */

////////////////////////////////////////////////////////////////////////////////
// Global Return codes
#define ERR_GLOBAL 0x0000

#define GLOBALERR_TARGET_PORT \
	(0x06 +               \
	 ERR_GLOBAL) /**< target port not found, possibly the ADS Server is not started */
#define GLOBALERR_MISSING_ROUTE \
	(0x07 +                 \
	 ERR_GLOBAL) /**< target machine not found, possibly missing ADS routes */
#define GLOBALERR_NO_MEMORY (0x19 + ERR_GLOBAL) /**< No memory */
#define GLOBALERR_TCP_SEND (0x1A + ERR_GLOBAL) /**< TCP send error */

////////////////////////////////////////////////////////////////////////////////
// Router Return codes
#define ERR_ROUTER 0x0500

#define ROUTERERR_PORTALREADYINUSE \
	(0x06 + ERR_ROUTER) /**< The desired port number is already assigned */
#define ROUTERERR_NOTREGISTERED (0x07 + ERR_ROUTER) /**< Port not registered */
#define ROUTERERR_NOMOREQUEUES \
	(0x08 + ERR_ROUTER) /**< The maximum number of Ports reached */

////////////////////////////////////////////////////////////////////////////////
// ADS Return codes
#define ADSERR_NOERR 0x00
#define ERR_ADSERRS 0x0700

#define ADSERR_DEVICE_ERROR \
	(0x00 + ERR_ADSERRS) /**< Error class < device error > */
#define ADSERR_DEVICE_SRVNOTSUPP \
	(0x01 + ERR_ADSERRS) /**< Service is not supported by server */
#define ADSERR_DEVICE_INVALIDGRP (0x02 + ERR_ADSERRS) /**< invalid indexGroup */
#define ADSERR_DEVICE_INVALIDOFFSET \
	(0x03 + ERR_ADSERRS) /**< invalid indexOffset */
#define ADSERR_DEVICE_INVALIDACCESS \
	(0x04 + ERR_ADSERRS) /**< reading/writing not permitted */
#define ADSERR_DEVICE_INVALIDSIZE \
	(0x05 + ERR_ADSERRS) /**< parameter size not correct */
#define ADSERR_DEVICE_INVALIDDATA \
	(0x06 + ERR_ADSERRS) /**< invalid parameter value(s) */
#define ADSERR_DEVICE_NOTREADY \
	(0x07 + ERR_ADSERRS) /**< device is not in a ready state */
#define ADSERR_DEVICE_BUSY (0x08 + ERR_ADSERRS) /**< device is busy */
#define ADSERR_DEVICE_INVALIDCONTEXT \
	(0x09 + ERR_ADSERRS) /**< invalid context (must be InWindows) */
#define ADSERR_DEVICE_NOMEMORY (0x0A + ERR_ADSERRS) /**< out of memory */
#define ADSERR_DEVICE_INVALIDPARM \
	(0x0B + ERR_ADSERRS) /**< invalid parameter value(s) */
#define ADSERR_DEVICE_NOTFOUND \
	(0x0C + ERR_ADSERRS) /**< not found (files, ...) */
#define ADSERR_DEVICE_SYNTAX \
	(0x0D + ERR_ADSERRS) /**< syntax error in comand or file */
#define ADSERR_DEVICE_INCOMPATIBLE \
	(0x0E + ERR_ADSERRS) /**< objects do not match */
#define ADSERR_DEVICE_EXISTS (0x0F + ERR_ADSERRS) /**< object already exists */
#define ADSERR_DEVICE_SYMBOLNOTFOUND \
	(0x10 + ERR_ADSERRS) /**< symbol not found */
#define ADSERR_DEVICE_SYMBOLVERSIONINVALID \
	(0x11 +                            \
	 ERR_ADSERRS) /**< symbol version invalid, possibly caused by an 'onlinechange' -> try to release handle and get a new one */
#define ADSERR_DEVICE_INVALIDSTATE \
	(0x12 + ERR_ADSERRS) /**< server is in invalid state */
#define ADSERR_DEVICE_TRANSMODENOTSUPP \
	(0x13 + ERR_ADSERRS) /**< AdsTransMode not supported */
#define ADSERR_DEVICE_NOTIFYHNDINVALID \
	(0x14 +                        \
	 ERR_ADSERRS) /**< Notification handle is invalid, possibly caussed by an 'onlinechange' -> try to release handle and get a new one */
#define ADSERR_DEVICE_CLIENTUNKNOWN \
	(0x15 + ERR_ADSERRS) /**< Notification client not registered */
#define ADSERR_DEVICE_NOMOREHDLS \
	(0x16 + ERR_ADSERRS) /**< no more notification handles */
#define ADSERR_DEVICE_INVALIDWATCHSIZE \
	(0x17 + ERR_ADSERRS) /**< size for watch to big */
#define ADSERR_DEVICE_NOTINIT \
	(0x18 + ERR_ADSERRS) /**< device not initialized */
#define ADSERR_DEVICE_TIMEOUT (0x19 + ERR_ADSERRS) /**< device has a timeout */
#define ADSERR_DEVICE_NOINTERFACE \
	(0x1A + ERR_ADSERRS) /**< query interface failed */
#define ADSERR_DEVICE_INVALIDINTERFACE \
	(0x1B + ERR_ADSERRS) /**< wrong interface required */
#define ADSERR_DEVICE_INVALIDCLSID \
	(0x1C + ERR_ADSERRS) /**< class ID is invalid */
#define ADSERR_DEVICE_INVALIDOBJID \
	(0x1D + ERR_ADSERRS) /**< object ID is invalid */
#define ADSERR_DEVICE_PENDING (0x1E + ERR_ADSERRS) /**< request is pending */
#define ADSERR_DEVICE_ABORTED (0x1F + ERR_ADSERRS) /**< request is aborted */
#define ADSERR_DEVICE_WARNING (0x20 + ERR_ADSERRS) /**< signal warning */
#define ADSERR_DEVICE_INVALIDARRAYIDX \
	(0x21 + ERR_ADSERRS) /**< invalid array index */
#define ADSERR_DEVICE_SYMBOLNOTACTIVE \
	(0x22 +                       \
	 ERR_ADSERRS) /**< symbol not active, possibly caussed by an 'onlinechange' -> try to release handle and get a new one */
#define ADSERR_DEVICE_ACCESSDENIED (0x23 + ERR_ADSERRS) /**< access denied */
#define ADSERR_DEVICE_LICENSENOTFOUND \
	(0x24 +                       \
	 ERR_ADSERRS) /**< no license found -> Activate license for TwinCAT 3 function*/
#define ADSERR_DEVICE_LICENSEEXPIRED \
	(0x25 + ERR_ADSERRS) /**< license expired */
#define ADSERR_DEVICE_LICENSEEXCEEDED \
	(0x26 + ERR_ADSERRS) /**< license exceeded */
#define ADSERR_DEVICE_LICENSEINVALID \
	(0x27 + ERR_ADSERRS) /**< license invalid */
#define ADSERR_DEVICE_LICENSESYSTEMID \
	(0x28 + ERR_ADSERRS) /**< license invalid system id */
#define ADSERR_DEVICE_LICENSENOTIMELIMIT \
	(0x29 + ERR_ADSERRS) /**< license not time limited */
#define ADSERR_DEVICE_LICENSEFUTUREISSUE \
	(0x2A + ERR_ADSERRS) /**< license issue time in the future */
#define ADSERR_DEVICE_LICENSETIMETOLONG \
	(0x2B + ERR_ADSERRS) /**< license time period to long */
#define ADSERR_DEVICE_EXCEPTION \
	(0x2C +                 \
	 ERR_ADSERRS) /**< exception in device specific code -> Check each device transistions */
#define ADSERR_DEVICE_LICENSEDUPLICATED \
	(0x2D + ERR_ADSERRS) /**< license file read twice */
#define ADSERR_DEVICE_SIGNATUREINVALID \
	(0x2E + ERR_ADSERRS) /**< invalid signature */
#define ADSERR_DEVICE_CERTIFICATEINVALID \
	(0x2F + ERR_ADSERRS) /**< public key certificate */

#define ADSERR_CLIENT_ERROR \
	(0x40 + ERR_ADSERRS) /**< Error class < client error > */
#define ADSERR_CLIENT_INVALIDPARM \
	(0x41 + ERR_ADSERRS) /**< invalid parameter at service call */
#define ADSERR_CLIENT_LISTEMPTY \
	(0x42 + ERR_ADSERRS) /**< polling list	is empty */
#define ADSERR_CLIENT_VARUSED \
	(0x43 + ERR_ADSERRS) /**< var connection already in use */
#define ADSERR_CLIENT_DUPLINVOKEID (0x44 + ERR_ADSERRS) /**< invoke id in use */
#define ADSERR_CLIENT_SYNCTIMEOUT \
	(0x45 +                   \
	 ERR_ADSERRS) /**< timeout elapsed -> Check ADS routes of sender and receiver and your [firewall setting](http://infosys.beckhoff.com/content/1033/tcremoteaccess/html/tcremoteaccess_firewall.html?id=12027) */
#define ADSERR_CLIENT_W32ERROR \
	(0x46 + ERR_ADSERRS) /**< error in win32 subsystem */
#define ADSERR_CLIENT_TIMEOUTINVALID \
	(0x47 + ERR_ADSERRS) /**< Invalid client timeout value */
#define ADSERR_CLIENT_PORTNOTOPEN (0x48 + ERR_ADSERRS) /**< ads dll */
#define ADSERR_CLIENT_NOAMSADDR (0x49 + ERR_ADSERRS) /**< ads dll */
#define ADSERR_CLIENT_SYNCINTERNAL \
	(0x50 + ERR_ADSERRS) /**< internal error in ads sync */
#define ADSERR_CLIENT_ADDHASH (0x51 + ERR_ADSERRS) /**< hash table overflow */
#define ADSERR_CLIENT_REMOVEHASH \
	(0x52 + ERR_ADSERRS) /**< key not found in hash table */
#define ADSERR_CLIENT_NOMORESYM \
	(0x53 + ERR_ADSERRS) /**< no more symbols in cache */
#define ADSERR_CLIENT_SYNCRESINVALID \
	(0x54 + ERR_ADSERRS) /**< invalid response received */
#define ADSERR_CLIENT_SYNCPORTLOCKED \
	(0x55 + ERR_ADSERRS) /**< sync port is locked */

#pragma pack(push, 1)

/**
 * @brief The NetId of and ADS device can be represented in this structure.
 */
struct AmsNetId {
	/** NetId, consisting of 6 digits. */
	uint8_t b[6];

	AmsNetId(uint32_t ipv4Addr = 0);
	AmsNetId(const std::string &addr);
	AmsNetId(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
	operator bool() const;
};

/**
 * @brief The complete address of an ADS device can be stored in this structure.
 */
struct AmsAddr {
	/** AMS Net Id */
	AmsNetId netId;

	/** AMS Port number */
	uint16_t port;
};

/**
 * @brief The structure contains the version number, revision number and build number.
 */
struct AdsVersion {
	/** Version number. */
	uint8_t version;

	/** Revision number. */
	uint8_t revision;

	/** Build number */
	uint16_t build;
};

enum ADSTRANSMODE {
	ADSTRANS_NOTRANS = 0,
	ADSTRANS_CLIENTCYCLE = 1,
	ADSTRANS_CLIENTONCHA = 2,
	ADSTRANS_SERVERCYCLE = 3,
	ADSTRANS_SERVERONCHA = 4,
	ADSTRANS_SERVERCYCLE2 = 5,
	ADSTRANS_SERVERONCHA2 = 6,
	ADSTRANS_CLIENT1REQ = 10,
	ADSTRANS_MAXMODES
};

enum ADSSTATE : uint16_t {
	ADSSTATE_INVALID = 0,
	ADSSTATE_IDLE = 1,
	ADSSTATE_RESET = 2,
	ADSSTATE_INIT = 3,
	ADSSTATE_START = 4,
	ADSSTATE_RUN = 5,
	ADSSTATE_STOP = 6,
	ADSSTATE_SAVECFG = 7,
	ADSSTATE_LOADCFG = 8,
	ADSSTATE_POWERFAILURE = 9,
	ADSSTATE_POWERGOOD = 10,
	ADSSTATE_ERROR = 11,
	ADSSTATE_SHUTDOWN = 12,
	ADSSTATE_SUSPEND = 13,
	ADSSTATE_RESUME = 14,
	ADSSTATE_CONFIG = 15,
	ADSSTATE_RECONFIG = 16,
	ADSSTATE_STOPPING = 17,
	ADSSTATE_INCOMPATIBLE = 18,
	ADSSTATE_EXCEPTION = 19,
	ADSSTATE_MAXSTATES
};

/**
 * @brief This structure contains all the attributes for the definition of a notification.
 *
 * The ADS DLL is buffered from the real time transmission by a FIFO.
 * TwinCAT first writes every value that is to be transmitted by means
 * of the callback function into the FIFO. If the buffer is full, or if
 * the nMaxDelay time has elapsed, then the callback function is invoked
 * for each entry. The nTransMode parameter affects this process as follows:
 *
 * @par ADSTRANS_SERVERCYCLE
 * The value is written cyclically into the FIFO at intervals of
 * nCycleTime. The smallest possible value for nCycleTime is the cycle
 * time of the ADS server; for the PLC, this is the task cycle time.
 * The cycle time can be handled in 1ms steps. If you enter a cycle time
 * of 0 ms, then the value is written into the FIFO with every task cycle.
 *
 * @par ADSTRANS_SERVERONCHA
 * A value is only written into the FIFO if it has changed. The real-time
 * sampling is executed in the time given in nCycleTime. The cycle time
 * can be handled in 1ms steps. If you enter 0 ms as the cycle time, the
 * variable is written into the FIFO every time it changes.
 *
 * Warning: Too many read operations can load the system so heavily that
 * the user interface becomes much slower.
 *
 * Tip: Set the cycle time to the most appropriate values, and always
 * close connections when they are no longer required.
 */
struct AdsNotificationAttrib {
	/** Length of the data that is to be passed to the callback function. */
	uint32_t cbLength;

	/**
     * ADSTRANS_SERVERCYCLE: The notification's callback function is invoked cyclically.
     * ADSTRANS_SERVERONCHA: The notification's callback function is only invoked when the value changes.
     */
	uint32_t nTransMode;

	/** The notification's callback function is invoked at the latest when this time has elapsed. The unit is 100 ns. */
	uint32_t nMaxDelay;

	union {
		/** The ADS server checks whether the variable has changed after this time interval. The unit is 100 ns. */
		uint32_t nCycleTime;
		uint32_t dwChangeFilter;
	};
};

/**
 * @brief This structure is also passed to the callback function.
 */
struct AdsNotificationHeader {
/* Original TwinCAT SDK headers have
 * uint32_t | uint64_t | uint32_t
 * We use the same order when compiled as a shared library to allow
 * consumers to dynamically link against us or the upstream TwinCAT.
 */
#ifdef BHF_ADS_USE_TWINCAT_ORDER
	/** Handle for the notification. Is specified when the notification is defined. */
	uint32_t hNotification;

	/** Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). */
	uint64_t nTimeStamp;
#else
	/** Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). */
	uint64_t nTimeStamp;

	/** Handle for the notification. Is specified when the notification is defined. */
	uint32_t hNotification;
#endif
	/** Number of bytes transferred. */
	uint32_t cbSampleSize;
};

/**
 * @brief Type definition of the callback function required by the AdsSyncAddDeviceNotificationReqEx() function.
 * @param[in] pAddr Structure with NetId and port number of the ADS server.
 * @param[in] pNotification pointer to a AdsNotificationHeader structure
 * @param[in] hUser custom handle pass to AdsSyncAddDeviceNotificationReqEx() during registration
 */
typedef void (*PAdsNotificationFuncEx)(
	const AmsAddr *pAddr, const AdsNotificationHeader *pNotification,
	uint32_t hUser);

#define ADSSYMBOLFLAG_PERSISTENT ((uint32_t)(1 << 0))
#define ADSSYMBOLFLAG_BITVALUE ((uint32_t)(1 << 1))
#define ADSSYMBOLFLAG_REFERENCETO ((uint32_t)(1 << 2))
#define ADSSYMBOLFLAG_TYPEGUID ((uint32_t)(1 << 3))
#define ADSSYMBOLFLAG_TCCOMIFACEPTR ((uint32_t)(1 << 4))
#define ADSSYMBOLFLAG_READONLY ((uint32_t)(1 << 5))
#define ADSSYMBOLFLAG_CONTEXTMASK ((uint32_t)0xF00)

/**
 * @brief This structure describes the header of ADS symbol information
 *
 * Calling AdsSyncReadWriteReqEx2 with IndexGroup == ADSIGRP_SYM_INFOBYNAMEEX
 * will return ADS symbol information in the provided readData buffer.
 * The header of that information is structured as AdsSymbolEntry and can
 * be followed by zero terminated strings for "symbol name", "type name"
 * and a "comment"
 */
struct AdsSymbolEntry {
	uint32_t entryLength; // length of complete symbol entry
	uint32_t iGroup; // indexGroup of symbol: input, output etc.
	uint32_t iOffs; // indexOffset of symbol
	uint32_t size; // size of symbol ( in bytes, 0 = bit )
	uint32_t dataType; // adsDataType of symbol
	uint32_t flags; // see ADSSYMBOLFLAG_*
	uint16_t nameLength; // length of symbol name (null terminating character not counted)
	uint16_t typeLength; // length of type name (null terminating character not counted)
	uint16_t commentLength; // length of comment (null terminating character not counted)
};

/**
 * @brief This structure is used to provide ADS symbol information for ADS SUM commands
 */
struct AdsSymbolInfoByName {
	uint32_t indexGroup;
	uint32_t indexOffset;
	uint32_t cbLength;
};
#pragma pack(pop)

enum nSystemServiceIndexGroups : uint32_t {
	SYSTEMSERVICE_FOPEN = 120,
	SYSTEMSERVICE_FCLOSE = 121,
	SYSTEMSERVICE_FREAD = 122,
	SYSTEMSERVICE_FWRITE = 123,
	SYSTEMSERVICE_FDELETE = 131,
	SYSTEMSERVICE_FFILEFIND = 133,
	SYSTEMSERVICE_STARTPROCESS = 500,
	SYSTEMSERVICE_SETNUMPROC = 1200
};
