#ifndef PCAPPP_FILE_DEVICE
#define PCAPPP_FILE_DEVICE

#include "PcapDevice.h"
#include "RawPacket.h"
#include <fstream>

// forward declaration for structs and typedefs defined in pcap.h
struct pcap_dumper;
typedef struct pcap_dumper pcap_dumper_t;

/// @file

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

	/**
	 * @class IFileDevice
	 * An abstract class (cannot be instantiated, has a private c'tor) which is the parent class for all file devices
	 */
	class IFileDevice : public IPcapDevice
	{
	protected:
		std::string m_FileName;

		explicit IFileDevice(const std::string& fileName);
		virtual ~IFileDevice();

	public:

		/**
		* @return The name of the file
		*/
		std::string getFileName() const;


		//override methods

		/**
		 * Close the file
		 */
		virtual void close();
	};


	/**
	 * @class IFileReaderDevice
	 * An abstract class (cannot be instantiated, has a private c'tor) which is the parent class for file reader devices
	 */
	class IFileReaderDevice : public IFileDevice
	{
	protected:
		uint32_t m_NumOfPacketsRead;
		uint32_t m_NumOfPacketsNotParsed;

		/**
		 * A constructor for this class that gets the pcap full path file name to open. Notice that after calling this constructor the file
		 * isn't opened yet, so reading packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file to read
		 */
		IFileReaderDevice(const std::string& fileName);

	public:

		/**
		 * A destructor for this class
		 */
		virtual ~IFileReaderDevice() {}

		/**
		* @return The file size in bytes
		*/
		uint64_t getFileSize() const;

		virtual bool getNextPacket(RawPacket& rawPacket) = 0;

		/**
		 * Read the next N packets into a raw packet vector
		 * @param[out] packetVec The raw packet vector to read packets into
		 * @param[in] numOfPacketsToRead Number of packets to read. If value <0 all remaining packets in the file will be read into the
		 * raw packet vector (this is the default value)
		 * @return The number of packets actually read
		 */
		int getNextPackets(RawPacketVector& packetVec, int numOfPacketsToRead = -1);

		/**
		 * A static method that creates an instance of the reader best fit to read the file. It decides by the file extension: for .pcapng
		 * files it returns an instance of PcapNgFileReaderDevice and for all other extensions it returns an instance of PcapFileReaderDevice
		 * @param[in] fileName The file name to open
		 * @return An instance of the reader to read the file. Notice you should free this instance when done using it
		 */
		static IFileReaderDevice* getReader(const std::string& fileName);
	};


	/**
	 * @class PcapFileReaderDevice
	 * A class for opening a pcap file in read-only mode. This class enable to open the file and read all packets, packet-by-packet
	 */
	class PcapFileReaderDevice : public IFileReaderDevice
	{
	private:
		LinkLayerType m_PcapLinkLayerType;

		// private copy c'tor
		PcapFileReaderDevice(const PcapFileReaderDevice& other);
		PcapFileReaderDevice& operator=(const PcapFileReaderDevice& other);

	public:
		/**
		 * A constructor for this class that gets the pcap full path file name to open. Notice that after calling this constructor the file
		 * isn't opened yet, so reading packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file to read
		 */
		PcapFileReaderDevice(const std::string& fileName) : IFileReaderDevice(fileName), m_PcapLinkLayerType(LINKTYPE_ETHERNET) {}

		/**
		 * A destructor for this class
		 */
		virtual ~PcapFileReaderDevice() {}

		/**
		* @return The link layer type of this file
		*/
		LinkLayerType getLinkLayerType() const { return m_PcapLinkLayerType; }


		//overridden methods

		/**
		 * Read the next packet from the file. Before using this method please verify the file is opened using open()
		 * @param[out] rawPacket A reference for an empty RawPacket where the packet will be written
		 * @return True if a packet was read successfully. False will be returned if the file isn't opened (also, an error log will be printed)
		 * or if reached end-of-file
		 */
		bool getNextPacket(RawPacket& rawPacket);

		/**
		 * Open the file name which path was specified in the constructor in a read-only mode
		 * @return True if file was opened successfully or if file is already opened. False if opening the file failed for some reason (for example:
		 * file path does not exist)
		 */
		bool open();

		/**
		 * Get statistics of packets read so far. In the PcapStats struct, only the packetsRecv member is relevant. The rest of the members will contain 0
		 * @param[out] stats The stats struct where stats are returned
		 */
		void getStatistics(PcapStats& stats) const;
	};

	/**
	 * @class SnoopFileReaderDevice
	 * A class for opening a snoop file in read-only mode. This class enable to open the file and read all packets, packet-by-packet
	 */
	class SnoopFileReaderDevice : public IFileReaderDevice
	{
	private:
		#pragma pack(1)
		/*
		 * File format header.
		 */
		typedef struct {
		    uint64_t        identification_pattern;
		    uint32_t        version_number;
		    uint32_t        datalink_type;
		} snoop_file_header_t;

		/*
		 * Packet record header.
		 */
		typedef struct {
		    uint32_t        original_length;     /* original packet length */
		    uint32_t        included_length;     /* saved packet length */
		    uint32_t        packet_record_length;/* total record length */
		    uint32_t        ndrops_cumulative;   /* cumulative drops */
		    uint32_t        time_sec;            /* timestamp */
		    uint32_t        time_usec;           /* microsecond timestamp */
		} snoop_packet_header_t;
		#pragma pack()

		LinkLayerType m_PcapLinkLayerType;
		std::ifstream m_snoopFile;

		// private copy c'tor
		SnoopFileReaderDevice(const PcapFileReaderDevice& other);
		SnoopFileReaderDevice& operator=(const PcapFileReaderDevice& other);

	public:
		/**
		 * A constructor for this class that gets the snoop full path file name to open. Notice that after calling this constructor the file
		 * isn't opened yet, so reading packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file to read
		 */
		SnoopFileReaderDevice(const std::string& fileName) : IFileReaderDevice(fileName), m_PcapLinkLayerType(LINKTYPE_ETHERNET) {}

		/**
		 * A destructor for this class
		 */
		virtual ~SnoopFileReaderDevice();

		/**
		* @return The link layer type of this file
		*/
		LinkLayerType getLinkLayerType() const { return m_PcapLinkLayerType; }


		//overridden methods

		/**
		 * Read the next packet from the file. Before using this method please verify the file is opened using open()
		 * @param[out] rawPacket A reference for an empty RawPacket where the packet will be written
		 * @return True if a packet was read successfully. False will be returned if the file isn't opened (also, an error log will be printed)
		 * or if reached end-of-file
		 */
		bool getNextPacket(RawPacket& rawPacket);

		/**
		 * Open the file name which path was specified in the constructor in a read-only mode
		 * @return True if file was opened successfully or if file is already opened. False if opening the file failed for some reason (for example:
		 * file path does not exist)
		 */
		bool open();

		/**
		 * Get statistics of packets read so far. In the PcapStats struct, only the packetsRecv member is relevant. The rest of the members will contain 0
		 * @param[out] stats The stats struct where stats are returned
		 */
		void getStatistics(PcapStats& stats) const;

		/**
		 * Close the snoop file
		 */
		void close();
	};


	/**
	 * @class PcapNgFileReaderDevice
	 * A class for opening a pcap-ng file in read-only mode. This class enable to open the file and read all packets, packet-by-packet
	 */
	class PcapNgFileReaderDevice : public IFileReaderDevice
	{
	private:
		void* m_LightPcapNg;
		BpfFilterWrapper m_BpfWrapper;

		// private copy c'tor
		PcapNgFileReaderDevice(const PcapNgFileReaderDevice& other);
		PcapNgFileReaderDevice& operator=(const PcapNgFileReaderDevice& other);

	public:
		/**
		 * A constructor for this class that gets the pcap-ng full path file name to open. Notice that after calling this constructor the file
		 * isn't opened yet, so reading packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file to read
		 */
		PcapNgFileReaderDevice(const std::string& fileName);

		/**
		 * A destructor for this class
		 */
		virtual ~PcapNgFileReaderDevice() { close(); }

		/**
		 * The pcap-ng format allows storing metadata at the header of the file. Part of this metadata is a string specifying the
		 * operating system that was used for capturing the packets. This method reads this string from the metadata (if exists) and
		 * returns it
		 * @return The operating system string if exists, or an empty string otherwise
		 */
		std::string getOS() const;

		/**
		 * The pcap-ng format allows storing metadata at the header of the file. Part of this metadata is a string specifying the
		 * hardware that was used for capturing the packets. This method reads this string from the metadata (if exists) and
		 * returns it
		 * @return The hardware string if exists, or an empty string otherwise
		 */
		std::string getHardware() const;

		/**
		 * The pcap-ng format allows storing metadata at the header of the file. Part of this metadata is a string specifying the
		 * capture application that was used for capturing the packets. This method reads this string from the metadata (if exists) and
		 * returns it
		 * @return The capture application string if exists, or an empty string otherwise
		 */
		std::string getCaptureApplication() const;

		/**
		 * The pcap-ng format allows storing metadata at the header of the file. Part of this metadata is a string containing a user-defined
		 * comment (can be any string). This method reads this string from the metadata (if exists) and
		 * returns it
		 * @return The comment written inside the file if exists, or an empty string otherwise
		 */
		std::string getCaptureFileComment() const;

		/**
		 * The pcap-ng format allows storing a user-defined comment for every packet (besides the comment per-file). This method reads
		 * the next packet and the comment attached to it (if such comment exists), and returns them both
		 * @param[out] rawPacket A reference for an empty RawPacket where the packet will be written
		 * @param[out] packetComment The comment attached to the packet or an empty string if no comment exists
		 * @return True if a packet was read successfully. False will be returned if the file isn't opened (also, an error log will be printed)
		 * or if reached end-of-file
		 */
		bool getNextPacket(RawPacket& rawPacket, std::string& packetComment);

		//overridden methods

		/**
		 * Read the next packet from the file. Before using this method please verify the file is opened using open()
		 * @param[out] rawPacket A reference for an empty RawPacket where the packet will be written
		 * @return True if a packet was read successfully. False will be returned if the file isn't opened (also, an error log will be printed)
		 * or if reached end-of-file
		 */
		bool getNextPacket(RawPacket& rawPacket);

		/**
		 * Open the file name which path was specified in the constructor in a read-only mode
		 * @return True if file was opened successfully or if file is already opened. False if opening the file failed for some reason (for example:
		 * file path does not exist)
		 */
		bool open();

		/**
		 * Get statistics of packets read so far.
		 * @param[out] stats The stats struct where stats are returned
		 */
		void getStatistics(PcapStats& stats) const;

		/**
		 * Set a filter for PcapNG reader device. Only packets that match the filter will be received
		 * @param[in] filterAsString The filter to be set in Berkeley Packet Filter (BPF) syntax (http://biot.com/capstats/bpf.html)
		 * @return True if filter set successfully, false otherwise
		 */
		bool setFilter(std::string filterAsString);

		/**
		 * Close the pacp-ng file
		 */
		void close();
	};


	/**
	 * @class IFileWriterDevice
	 * An abstract class (cannot be instantiated, has a private c'tor) which is the parent class for file writer devices
	 */
	class IFileWriterDevice : public IFileDevice
	{
	protected:
		uint32_t m_NumOfPacketsWritten;
		uint32_t m_NumOfPacketsNotWritten;

		IFileWriterDevice(const std::string& fileName);

	public:

		/**
		 * A destructor for this class
		 */
		virtual ~IFileWriterDevice() {}

		virtual bool writePacket(RawPacket const& packet) = 0;

		virtual bool writePackets(const RawPacketVector& packets) = 0;

		using IFileDevice::open;
		virtual bool open(bool appendMode) = 0;
	};


	/**
	 * @class PcapFileWriterDevice
	 * A class for opening a pcap file for writing or create a new pcap file and write packets to it. This class adds
	 * a unique capability that isn't supported in WinPcap and in older libpcap versions which is to open a pcap file
	 * in append mode where packets are written at the end of the pcap file instead of running it over
	 */
	class PcapFileWriterDevice : public IFileWriterDevice
	{
	private:
		pcap_dumper_t* m_PcapDumpHandler;
		LinkLayerType m_PcapLinkLayerType;
		bool m_AppendMode;
		FILE* m_File;

		// private copy c'tor
		PcapFileWriterDevice(const PcapFileWriterDevice& other);
		PcapFileWriterDevice& operator=(const PcapFileWriterDevice& other);

		void closeFile();

	public:
		/**
		 * A constructor for this class that gets the pcap full path file name to open for writing or create. Notice that after calling this
		 * constructor the file isn't opened yet, so writing packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file
		 * @param[in] linkLayerType The link layer type all packet in this file will be based on. The default is Ethernet
		 */
		PcapFileWriterDevice(const std::string& fileName, LinkLayerType linkLayerType = LINKTYPE_ETHERNET);

		/**
		 * A destructor for this class
		 */
		~PcapFileWriterDevice() {}

		/**
		 * Write a RawPacket to the file. Before using this method please verify the file is opened using open(). This method won't change the
		 * written packet
		 * @param[in] packet A reference for an existing RawPcket to write to the file
		 * @return True if a packet was written successfully. False will be returned if the file isn't opened
		 * or if the packet link layer type is different than the one defined for the file
		 * (in all cases, an error will be printed to log)
		 */
		bool writePacket(RawPacket const& packet);

		/**
		 * Write multiple RawPacket to the file. Before using this method please verify the file is opened using open(). This method won't change
		 * the written packets or the RawPacketVector instance
		 * @param[in] packets A reference for an existing RawPcketVector, all of its packets will be written to the file
		 * @return True if all packets were written successfully to the file. False will be returned if the file isn't opened (also, an error
		 * log will be printed) or if at least one of the packets wasn't written successfully to the file
		 */
		bool writePackets(const RawPacketVector& packets);

		//override methods

		/**
		 * Open the file in a write mode. If file doesn't exist, it will be created. If it does exist it will be
		 * overwritten, meaning all its current content will be deleted
		 * @return True if file was opened/created successfully or if file is already opened. False if opening the file failed for some reason
		 * (an error will be printed to log)
		 */
		virtual bool open();

		/**
		 * Same as open(), but enables to open the file in append mode in which packets will be appended to the file
		 * instead of overwrite its current content. In append mode file must exist, otherwise opening will fail
		 * @param[in] appendMode A boolean indicating whether to open the file in append mode or not. If set to false
		 * this method will act exactly like open(). If set to true, file will be opened in append mode
		 * @return True of managed to open the file successfully. In case appendMode is set to true, false will be returned
		 * if file wasn't found or couldn't be read, if file type is not pcap, or if link type specified in c'tor is
		 * different from current file link type. In case appendMode is set to false, please refer to open() for return
		 * values
		 */
		bool open(bool appendMode);

		/**
		 * Flush and close the pacp file
		 */
		virtual void close();

		/**
		 * Flush packets to disk.
		 */
		void flush();

		/**
		 * Get statistics of packets written so far.
		 * @param[out] stats The stats struct where stats are returned
		 */
		virtual void getStatistics(PcapStats& stats) const;
	};


	/**
	 * @class PcapNgFileWriterDevice
	 * A class for opening a pcap-ng file for writing or creating a new pcap-ng file and write packets to it. This class adds
	 * unique capabilities such as writing metadata attributes into the file header, adding comments per packet and opening
	 * the file in append mode where packets are added to a file instead of overriding it. This capabilities are part of the
	 * pcap-ng standard but aren't supported in most tools and libraries
	 */
	class PcapNgFileWriterDevice : public IFileWriterDevice
	{
	private:
		void* m_LightPcapNg;
		int m_CompressionLevel;
		BpfFilterWrapper m_BpfWrapper;

		// private copy c'tor
		PcapNgFileWriterDevice(const PcapFileWriterDevice& other);
		PcapNgFileWriterDevice& operator=(const PcapNgFileWriterDevice& other);

	public:

		/**
		 * A constructor for this class that gets the pcap-ng full path file name to open for writing or create. Notice that after calling this
		 * constructor the file isn't opened yet, so writing packets will fail. For opening the file call open()
		 * @param[in] fileName The full path of the file
		 * @param[in] compressionLevel The compression level to use when writing the file, use 0 to disable compression or 10 for max compression. Default is 0
		 */
		PcapNgFileWriterDevice(const std::string& fileName, int compressionLevel = 0);

		/**
		 * A destructor for this class
		 */
		virtual ~PcapNgFileWriterDevice() { close(); }

		/**
		 * Open the file in a write mode. If file doesn't exist, it will be created. If it does exist it will be
		 * overwritten, meaning all its current content will be deleted. As opposed to open(), this method also allows writing several
		 * metadata attributes that will be stored in the header of the file
		 * @param[in] os A string describing the operating system that was used to capture the packets. If this string is empty or null it
		 * will be ignored
		 * @param[in] hardware A string describing the hardware that was used to capture the packets. If this string is empty or null it
		 * will be ignored
		 * @param[in] captureApp A string describing the application that was used to capture the packets. If this string is empty or null it
		 * will be ignored
		 * @param[in] fileComment A string containing a user-defined comment that will be part of the metadata of the file.
		 * If this string is empty or null it will be ignored
		 * @return True if file was opened/created successfully or if file is already opened. False if opening the file failed for some reason
		 * (an error will be printed to log)
		 */
		bool open(const std::string& os, const std::string& hardware, const std::string& captureApp, const std::string& fileComment);

		/**
		 * The pcap-ng format allows adding a user-defined comment for each stored packet. This method writes a RawPacket to the file and
		 * adds a comment to it. Before using this method please verify the file is opened using open(). This method won't change the
		 * written packet or the input comment
		 * @param[in] packet A reference for an existing RawPcket to write to the file
		 * @param[in] comment The comment to be written for the packet. If this string is empty or null it will be ignored
		 * @return True if a packet was written successfully. False will be returned if the file isn't opened (an error will be printed to log)
		 */
		bool writePacket(RawPacket const& packet, const std::string& comment);

		//overridden methods

		/**
		 * Write a RawPacket to the file. Before using this method please verify the file is opened using open(). This method won't change the
		 * written packet
		 * @param[in] packet A reference for an existing RawPcket to write to the file
		 * @return True if a packet was written successfully. False will be returned if the file isn't opened (an error will be printed to log)
		 */
		bool writePacket(RawPacket const& packet);

		/**
		 * Write multiple RawPacket to the file. Before using this method please verify the file is opened using open(). This method won't change
		 * the written packets or the RawPacketVector instance
		 * @param[in] packets A reference for an existing RawPcketVector, all of its packets will be written to the file
		 * @return True if all packets were written successfully to the file. False will be returned if the file isn't opened (also, an error
		 * log will be printed) or if at least one of the packets wasn't written successfully to the file
		 */
		bool writePackets(const RawPacketVector& packets);

		/**
		 * Open the file in a write mode. If file doesn't exist, it will be created. If it does exist it will be
		 * overwritten, meaning all its current content will be deleted
		 * @return True if file was opened/created successfully or if file is already opened. False if opening the file failed for some reason
		 * (an error will be printed to log)
		 */
		bool open();

		/**
		 * Same as open(), but enables to open the file in append mode in which packets will be appended to the file
		 * instead of overwrite its current content. In append mode file must exist, otherwise opening will fail
		 * @param[in] appendMode A boolean indicating whether to open the file in append mode or not. If set to false
		 * this method will act exactly like open(). If set to true, file will be opened in append mode
		 * @return True of managed to open the file successfully. In case appendMode is set to true, false will be returned
		 * if file wasn't found or couldn't be read, if file type is not pcap-ng. In case appendMode is set to false, please refer to open()
		 * for return values
		 */
		bool open(bool appendMode);

		/**
		 * Flush packets to the pcap-ng file
		 */
		void flush();

		/**
		 * Flush and close the pcap-ng file
		 */
		void close();

		/**
		 * Get statistics of packets written so far.
		 * @param[out] stats The stats struct where stats are returned
		 */
		void getStatistics(PcapStats& stats) const;

		/**
		 * Set a filter for PcapNG writer device. Only packets that match the filter will be persisted
		 * @param[in] filterAsString The filter to be set in Berkeley Packet Filter (BPF) syntax (http://biot.com/capstats/bpf.html)
		 * @return True if filter set successfully, false otherwise
		 */
		bool setFilter(std::string filterAsString);

	};

}// namespace pcpp

#endif
