#ifndef PCAPPP_PF_RING_DEVICE
#define PCAPPP_PF_RING_DEVICE

#include "Device.h"
#include "MacAddress.h"
#include "SystemUtils.h"
#include "Packet.h"
#include <pthread.h>

/// @file

// forward declaration of PF_RING structs
struct __pfring;
typedef struct __pfring pfring;

/**
* \namespace pcpp
* \brief The main namespace for the PcapPlusPlus lib
*/
namespace pcpp
{

	class PfRingDevice;

	typedef void (*OnPfRingPacketsArriveCallback)(RawPacket* packets, uint32_t numOfPackets, uint8_t threadId, PfRingDevice* device, void* userCookie);


	/**
	 * @class PfRingDevice
	 * A class representing a PF_RING port
	 */
	class PfRingDevice : public IDevice, public IFilterableDevice
	{
		friend class PfRingDeviceList;
	private:

		struct CoreConfiguration
		{
			pthread_t RxThread;
			pfring* Channel;
			bool IsInUse;
			bool IsAffinitySet;

			CoreConfiguration();
			void clear();
		};

		pfring** m_PfRingDescriptors;
		uint8_t m_NumOfOpenedRxChannels;
		std::string m_DeviceName;
		int m_InterfaceIndex;
		MacAddress m_MacAddress;
		int m_DeviceMTU;
		CoreConfiguration m_CoreConfiguration[MAX_NUM_OF_CORES];
		bool m_StopThread;
		OnPfRingPacketsArriveCallback m_OnPacketsArriveCallback;
		void* m_OnPacketsArriveUserCookie;
		bool m_ReentrantMode;
		bool m_HwClockEnabled;
		bool m_IsFilterCurrentlySet;

		PfRingDevice(const char* deviceName);

		bool initCoreConfigurationByCoreMask(CoreMask coreMask);
		static void* captureThreadMain(void *ptr);

		int openSingleRxChannel(const char* deviceName, pfring** ring);

		bool getIsHwClockEnable() { setPfRingDeviceAttributes(); return m_HwClockEnabled; }
		bool setPfRingDeviceClock(pfring* ring);

		void clearCoreConfiguration();
		int getCoresInUseCount() const;

		void setPfRingDeviceAttributes();

		bool sendData(const uint8_t* packetData, int packetDataLength, bool flushTxQueues);
	public:

		/**
		 * An enum representing the type of packet distribution between different RX channels
		 */
		enum ChannelDistribution
		{
			/**
			 * Packets are distributed between channels in a round-robin manner
			 */
			RoundRobin,
			/**
			 * Packets are distributed between channels per flow (each flow goes for different channel)
			 */
			PerFlow
		};

		/**
		 * @struct PfRingStats
		 * A container for PfRingDevice statistics
		 */
		struct PfRingStats
		{
			/** Number of packets received */
			uint64_t recv;
			/** Number of packets dropped */
			uint64_t drop;
		};

		/**
		 * A destructor for PfRingDevice class
		 */
		~PfRingDevice();

		/**
		 * Get the MAC address of the current device
		 * @return The MAC address of the current device
		 */
		MacAddress getMacAddress() { setPfRingDeviceAttributes(); return m_MacAddress; }

		/**
		 * Get PF_RING interface index of the current device
		 * @return PF_RING interface index of the current device
		 */
		int getInterfaceIndex() { setPfRingDeviceAttributes(); return m_InterfaceIndex; }

		/**
		 * Get MTU of the current device
		 * @return Upon success return the device MTU, 0 otherwise
		 */
		int getMtu() { setPfRingDeviceAttributes(); return m_DeviceMTU; }

		/**
		 * Return true if device supports hardware timestamping. If it does, this feature will be automatically set
		 * for this device. You can read more about this in PF_RING documentation
		 * @return True if device supports hardware timestamping, false otherwise
		 */
		bool isHwClockEnabledForDevice() { setPfRingDeviceAttributes(); return m_HwClockEnabled; }

		/**
		 * Gets the interface name (e.g eth0, eth1, etc.)
		 * @return The interface name
		 */
		std::string getDeviceName() const { return m_DeviceName; }


		/**
		 * Start single-threaded capturing with callback. Works with open() or openSingleRxChannel().
		 * @param[in] onPacketsArrive A callback to call whenever a packet arrives
		 * @param[in] onPacketsArriveUserCookie A cookie that will be delivered to onPacketsArrive callback on every packet
		 * @return True if this action succeeds, false otherwise
		 */
		bool startCaptureSingleThread(OnPfRingPacketsArriveCallback onPacketsArrive, void* onPacketsArriveUserCookie);

		/**
		 * Start multi-threaded (multi-core) capturing with callback. Works with openMultiRxChannels().
		 * This method will return an error if the number of opened channels is different than the number of threads/cores
		 * requested
		 * @param[in] onPacketsArrive A callback to call whenever a packet arrives
		 * @param[in] onPacketsArriveUserCookie A cookie that will be delivered to onPacketsArrive callback on every packet
		 * @param[in] coreMask The cores to be used as mask. For example:
		 * @return True if this action succeeds, false otherwise
		 */
		bool startCaptureMultiThread(OnPfRingPacketsArriveCallback onPacketsArrive, void* onPacketsArriveUserCookie, CoreMask coreMask);

		/**
		 * Stops capturing packets (works will all type of startCapture*)
		 */
		void stopCapture();


		/**
		 * Opens a single RX channel (=RX queue) on this interface. All packets will be received on a single thread
		 * without core affinity. If the channel ID requested doesn't exist on this interface, the method will fail
		 * (return false)
		 * @param[in] channelId The requested channel ID
		 * @return True if this action succeeds, false otherwise
		 */
		bool openSingleRxChannel(uint8_t channelId);

		/**
		 * Opens a set of RX channels (=RX queues) on this interface, identified by their IDs. All packets will be received on a single thread
		 * without core affinity. If one of the channel IDs requested doesn't exist on this interface, the method will fail
		 * (return false)
		 * @param[in] channelIds An array of channel IDs
		 * @param[in] numOfChannelIds The channel ID array size
		 * @return True if this action succeeds, false otherwise
		 */
		bool openMultiRxChannels(const uint8_t* channelIds, int numOfChannelIds);

		/**
		 * Opens numOfRxChannelsToOpen RX channels. If numOfRxChannelsToOpen is larger than available RX queues for this
		 * interface than a number of RX channels will be opened on each RX queue. For example: if the user asks for 10
		 * RX channels but the interface has only 4 RX queues, then 3 RX channels will be opened for RX-queue0 and RX-queue2,
		 * and 2 RX channels will be opened for RX-queue2 and RX-queue3.
		 * Packets will be distributed between different RX queues on per-flow manner, but within multiple RX channels in
		 * the same RX queue packet will be distributed according to distribution requested by "dist"
		 * @param[in] numOfRxChannelsToOpen Number of RX channels to open
		 * @param[in] dist Distribution method
		 * @return True if this action succeeds, false otherwise
		 */
		bool openMultiRxChannels(uint8_t numOfRxChannelsToOpen, ChannelDistribution dist);

		/**
		 * Gets the number of RX channels currently open. RX channels aren't necessary interface's RX queues
		 * because in some cases the user asks to open several channels on the same queue. For example: if the user uses
		 * openMultiRxChannels() and asks to open 8 channels but interface has only 4 RX queues, 2 channels will be
		 * opened for each RX queue
		 * @return Number of opened RX channels
		 */
		uint8_t getNumOfOpenedRxChannels() const { return m_NumOfOpenedRxChannels; }

		/**
		 * Gets the total number of RX channels (RX queues) this interface has
		 * @return The number of RX channels (queues) for this interface
		 */
		uint8_t getTotalNumOfRxChannels() const;

		/**
		 * Gets the core used in the current thread context
		 * @return The system core used in the current thread context
		 */
		SystemCore getCurrentCoreId() const;

		/**
		 * Get the statistics of a specific thread/core (=RX channel)
		 * @param[in] core The requested core
		 * @param[out] stats A reference for the stats object where the stats are written. Current values will be overridden
		 */
		void getThreadStatistics(SystemCore core, PfRingStats& stats) const;

		/**
		 * Get the statistics of the current thread/core (=RX channel)
		 * @param[out] stats A reference for the stats object where the stats are written. Current values will be overridden
		 */
		void getCurrentThreadStatistics(PfRingStats& stats) const;

		/**
		 * Get the statistics for the entire device. If more than 1 RX channel is opened, this method aggregates the stats
		 * of all channels
		 * @param[out] stats A reference for the stats object where the stats are written. Current values will be overridden
		 */
		void getStatistics(PfRingStats& stats) const;

		/**
		 * Return true if filter is currently set
		 * @return True if filter is currently set, false otherwise
		 */
		bool isFilterCurrentlySet() const;

		/**
		 * Send a raw packet. This packet must be fully specified (the MAC address up)
		 * and it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the data in any way (hence the "const" declaration).
		 * Note this method flushes the TX queues after the data is sent. So if you want to send several packets
		 * In the burst please use sendPackets()
		 * @param[in] rawPacket The raw packet to send
		 * @return True if raw packet was sent completely, false otherwise
		 */
		bool sendPacket(const RawPacket& rawPacket);

		/**
		 * Send raw data. This data must be a valid and fully specified packet (the MAC address up);
		 * it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the data in any way (hence the "const" declaration).
		 * Note this method flushes the TX queues after the data is sent. So if you want to send several packets
		 * in the burst please use sendPackets()
		 * @param[in] packetData The raw data to send
		 * @param[in] packetDataLength the length of packetData
		 * @return True if raw packet was sent completely, false otherwise
		 *
		 */
		bool sendPacket(const uint8_t* packetData, int packetDataLength);

		/**
		 * Send a packet. This packet must be fully specified (the MAC address up)
		 * and it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the data in any way (hence the "const" declaration).
		 * Note this method flushes the TX queues after the data is sent. So if you want to send several packets
		 * In the burst please use sendPackets()
		 * @param[in] packet The packet to send
		 * @return True if raw packet was sent completely, false otherwise
		 */
		bool sendPacket(const Packet& packet);

		/**
		 * Send raw packets. All raw packets must be fully specified (the MAC address up)
		 * and it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the raw packets data in any way (hence the "const" declaration).
		 * This method flushes the TX queues only when the last packet is sent
		 * @param[in] rawPacketsArr The RawPacket array
		 * @param[in] arrLength RawPacket array length
		 * @return Number of packets that were sent completely
		 */
		int sendPackets(const RawPacket* rawPacketsArr, int arrLength);

		/**
		 * Send packets. All packets must be fully specified (the MAC address up)
		 * and it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the packets data in any way (hence the "const" declaration).
		 * This method flushes the TX queues only when the last packet is sent
		 * @param[in] packetsArr An array of pointers to Packet objects
		 * @param[in] arrLength Packet pointers array length
		 * @return Number of packets that were sent completely
		 */
		int sendPackets(const Packet** packetsArr, int arrLength);

		/**
		 * Send all raw packets pointed by the RawPacketVector. All packets must be fully specified (the MAC address up)
		 * and it will be transmitted as-is without any further manipulation.
		 * This method doesn't change or manipulate the packets data in any way (hence the "const" declaration).
		 * This method flushes the TX queues only when the last packet is sent
		 * @param[in] rawPackets The raw packet vector
		 * @return Number of raw packets that were sent completely
		 */
		int sendPackets(const RawPacketVector& rawPackets);


		// implement abstract methods


		/**
		 * Opens the entire device (including all RX channels/queues on this interface). All packets will be received
		 * on a single thread without core affinity
		 * @return True if this action succeeds, false otherwise
		 */
		bool open();

		/**
		 * Closes all RX channels currently opened in device
		 */
		void close();

		using IFilterableDevice::setFilter;

		/**
		 * Sets a BPF filter to the device
		 * @param[in] filterAsString The BPF filter in string format
		 */
		bool setFilter(std::string filterAsString);

		/**
		 * Remove a filter if currently set
		 * @return True if filter was removed successfully or if no filter was set, false otherwise
		 */
		bool clearFilter();
	};

} // namespace pcpp

#endif /* PCAPPP_PF_RING_DEVICE */
