/*****************************************************************************/
// Copyright 2006-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/** Data stream abstraction for serializing and deserializing sequences of
 *  basic types and RAW image data.
 */

/*****************************************************************************/

#ifndef __dng_stream__
#define __dng_stream__

/*****************************************************************************/

#include "dng_auto_ptr.h"
#include "dng_classes.h"
#include "dng_types.h"
#include "dng_memory.h"
#include "dng_rational.h"
#include "dng_uncopyable.h"
#include "dng_utils.h"

/*****************************************************************************/

// Constants for invalid offset in streams.

const uint64 kDNGStreamInvalidOffset = (uint64) (int64) -1;

/*****************************************************************************/

/// Base stream abstraction. Has support for going between stream and pointer
/// abstraction.

class dng_stream: private dng_uncopyable
	{
	
	public:
	
		enum
			{
			
			kSmallBufferSize =  8 * 1024,
			kBigBufferSize   = 64 * 1024,
			
			kDefaultBufferSize = kSmallBufferSize
			
			};
	
	private:
	
		bool fSwapBytes;
		
		bool fHaveLength;
		
		uint64 fLength;
		
		const uint64 fOffsetInOriginalFile;
	
		uint64 fPosition;
		
		AutoPtr<dng_memory_block> fMemBlock;
		
		uint8 *fBuffer;
		
		uint32 fBufferSize;
		
		uint64 fBufferStart;
		uint64 fBufferEnd;
		uint64 fBufferLimit;
		
		bool fBufferDirty;
		
		dng_abort_sniffer *fSniffer;
		
	protected:
	
		dng_stream (dng_abort_sniffer *sniffer = NULL,
					uint32 bufferSize = kDefaultBufferSize,
					uint64 offsetInOriginalFile = kDNGStreamInvalidOffset);
		
		virtual uint64 DoGetLength ();
	
		virtual void DoRead (void *data,
							 uint32 count,
							 uint64 offset);
							 
		virtual void DoSetLength (uint64 length);
							 
		virtual void DoWrite (const void *data,
							  uint32 count,
							  uint64 offset);
		
	public:
	
		/// Construct a stream with initial data.
		/// \param data Pointer to initial contents of stream.
		/// \param count Number of bytes data is valid for.
		/// \param offsetInOriginalFile If data came from a file originally,
		/// offset can be saved here for later use.

		dng_stream (const void *data,
					uint32 count,
					uint64 offsetInOriginalFile = kDNGStreamInvalidOffset);
		
		virtual ~dng_stream ();
		
		/// Getter for whether stream is swapping byte order on input/output.
		/// \retval If true, data will be swapped on input/output.

		bool SwapBytes () const
			{
			return fSwapBytes;
			}
		
		/// Setter for whether stream is swapping byte order on input/output.
		/// \param swapBytes If true, stream will swap byte order on input or
		/// output for future reads/writes.

		void SetSwapBytes (bool swapBytes)
			{
			fSwapBytes = swapBytes;
			}

		/// Getter for whether data in stream is big endian.
		/// \retval If true, data in stream is big endian.

		bool BigEndian () const;
		
		/// Setter for whether data in stream is big endian.
		/// \param bigEndian If true, data in stream is big endian.

		void SetBigEndian (bool bigEndian = true);
		
		/// Getter for whether data in stream is big endian.
		/// \retval If true, data in stream is big endian.

		bool LittleEndian () const
			{
			return !BigEndian ();
			}
		
		/// Setter for whether data in stream is big endian.
		/// \param littleEndian If true, data in stream is big endian.

		void SetLittleEndian (bool littleEndian = true)
			{
			SetBigEndian (!littleEndian);
			}
			
		/// Returns the size of the buffer used by the stream.
		
		uint32 BufferSize () const
			{
			return fBufferSize;
			}

        /// Change the buffer size on the stream, if possible.
                
        void SetBufferSize (dng_memory_allocator &allocator,
                            uint32 newBufferSize);

		/// Getter for length of data in stream.
		/// \retval Length of readable data in stream.
			
		uint64 Length ()
			{
			
			if (!fHaveLength)
				{
				
				fLength = DoGetLength ();
				
				fHaveLength = true;
				
				}
				
			return fLength;
			
			}

		/// Getter for current offset in stream.
		/// \retval current offset from start of stream.

		uint64 Position () const
			{
			return fPosition;
			}
			
		/// Getter for current position in original file, taking into account
		/// OffsetInOriginalFile stream data was taken from.
		/// \retval kInvalidOffset if no offset in original file is set, sum 
		/// of offset in original file and current position otherwise.

		uint64 PositionInOriginalFile () const;
		
		/// Getter for offset in original file.
		/// \retval kInvalidOffset if no offset in original file is set,
		/// offset in original file otherwise.

		uint64 OffsetInOriginalFile () const;
		
		/// Return pointer to stream contents if the stream is entirely 
		/// available as a single memory block, NULL otherwise.

		const void * Data () const;
		
		/// Return the entire stream as a single memory block.
		/// This works for all streams, but requires copying the data to a new buffer.
		/// \param allocator Allocator used to allocate memory.

		dng_memory_block * AsMemoryBlock (dng_memory_allocator &allocator);

		/// Seek to a new position in stream for reading.

		void SetReadPosition (uint64 offset);
		
		/// Skip forward in stream.
		/// \param delta Number of bytes to skip forward.

		void Skip (uint64 delta)
			{
			SetReadPosition (Position () + delta);
			}
        
        /// Quick check to see if data range in completely buffered.

        bool DataInBuffer (uint32 count,
                           uint64 offset)
            {
            return (offset         >= fBufferStart &&
                    offset + count <= fBufferEnd);
            }
        
		/// Get data from stream. Exception is thrown and no data is read if 
		/// insufficient data available in stream.
		/// \param data Buffer to put data into. Must be valid for count bytes.
		/// \param count Bytes of data to read.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file 
		/// if not enough data in stream.
		
		void Get (void *data, uint32 count, uint32 maxOverRead=0);

		/// Seek to a new position in stream for writing.
		
		void SetWritePosition (uint64 offset);
		
		/// Force any stored data in stream to be written to underlying storage.

		void Flush ();

		/// Set length of available data.
		/// \param length Number of bytes of avialble data in stream.

		void SetLength (uint64 length);

		/// Write data to stream.
		/// \param data Buffer of data to write to stream.
		/// \param count Bytes of in data.

		void Put (const void *data, uint32 count);

		/// Get an unsigned 8-bit integer from stream and advance read position.
		/// \retval One unsigned 8-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file 
		/// if not enough data in stream.
		
		uint8 Get_uint8 ()
			{
			
			// Fast check to see if in buffer
			
			if (fPosition >= fBufferStart && fPosition < fBufferEnd)
				{
				
				return fBuffer [fPosition++ - fBufferStart];
				
				}
				
			// Not in buffer, let main routine do the work.
			
			uint8 x;
			
			Get (&x, 1);
			
			return x;
				
			}
		
		/// Put an unsigned 8-bit integer to stream and advance write position.
		/// \param x One unsigned 8-bit integer.
		
		void Put_uint8 (uint8 x)
			{
			
			if (fBufferDirty               &&
			    fPosition  >= fBufferStart &&
				fPosition  <= fBufferEnd   &&
				fPosition  <  fBufferLimit)
				{
				
				fBuffer [fPosition - fBufferStart] = x;
				
				fPosition++;
				
				if (fBufferEnd < fPosition)
					fBufferEnd = fPosition;
					
				fLength = Max_uint64 (Length (), fPosition);
				
				}
				
			else
				{
				
				Put (&x, 1);
				
				}
	
			}
			
		/// Get an unsigned 16-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One unsigned 16-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.
		
		uint16 Get_uint16 ();
		
		/// Put an unsigned 16-bit integer to stream and advance write position.
		/// Byte swap if byte swapping is turned on.
		/// \param x One unsigned 16-bit integer.

		void Put_uint16 (uint16 x);
		
		/// Get an unsigned 32-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One unsigned 32-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.
		
		uint32 Get_uint32();

#if !qDNGBigEndian
		inline // ep, enable compiler inlining
		uint32 Get_uint32_LE ()
			{
	
			uint32 x;
	
			Get (&x, 4, 3); // Allow 3-byte overread (undefined data returned but not used)

			// No check for fSwapBytes

			return x;
	
			}
#endif

		/// Put an unsigned 32-bit integer to stream and advance write position. 
		/// Byte swap if byte swapping is turned on.
		/// \param x One unsigned 32-bit integer.

		void Put_uint32 (uint32 x);
		
		/// Get an unsigned 64-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One unsigned 64-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.
		
		uint64 Get_uint64 ();
		
		/// Put an unsigned 64-bit integer to stream and advance write position. 
		/// Byte swap if byte swapping is turned on.
		/// \param x One unsigned 64-bit integer.

		void Put_uint64 (uint64 x);
		
		/// Get one 8-bit integer from stream and advance read position.
		/// \retval One 8-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		int8 Get_int8 ()
			{
			return (int8) Get_uint8 ();
			}
			
		/// Put one 8-bit integer to stream and advance write position.
		/// \param x One  8-bit integer.

		void Put_int8 (int8 x)
			{
			Put_uint8 ((uint8) x);
			}

		/// Get one 16-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One 16-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file 
		/// if not enough data in stream.

		int16 Get_int16 ()
			{
			return (int16) Get_uint16 ();
			}
			
		/// Put one 16-bit integer to stream and advance write position.
		/// Byte swap if byte swapping is turned on.
		/// \param x One 16-bit integer.

		void Put_int16 (int16 x)
			{
			Put_uint16 ((uint16) x);
			}

		/// Get one 32-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One 32-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		int32 Get_int32 ()
			{
			return (int32) Get_uint32 ();
			}
			
		/// Put one 32-bit integer to stream and advance write position.
		/// Byte swap if byte swapping is turned on.
		/// \param x One 32-bit integer.

		void Put_int32 (int32 x)
			{
			Put_uint32 ((uint32) x);
			}
			
		/// Get one 64-bit integer from stream and advance read position. 
		/// Byte swap if byte swapping is turned on.
		/// \retval One 64-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		int64 Get_int64 ()
			{
			return (int64) Get_uint64 ();
			}
			
		/// Put one 64-bit integer to stream and advance write position.
		/// Byte swap if byte swapping is turned on.
		/// \param x One 64-bit integer.

		void Put_int64 (int64 x)
			{
			Put_uint64 ((uint64) x);
			}
			
		/// Get one 32-bit IEEE floating-point number from stream and advance 
		/// read position. Byte swap if byte swapping is turned on.
		/// \retval One 32-bit IEEE floating-point number.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file 
		/// if not enough data in stream.

		real32 Get_real32 ();
		
		/// Put one 32-bit IEEE floating-point number to stream and advance write
		/// position. Byte swap if byte swapping is turned on.
		/// \param x One 32-bit IEEE floating-point number.

		void Put_real32 (real32 x);
		
		/// Get one 64-bit IEEE floating-point number from stream and advance
		/// read position. Byte swap if byte swapping is turned on.
		/// \retval One 64-bit IEEE floating-point number .
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		real64 Get_real64 ();
		
		/// Put one 64-bit IEEE floating-point number to stream and advance write
		/// position. Byte swap if byte swapping is turned on.
		/// \param x One64-bit IEEE floating-point number.

		void Put_real64 (real64 x);
		
		/// Get an 8-bit character string from stream and advance read position.
		/// Routine always reads until a NUL character (8-bits of zero) is read.
		/// (That is, only maxLength bytes will be returned in buffer, but the
		/// stream is always advanced until a NUL is read or EOF is reached.)
		/// \param data Buffer in which string is returned.
		/// \param maxLength Maximum number of bytes to place in buffer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if stream runs out before NUL is seen.

		void Get_CString (char *data,
						  uint32 maxLength);
		
		/// Get a 16-bit character string from stream and advance read position.
		/// 16-bit characters are truncated to 8-bits.
		/// Routine always reads until a NUL character (16-bits of zero) is read.
		/// (That is, only maxLength bytes will be returned in buffer, but the 
		/// stream is always advanced until a NUL is read or EOF is reached.)
		/// \param data Buffer to place string in.
		/// \param maxLength Maximum number of bytes to place in buffer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if stream runs out before NUL is seen.

		void Get_UString (char *data,
						  uint32 maxLength);
						  
		/// Writes the specified number of zero bytes to stream.
		/// \param count Number of zero bytes to write.
		
		void PutZeros (uint64 count);
		
		/// Writes zeros to align the stream position to a multiple of 2.
		
		void PadAlign2 ();
		
		/// Writes zeros to align the stream position to a multiple of 4.
		
		void PadAlign4 ();
		
		/// Get a value of size indicated by tag type from stream and advance
		/// read position. Byte swap if byte swapping is turned on and tag type
		/// is larger than a byte. Value is returned as an unsigned 32-bit integer. 
		/// \param tagType Tag type of data stored in stream.
		/// \retval One unsigned 32-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		uint32 TagValue_uint32 (uint32 tagType);

		/// Get a value of size indicated by tag type from stream and advance read
		/// position. Byte swap if byte swapping is turned on and tag type is larger
		/// than a byte. Value is returned as a 32-bit integer. 
		/// \param tagType Tag type of data stored in stream.
		/// \retval One 32-bit integer.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file 
		/// if not enough data in stream.

		int32 TagValue_int32 (uint32 tagType);
		
		/// Get a value of size indicated by tag type from stream and advance read 
		/// position. Byte swap if byte swapping is turned on and tag type is larger
		/// than a byte. Value is returned as a dng_urational. 
		/// \param tagType Tag type of data stored in stream.
		/// \retval One dng_urational.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		dng_urational TagValue_urational (uint32 tagType);

		/// Get a value of size indicated by tag type from stream and advance read
		/// position. Byte swap if byte swapping is turned on and tag type is larger
		/// than a byte. Value is returned as a dng_srational. 
		/// \param tagType Tag type of data stored in stream.
		/// \retval One dng_srational.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.
		
		dng_srational TagValue_srational (uint32 tagType);

		/// Get a value of size indicated by tag type from stream and advance read
		/// position. Byte swap if byte swapping is turned on and tag type is larger
		/// than a byte. Value is returned as a 64-bit IEEE floating-point number. 
		/// \param tagType Tag type of data stored in stream.
		/// \retval One 64-bit IEEE floating-point number.
		/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
		/// if not enough data in stream.

		real64 TagValue_real64 (uint32 tagType);

		/// Getter for sniffer associated with stream.
		/// \retval The sniffer for this stream.
		
		dng_abort_sniffer * Sniffer () const
			{
			return fSniffer;
			}
			
		/// Putter for sniffer associated with stream.
		/// \param sniffer The new sniffer to use (or NULL for none).
		
		void SetSniffer (dng_abort_sniffer *sniffer)
			{
			fSniffer = sniffer;
			}
			
		/// Copy a specified number of bytes to a target stream.
		/// \param dstStream The target stream.
		/// \param count The number of bytes to copy.
		
		virtual void CopyToStream (dng_stream &dstStream,
								   uint64 count);
								   
		/// Makes the target stream a copy of this stream.
		/// \param dstStream The target stream.
		
		void DuplicateStream (dng_stream &dstStream);
		
	};
	
/*****************************************************************************/

class dng_stream_double_buffered : public dng_stream
    {
    
    private:
    
        dng_stream &fStream;
        
    public:
    
        dng_stream_double_buffered (dng_stream &stream,
                                    uint32 bufferSize = kDefaultBufferSize)
        
            :   dng_stream ((dng_abort_sniffer *) NULL,
                            bufferSize,
                            stream.OffsetInOriginalFile ())
        
            ,   fStream (stream)
        
            {
            SetBigEndian (fStream.BigEndian ());
            }
        
    protected:
    
		virtual uint64 DoGetLength ()
            {
            return fStream.Length ();
            }
	
		virtual void DoRead (void *data,
							 uint32 count,
							 uint64 offset)
            {
            fStream.SetReadPosition (offset);
            fStream.Get (data, count);
            }

    };

/*****************************************************************************/

class dng_stream_contiguous_read_hint
    {
    
    private:
    
        dng_stream &fStream;
        
        dng_memory_allocator &fAllocator;
        
        uint32 fOldBufferSize;
        
    public:
        
        dng_stream_contiguous_read_hint (dng_stream &stream,
                                         dng_memory_allocator &allocator,
                                         uint64 offset,
                                         uint64 count);
        
        ~dng_stream_contiguous_read_hint ();
     
    };

/*****************************************************************************/

class TempBigEndian
	{
	
	private:
	
		dng_stream & fStream;
		
		bool fOldSwap;
		
	public:
	
		TempBigEndian (dng_stream &stream,
					   bool bigEndian = true);
						 
		virtual ~TempBigEndian ();
		
	};
			
/*****************************************************************************/

class TempLittleEndian: public TempBigEndian
	{
	
	public:
	
		TempLittleEndian (dng_stream &stream,
						  bool littleEndian = true)
			
			:	TempBigEndian (stream, !littleEndian)
			
			{
			}
	
		virtual ~TempLittleEndian ()
			{
			}

	};
				
/*****************************************************************************/

class TempStreamSniffer: private dng_uncopyable
	{
	
	private:
	
		dng_stream & fStream;
		
		dng_abort_sniffer *fOldSniffer;
		
	public:
	
		TempStreamSniffer (dng_stream &stream,
					       dng_abort_sniffer *sniffer);
						 
		~TempStreamSniffer ();
		
	};
				
/*****************************************************************************/

class PreserveStreamReadPosition: private dng_uncopyable
	{
	
	private:
	
		dng_stream & fStream;
	
		uint64 fPosition;
	
	public:
	
		PreserveStreamReadPosition (dng_stream &stream)

			:	fStream	  (stream)
			,	fPosition (stream.Position ())

			{
			}
			
		~PreserveStreamReadPosition ()
			{
			fStream.SetReadPosition (fPosition);
			}
	
	};
				
/*****************************************************************************/

#endif
				
/*****************************************************************************/
