/** @file
 * This is the header file for the FitsFile and FitsIOException class.
 * @author André Offringa <offringa@gmail.com>
 */
#ifndef FITSFILE_H
#define FITSFILE_H

#include <fitsio.h>
#include <string>
#include <stdexcept>
#include <vector>

#include <math.h>

#include "../structures/types.h"

/**
 * This class represents an exception that occurred during reading/writing of a FITS file.
 * @see FitsFile
 */
class FitsIOException : public std::runtime_error {
public:
	explicit FitsIOException(const std::string &description) : std::runtime_error(description) { }
	~FitsIOException() noexcept { }
};

/**
 * This class wraps the cfitsio library. It can be used to read or write a FITS file with
 * a (two dimensional) image. The fits file supports some other things, like tables, which
 * are not supported in this class. The FITS files generated by the MeqTrees process can
 * be read by this file. The FITS file can store multiple images in one file.
 *
 * This class works closely together with the Image2D class.
 * @see Image2D
 */
class FitsFile {
	public:
		/**
		 * Specifies how a FITS file should be opened
		 */
		enum FileMode {
			/**
			 * Only open for reading
			 */
			ReadOnlyMode,
	 		/**
			 * Open for reading and writing
			 */
	 		ReadWriteMode
		};
		
		/**
		 * The HDU is an entity inside a FITS file. This enum defines the possible entity types.
		 */
		enum HDUType {
			/**
			 * One or more two dimensional images
			 */
			ImageHDUType,
	 		/**
			 * An ASCII table
			 */
			ASCIITableHDUType,
			/**
			 * A binary table
			 */
			BinaryTableHDUType
		};
		
		/**
		 * This enum defines the possible image types in an image HDU.
		 */
		enum ImageType {
			/**
			 * Image consisting of 8 bit integers.
			 */
			Int8ImageType,
			/**
			 * Image consisting of 16 bit integers.
			 */
			Int16ImageType,
			/**
			 * Image consisting of 32 bit integers.
			 */
			Int32ImageType,
			/**
			 * Image consisting of 32 bit floats.
			 */
			Float32ImageType,
			/**
			 * Image consisting of 64 bit doubles.
			 */
			Double64ImageType
		};
		
		/**
		 * Construct a new FitsFile class associated with the specified filename.
		 * @param filename The file name of the fits file, to be opened with Open() or
		 * created with Create().
		 */
		explicit FitsFile(const std::string &filename);
		
		/**
		 * Destructor.
		 * @throws FitsIOException in case something failed due to an IO error.
		 */
		~FitsFile();
		
		/**
		 * Open the file.
		 * @param mode In which way the file should be opened.
		 * @throws FitsIOException in case opening failed due to an IO error.
		 */
		void Open(FileMode mode = ReadOnlyMode);
		
		/**
		 * Create a new FITS file.
		 * @throws FitsIOException in case writing failed due to an IO error.
		 */
		void Create();
		
		/**
		 * Close the file, releasing resources.
		 * @throws FitsIOException in case something happent due to an IO error.
		 */
		void Close();
		
		/**
		 * Determine whether the file is ready for reading and/or writing.
		 * @return @c true if the file has been opened.
		 */
		bool IsOpen() const { return _isOpen; }
		
		/**
		 * Retrieve the number of HDU blocks inside the file.
		 * @return The number of HDU blocks inside the file.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		int GetHDUCount();
		
		/**
		 * Retrieve the index of the current HDU block.
		 * @return The index of the current HDU block.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		int GetCurrentHDU();
		
		/**
		 * Retrieve the type of the current HDU block.
		 * @return The type of the current HDU block.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		enum HDUType GetCurrentHDUType();
		
		/**
		 * Start reading another HDU block, specified by its index.
		 * @param hduNumber Index of the HDU block to move to.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		void MoveToHDU(int hduNumber);
		
		/**
		 * Retrieve what kind of image this HDU image block is. Only call this
		 * method if GetCurrentHDUType() returned ImageHDUType.
		 * @return The image type.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		FitsFile::ImageType GetCurrentImageType();
		
		/**
		 * Retrieve the number of dimensions of an image.
		 * @return The number of dimensions.
		 * @see GetCurrentImageSize()
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		int GetCurrentImageDimensionCount();
		
		/**
		 * Retrieve the size of a specific dimension.
		 * @param dimension The dimension to retrieve the size for (first dimension = 0)
		 * @return The size of the dimension.
		 * @see GetCurrentImageDimensionCount()
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		long GetCurrentImageSize(int dimension);

		/**
		 * Writes a new image HUD to the FITS file. Does not write the data itself, only the headers. The WriteImage()
		 * call should be called next.
		 * @param imageType Type of image
		 * @param width Width of image
		 * @param height Height of image
		 * @throws FitsIOException in case writing failed due to an IO error.
		 */
		void AppendImageHUD(enum FitsFile::ImageType imageType, long width, long height);

		/**
		 * Reads one image into a buffer. The image will be converted to @c long @c doubles.
		 * @param startPos This specifies where to start reading. If several images are stored in the 3rd or 4th dimension,
		 * they can be read one by one by starting at different start positions.
		 * @param buffer The buffer where the image will be stored.
		 * @param bufferSize Size of the buffer. Reading will stop once the buffer is full. It makes sense to use buffer the size
		 * of the 1st x 2nd dimension.
		 * @param nullValue What value should be used to represent null values.
		 * @throws FitsIOException in case reading failed due to an IO error.
		 */
		void ReadCurrentImageData(long startPos, num_t *buffer, long bufferSize, long double nullValue = nan("Unset value"));
		
		/**
		 * Writes an image to the FITS file.
		 * @param startPos Where inside the image to start writing.
		 * @param buffer Buffer containing the image data
		 * @param bufferSize Size of the buffer
		 * @param nullValue What value was used to represent null values.
		 * @see ReadCurrentImageData
		 * @throws FitsIOException in case writing failed due to an IO error.
		 */
		void WriteImage(long startPos, double *buffer, long bufferSize, double nullValue = nan("Unset value"));
		void WriteImage(long startPos, float *buffer, long bufferSize, float nullValue = nan("Unset value"));
		
		int GetKeywordCount();
		bool HasKeyword(const std::string &keywordName);
		std::string GetKeyword(int keywordNumber);
		std::string GetKeywordValue(int keywordNumber);
		std::string GetKeywordValue(const std::string &keywordName);
		bool GetKeywordValue(const std::string &keywordName, std::string &value);
		std::string GetKeywordComment(int keywordNumber);
		int GetRowCount();
		int GetColumnCount();
		int GetColumnType(int colNumber);
		bool HasGroups();
		int GetIntKeywordValue(int keywordNumber);
		int GetIntKeywordValue(const std::string &keywordName);
		double GetDoubleKeywordValue(int keywordNumber);
		double GetDoubleKeywordValue(const std::string &keywordName);
		int GetGroupCount();
		int GetParameterCount();
		long GetImageSize();
		long GetGroupSize();
		void ReadGroup(long groupIndex, long double *groupData);
		void ReadGroupData(long groupIndex, long double *groupData);
		void ReadGroupParameters(long groupIndex, long double *parametersData);
		void ReadTableCell(int row, int col, long double *output, size_t size);
		void ReadTableCell(int row, int col, double *output, size_t size);
		void ReadTableCell(int row, int col, bool *output, size_t size);
		void ReadTableCell(int row, int col, char *output);
		void WriteTableCell(int row, int col, double *data, size_t size);
		void WriteTableCell(int row, int col, const bool *data, size_t size);
		bool HasTableColumn(const std::string &columnName, int& columnIndex);
		int GetTableColumnIndex(const std::string &columnName);
		int GetTableColumnArraySize(int columnIndex);
		std::string GetTableDimensionName(int index);
		std::vector<long> GetColumnDimensions(int columnIndex);
		long GetColumnDimensionSize(int columnIndex, int dimension);
		int GetGroupParameterIndex(const std::string &parameterName);
		int GetGroupParameterIndex(const std::string &parameterName, int number);
		bool HasGroupParameter(const std::string &parameterName);
		bool HasGroupParameter(const std::string &parameterName, int number);
		const std::string &Filename() const { return _filename; }
	private:
		const std::string _filename;
		fitsfile *_fptr;
		bool _isOpen;
		inline void CheckStatus(int status) const;
		inline void CheckOpen() const;
};
#endif
