// vi: ts=4

/*! \file DPX.h */

/*
 * Copyright (c) 2009, Patrick A. Palmer.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Patrick A. Palmer nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once
#ifndef _DPX_H
#define _DPX_H 1

#include <cstdio>
#include <limits>

#include "DPXHeader.h"
#include "DPXStream.h"


/*!
 * \def OPENDPX_VERSION 
 * \brief OpenDPX Version
 */
#define OPENDPX_VERSION		"0.5.0"



	
/*!
 * \namespace dpx
 * \brief OpenDPX namespace 
 */
namespace dpx
{
	// forward definitions
	class Codec;
	class ElementReadStream;
	
	/*! 
	 * \enum Endian
	 * \brief DPX files can be stored in big- or little-endian byte order 
	 */
	enum Endian
	{
		kLittleEndian,								//!< increasing numeric significance with increasing memory
		kBigEndian									//!< big end first
	};
	

	/*! \struct Block
	 * \brief Rectangle block definition defined by two points
	 */
	struct Block 
	{
		int x1, y1, x2, y2;
		
		/*!
		 * \brief Constructor
		 */
		inline Block();

		/*!
		 * \brief Constructor
		 *
		 * \param x1 upper left x coordinate
		 * \param y1 upper left y coordinate
		 * \param x2 lower right x coordinate
		 * \param y2 lower right y coordinate
		 */
		Block(const int x1, const int y1, const int x2, const int y2);

		/*!
		 * \brief Set the block coordinates
		 *
		 * \param x1 upper left x coordinate
		 * \param y1 upper left y coordinate
		 * \param x2 lower right x coordinate
		 * \param y2 lower right y coordinate
		 */
		void Set(const int x1, const int y1, const int x2, const int y2);

		/*!
		 * \brief Check to see if a point is within the block
		 *
		 * \param x x coordinate
		 * \param y y coordinate
		 * \return true/false if coordinates within block
		 */
		inline bool Inside(const int x, const int y) const;

		/*!
		 * \brief Rearrange coordinates if necessary so the first coordinate is upper left and the second is lower right
		 */
		inline void Check();
	};


	// Current platform endian byte order
	extern Endian systemByteOrder;

	// namespace functions
	
	/*!
	 * \brief determine if the image file is DPX
	 *
	 * \param file buffer to read and search
	 * \return true/false if identified as DPX
	 */
	bool IdentifyFile(InStream *file);

	/*!
	 * \brief determine if the image file is DPX
	 *
	 * \param data memory to search
	 * \return true/false if identified as DPX
	 */
	bool IdentifyFile(const void *data);
	
	/*!
	 * \brief returns a char * of the default DPX file extension
	 *
	 * \return .dpx file extension
	 */
	inline const char *DefaultExtension();

	/*!
	 * returns a string of the highest SMPTE DPX version supported by this library
	 *
	 * \return SMPTE DPX version
	 */
	inline const char *Version();


	/*!
	 * \brief returns the version string for this library
	 *
	 * \return OpenDPX version
	 */
	inline const char *LibraryVersion();





	/*!
	 * \class Reader
	 * \brief DPX Image Reader class
	 */
	 
	class Reader
	{

	public:

		/*!
		 * \brief DPX header
		 */	
		Header header;

		/*!
		 * \brief Constructor
		 */			
		Reader();
		
		/*!
		 * \brief Destructor
		 */			
		virtual ~Reader();

		/*!
		 * \brief Set the InStream object to be used to read images
		 *
		 * \param stream Object to use for low level reads
		 */	
		void SetInStream(InStream *stream);

		/*!
		 * \brief clear any caching or memory allocated specific to an image
		 */			
		void Reset();
		
		/*!
		 * \brief Read the dpx header into the header member
		 *
		 * \return success true/false
		 */	
		bool ReadHeader();

		/*!
		 * \brief Read an image element into a buffer
		 * 
		 * the size of the buffer must be large enough
		 * simple calculation would be:
		 *     width * height * num_of_components * size_of_component
		 * 
		 * \param element element (0-7)
		 * \param data buffer
		 * \return success true/false
		 */			
		bool ReadImage(const int element, void *data);

		/*!
		 * \brief Read a rectangular image block into a buffer from the specified image element
		 *
		 * \param element element (0-7)
		 * \param data buffer
		 * \param block image area to read
		 * \return success true/false
		 */	
		bool ReadBlock(const int element, unsigned char *data, Block &block);

		/*!
		 * \brief Read the user data into a buffer.  
		 *
		 * Buffer must be large enough to hold the user data.
		 *
		 * \param data buffer
		 * \return success true/false
		 */	
		bool ReadUserData(unsigned char *data);
		

	protected:			
		InStream *fd;
		
		Codec *codex[DPX_MAX_ELEMENTS];
		ElementReadStream *rio;
	};
	




	/*!
	 * \class Writer
	 * \brief DPX Image Writer class
	 */
	 
	class Writer
	{
	
	public:
	
		/*!
		 * \brief DPX Header
		 */		
		Header header;

		/*!
		 * \brief Constructor
		 */			
		Writer();
		
		/*!
		 * \brief Destructor
		 */	
		virtual ~Writer();
		
		/*!
		 * \brief Start defining the header and writing the images
		 */		
		void Start();

		/*!
		 * \brief Set the basic file information about DPX
		 *
		 * \param fileName name of this created file (100 characters max)
		 * \param creationTimeDate creation time and date - format is "YYYY:MM:DD:HH:MM:SSLTZ"
		 *               where HH is 24 hour time, LTZ is local time zone using either
		 *               three character notation (i.e., -04) or five character notation
		 *               representing hours and minutes offset from Greenwich Mean time
		 *               (i.e., -0700) (24 characters max)
		 * \param creator creator (100 characters max)
		 * \param project project name (200 characters max)
		 * \param copyright copyright statement (200 characters max)
		 * \param encryptKey encryption key
		 * \param swapEndian whether to write the image header in reverse to native endianness
		 */			
		void SetFileInfo(const char *fileName, const char *creationTimeDate = 0, const char *creator = 0,
			const char *project = 0, const char *copyright = 0, const U32 encryptKey = ~0,
			const bool swapEndian = false);

		/*!
		 * \brief Set the Width and Height of the images
		 *
		 * \param width width of the image
		 * \param height height of the image
		 */			
		void SetImageInfo(const U32 width, const U32 height);

		/*!
		 * \brief Get the next available element
		 * \return next available
		 */
		int NextAvailElement() const;


		/*!
		 * \brief Set the parameters on an element  
		 *
		 * There are 8 elements maximum in an single DPX and each element used must be set before writing the header
		 *
		 * \param element element number (0-7)
		 * \param desc image descriptor
		 * \param bitDepth bit depth of image, valid values are [8,10,12,16,32,64]
		 * \param transfer transfer characteristic
		 * \param colorimetric colorimetric specification
		 * \param packing packing type
		 * \param encoding encoding type
		 * \param dataSign
		 * \param lowData
		 * \param lowQuantity
		 * \param highData
		 * \param highQuantity
		 * \param eolnPadding end of line padding (in bytes)
		 * \param eoimPadding end of image padding (in bytes)
		 */			
		void SetElement(const int element = 0, 
			const Descriptor desc = kRGB, 
			const U8 bitDepth = 10,
			const Characteristic transfer = kLogarithmic, 
			const Characteristic colorimetric = kLogarithmic,
			const Packing packing = kFilledMethodA, 
			const Encoding encoding = kNone,
			const U32 dataSign = 0, 
			const U32 lowData = ~0, const R32 lowQuantity = std::numeric_limits<float>::quiet_NaN(),
			const U32 highData = ~0, const R32 highQuantity = std::numeric_limits<float>::quiet_NaN(),
			const U32 eolnPadding = 0, const U32 eoimPadding = 0);

		/*!
		 * \brief Set the OutStream object will use to write the files
		 *
		 * \param stream OutStream object
		 */	
		void SetOutStream(OutStream *stream);

		/*!
		 * \brief Set the size of the user data area
		 * 
		 * \param size size of user data
		 */	
		void SetUserData(const long size);

		/*!
		 * \brief Write the header
		 *
		 * \return success true/false
		 */	
		bool WriteHeader();
		
		/*!
		 * \brief Write the user data
		 *
		 * \param data buffer - must match size set in Writer::SetUserData()
		 * \return success true/false
		 */
		bool WriteUserData(void *data);

        /*!
         * \brief Write out some padded data to the specified boundary
         *
         * \param alignment -- Where the block boundary should be
         * \return success true/false
         */
        bool WritePadData(const int alignment);

		/*!
		 * \brief Write the entire element to the dpx file
		 *
		 * \param element element number (0-7)
		 * \param data buffer
         * \param alignment -- defines the imageAlignment to make sure the image is on typically an 8K alignment.
		 * \return success true/false
		 */			
		bool WriteElement(const int element, void *data);
		bool WriteElement(const int element, void *data, const DataSize size);
		bool WriteElement(const int element, void *data, const long count);

		/**
		 * \brief Finish up writing image
		 *
		 * \return success true/false
		 */			
		bool Finish();


	protected:
		long fileLoc;
		OutStream *fd;
		
		bool WriteThrough(void *, const U32, const U32, const int, const int, const U32, const U32, char *);
		
	};
	
}



inline const char *dpx::DefaultExtension()
{
	return "dpx";
}


inline const char *dpx::Version()
{
	return SMPTE_VERSION;
}


inline const char *dpx::LibraryVersion()
{
	return OPENDPX_VERSION;
}


inline dpx::Block::Block() : x1(0), y1(0), x2(0), y2(0)
{ 
}


inline dpx::Block::Block(const int x1, const int y1, const int x2, const int y2) : x1(x1), y1(y1), x2(x2), y2(y2)
{ 
	this->Check();
}


inline void dpx::Block::Set(const int x1, const int y1, const int x2, const int y2)
{
	this->x1 = x1; 
	this->y1 = y1; 
	this->x2 = x2;
	this->y2 = y2;
}


// check the coordinates that x1 < x2 and y1 < y2
inline void dpx::Block::Check()
{
	if (this->x1 > this->x2)
	{
		int t = x1;
		this->x1 = this->x2;
		this->x2 = t;
	}
	if (this->y1 > this->y2)
	{
		int t = y1;
		this->y1 = this->y2;
		this->y2 = t;
	}
}


inline bool dpx::Block::Inside(const int x, const int y) const
{
	if (x >= this->x1 && x <= this->x2 && y >= this->y1 && y <= this->y2)
			return true;
	return false;
}


#endif

