// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2010 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 "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.

#include "public/include/XMP_Const.h"
#include "public/include/XMP_IO.hpp"

#include "source/XMP_LibUtils.hpp"
#include "source/XMPFiles_IO.hpp"
#include "source/XIO.hpp"


#define EMPTY_FILE_PATH ""
#define XMP_FILESIO_STATIC_START try { int a;
#define XMP_FILESIO_STATIC_END1(errorCallbackPtr, filePath, severity)											\
		a = 1;																									\
	} catch ( XMP_Error & error ) {																				\
		if ( (errorCallbackPtr) != NULL ) (errorCallbackPtr)->NotifyClient ( (severity), error, (filePath) );	\
		else throw;																								\
	}
#define XMP_FILESIO_START try { int b;
#define XMP_FILESIO_END1(severity)																				\
		b = 1;																									\
	} catch ( XMP_Error & error ) {																				\
		if ( errorCallback != NULL ) errorCallback->NotifyClient ( (severity), error, filePath.c_str() );		\
		else throw;																								\
	}
#define XMP_FILESIO_END2(filePath, severity)																	\
		b = 1;																									\
	} catch ( XMP_Error & error ) {																				\
		if ( errorCallback != NULL ) errorCallback->NotifyClient ( (severity), error, (filePath) );				\
		else throw;																								\
	}
#define XMP_FILESIO_STATIC_NOTIFY_ERROR(errorCallbackPtr, filePath, severity, error)							\
	if ( (errorCallbackPtr) != NULL ) errorCallbackPtr->NotifyClient ( (severity), (error), (filePath) );
#define XMP_FILESIO_NOTIFY_ERROR(filePath, severity, error)														\
	XMP_FILESIO_STATIC_NOTIFY_ERROR(errorCallback, (filePath), (severity), (error))


// =================================================================================================
// XMPFiles_IO::New_XMPFiles_IO
// ============================

/* class static */
XMPFiles_IO * XMPFiles_IO::New_XMPFiles_IO (
	const char * filePath,
	bool readOnly,
	GenericErrorCallback * _errorCallback,
	XMP_ProgressTracker * _progressTracker )
{
	XMP_FILESIO_STATIC_START
	Host_IO::FileRef hostFile = Host_IO::noFileRef;
	
	switch ( Host_IO::GetFileMode ( filePath ) ) {
		case Host_IO::kFMode_IsFile:
			hostFile = Host_IO::Open ( filePath, readOnly );
			break;
		case Host_IO::kFMode_DoesNotExist:
			break;
		default:
			XMP_Throw ( "New_XMPFiles_IO, path must be a file or not exist", kXMPErr_FilePathNotAFile );
	}
	if ( hostFile == Host_IO::noFileRef ) {
		XMP_Error error (kXMPErr_NoFile, "New_XMPFiles_IO, file does not exist");
		XMP_FILESIO_STATIC_NOTIFY_ERROR ( _errorCallback, filePath, kXMPErrSev_Recoverable, error );
		return 0;
	}

	Host_IO::Rewind ( hostFile );	// Make sure offset really is 0.

	XMPFiles_IO * newFile = new XMPFiles_IO ( hostFile, filePath, readOnly, _errorCallback, _progressTracker );
	return newFile;
	XMP_FILESIO_STATIC_END1 ( _errorCallback, filePath, kXMPErrSev_FileFatal )
	return NULL;

}	// XMPFiles_IO::New_XMPFiles_IO

// =================================================================================================
// XMPFiles_IO::XMPFiles_IO
// ========================

XMPFiles_IO::XMPFiles_IO (
	Host_IO::FileRef hostFile,
	const char * _filePath,
	bool _readOnly,
	GenericErrorCallback * _errorCallback,
	XMP_ProgressTracker * _progressTracker )
	: readOnly(_readOnly)
	, filePath(_filePath)
	, fileRef(hostFile)
	, currOffset(0)
	, isTemp(false)
	, derivedTemp(0)
	, progressTracker(_progressTracker)
	, errorCallback(_errorCallback)
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );

	this->currLength = Host_IO::Length ( this->fileRef );
	XMP_FILESIO_END2 ( _filePath, kXMPErrSev_FileFatal )
}	// XMPFiles_IO::XMPFiles_IO

// =================================================================================================
// XMPFiles_IO::~XMPFiles_IO
// =========================

XMPFiles_IO::~XMPFiles_IO()
{
	try {
		XMP_FILESIO_START
		if ( this->derivedTemp != 0 ) this->DeleteTemp();
		if ( this->fileRef != Host_IO::noFileRef ) Host_IO::Close ( this->fileRef );
		if ( this->isTemp && (! this->filePath.empty()) ) Host_IO::Delete ( this->filePath.c_str() );
		XMP_FILESIO_END1 ( kXMPErrSev_Recoverable )
	} catch ( ... ) {
		// All of the above is fail-safe cleanup, ignore problems.
	}
}	// XMPFiles_IO::~XMPFiles_IO

// =================================================================================================
// XMPFiles_IO::operator=
// ======================

void XMPFiles_IO::operator = ( const XMP_IO& in )
{
	XMP_FILESIO_START
	XMP_Throw ( "No assignment for XMPFiles_IO", kXMPErr_InternalFailure );
	XMP_FILESIO_END1 ( kXMPErrSev_OperationFatal )

};	// XMPFiles_IO::operator=

// =================================================================================================
// XMPFiles_IO::Read
// =================

XMP_Uns32 XMPFiles_IO::Read ( void * buffer, XMP_Uns32 count, bool readAll /* = false */ )
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_Assert ( this->currLength == Host_IO::Length ( this->fileRef ) );
	XMP_Assert ( this->currOffset <= this->currLength );

	if ( count > (this->currLength - this->currOffset) ) {
		if ( readAll ) XMP_Throw ( "XMPFiles_IO::Read, not enough data", kXMPErr_EnforceFailure );
		count = (XMP_Uns32) (this->currLength - this->currOffset);
	}

	XMP_Uns32 amountRead = Host_IO::Read ( this->fileRef, buffer, count );
	XMP_Enforce ( amountRead == count );

	this->currOffset += amountRead;
	return amountRead;
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )
	return 0;

}	// XMPFiles_IO::Read

// =================================================================================================
// XMPFiles_IO::Write
// ==================

void XMPFiles_IO::Write ( const void * buffer, XMP_Uns32 count )
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_Assert ( this->currLength == Host_IO::Length ( this->fileRef ) );
	XMP_Assert ( this->currOffset <= this->currLength );

	try {
		if ( this->readOnly )
			XMP_Throw ( "New_XMPFiles_IO, write not permitted on read only file", kXMPErr_FilePermission );
		Host_IO::Write ( this->fileRef, buffer, count );
		if ( this->progressTracker != 0 ) this->progressTracker->AddWorkDone ( (float) count );
	} catch ( ... ) {
		try {
			// we should try to maintain the state as best as possible
			// but no exception should escape from this backup plan.
			// Make sure the internal state reflects partial writes.
			this->currOffset = Host_IO::Offset ( this->fileRef );
			this->currLength = Host_IO::Length ( this->fileRef );
		} catch ( ... ) {
			// don't do anything
		}
		throw;
	}

	this->currOffset += count;
	if ( this->currOffset > this->currLength ) this->currLength = this->currOffset;
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )

}	// XMPFiles_IO::Write

// =================================================================================================
// XMPFiles_IO::Seek
// =================

XMP_Int64 XMPFiles_IO::Seek ( XMP_Int64 offset, SeekMode mode )
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_Assert ( this->currLength == Host_IO::Length ( this->fileRef ) );

	XMP_Int64 newOffset = offset;
	if ( mode == kXMP_SeekFromCurrent ) {
		newOffset += this->currOffset;
	} else if ( mode == kXMP_SeekFromEnd ) {
		newOffset += this->currLength;
	}
	XMP_Enforce ( newOffset >= 0 );

	if ( newOffset <= this->currLength ) {
		this->currOffset = Host_IO::Seek ( this->fileRef, offset, mode );
	} else if ( this->readOnly ) {
		XMP_Throw ( "XMPFiles_IO::Seek, read-only seek beyond EOF", kXMPErr_EnforceFailure );
	} else {
		Host_IO::SetEOF ( this->fileRef, newOffset );	// Extend a file open for writing.
		this->currLength = newOffset;
		this->currOffset = Host_IO::Seek ( this->fileRef, 0, kXMP_SeekFromEnd );
	}

	XMP_Assert ( this->currOffset == newOffset );
	return this->currOffset;
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal );
	return -1;

}	// XMPFiles_IO::Seek

// =================================================================================================
// XMPFiles_IO::Length
// ===================

XMP_Int64 XMPFiles_IO::Length()
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_Assert ( this->currLength == Host_IO::Length ( this->fileRef ) );
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )
	return this->currLength;

}	// XMPFiles_IO::Length

// =================================================================================================
// XMPFiles_IO::Truncate
// =====================

void XMPFiles_IO::Truncate ( XMP_Int64 length )
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_Assert ( this->currLength == Host_IO::Length ( this->fileRef ) );

	if ( this->readOnly )
		XMP_Throw ( "New_XMPFiles_IO, truncate not permitted on read only file", kXMPErr_FilePermission );

	XMP_Enforce ( length <= this->currLength );
	Host_IO::SetEOF ( this->fileRef, length );

	this->currLength = length;
	if ( this->currOffset > this->currLength ) this->currOffset = this->currLength;

	// ! Seek to the expected offset, some versions of Host_IO::SetEOF implicitly seek to EOF.
	Host_IO::Seek ( this->fileRef, this->currOffset, kXMP_SeekFromStart );
	XMP_Assert ( this->currOffset == Host_IO::Offset ( this->fileRef ) );
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )

}	// XMPFiles_IO::Truncate

// =================================================================================================
// XMPFiles_IO::DeriveTemp
// =======================

XMP_IO* XMPFiles_IO::DeriveTemp()
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );

	if ( this->derivedTemp != 0 ) return this->derivedTemp;

	if ( this->readOnly ) {
		XMP_Throw ( "XMPFiles_IO::DeriveTemp, can't derive from read-only", kXMPErr_InternalFailure );
	}
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )

	std::string tempPath;

	XMP_FILESIO_START
	tempPath = Host_IO::CreateTemp ( this->filePath.c_str() );

	XMPFiles_IO* newTemp = XMPFiles_IO::New_XMPFiles_IO ( tempPath.c_str(), Host_IO::openReadWrite );
	if ( newTemp == 0 ) {
		Host_IO::Delete ( tempPath.c_str() );
		XMP_Throw ( "XMPFiles_IO::DeriveTemp, can't open temp file", kXMPErr_InternalFailure );
	}

	newTemp->isTemp = true;
	this->derivedTemp = newTemp;
	newTemp->progressTracker = this->progressTracker;	// Automatically track writes to the temp file.
	XMP_FILESIO_END2 ( tempPath.c_str(), kXMPErrSev_FileFatal )
	return this->derivedTemp;

}	// XMPFiles_IO::DeriveTemp

// =================================================================================================
// XMPFiles_IO::AbsorbTemp
// =======================

void XMPFiles_IO::AbsorbTemp()
{
	XMP_FILESIO_START
	XMP_Assert ( this->fileRef != Host_IO::noFileRef );

	XMPFiles_IO * temp = this->derivedTemp;
	if ( temp == 0 ) {
		XMP_Throw ( "XMPFiles_IO::AbsorbTemp, no temp to absorb", kXMPErr_InternalFailure );
	}
	XMP_Assert ( temp->isTemp );

	this->Close();
	temp->Close();

	Host_IO::SwapData ( this->filePath.c_str(), temp->filePath.c_str() );
	this->DeleteTemp();

	this->fileRef = Host_IO::Open ( this->filePath.c_str(), Host_IO::openReadWrite );
	this->currLength = Host_IO::Length ( this->fileRef );
	this->currOffset = 0;
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )

}	// XMPFiles_IO::AbsorbTemp

// =================================================================================================
// XMPFiles_IO::DeleteTemp
// =======================

void XMPFiles_IO::DeleteTemp()
{
	XMP_FILESIO_START
	XMPFiles_IO * temp = this->derivedTemp;

	if ( temp != 0 ) {

		if ( temp->fileRef != Host_IO::noFileRef ) {
			Host_IO::Close ( temp->fileRef );
			temp->fileRef = Host_IO::noFileRef;
		}

		if ( ! temp->filePath.empty() ) {
			Host_IO::Delete ( temp->filePath.c_str() );
			temp->filePath.erase();
		}

		delete temp;
		this->derivedTemp = 0;

	}
	XMP_FILESIO_END2 ( this->derivedTemp->filePath.c_str(), kXMPErrSev_FileFatal )

}	// XMPFiles_IO::DeleteTemp

// =================================================================================================
// XMPFiles_IO::Close
// ==================

void XMPFiles_IO::Close()
{
	XMP_FILESIO_START
	if ( this->fileRef != Host_IO::noFileRef ) {
		Host_IO::Close ( this->fileRef );
		this->fileRef = Host_IO::noFileRef;
	}
	XMP_FILESIO_END1 ( kXMPErrSev_FileFatal )

}	// XMPFiles_IO::Close

// =================================================================================================
