//================================================================================================
// Copyright Nikon Corporation - All rights reserved
//
// MODIFIED VERSION: 2014 Eric J Martin MBARI
//
// View this file in a non-proportional font, tabs = 3
//================================================================================================

#include <stdlib.h>
#include <stdio.h>
#if defined( _WIN32 )
#include <windows.h>
#include <mmsystem.h>
#endif
#include "Maid3.h"
#include "Maid3d1.h"
#include "CtrlSample.h"
#include "jhead_mem.h" // For exif editing

#if defined( _WIN32 )
ULONG g_ulProgressValue;// used in only ProgressProc
#elif defined(__APPLE__)
ULONG g_ulProgressValue;// used in only ProgressProc
#endif
BOOL	g_bFirstCall = true;// used in ProgressProc, and DoDeleteDramImage

//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK ModEventProc( NKREF refProc, ULONG ulEvent, NKPARAM data )
{
	BOOL bRet;
	LPRefObj pRefParent = (LPRefObj)refProc, pRefChild = NULL;

	switch(ulEvent){
	case kNkMAIDEvent_AddChild:
		bRet = AddChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		pRefChild = GetRefChildPtr_ID( pRefParent, (SLONG)data );
		// Enumerate children(Item and Data Objects) and open them.
		bRet = EnumChildrten( pRefChild->pObject );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_RemoveChild:
		bRet = RemoveChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_WarmingUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmingUp to Module object is not supported.\n" );
		break;
	case kNkMAIDEvent_WarmedUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmedUp to Module object is not supported.\n" );
		break;
	case kNkMAIDEvent_CapChange:
		// re-enumerate the capabilities
		if ( pRefParent->pCapArray != NULL ) {
			free( pRefParent->pCapArray );
			pRefParent->ulCapCount = 0;
			pRefParent->pCapArray = NULL;
		}
		bRet = EnumCapabilities( pRefParent->pObject, &(pRefParent->ulCapCount), &(pRefParent->pCapArray), NULL, NULL );
		if ( bRet == false ) return;
		// ToDo: Execute a process when the property of a capability was changed.
		break;
	case kNkMAIDEvent_CapChangeValueOnly:
		// ToDo: Execute a process when the value of a capability was changed.
		printf( "ModEventProc(): The value of Capability(CapID=0x%X) was changed.\n", data );
		break;
	case kNkMAIDEvent_OrphanedChildren:
		// ToDo: Close children(Source Objects).
		break;
	default:
		puts( "Detected unknown Event to the Module object.\n" );
	}
}
//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK SrcEventProc( NKREF refProc, ULONG ulEvent, NKPARAM data )
{
	BOOL bRet;
	LPRefObj pRefParent = (LPRefObj)refProc, pRefChild = NULL;

	switch(ulEvent){
	case kNkMAIDEvent_AddChild:
		bRet = AddChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		pRefChild = GetRefChildPtr_ID( pRefParent, (SLONG)data );
		// Enumerate children(Data Objects) and open them.
		bRet = EnumChildrten( pRefChild->pObject );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_RemoveChild:
		bRet = RemoveChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_WarmingUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmingUp to Source object is not supported.\n" );
		break;
	case kNkMAIDEvent_WarmedUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmedUp to Source object is not supported.\n" );
		break;
	case kNkMAIDEvent_CapChange:
		// re-enumerate the capabilities
		if ( pRefParent->pCapArray != NULL ) {
			free( pRefParent->pCapArray );
			pRefParent->ulCapCount = 0;
			pRefParent->pCapArray = NULL;
		}
		bRet = EnumCapabilities( pRefParent->pObject, &(pRefParent->ulCapCount), &(pRefParent->pCapArray), NULL, NULL );
		if ( bRet == false ) return;
		// ToDo: Execute a process when the property of a capability was changed.
		break;
	case kNkMAIDEvent_CapChangeValueOnly:
		// ToDo: Execute a process when the value of a capability was changed.
		//EJM Edit
		//printf( "SrcEventProc(): The value of Capability(CapID=0x%X) was changed.\n", data );
		break;
	case kNkMAIDEvent_OrphanedChildren:
		// ToDo: Close children(Item Objects).
		break;
	case kNkMAIDEvent_AddPreviewImage:
		// A Preview data is a specification deleted from the inside of the camera after reading a main image is finished. 
		// Current module is a specification that immediately begins the acquisition of a main image regardless of 
		// the request of the client when the creation completion notification of a main image is received from the camera.
		// After module read the image, the image data is deleted from the inside of the camera.
		// Therefore, the acquisition of a Preview data  is not a function secured 100%. 
		// However, if client could read a Preview data even once, the module generates the Preview cache internally, 
		// so the client can get data after receiving kNkMAIDEvent_AddChild even, until Item object is closed.
		break;
	case kNkMAIDEvent_CaptureComplete:
		// ToDo: Show the image transfer finished.
		break;

	default:
		puts( "Detected unknown Event to the Source object.\n" );
	}
}
//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK ItmEventProc( NKREF refProc, ULONG ulEvent, NKPARAM data )
{
	BOOL bRet;
	LPRefObj pRefParent = (LPRefObj)refProc;

	switch(ulEvent){
	case kNkMAIDEvent_AddChild:
		bRet = AddChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_RemoveChild:
		bRet = RemoveChild( pRefParent, (SLONG)data );
		if ( bRet == false ) return;
		break;
	case kNkMAIDEvent_WarmingUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmingUp to Item object is not supported.\n" );
		break;
	case kNkMAIDEvent_WarmedUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmedUp to Item object is not supported.\n" );
		break;
	case kNkMAIDEvent_CapChange:
		// re-enumerate the capabilities
		if ( pRefParent->pCapArray != NULL ) {
			free( pRefParent->pCapArray );
			pRefParent->ulCapCount = 0;
			pRefParent->pCapArray = NULL;
		}
		bRet = EnumCapabilities( pRefParent->pObject, &(pRefParent->ulCapCount), &(pRefParent->pCapArray), NULL, NULL );
		if ( bRet == false ) return;
		// ToDo: Execute a process when the property of a capability was changed.
		break;
	case kNkMAIDEvent_CapChangeValueOnly:
		// ToDo: Execute a process when the value of a capability was changed.
		printf( "ItmEventProc(): The value of Capability(CapID=0x%X) was changed.\n", data );
		break;
	case kNkMAIDEvent_OrphanedChildren:
		// ToDo: Close children(Data Objects).
		break;
	default:
		puts( "Detected unknown Event to the Item object.\n" );
	}
}
//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK DatEventProc( NKREF refProc, ULONG ulEvent, NKPARAM data )
{
	BOOL bRet;
	LPRefObj	pRefParent = (LPRefObj)refProc;

	switch(ulEvent){
	case kNkMAIDEvent_AddChild:
		// The Type0001 Module does not use this event.
		puts( "Event_AddChild to Data object is not supported.\n" );
		break;
	case kNkMAIDEvent_RemoveChild:
		// The Type0001 Module does not use this event.
		puts( "Event_RemoveChild to Data object is not supported.\n" );
		break;
	case kNkMAIDEvent_WarmingUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmingUp to Data object is not supported.\n" );
		break;
	case kNkMAIDEvent_WarmedUp:
		// The Type0001 Module does not use this event.
		puts( "Event_WarmedUp to Data object is not supported.\n" );
		break;
	case kNkMAIDEvent_CapChange:// module notify that a capability is changed.
		// re-enumerate the capabilities
		if ( pRefParent->pCapArray != NULL ) {
			free( pRefParent->pCapArray );
			pRefParent->ulCapCount = 0;
			pRefParent->pCapArray = NULL;
		}
		bRet = EnumCapabilities( pRefParent->pObject, &(pRefParent->ulCapCount), &(pRefParent->pCapArray), NULL, NULL );
		if ( bRet == false ) return;
		// ToDo: Execute a process when the property of a capability was changed.
	case kNkMAIDEvent_CapChangeValueOnly:
		// ToDo: Execute a process when the value of a capability was changed.
		printf( "DatEventProc(): The value of Capability(CapID=0x%X) was changed.\n", data );
		break;
	case kNkMAIDEvent_OrphanedChildren:
		// The Type0001 Module does not use this event.
		puts( "Event_OrphanedChildren to Data object is not supported.\n" );
		break;
	default:
		puts( "Detected unknown Event to the Data object.\n" );
	}
}
//------------------------------------------------------------------------------------------------------------------------------------
// copy the delivered data
NKERROR CALLPASCAL CALLBACK DataProc( NKREF ref, LPVOID pInfo, LPVOID pData )
{
	LPNkMAIDDataInfo pDataInfo = (LPNkMAIDDataInfo)pInfo;
	LPNkMAIDImageInfo pImageInfo = (LPNkMAIDImageInfo)pInfo;
	LPNkMAIDFileInfo pFileInfo = (LPNkMAIDFileInfo)pInfo;
	ULONG ulTotalSize, ulOffset;
	LPVOID pCurrentBuffer;
	ULONG ulByte;
	extern char sNextFileName[]; // array [32]
	extern char sLastFileName[]; // array [32]
	extern char sNextDTOrig[];   // array [20]
	extern char sNextDTOrigSS[]; // array [4]
	extern FILE * LOGFDS;

	if ( pDataInfo->ulType & kNkMAIDDataObjType_File ) {

		if( ((LPRefDataProc)ref)->ulOffset == 0 && ((LPRefDataProc)ref)->pBuffer == NULL )
			((LPRefDataProc)ref)->pBuffer = malloc( pFileInfo->ulTotalLength );
		if ( ((LPRefDataProc)ref)->pBuffer == NULL ) {
			puts( "There is not enough memory." );
			return kNkMAIDResult_OutOfMemory;
		}
		ulOffset = ((LPRefDataProc)ref)->ulOffset;
		pCurrentBuffer = (LPVOID)((ULONG)((LPRefDataProc)ref)->pBuffer + ((LPRefDataProc)ref)->ulOffset);
		memmove( pCurrentBuffer, pData, pFileInfo->ulLength);
		ulOffset += pFileInfo->ulLength;

		if( ulOffset < pFileInfo->ulTotalLength ) {
			// We have not finished the delivery.
			((LPRefDataProc)ref)->ulOffset = ulOffset;
		} else {
			// We have finished the delivery. We will save this file.
			FILE *stream;
			char filename[256], Prefix[16], Ext[16];
			UWORD i = 0;
			if ( pDataInfo->ulType & kNkMAIDDataObjType_Image )
				strcpy(Prefix,"Image");
			else if ( pDataInfo->ulType & kNkMAIDDataObjType_Thumbnail )
				strcpy(Prefix,"Thumb");
			else
				strcpy(Prefix,"Unknown");
			switch( pFileInfo->ulFileDataType ) {
			case kNkMAIDFileDataType_JPEG:
				strcpy(Ext,".jpg");
				break;
			case kNkMAIDFileDataType_TIFF:
				strcpy(Ext,".tif");
				break;
			case kNkMAIDFileDataType_NIF:
				strcpy(Ext,".nef");
				break;
			case kNkMAIDFileDataType_NDF:
				strcpy(Ext,".ndf");
				break;
			default:
				strcpy(Ext,".dat");
			}


			// Edit Exif information
			ReadJpegFileFromMem((unsigned char *)((LPRefDataProc)ref)->pBuffer,pFileInfo->ulTotalLength,READ_EXIF);
			
			// Write in Date time original 
			if (ImageInfo.DatePointer)
				memcpy(ImageInfo.DatePointer, sNextDTOrig, 19);
			else
				fprintf(stderr, "File contains no Exif timestamp to change\n");
			
			// Write-in subseconds
			if (ImageInfo.SubsecondsPointer)
				memcpy(ImageInfo.SubsecondsPointer, sNextDTOrigSS, 3);
			else
				fprintf(stderr, "File contains no Exif subseconds to change\n");
			
			// Commit Changes to the File in Memory
			WriteJpegHeaders((unsigned char *)((LPRefDataProc)ref)->pBuffer);

			// Free the exif header copied into memory 
			DiscardData();

			// Start Writing out the file.
			sprintf( filename, "%s%s", sNextFileName, Ext );
			while( true ) {
				//original Format, without globally issued value.
				//sprintf( filename, "C:\\\%s%03d%s", Prefix, ++i, Ext );
				if ( (stream = fopen(filename, "r") ) != NULL ) {//test if the file exists
					fclose(stream);
					sprintf( filename, "%s-%03d%s", sNextFileName, ++i, Ext );
					fprintf(stderr, "ERROR: Trying to write to existing file... renaming [%s].\n",filename);
				}
				else
					break;
			}

			// Open File 
			if ( (stream = fopen(filename, "wb") ) == NULL) {
				fprintf(stderr,"ERROR: Cannot open file for writing [%s].\n",filename);
				return kNkMAIDResult_UnexpectedError;
			}

			// Write Data
			fwrite(((LPRefDataProc)ref)->pBuffer, 1, pFileInfo->ulTotalLength, stream);
			fclose(stream);
			free(((LPRefDataProc)ref)->pBuffer);
			((LPRefDataProc)ref)->pBuffer = NULL;
			((LPRefDataProc)ref)->ulOffset = 0;
			// If the flag of fRemoveObject in NkMAIDFileInfo structure is true, we should remove this item.
			if ( pFileInfo->fRemoveObject && (pDataInfo->ulType & kNkMAIDDataObjType_Image) )
				g_bFileRemoved = true;
			sprintf(sLastFileName,"%s", filename); // for logging purposes in the NikonThread.
		}
	} 
	else {
		ulTotalSize = pImageInfo->ulRowBytes * pImageInfo->szTotalPixels.h;
		if( ((LPRefDataProc)ref)->ulOffset == 0 && ((LPRefDataProc)ref)->pBuffer == NULL )
			((LPRefDataProc)ref)->pBuffer = malloc( ulTotalSize );
		if ( ((LPRefDataProc)ref)->pBuffer == NULL ) {
			puts( "There is not enough memory." );
			return kNkMAIDResult_OutOfMemory;
		}
		ulOffset = ((LPRefDataProc)ref)->ulOffset;
		pCurrentBuffer = (LPVOID)((ULONG)((LPRefDataProc)ref)->pBuffer + ulOffset);
		ulByte = pImageInfo->ulRowBytes * pImageInfo->rData.h;
		memmove( pCurrentBuffer, pData, ulByte );
		ulOffset += ulByte;

		if( ulOffset < ulTotalSize ) {
			// We have not finished the delivery.
			((LPRefDataProc)ref)->ulOffset = ulOffset;
		} else {
			// We have finished the delivery. We will save this file.
			FILE *stream;
			char filename[256], Prefix[16];
			UWORD i = 0;
			if ( pDataInfo->ulType & kNkMAIDDataObjType_Image )
				strcpy(Prefix,"Image");
			else if ( pDataInfo->ulType & kNkMAIDDataObjType_Thumbnail )
				strcpy(Prefix,"Thumb");
			else
				strcpy(Prefix,"Unknown");
			while( true ) {
				sprintf( filename, "%s%03d.raw", Prefix, ++i );
				if ( (stream = fopen(filename, "r") ) != NULL )
					fclose(stream);
				else
					break;
			}
			if ( (stream = fopen(filename, "wb") ) == NULL)
				return kNkMAIDResult_UnexpectedError;
			fwrite(((LPRefDataProc)ref)->pBuffer, 1, ulTotalSize, stream);
			fclose(stream);
			free(((LPRefDataProc)ref)->pBuffer);
			((LPRefDataProc)ref)->pBuffer = NULL;
			((LPRefDataProc)ref)->ulOffset = 0;
			// If the flag of fRemoveObject in NkMAIDFileInfo structure is true, we should remove this item.
			if ( pImageInfo->fRemoveObject && (pDataInfo->ulType & kNkMAIDDataObjType_Image) )
				g_bFileRemoved = true;
		}
	}
	return kNkMAIDResult_NoError;
}
//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK CompletionProc(
	LPNkMAIDObject	pObject,			// module, source, item, or data object
	ULONG				ulCommand,		// Command, one of eNkMAIDCommand
	ULONG				ulParam,			// parameter for the command
	ULONG				ulDataType,		// Data type, one of eNkMAIDDataType
	NKPARAM			data,				// Pointer or long integer
	NKREF				refComplete,	// Reference set by client
	NKERROR			nResult )		// One of eNkMAIDResult)
{
	((LPRefCompletionProc)refComplete)->nResult = nResult;
	(*((LPRefCompletionProc)refComplete)->pulCount) ++;

	// if the Command is CapStart acquire, we terminate RefDeliver.
	if(ulCommand == kNkMAIDCommand_CapStart && ulParam == kNkMAIDCapability_Acquire) {
		LPRefDataProc pRefDeliver = (LPRefDataProc)((LPRefCompletionProc)refComplete)->pRef;
		if ( pRefDeliver != NULL ) {
			if ( pRefDeliver->pBuffer != NULL )
				free( pRefDeliver->pBuffer );
			free( pRefDeliver );
		}
	}
	// terminate refComplete.
	if ( refComplete != NULL )
		free( refComplete );

}
//------------------------------------------------------------------------------------------------------------------------------------

void CALLPASCAL CALLBACK ProgressProc(
	ULONG				ulCommand,			// Command, one of eNkMAIDCommand
	ULONG				ulParam,				// parameter for the command
	NKREF				refProc,				// Reference set by client
	ULONG				ulDone,				// Numerator
	ULONG				ulTotal )			// Denominator
{
	ULONG	ulNewProgressValue, ulCount;
	if ( ulTotal == 0 ) {
		// when we don't know how long this process is, we show such as barber's pole.
		if ( ulDone == 1 ) {
#if defined( _WIN32 )
			ulNewProgressValue = timeGetTime();
			if( (ulNewProgressValue < g_ulProgressValue) || (ulNewProgressValue > g_ulProgressValue + 500) ) {
				printf( "c" );
				g_ulProgressValue = ulNewProgressValue;
			}
#elif defined(__APPLE__)
			ulNewProgressValue = TickCount();
			if( (ulNewProgressValue < g_ulProgressValue) || (ulNewProgressValue > g_ulProgressValue + 30) ) {
				printf( "c" );
				g_ulProgressValue = ulNewProgressValue;
			}
#endif
		} else if ( ulDone == 0 ) {
			printf( "o" );
		}
	} else {
		// when we know how long this process is, we show progress bar.
		//if ( ulDone == 0 ) {
		//	if ( g_bFirstCall == true ) {
		//		g_ulProgressValue = 0;
		//		g_bFirstCall = false;
		//		printf("\n0       20        40        60        80        100");
		//		printf("\n---------+---------+---------+---------+---------+\n");
		//	}
		//} else {
		//	// show progress bar
		//	ulNewProgressValue = (50 * ulDone + ulTotal - 1) / ulTotal;
		//	ulCount = ulNewProgressValue - g_ulProgressValue;
		//	while ( ulCount-- )
		//		printf( "]" );
		//	g_ulProgressValue = ulNewProgressValue;
		//	if ( ulDone == ulTotal ) {
		//		printf( "\n" );
		//		g_bFirstCall = true;
		//	}
		//}
	}
}
//------------------------------------------------------------------------------------------------------------------------------------

ULONG CALLPASCAL CALLBACK UIRequestProc( NKREF ref, LPNkMAIDUIRequestInfo pUIRequest )
{
	short	 nRet = kNkMAIDUIRequestResult_None;
	char	sAns[256];

	// display message
	if (pUIRequest->lpPrompt)
		printf( "\n%s\n", pUIRequest->lpPrompt );
	if (pUIRequest->lpDetail)
		printf( "\n%s\n", pUIRequest->lpDetail );

	// get an answer
	switch( pUIRequest->ulType ){
	case kNkMAIDUIRequestType_Ok:
		do {
			printf("\nPress 'O' key. ('O': OK)\n>");
			scanf( "%s", sAns );
		} while ( *sAns != 'o' && *sAns != 'O' );
		nRet = kNkMAIDUIRequestResult_Ok;
		break;
	case kNkMAIDUIRequestType_OkCancel:
		do {
			printf("\nPress 'O' or 'C' key. ('O': OK   'C': Cancel)\n>");
			scanf( "%s", sAns );
		} while ( *sAns != 'o' && *sAns != 'O' && *sAns != 'c' && *sAns != 'C' );
		if ( *sAns == 'o' || *sAns == 'O' )
			nRet = kNkMAIDUIRequestResult_Ok;
		else if ( *sAns == 'c' || *sAns == 'C' )
			nRet = kNkMAIDUIRequestResult_Cancel;
		break;
	case kNkMAIDUIRequestType_YesNo:
		do {
			printf("\nPress 'Y' or 'N' key. ('Y': Yes   'N': No)\n>");
			scanf( "%s", sAns );
		} while ( *sAns != 'y' && *sAns != 'Y' && *sAns != 'n' && *sAns != 'N' );
		if ( *sAns == 'y' || *sAns == 'Y' )
			nRet = kNkMAIDUIRequestResult_Yes;
		else if ( *sAns == 'n' || *sAns == 'N' )
			nRet = kNkMAIDUIRequestResult_No;
		break;
	case kNkMAIDUIRequestType_YesNoCancel:
		do {
			printf("\nPress 'Y' or 'N' or 'C' key. ('Y': Yes   'N': No   'C': Cancel)\n>");
			scanf( "%s", sAns );
		} while ( *sAns != 'y' && *sAns != 'Y' && *sAns != 'n' && *sAns != 'N' && *sAns != 'c' && *sAns != 'C' );
		if ( *sAns == 'y' || *sAns == 'Y' )
			nRet = kNkMAIDUIRequestResult_Yes;
		else if ( *sAns == 'n' || *sAns == 'N' )
			nRet = kNkMAIDUIRequestResult_No;
		else if ( *sAns == 'c' || *sAns == 'C' )
			nRet = kNkMAIDUIRequestResult_Cancel;
		break;
	default:
		nRet = kNkMAIDUIRequestResult_None;
	}

	return nRet;
}
//------------------------------------------------------------------------------------------------------------------------------------
