/*
  Hatari - floppy_ipf.c

  This file is distributed under the GNU General Public License, version 2 or at
  your option any later version. Read the file gpl.txt for details.

  IPF disk image support.

  IPF files are handled using the capsimage library, which emulates the FDC
  at low level and allows to read complex protections.

  RAW files made with KryoFlux board or CT RAW dumped with an Amiga are also handled
  by the capsimage library.
*/
const char floppy_ipf_fileid[] = "Hatari floppy_ipf.c : " __DATE__ " " __TIME__;

#include "main.h"
#include "file.h"
#include "floppy.h"
#include "floppy_ipf.h"
#include "fdc.h"
#include "log.h"
#include "memorySnapShot.h"
#include "screen.h"
#include "video.h"
#include "cycles.h"

#ifdef HAVE_CAPSIMAGE
#if CAPSIMAGE_VERSION == 5
#include <caps5/CapsLibAll.h>
#else
#include <caps/fdc.h>
#define CAPS_LIB_RELEASE	4
#define CAPS_LIB_REVISION	2
#endif
/* Macro to check release and revision */
#define	CAPS_LIB_REL_REV	( CAPS_LIB_RELEASE * 100 + CAPS_LIB_REVISION )
#endif


typedef struct
{
#ifdef HAVE_CAPSIMAGE
	Uint32			CapsLibRelease;
	Uint32			CapsLibRevision;

	struct CapsFdc		Fdc;				/* Fdc state */
	struct CapsDrive 	Drive[ MAX_FLOPPYDRIVES ];	/* Physical drives */
	CapsLong		CapsImage[ MAX_FLOPPYDRIVES ];	/* For the IPF disk images */

	int			Rev_Track[ MAX_FLOPPYDRIVES ];	/* Needed to handle CAPSSetRevolution for type II/III commands */
	int			Rev_Side[ MAX_FLOPPYDRIVES ];

	bool			DriveEnabled[ MAX_FLOPPYDRIVES ];/* Is drive ON or OFF */
	bool			DoubleSided[ MAX_FLOPPYDRIVES ];/* Is drive double sided or not */

	Sint64			FdcClock;			/* Current value of CyclesGlobalClockCounter */
#endif
} IPF_STRUCT;


static IPF_STRUCT	IPF_State;			/* All variables related to the IPF support */


#ifdef HAVE_CAPSIMAGE
static void	IPF_CallBack_Trk ( struct CapsFdc *pc , CapsULong State );
static void	IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State );
static void	IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State );
static void	IPF_Drive_Update_Enable_Side ( void );
#endif




/*-----------------------------------------------------------------------*/
/**
 * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
 * We must take care of whether Hatari was compiled with IPF support of not
 * when saving/restoring snapshots to avoid incompatibilies.
 */
void IPF_MemorySnapShot_Capture(bool bSave)
{
	int	StructSize;
	int	Drive;

	if ( bSave )					/* Saving snapshot */
	{
		StructSize = sizeof ( IPF_State );	/* 0 if HAVE_CAPSIMAGE is not defined */
		MemorySnapShot_Store(&StructSize, sizeof(StructSize));
fprintf ( stderr , "ipf save %d\n" , StructSize );
		if ( StructSize > 0 )
			MemorySnapShot_Store(&IPF_State, sizeof(IPF_State));
	}

	else						/* Restoring snapshot */
	{
		MemorySnapShot_Store(&StructSize, sizeof(StructSize));
fprintf ( stderr , "ipf load %d\n" , StructSize );
		if ( ( StructSize == 0 ) && ( sizeof ( IPF_State ) > 0 ) )
		{
			Log_AlertDlg(LOG_ERROR, "This memory snapshot doesn't include IPF data but this version of Hatari was built with IPF support");
			return;				/* Continue restoring the rest of the memory snapshot */
		}
		else if ( ( StructSize > 0 ) && ( sizeof ( IPF_State ) == 0 ) )
		{
			Log_AlertDlg(LOG_ERROR, "This memory snapshot includes IPF data but this version of Hatari was not built with IPF support");
			MemorySnapShot_Skip( StructSize );	/* Ignore the IPF data */
			return;				/* Continue restoring the rest of the memory snapshot */
		}
		else if ( ( StructSize > 0 ) && ( StructSize != sizeof ( IPF_State ) ) )
		{
			Log_AlertDlg(LOG_ERROR, "This memory snapshot includes IPF data different from the ones handled in this version of Hatari");
			MemorySnapShot_Skip( StructSize );	/* Ignore the IPF data */
			return;				/* Continue restoring the rest of the memory snapshot */
		}

		if ( StructSize > 0 )
		{
			MemorySnapShot_Store(&IPF_State, sizeof(IPF_State));

#ifdef HAVE_CAPSIMAGE
			/* For IPF structures, we need to update some pointers in Fdc/Drive/CapsImage */
			/* drive : PUBYTE trackbuf, PUDWORD timebuf */
			/* fdc : PCAPSDRIVE driveprc, PCAPSDRIVE drive, CAPSFDCHOOK callback functions */
			IPF_State.Fdc.drive = IPF_State.Drive;		/* Connect drives array to the FDC */
			if ( IPF_State.Fdc.driveprc != NULL )		/* Recompute active drive's pointer */
				IPF_State.Fdc.driveprc = IPF_State.Fdc.drive + IPF_State.Fdc.driveact;

			CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 0 );	/* Invalidate buffered track data for drive 0 */
			CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 1 );	/* Invalidate buffered track data for drive 1 */

			/* Set callback functions */
			IPF_State.Fdc.cbirq = IPF_CallBack_Irq;
			IPF_State.Fdc.cbdrq = IPF_CallBack_Drq;
			IPF_State.Fdc.cbtrk = IPF_CallBack_Trk;
#endif

			/* Call IPF_Insert to recompute IPF_State.CapsImage[ Drive ] */
			for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ )
				if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_IPF )
					if ( IPF_Insert ( Drive , EmulationDrives[Drive].pBuffer , EmulationDrives[Drive].nImageBytes ) == false )
					{
						Log_AlertDlg(LOG_ERROR, "Error restoring IPF image %s in drive %d" ,
							EmulationDrives[Drive].sFileName , Drive );
						return;
					}

		fprintf ( stderr , "ipf load ok\n" );
		}
	}
}




/*-----------------------------------------------------------------------*/
/**
 * Does filename end with a .IPF or .RAW or .CTR extension ? If so, return true.
 * .RAW and .CTR support requires caps lib >= 5.1
 */
bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ)
{
	return ( File_DoesFileExtensionMatch(pszFileName,".ipf" )
		|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ipf.gz") )
#if CAPS_LIB_REL_REV >= 501
		|| File_DoesFileExtensionMatch(pszFileName,".raw" )
		|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".raw.gz") )
		|| File_DoesFileExtensionMatch(pszFileName,".ctr" )
		|| ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ctr.gz") )
#endif
		);
}


/*-----------------------------------------------------------------------*/
/**
 * Load .IPF file into memory, set number of bytes loaded and return a pointer
 * to the buffer.
 */
Uint8 *IPF_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType)
{
#ifndef HAVE_CAPSIMAGE
	Log_AlertDlg(LOG_ERROR, "This version of Hatari was not built with IPF support, this disk image can't be handled.");
	return NULL;

#else
	Uint8 *pIPFFile;

	*pImageSize = 0;

	/* Just load directly a buffer, and set ImageSize accordingly */
	pIPFFile = File_Read(pszFileName, pImageSize, NULL);
	if (!pIPFFile)
	{
		*pImageSize = 0;
		return NULL;
	}
	
	*pImageType = FLOPPY_IMAGE_TYPE_IPF;
	return pIPFFile;
#endif
}


/*-----------------------------------------------------------------------*/
/**
 * Save .IPF file from memory buffer. Returns true is all OK.
 */
bool IPF_WriteDisk(int Drive, const char *pszFileName, Uint8 *pBuffer, int ImageSize)
{
	/* saving is not supported for IPF files */
	return false;
}




/*
 * Init the FDC and the drives used to handle IPF images
 */
bool	IPF_Init ( void )
{
#ifndef HAVE_CAPSIMAGE
	return true;

#else
	int	i;
	struct CapsVersionInfo	caps_vi;

	fprintf ( stderr , "IPF : IPF_Init\n" );

	if ( CAPSInit() != imgeOk )
        {
		fprintf ( stderr , "IPF : Could not initialize the capsimage library\n" );
		return false;
        }

	if ( CAPSGetVersionInfo ( &caps_vi , 0 ) != imgeOk )
        {
		fprintf ( stderr , "IPF : CAPSVersionInfo failed\n" );
		return false;
        }
	fprintf ( stderr , "IPF : capsimage library version release=%d revision=%d\n" , (int)caps_vi.release , (int)caps_vi.revision );
	IPF_State.CapsLibRelease = caps_vi.release;
	IPF_State.CapsLibRevision = caps_vi.revision;

	/* Default values for each physical drive */
	memset ( IPF_State.Drive , 0 , sizeof ( IPF_State.Drive ) );
	for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ )
	{
		IPF_State.Drive[ i ].type = sizeof ( struct CapsDrive );
		IPF_State.Drive[ i ].rpm = CAPSDRIVE_35DD_RPM;
		IPF_State.Drive[ i ].maxtrack = CAPSDRIVE_35DD_HST;

		IPF_State.Rev_Track[ i ] = -1;
		IPF_State.Rev_Side[ i ] = -1;

		IPF_State.DriveEnabled[ i ] = true;
		IPF_State.DoubleSided[ i ] = true;
	}

	/* Init FDC with 2 physical drives */
	memset ( &IPF_State.Fdc , 0 , sizeof ( IPF_State.Fdc ) );
	IPF_State.Fdc.type = sizeof( struct CapsFdc );
	IPF_State.Fdc.model = cfdcmWD1772;
	IPF_State.Fdc.drive = IPF_State.Drive;
	IPF_State.Fdc.drivecnt = MAX_FLOPPYDRIVES;

	if ( CAPSFdcInit ( &IPF_State.Fdc ) != imgeOk)
	{
		fprintf ( stderr , "IPF : CAPSFdcInit failed\n" );
		return false;
	}

	/* 2 drives by default */
	IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES;
	/* Update drives' state in case we have some drives ON or OFF in config or parameters */
	IPF_Drive_Update_Enable_Side ();

	/* FDC clock */
	IPF_State.Fdc.clockfrq = 8000000;

	/* Set callback functions */
	IPF_State.Fdc.cbirq = IPF_CallBack_Irq;
	IPF_State.Fdc.cbdrq = IPF_CallBack_Drq;
	IPF_State.Fdc.cbtrk = IPF_CallBack_Trk;

	CAPSFdcReset ( &IPF_State.Fdc );

	return true;
#endif
}




/*
 * Exit
 */
void	IPF_Exit ( void )
{
#ifndef HAVE_CAPSIMAGE
#else
	CAPSExit();
#endif
}




/*
 * Init the ressources to handle the IPF image inserted into a drive (0=A: 1=B:)
 */
bool	IPF_Insert ( int Drive , Uint8 *pImageBuffer , long ImageSize )
{
#ifndef HAVE_CAPSIMAGE
	return false;

#else
	CapsLong	ImageId;
#if CAPS_LIB_REL_REV >= 501
	CapsLong	ImageType;
#endif

	ImageId = CAPSAddImage();
	if ( ImageId < 0 )
	{
		fprintf ( stderr , "IPF : error CAPSAddImage\n" );
		return false;
	}

#if CAPS_LIB_REL_REV >= 501
	ImageType = CAPSGetImageTypeMemory ( pImageBuffer , ImageSize );
	if ( ImageType == citError )
	{
		fprintf ( stderr , "IPF : error CAPSGetImageTypeMemory\n" );
		return false;
	}
	else if ( ImageType == citUnknown )
	{
		fprintf ( stderr , "IPF : unknown image type\n" );
		return false;
	}

	fprintf ( stderr , "IPF : IPF_Insert drive=%d buf=%p size=%ld imageid=%d type=" , Drive , pImageBuffer , ImageSize , ImageId );
	switch ( ImageType ) {
		case citIPF:		fprintf ( stderr , "IPF\n" ); break;
		case citCTRaw:		fprintf ( stderr , "CT RAW\n" ); break;
		case citKFStream:	fprintf ( stderr , "KF STREAM\n" ) ; break;
		case citDraft:		fprintf ( stderr , "DRAFT\n" ) ; break;
		default :		fprintf ( stderr , "NOT SUPPORTED\n" );
					return false;
	}
#endif

	if ( CAPSLockImageMemory ( ImageId , pImageBuffer , (CapsULong)ImageSize , DI_LOCK_MEMREF ) == imgeOk )
	{
		struct CapsImageInfo cii;
		int		i;

		/* Print some debug infos */
		if ( CAPSGetImageInfo ( &cii , ImageId ) == imgeOk )
		{
			printf("Type: %d\n", (int)cii.type);
			printf("Release: %d\n", (int)cii.release);
			printf("Revision: %d\n", (int)cii.revision);
			printf("Min Cylinder: %d\n", (int)cii.mincylinder);
			printf("Max Cylinder: %d\n", (int)cii.maxcylinder);
			printf("Min Head: %d\n", (int)cii.minhead);
			printf("Max Head: %d\n", (int)cii.maxhead);
			printf("Creation Date: %04d/%02d/%02d %02d:%02d:%02d.%03d\n", (int)cii.crdt.year, (int)cii.crdt.month, (int)cii.crdt.day, (int)cii.crdt.hour, (int)cii.crdt.min, (int)cii.crdt.sec, (int)cii.crdt.tick);
			printf("Platforms:");
			for (i = 0; i < CAPS_MAXPLATFORM; i++)
				if (cii.platform[i] != ciipNA)
					printf ( " %s" , CAPSGetPlatformName(cii.platform[i]) );
			printf("\n");

			/* Some IPF disks are not correctly supported yet : display a warning */
			if ( (int)cii.release == 3222 ) 				/* Sundog */
                		Log_AlertDlg ( LOG_INFO , "'Sundog' is not correctly supported yet, it requires write access." );
			else if ( (int)cii.release == 3058 ) 				/* Lethal Xcess */
                		Log_AlertDlg ( LOG_INFO , "'Lethal Xcess' is not correctly supported yet, protection will fail" );
		}
	}
	else
	{
		CAPSRemImage ( ImageId ) ;
		return false;
	}

	if ( CAPSLoadImage ( ImageId , DI_LOCK_DENALT | DI_LOCK_DENVAR | DI_LOCK_UPDATEFD ) != imgeOk )
	{
		fprintf ( stderr , "IPF : error CAPSLoadImage\n" );
		CAPSUnlockImage ( ImageId );
		CAPSRemImage ( ImageId ) ;
		return false;
	}

	
	IPF_State.CapsImage[ Drive ] = ImageId;

	IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_IN;				/* Disk inserted, keep the value for "write protect" */

	CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive );				/* Invalidate previous buffered track data for drive, if any */

	IPF_State.Rev_Track[ Drive ] = -1;						/* Invalidate previous track/side to handle revolution's count */
	IPF_State.Rev_Side[ Drive ] = -1;

	return true;
#endif
}




/*
 * When ejecting a disk, free the ressources associated with an IPF image
 */
bool	IPF_Eject ( int Drive )
{
#ifndef HAVE_CAPSIMAGE
	return false;

#else
	fprintf ( stderr , "IPF : IPF_Eject drive=%d imageid=%d\n" , Drive , IPF_State.CapsImage[ Drive ] );

	CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive );				/* Invalidate previous buffered track data for drive, if any */

	if ( CAPSUnlockImage ( IPF_State.CapsImage[ Drive ] ) < 0 )
	{
		fprintf ( stderr , "IPF : error CAPSUnlockImage\n" );
		return false;
	}

	if ( CAPSRemImage ( IPF_State.CapsImage[ Drive ] ) < 0 )
	{
		fprintf ( stderr , "IPF : error CAPSRemImage\n" );
		return false;
	}

	IPF_State.CapsImage[ Drive ] = -1;

	IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_IN;

	return true;
#endif
}




/*
 * Reset FDC state when a reset signal was received
 */
void IPF_Reset ( void )
{
#ifdef HAVE_CAPSIMAGE
	CAPSFdcReset ( &IPF_State.Fdc );

	IPF_State.FdcClock = CyclesGlobalClockCounter;
#endif
}




/*
 * Callback function used when track is changed.
 * We need to update the track data by calling CAPSLockTrack
 */
#ifdef HAVE_CAPSIMAGE
static void	IPF_CallBack_Trk ( struct CapsFdc *pc , CapsULong State )
{
	int	Drive = State;				/* State is the drive number in that case */
	struct CapsDrive *pd = pc->drive+Drive;		/* Current drive where the track change occurred */
	struct CapsTrackInfoT1 cti;

	cti.type=1;
	if ( CAPSLockTrack ( &cti , IPF_State.CapsImage[ Drive ] , pd->buftrack , pd->bufside ,
			DI_LOCK_DENALT|DI_LOCK_DENVAR|DI_LOCK_UPDATEFD|DI_LOCK_TYPE ) != imgeOk )
		return;

	LOG_TRACE(TRACE_FDC, "fdc ipf callback trk drive=%d buftrack=%d bufside=%d VBL=%d HBL=%d\n" , Drive ,
		  (int)pd->buftrack , (int)pd->bufside , nVBLs , nHBL );

	pd->ttype	= cti.type;
	pd->trackbuf	= cti.trackbuf;
	pd->timebuf	= cti.timebuf;
	pd->tracklen	= cti.tracklen;
	pd->overlap	= cti.overlap;
}
#endif




/*
 * Callback function used when the FDC change the IRQ signal
 */
#ifdef HAVE_CAPSIMAGE
static void	IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State )
{
	LOG_TRACE(TRACE_FDC, "fdc ipf callback irq state=0x%x VBL=%d HBL=%d\n" , (int)State , nVBLs , nHBL );

	if ( State )
		FDC_SetIRQ ( FDC_IRQ_SOURCE_OTHER );	/* IRQ bit was set */
	else
		FDC_ClearIRQ ();			/* IRQ bit was reset */
}
#endif




/*
 * Callback function used when the FDC change the DRQ signal
 * -> copy the byte to/from the DMA's FIFO if it's a read or a write to the disk
 */
#ifdef HAVE_CAPSIMAGE
static void	IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State )
{
	Uint8	Byte;

	if ( State == 0 )
		return;					/* DRQ bit was reset, do nothing */

	if ( FDC_DMA_GetModeControl_R_WR () != 0 )	/* DMA write mode */
	{
		Byte = FDC_DMA_FIFO_Pull ();		/* Get a byte from the DMA FIFO */
		CAPSFdcWrite ( &IPF_State.Fdc , 3 , Byte );	/* Write to FDC's reg 3 */

		LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x write byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL );
	}

	else						/* DMA read mode */
	{
		Byte = CAPSFdcRead ( &IPF_State.Fdc , 3 );	/* Read from FDC's reg 3 */
		FDC_DMA_FIFO_Push ( Byte );		/* Add byte to the DMA FIFO */

		LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x read byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL );
	}
}
#endif



/*
 * This function is used to enable/disable a drive when
 * using the UI or command line parameters
 *
 * NOTE : for now, IPF only supports changing drive 1, drive 0
 * is always ON.
 */
void	IPF_Drive_Set_Enable ( int Drive , bool value )
{
#ifndef HAVE_CAPSIMAGE
	return;

#else
	IPF_State.DriveEnabled[ Drive ] = value;			/* Store the new state */

	IPF_Drive_Update_Enable_Side ();				/* Update IPF's internal state */
#endif
}


/*
 * This function is used to configure a drive as single sided
 * or double sided when using the UI or command line parameters
 */
void	IPF_Drive_Set_DoubleSided ( int Drive , bool value )
{
#ifndef HAVE_CAPSIMAGE
	return;

#else
	IPF_State.DoubleSided[ Drive ] = value;				/* Store the new state */

	IPF_Drive_Update_Enable_Side ();				/* Update IPF's internal state */
#endif
}


/*
 * Update IPF's internal state depending on which drives are ON or OFF
 * and if the drive is single or double sided (for capslib >= 5.1)
 */
#ifdef HAVE_CAPSIMAGE
static void	IPF_Drive_Update_Enable_Side ( void )
{
#if CAPS_LIB_REL_REV >= 501
	int	i;
#endif

	if ( IPF_State.DriveEnabled[ 1 ] )
	        IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES;		/* Should be 2 */
	else
	        IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES - 1;		/* Should be 1 */

#if CAPS_LIB_REL_REV >= 501
	for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ )
	{
		if ( IPF_State.DoubleSided[ i ] )
			IPF_State.Drive[ i ].diskattr &= ~CAPSDRIVE_DA_SS;	/* Double sided */
		else
			IPF_State.Drive[ i ].diskattr |= CAPSDRIVE_DA_SS;	/* Single sided */
	}
#endif
}
#endif


/*
 * Set the drive and the side to be used for the next FDC commands
 * io_porta_old is the previous value, io_porta_new is the new value
 * to take into account.
 * We report a side change only when a drive is selected.
 */
void	IPF_SetDriveSide ( Uint8 io_porta_old , Uint8 io_porta_new )
{
#ifndef HAVE_CAPSIMAGE
	return;

#else
	int	Side;

	LOG_TRACE(TRACE_FDC, "fdc ipf change drive/side io_porta_old=0x%x io_porta_new=0x%x VBL=%d HBL=%d\n" , io_porta_old , io_porta_new , nVBLs , nHBL );

	Side = ( (~io_porta_new) & 0x01 );		/* Side 0 or 1 */

	IPF_State.Fdc.drivenew = -1;			/* By default, don't select any drive */

	/* Check drive 1 first */
	if ( ( io_porta_new & 0x04 ) == 0 )
	{
		IPF_State.Drive[ 1 ].newside = Side;
		IPF_State.Fdc.drivenew = 1;		/* Select drive 1 */
	}

	/* If both drive 0 and drive 1 are enabled, we keep only drive 0 as newdrive */
	if ( ( io_porta_new & 0x02 ) == 0 )
	{
		IPF_State.Drive[ 0 ].newside = Side;
		IPF_State.Fdc.drivenew = 0;		/* Select drive 0 (and un-select drive 1 if set above) */
	}

	IPF_Emulate();					/* Update emulation's state up to this point, then set new drive/side */
#endif
}




/*
 * Write a byte into one of the FDC registers
 * 0=command   1=track   2=sector   3=data
 */
void	IPF_FDC_WriteReg ( Uint8 Reg , Uint8 Byte )
{
#ifndef HAVE_CAPSIMAGE
	return;						/* This should not be reached (an IPF image can't be inserted without capsimage) */

#else
	LOG_TRACE(TRACE_FDC, "fdc ipf write reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL );

	
#if CAPS_LIB_REL_REV >= 501
	/* In the case of CTR images, we must reset the revolution counter */
	/* when a command access data on disk and track/side changed since last access */
	if ( Reg == 0 )
	{
		int	Type;
		int	Drive;

		Type = FDC_GetCmdType ( Byte );
		if ( ( Type == 2 ) || ( Type == 3 ) )
		{
			Drive = IPF_State.Fdc.driveact;
			if ( ( Drive >= 0 )
			  && ( ( IPF_State.Drive[ Drive ].side != IPF_State.Rev_Side[ Drive ] ) || ( IPF_State.Drive[ Drive ].track != IPF_State.Rev_Track[ Drive ] ) ) )
			{
				IPF_State.Rev_Side[ Drive ] = IPF_State.Drive[ Drive ].side;
				IPF_State.Rev_Track[ Drive ] = IPF_State.Drive[ Drive ].track;
				CAPSSetRevolution ( IPF_State.CapsImage[ Drive ] , 0 );
			}
		}
	}
#endif

	IPF_Emulate();					/* Update emulation's state up to this point */

	CAPSFdcWrite ( &IPF_State.Fdc , Reg , Byte );
#endif
}




/*
 * Read the content of one of the FDC registers
 * 0=status   1=track   2=sector   3=data
 */
Uint8	IPF_FDC_ReadReg ( Uint8 Reg )
{
#ifndef HAVE_CAPSIMAGE
	return 0;					/* This should not be reached (an IPF image can't be inserted without capsimage) */
#else
	Uint8	Byte;

	IPF_Emulate();					/* Update emulation's state up to this point */

	Byte = CAPSFdcRead ( &IPF_State.Fdc , Reg );
	LOG_TRACE(TRACE_FDC, "fdc ipf read reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL );

	return Byte;
#endif
}




/*
 * Return the content of some registers to display them in the statusbar
 * We should not call IPF_Emulate() or similar, reading should not change emulation's state
 */
void	IPF_FDC_StatusBar ( Uint8 *pCommand , Uint8 *pHead , Uint8 *pTrack , Uint8 *pSector , Uint8 *pSide )
{
#ifndef HAVE_CAPSIMAGE
	return;						/* This should not be reached (an IPF image can't be inserted without capsimage) */
#else
	int	Drive;

	Drive = IPF_State.Fdc.driveact;
	if ( Drive < 0 )				/* If no drive enabled, use drive O for Head/Side */
		Drive = 0;

	/* We read directly in the structures, to be sure we don't change emulation's state */
	*pCommand	= IPF_State.Fdc.r_command;
	*pHead		= IPF_State.Drive[ Drive ].track;
	*pTrack 	= IPF_State.Fdc.r_track;
	*pSector	= IPF_State.Fdc.r_sector;
	*pSide		= IPF_State.Drive[ Drive ].side;
#endif
}




/*
 * Run the FDC emulation during NbCycles cycles (relative to the 8MHz FDC's clock)
 */
void	IPF_Emulate ( void )
{
#ifndef HAVE_CAPSIMAGE
	return;

#else
	int	NbCycles;
	int	Drive;

	NbCycles = CyclesGlobalClockCounter - IPF_State.FdcClock;	/* Number of cycles since last emulation */
	if ( NbCycles < 0 )
		NbCycles = 0;						/* We should call CAPSFdcEmulate even when NbCycles=0 */

//	LOG_TRACE(TRACE_FDC, "fdc ipf emulate cycles=%d VBL=%d HBL=%d clock=%lld\n" , NbCycles , nVBLs , nHBL , CyclesGlobalClockCounter );

	/* Update Write Protect status for each drive */
	for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ )
		if ( Floppy_IsWriteProtected ( Drive ) )
			IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_WP;		/* Disk write protected */
		else
			IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_WP;		/* Disk is not write protected */


	CAPSFdcEmulate ( &IPF_State.Fdc , NbCycles );			/* Process at max NbCycles */
	IPF_State.FdcClock += IPF_State.Fdc.clockact;			/* clockact can be < NbCycle in some cases */

	/* Update UI's LEDs depending on Status Register */
	FDC_Drive_Set_BusyLed ( (IPF_State.Fdc.r_st0 & ~IPF_State.Fdc.r_stm) | (IPF_State.Fdc.r_st1 & IPF_State.Fdc.r_stm) );
#endif
}



