/*****************************************************************************/
// Copyright 2008-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.
/*****************************************************************************/

#include "dng_opcodes.h"

#include "dng_bottlenecks.h"
#include "dng_exceptions.h"
#include "dng_filter_task.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_negative.h"
#include "dng_parse_utils.h"
#include "dng_stream.h"
#include "dng_tag_values.h"

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

dng_opcode::dng_opcode (uint32 opcodeID,
						uint32 minVersion,
						uint32 flags)
						
	:	fOpcodeID          (opcodeID)
	,	fMinVersion        (minVersion)
	,	fFlags             (flags)
	,	fWasReadFromStream (false)
	,	fStage             (0)
	
	{
	
	}
					
/*****************************************************************************/

dng_opcode::dng_opcode (uint32 opcodeID,
					    dng_stream &stream,
						const char *name)
						
	:	fOpcodeID          (opcodeID)
	,	fMinVersion        (0)
	,	fFlags             (0)
	,	fWasReadFromStream (true)
	,	fStage             (0)
	
	{
	
	fMinVersion = stream.Get_uint32 ();
	fFlags      = stream.Get_uint32 ();
	
	#if qDNGValidate
	
	if (gVerbose)
		{
		
		printf ("\nOpcode: ");
		
		if (name)
			{
			printf ("%s", name);
			}
		else
			{
			printf ("Unknown (%u)", (unsigned) opcodeID);
			}
		
		printf (", minVersion = %u.%u.%u.%u",
				(unsigned) ((fMinVersion >> 24) & 0x0FF),
				(unsigned) ((fMinVersion >> 16) & 0x0FF),
				(unsigned) ((fMinVersion >>  8) & 0x0FF),
				(unsigned) ((fMinVersion      ) & 0x0FF));
				
		printf (", flags = %u\n", (unsigned) fFlags);
		
		}
	
	#else
	
	(void) name;
	
	#endif
	
	}
					
/*****************************************************************************/

dng_opcode::~dng_opcode ()
	{
	
	}

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

void dng_opcode::PutData (dng_stream &stream) const
	{
	
	// No data by default
	
	stream.Put_uint32 (0);
	
	}

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

bool dng_opcode::AboutToApply (dng_host &host,
							   dng_negative &negative,
							   const dng_rect &imageBounds,
							   uint32 imagePlanes)
	{
	
	if (SkipIfPreview () && host.ForPreview ())
		{
		
		negative.SetIsPreview (true);
		
		}
		
	else if (MinVersion () > dngVersion_Current &&
			 WasReadFromStream ())
		{
		
		if (!Optional ())
			{
			
			// Somebody screwed up computing the DNGBackwardVersion...
			
			ThrowBadFormat ();
			
			}
		
		}

	else if (!IsValidForNegative (negative))
		{
		
		ThrowBadFormat ();
		
		}
		
	else if (!IsNOP ())
		{

		DoAboutToApply (host,
						negative,
						imageBounds,
						imagePlanes);
		
		return true;
		
		}
		
	return false;
	
	}

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

dng_opcode_Unknown::dng_opcode_Unknown (dng_host &host,
										uint32 opcodeID,
										dng_stream &stream)
										
	:	dng_opcode (opcodeID,
					stream,
					NULL)
										
	,	fData ()
										
	{
	
	uint32 size = stream.Get_uint32 ();
	
	if (size)
		{
		
		fData.Reset (host.Allocate (size));
		
		stream.Get (fData->Buffer      (),
					fData->LogicalSize ());
					
		#if qDNGValidate
		
		if (gVerbose)
			{
			
			DumpHexAscii (fData->Buffer_uint8 (),
						  fData->LogicalSize  ());
			
			}
			
		#endif
					
		}
	
	}

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

void dng_opcode_Unknown::PutData (dng_stream &stream) const
	{
	
	if (fData.Get ())
		{
		
		stream.Put_uint32 (fData->LogicalSize ());
		
		stream.Put (fData->Buffer      (),
					fData->LogicalSize ());
					 
		}
		
	else
		{
		
		stream.Put_uint32 (0);
		
		}
	
	}

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

void dng_opcode_Unknown::Apply (dng_host & /* host */,
							    dng_negative & /* negative */,
							    AutoPtr<dng_image> & /* image */)
	{
	
	// We should never need to apply an unknown opcode.
	
	if (!Optional ())
		{
	
		ThrowBadFormat ();
		
		}
	
	}

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

class dng_filter_opcode_task: public dng_filter_task
	{
	
	private:
	
		dng_filter_opcode &fOpcode;
		
		dng_negative &fNegative;
		
	public:
	
		dng_filter_opcode_task (dng_filter_opcode &opcode,
								dng_negative &negative,
								const dng_image &srcImage,
						 		dng_image &dstImage)
												
			:	dng_filter_task ("dng_filter_opcode_task",
								 srcImage,
								 dstImage)
								 
			,	fOpcode   (opcode)
			,	fNegative (negative)
			
			{
			
			fSrcPixelType = fOpcode.BufferPixelType (srcImage.PixelType ());
			
			fDstPixelType = fSrcPixelType;
			
			fSrcRepeat = opcode.SrcRepeat ();
			
			}
	
		virtual dng_rect SrcArea (const dng_rect &dstArea)
			{
			
			return fOpcode.SrcArea (dstArea,
									fDstImage.Bounds ());
			
			}

		virtual dng_point SrcTileSize (const dng_point &dstTileSize)
			{
			
			return fOpcode.SrcTileSize (dstTileSize,
										fDstImage.Bounds ());
			
			}

		virtual void ProcessArea (uint32 threadIndex,
								  dng_pixel_buffer &srcBuffer,
								  dng_pixel_buffer &dstBuffer)
			{
			
			fOpcode.ProcessArea (fNegative,
								 threadIndex,
								 srcBuffer,
								 dstBuffer,
								 dstBuffer.Area (),
								 fDstImage.Bounds ());
								 
			}

		virtual void Start (uint32 threadCount,
							const dng_rect &dstArea,
							const dng_point &tileSize,
							dng_memory_allocator *allocator,
							dng_abort_sniffer *sniffer)
			{
			
			dng_filter_task::Start (threadCount,
									dstArea,
									tileSize,
									allocator,
									sniffer);
									
			fOpcode.Prepare (fNegative,
							 threadCount,
						     tileSize,
							 fDstImage.Bounds (),
							 fDstImage.Planes (),
							 fDstPixelType,
						     *allocator);
						   
			}
							
	};

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

dng_filter_opcode::dng_filter_opcode (uint32 opcodeID,
									  uint32 minVersion,
									  uint32 flags)
									  
	:	dng_opcode (opcodeID,
					minVersion,
					flags)
					
	{
	
	}

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

dng_filter_opcode::dng_filter_opcode (uint32 opcodeID,
									  dng_stream &stream,
									  const char *name)
									  
	:	dng_opcode (opcodeID,
					stream,
					name)
					
	{
	
	}

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

void dng_filter_opcode::Apply (dng_host &host,
							   dng_negative &negative,
							   AutoPtr<dng_image> &image)
	{

	dng_rect modifiedBounds = ModifiedBounds (image->Bounds ());
	
	if (modifiedBounds.NotEmpty ())
		{

		// Allocate destination image.
		
		AutoPtr<dng_image> dstImage;
		
		// If we are processing the entire image, allocate an
		// undefined image.
		
		if (modifiedBounds == image->Bounds ())
			{
			
			dstImage.Reset (host.Make_dng_image (image->Bounds	  (),
												 image->Planes	  (),
												 image->PixelType ()));
												 
			}
			
		// Else start with a clone of the existing image.
			
		else
			{
			
			dstImage.Reset (image->Clone ());
			
			}
																  
		// Filter the image.
		
		dng_filter_opcode_task task (*this,
									 negative,
									 *image,
									 *dstImage);

		host.PerformAreaTask (task,
							  modifiedBounds);
							
		// Return the new image.
		
		image.Reset (dstImage.Release ());
		
		}
	
	}
		
/*****************************************************************************/

class dng_inplace_opcode_task: public dng_area_task
	{
	
	private:
	
		dng_inplace_opcode &fOpcode;
		
		dng_negative &fNegative;
		
		dng_image &fImage;
		
		uint32 fPixelType;
		
		AutoPtr<dng_memory_block> fBuffer [kMaxMPThreads];

	public:
	
		dng_inplace_opcode_task (dng_inplace_opcode &opcode,
								 dng_negative &negative,
						 		 dng_image &image)
												
			:	dng_area_task ("dng_inplace_opcode_task")
								 
			,	fOpcode    (opcode)
			,	fNegative  (negative)
			,	fImage     (image)
			,	fPixelType (opcode.BufferPixelType (image.PixelType ()))
			
			{
			
			}
			
		virtual void Start (uint32 threadCount,
							const dng_rect & /* dstArea */,
							const dng_point &tileSize,
							dng_memory_allocator *allocator,
							dng_abort_sniffer * /* sniffer */)
			{
			
			uint32 bufferSize = ComputeBufferSize (fPixelType, 
												   tileSize,
												   fImage.Planes (), 
												   padSIMDBytes);
								   
			for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++)
				{
				
				fBuffer [threadIndex] . Reset (allocator->Allocate (bufferSize));
				
				}
				
			fOpcode.Prepare (fNegative,
							 threadCount,
						     tileSize,
							 fImage.Bounds (),
							 fImage.Planes (),
							 fPixelType,
						     *allocator);
		
			}
							
		virtual void Process (uint32 threadIndex,
							  const dng_rect &tile,
							  dng_abort_sniffer * /* sniffer */)
			{
			
			// Setup buffer.
			
			dng_pixel_buffer buffer (tile, 
									 0, 
									 fImage.Planes (), 
									 fPixelType,
									 pcRowInterleavedAlignSIMD,
									 fBuffer [threadIndex]->Buffer ());
			
			// Get source pixels.
			
			fImage.Get (buffer);
						   
			// Process area.
			
			fOpcode.ProcessArea (fNegative,
								 threadIndex,
								 buffer,
								 tile,
								 fImage.Bounds ());

			// Save result pixels.
			
			fImage.Put (buffer);
	
			}
		
	};
	
/*****************************************************************************/

dng_inplace_opcode::dng_inplace_opcode (uint32 opcodeID,
									    uint32 minVersion,
									    uint32 flags)
									  
	:	dng_opcode (opcodeID,
					minVersion,
					flags)
					
	{
	
	}

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

dng_inplace_opcode::dng_inplace_opcode (uint32 opcodeID,
									    dng_stream &stream,
									    const char *name)
									  
	:	dng_opcode (opcodeID,
					stream,
					name)
					
	{
	
	}

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

void dng_inplace_opcode::Apply (dng_host &host,
							    dng_negative &negative,
							    AutoPtr<dng_image> &image)
	{
	
	dng_rect modifiedBounds = ModifiedBounds (image->Bounds ());
	
	if (modifiedBounds.NotEmpty ())
		{

		dng_inplace_opcode_task task (*this,
									  negative,
									  *image);

		host.PerformAreaTask (task,
							  modifiedBounds);
							  
		}

	}
		
/*****************************************************************************/
