/*****************************************************************************/
// Copyright 2006-2022 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_assertions.h"
#include "dng_big_table.h"
#include "dng_bmff.h"
#include "dng_host.h"
#include "dng_memory_stream.h"
#include "dng_stream.h"

#include <unordered_map>
#include <utility>

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

static dng_string ReadName (dng_stream &stream)
	{
	
	char name [5];
	
	stream.Get (name, 4);
	
	name [4] = 0;
	
	return dng_string (name);
	
	}

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

dng_bmff_io::~dng_bmff_io ()
	{
	
	}

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

void dng_bmff_io::Read (dng_host &host,
						dng_stream &stream)
	{
	
	stream.SetBigEndian ();
	
	stream.SetReadPosition (0);
	
	// Skip first 4 bytes (length field of first box).
	
	stream.Skip (4);
	
	// Quick check to see if this looks like a valid ISO BMFF or JXL file.

	dng_string firstName (ReadName (stream));
	
	if (!(firstName.Matches ("ftyp", true) ||
		  firstName.Matches ("JXL ", true))) // TODO(erichan): add compile flag
		{
		ThrowBadFormat ();
		}

	// Reset to reading.
	
	stream.SetReadPosition (0);

	uint64 totalOffset = 0;

	while (stream.Position () < stream.Length ())
		{

		const uint32 storedLength = stream.Get_uint32 ();

		uint64 length = uint64 (storedLength);

		dng_string name = ReadName (stream);

		uint64 headerOffset = 0;

		if (storedLength == 1)
			{

			// Large box.

			length = stream.Get_uint64 ();

			DNG_REQUIRE (length >= 16,
						 "Box 64-bit length too small");

			headerOffset = 16;

			}

		else if (storedLength == 0)
			{

			// Last box: extends to EOF.

			headerOffset = 8;

			length = headerOffset + (stream.Length () - stream.Position ());

			}

		else
			{

			// Compact box.

			DNG_REQUIRE (length >= 8,
						 "Box 32-bit length too small");

			headerOffset = 8;

			}

		DNG_REQUIRE (length >= headerOffset,
					 "logic error in computing contentLength");

		const uint64 contentLength = length - headerOffset;

		// Store the box data if desired.

		if (ShouldReadBox (name, length))
			{

			auto box = std::make_shared<dng_bmff_box> ();

			box->fStoredLength = storedLength;

			box->fRealLength = length;

			box->fOffset = totalOffset;

			box->fName = name;

			// Currently do not support contents above 4 GB.

			DNG_REQUIRE (contentLength <= uint64 (0xFFFFFFFF),
						 "Unsupported contentLength too larget");

			const uint32 contentLength32 = uint32 (contentLength);

			box->fContent.reset (host.Allocate (contentLength32));

			fBoxes.push_back (box);
		
			stream.Get (box->fContent->Buffer (),
						contentLength32);

			}

		// Skip the box.

		else
			{

			stream.Skip (contentLength);
			
			}
			
		// Update total offset.

		totalOffset += length;

		}

	}

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

void dng_bmff_io::Write (dng_host & /* host */,
						 dng_stream &stream) const
	{

	stream.SetBigEndian ();

	for (const auto &box : fBoxes)
		{

		// Skip missing/invalid boxes.

		if (!box)
			continue;

		DNG_REQUIRE (box->fName.Length () == 4,
					 "name length wrong size");

		const uint32 dataLen = box->fContent ? box->fContent->LogicalSize () : 0;

		bool useLargeSize = ((box->fStoredLength == 1) ||
							 uint64 (dataLen + 8) > uint64 (0xFFFFFFFF));
		
		if (useLargeSize)
			{
			
			stream.Put_uint32 (1);		 // use large size

			stream.Put (box->fName.Get (), 4);

			stream.Put_uint64 (uint64 (dataLen) + 16);

			if (box->fContent && (dataLen > 0))
				stream.Put (box->fContent->Buffer (),
							dataLen);
				
			}

		else
			{

			// Compact or last box.

			if (box->fStoredLength == 0)	 // last box
				stream.Put_uint32 (0);

			else							 // compact box
				stream.Put_uint32 (dataLen + 8);

			stream.Put (box->fName.Get (), 4);

			if (box->fContent && (dataLen > 0))
				stream.Put (box->fContent->Buffer (),
							dataLen);

			}

		} // all boxes

	stream.Flush ();
	
	}

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

void dng_bmff_io::UpdateBigTables (dng_host &host,
								   const dng_big_table_dictionary &newTables,
								   const bool deleteUnused)
	{
	
	// 1 box per table.
	// 
	// Box tag: btbl (big table), registered here: https://mp4ra.org/#/request
	// 
	// Box layout is big-endian.
	// 
	//   length:   overall length of box  4 bytes
	//   tag code: btbl                   4 bytes
	//   version:  version identifier     4 bytes
	//   data                             variable
	// 
	// version 1 data:
	//   md5 fingerprint                  16 bytes
	//   big table data                   variable

	// LogPrint ("Writing Big Tables to JXL boxes\n");

	const dng_string kBigTableTagName ("btbl");

	// Step 1: Find all existing tables in the file. Along the way, we clean
	// up duplicate tables and no-longer-used tables (tables that aren't in
	// the given 'metadata' object).
		
	const auto &dictMap = newTables.Map ();

	std::unordered_map<dng_fingerprint,
					   dng_bmff_box_sptr,
					   dng_fingerprint_hash> digests;

	for (auto &box : fBoxes)
		{
			
		if (box &&
			(box->fName == kBigTableTagName) &&
			box->fContent &&
			(box->fContent->LogicalSize () > 20))
			{

			dng_stream tableStream (box->fContent->Buffer (),
									box->fContent->LogicalSize ());

			tableStream.SetBigEndian ();

			// Get version (4 bytes).

			uint32 version = tableStream.Get_uint32 ();

			if (version == 1)
				{
					
				// Get fingerprint (16 bytes).

				dng_fingerprint digest;

				tableStream.Get (digest.data,
								 uint32 (sizeof (digest.data)));

				if (digest.IsValid () &&
					(digests.find (digest) == digests.end ()))
					{
						
					// Not seen yet.

					if (dictMap.find (digest) == dictMap.end ())
						{
							
						// Not found in newTables, so we don't need this
						// table. If requested to delete unused tables, then
						// mark for delete.

						if (deleteUnused)
							box.reset ();
							
						}

					else
						{

						// Found in newTables, so we do need this table.
						// Remember that we saw it.

						digests.insert (std::make_pair (digest, box));

						}

					}

				else
					{
						
					// Already seen this table earlier in this loop, so
					// this is a duplicate.

					box.reset ();
						
					}

				}					
				
			}
			
		}

	// Step 2: Write new tables.

	for (auto it = dictMap.cbegin (); it != dictMap.cend (); ++it)
		{

		const dng_fingerprint &fingerprint = it->first;

		// LogPrint ("big table: %s\n",
		//		  fingerprint.ToUtf8HexString ().Get ());

		// Skip tables that we already have.

		if (digests.find (fingerprint) != digests.end ())
			continue;

		// Add this table.

		auto temp = std::make_shared<dng_bmff_box> ();

		temp->fName = kBigTableTagName;

		const dng_ref_counted_block &block = it->second;
		
		const uint32 blockSize = block.LogicalSize ();

		dng_memory_stream memStream (host.Allocator ());

		memStream.SetBigEndian ();

		// Write version.

		memStream.Put_uint32 (1);

		// Write fingerprint.

		memStream.Put (fingerprint.data,
					   uint32 (sizeof (fingerprint.data)));

		// Write table data.

		memStream.Put (block.Buffer (),
					   blockSize);

		temp->fContent.reset (memStream.AsMemoryBlock (host.Allocator ()));
		
		fBoxes.push_back (temp);
			
		}

	}

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