#include "config.h"

#include <string.h>
#include <stdlib.h>

#include "daedef.h"
#include "ssib.h"
#include "pib.h"
#include "util.h"
#include "page.h"
#include "fucb.h"
#include "stapi.h"
#include "dirapi.h"
#include "logapi.h"
#include "log.h"

#include "fileapi.h"
#include "dbapi.h"
DeclAssertFile;					/* Declare file name for assert macros */


/* variables used in redo only */
BYTE		*pbNext;		// redo only - location of next buffer entry
BYTE		*pbRead; 		// redo only - location of next rec to flush
INT			isecRead;		/* redo only - next disk to read. */
BOOL		fOnLastSec;		/* redo only - on last sector of cur lg file */

LGPOS		lgposLastRec;	/* mark for end of rec */


VOID GetLgposOfPbNext(LGPOS *plgpos)
	{
	char *pb = PbSecAligned(pbNext);
	int ib = pbNext - pb;
	int isec;

	if (pb > pbRead)
		isec = (pbRead + csecLGBuf * cbSec - pb) / cbSec;
	else
		isec = (pbRead - pb) / cbSec;
	isec = isecRead - isec;

	plgpos->isec = isec;
	plgpos->ib = ib;
	plgpos->usGeneration = plgfilehdrGlobal->lgposLastMS.usGeneration;
	}


#ifdef DEBUG

/* calculate the lgpos of the LR */
VOID PrintLgposReadLR ( VOID )
	{
	LGPOS lgpos;

	GetLgposOfPbNext(&lgpos);
	PrintF2("\n%2u,%3u,%3u",
			plgfilehdrGlobal->lgposLastMS.usGeneration,
			lgpos.isec, lgpos.ib);
	}

#endif


/*
 *  Read first record pointed by plgposFirst.
 *  Initialize isecRead, pbRead, and pbNext.
 */
ERR ErrLGLocateFirstRedoLogRec(
	LGPOS *plgposPrevMS,
	LGPOS *plgposFirst,				/* lgpos for first redo record */
	BYTE **ppbLR)
	{
	ERR err;
	char *pbNextT;

	/*  read first sector, actually we read 2 pages such that we can
	 *  guarrantee that when calculate the length of the record, the
	 *  fixed part of the record is read in.
	 */
	if (pbLGBufMin + 3 * cbSec > pbLGBufMax)
		return JET_errLogBufferTooSmall;

	if ( plgposPrevMS && plgposPrevMS->isec != 0 )
		{
		CallR(ErrLGRead(hfLog, plgposPrevMS->isec, pbLGBufMin, 1))
		CallS(ErrLGRead(hfLog, plgposPrevMS->isec + 1, pbLGBufMin + cbSec, 1));
		isecRead = plgposPrevMS->isec + 1;	/* sector next to read. */
		pbRead = pbLGBufMin + cbSec;
		pbNext = pbLGBufMin + plgposPrevMS->ib;
		pbLastMSFlush = pbNext;
		lgposLastMSFlush = *plgposPrevMS;
		}
	else
		{
		CallR(ErrLGRead(hfLog, plgposFirst->isec, pbLGBufMin, 1))
		CallS(ErrLGRead(hfLog, plgposFirst->isec + 1, pbLGBufMin + cbSec, 1));
		isecRead = plgposFirst->isec + 1;	/* sector next to read. */
		pbRead = pbLGBufMin + cbSec;
		pbNext = pbLGBufMin + plgposFirst->ib;

		/* initialize global variables */
		if ( lgposLastMSFlush.isec == plgposFirst->isec )
			{
			pbLastMSFlush = pbLGBufMin + lgposLastMSFlush.ib;
			}
		else
			{
			pbLastMSFlush = 0;
			memset( &lgposLastMSFlush, 0, sizeof(lgposLastMSFlush) );
			}
		}

	/*  continue reading more sectors till next MS log record or
	 *  a Fill log record is reached.
	 */
	pbNextT = pbNext;
	while (*(LRTYP*)pbNextT != lrtypFill)
		{
		char *pbAligned;

		/* goto next record */
		pbNextT += (ULONG) CbLGSizeOfRec((LR*)pbNextT);

		if (pbNextT > pbLGBufMax)
			return JET_errLogFileCorrupt;

		pbAligned = PbSecAligned(pbNextT);
		if (pbAligned >= pbRead)
			{
			int csecToRead;

			if ( plgposFirst->isec <= 2 &&
				 plgfilehdrGlobal->lgposLastMS.ib == 0 &&
				 plgfilehdrGlobal->lgposLastMS.isec <= 2 )
				{
				/* a special case where we tried to scan through this page and
				 * realize that no MS or fill record is read. Should not read
				 * beyond this page. Do not continue reading.
				 */
				break;
				}

			/*  physically read one more page to guarrantee that
			 *  the fix part of the log record is read in the
			 *  memory.
			 */
			csecToRead = (pbAligned - pbRead) / cbSec + 1;
			if (pbRead + csecToRead * cbSec > pbLGBufMax)
				return JET_errLogBufferTooSmall;

			CallR( ErrLGRead(hfLog, isecRead, pbRead, csecToRead ))
			isecRead += csecToRead;
			pbRead += csecToRead * cbSec;

			CallS( ErrLGRead(hfLog, isecRead, pbRead, 1 ));
			}

		/* reach next MS, break */
		if ( *(LRTYP*)pbNextT == lrtypMS )
			break;
		}

	if ( plgposPrevMS && plgposPrevMS->isec != 0 )
		{
		pbNext = pbLGBufMin + cbSec * ( plgposFirst->isec - plgposPrevMS->isec );		pbNext += plgposFirst->ib;
		}

	/* set up returned value. */
	*ppbLR = pbNext;

	return JET_errSuccess;
	}


/*
 *  Set pbNext to next available log record.
 */
ERR ErrLGGetNextRec( BYTE **ppbLR )
	{
	ERR		err;
	int		cb;
	char	*pb;
	LR		*plr;
	LGPOS	lgposT;
	BYTE	*pbNextOld;

	/* caller should have taken care of the Fill case. */
	Assert (*(LRTYP *)pbNext != lrtypFill);

	/* move to next log record. */
	pbNextOld = pbNext;
	pbNext += (ULONG) CbLGSizeOfRec((LR*)pbNext);

	/* check if next log record is out of buffer range. */

	if (pbNext == pbRead)
		{
		/* the record ends on the sector boundary */
		pbNext = pbLGBufMin;
		pbRead = pbNext;

		/* read in one more page. */
		if (pbLGBufMin + cbSec > pbLGBufMax)
			return JET_errLogBufferTooSmall;

		CallR(ErrLGRead(hfLog, isecRead, pbLGBufMin, 1))
		isecRead += 1;
		pbRead = pbLGBufMin + cbSec;
		}

	if (pbNext > pbRead)
		{
		pbNext = pbNextOld;
		return errLGNoMoreRecords;
		}

	GetLgposOfPbNext(&lgposT);
	if ( CmpLgpos( &lgposT, &lgposLastRec ) > 0 )
		{
		pbNext = pbNextOld;
		return errLGNoMoreRecords;
		}

	plr = (LR *) pbNext;

	if (plr->lrtyp == lrtypFill)
		{
		/* end of current log file. */
		goto Done;
		}
	else if (plr->lrtyp == lrtypMS)
		{
		LRMS *plrms = (LRMS *)plr;

#ifdef DEBUG
		// same as TraceRedo() in redo.c
		if (fDBGTraceRedo)
			{
			PrintLgposReadLR();
			ShowLR(plr);
			}
#endif

		/*  check if this MS was done completely by reading
		 *  the whole sector in. If it fails, then the sector
		 *  is the last sector available in the log file.
		 */
		fOnLastSec = ( plrms->isecForwardLink == 0 ||
						(  lgposLastMSFlush.isec != 0 &&
							(
							plrms->isecBackLink != lgposLastMSFlush.isec ||
							plrms->ibBackLink != lgposLastMSFlush.ib
					 )	)	);

		/*  The MS were read in successfully, reset LastMSFlush
		 *  so that when switching from read mode to write mode,
		 *  we will have a correct LastMSFlush pointers.
		 */
		pbLastMSFlush = (CHAR *) plrms;
		lgposLastMSFlush = lgposT;

		if ( !fOnLastSec )
			{
			if (isecRead <= plrms->isecForwardLink)
				{
				int csecToRead = plrms->isecForwardLink - isecRead + 1;

				Assert( csecToRead > 0 );
				
				pb = PbSecAligned(pbNext);
				cb = pbRead - pb;
				if (csecToRead + isecRead > csecLGBuf)
					{
					/* the multiple sector will not fit in rest of */
					/* the available buffer. Shift the buffer. */
					memmove(pbLGBufMin, pb, cb);
					
					pbRead = pbLGBufMin + cb;				/* pbRead */
					pbNext = pbNext - pb + pbLGBufMin;		/* pbNext */
					pbLastMSFlush = (CHAR *) plrms - pb + pbLGBufMin;
					}

				/* bring in multiple sectors */
				if (pbRead + csecToRead * cbSec > pbLGBufMax)
					{
					BYTE *pbLGBufMinT = pbLGBufMin;
					CallR( ErrLGInitLogBuffers( lLogBuffers ) );
					memcpy( pbLGBufMin, pbLGBufMinT, cb );
					
					pbRead = pbRead - pbLGBufMinT + pbLGBufMin;
					pbNext = pbNext - pbLGBufMinT + pbLGBufMin;
					pbLastMSFlush = pbLastMSFlush - pbLGBufMinT + pbLGBufMin;
					
					SysFree( pbLGBufMinT );
					}

				err = ErrLGRead(hfLog, isecRead, pbRead, csecToRead);
				if (err < 0)
					fOnLastSec = fTrue;
				else
					{
					/*	Get pb of new lrms
					/*/
					CHAR *pbLrmsNew = pbRead + ( csecToRead - 1 ) * cbSec + ((LRMS *)pbLastMSFlush)->ibForwardLink;
					LRMS *plrmsNew = (LRMS *) pbLrmsNew;

					/*	check if the check sum is correct
					/*/
					if ( plrmsNew->ulCheckSum != UlLGMSCheckSum( pbLrmsNew ) )
						fOnLastSec = fTrue;
					else
						{
						isecRead += csecToRead;
						pbRead += csecToRead * cbSec;
						}
					}
				}
			}

		/* skip MS and continue to read next record. */
		pbNextOld = pbNext;
		pbNext += CbLGSizeOfRec((LR*)pbNext);

		/* nomal end of generation */
		if ( fOnLastSec && plgfilehdrGlobal->fEndWithMS )
			return errLGNoMoreRecords;
		
		/* or abnormal end of log file */
		if ( fOnLastSec && PbSecAligned(pbNextOld) != PbSecAligned(pbNext))
		    {
			pbNext = pbNextOld;
			return errLGNoMoreRecords;
			}
		}
Done:
	*ppbLR = pbNext;
	return JET_errSuccess;
	}


//+------------------------------------------------------------------------
//
//	CbLGSizeOfRec
//	=======================================================================
//
//	ERR CbLGSizeOfRec( plgrec )
//
//	Returns the length of a log record.
//
//	PARAMETER	plgrec	pointer to log record
//
//	RETURNS		size of log record in bytes
//
//-------------------------------------------------------------------------
INT mplrtypcb[ lrtypMax ] = {
	/* 	0 	NOP      */			sizeof( LRTYP ),
	/* 	1 	Start    */			0,
	/* 	2 	Quit     */			0,
	/* 	3 	MS       */			sizeof( LRMS ),
	/* 	4 	Fill     */			sizeof( LRTYP ),

	/* 	5 	Begin    */			sizeof( LRBEGIN ),
	/*	6 	Commit   */			sizeof( LRCOMMIT ),
	/*	7 	Abort    */			sizeof( LRABORT ),

	/*	8 	CreateDB */			0,
	/* 	9 	AttachDB */			0,
	/*	10	DetachDB */			0,

	/*	11	InitFDP  */			sizeof( LRINITFDPPAGE ),

	/*	12	Split    */			0,
	/*	13	EmptyPage*/			sizeof( LREMPTYPAGE ),
	/*	14	PageMerge*/			0,

	/* 	15	InsertND */			0,
	/* 	16	InsertIL */			0,
	/* 	17	Replace  */			0,
	/* 	18	ReplaceC */			0,
	/* 	19	FDelete  */			sizeof( LRFLAGDELETE ),
	/*	20	LockRec	 */			0,
	
	/*  21  UpdtHdr  */			sizeof( LRUPDATEHEADER ),
	/* 	22	InsertI  */			sizeof( LRINSERTITEM ),
	/* 	23	InsertIS */			0,
	/* 	24	FDeleteI */			sizeof( LRFLAGITEM ),
	/* 	25	FInsertI */			sizeof( LRFLAGITEM ),
	/*	26	DeleteI  */			sizeof( LRDELETEITEM ),
	/*	27	SplitItm */			sizeof( LRSPLITITEMLISTNODE ),

	/*	28	Delta	 */			sizeof( LRDELTA ),

	/*	29	DelNode  */			sizeof( LRDELETE ),
	/*	30	ELC      */			sizeof( LRELC ),

	/*	31	FreeSpace*/			sizeof( LRFREESPACE ),
	/*	32	Undo     */			sizeof( LRUNDO ),
	/*  33  RcvrUndo1*/			0,
	/*  34  RcvrQuit1*/			0,
	/*  35  RcvrUndo2*/			0,
	/*  36  RcvrQuit2*/			0,
	/*  37  FullBkUp */			0,
	/*  38  IncBkUp  */			0,
	/*  39  CheckPage  */		sizeof( LRCHECKPAGE ),
	};


INT CbLGSizeOfRec( LR *plr )
	{
	INT		cb;

	Assert( plr->lrtyp < lrtypMax );

	if ( ( cb = mplrtypcb[plr->lrtyp] ) != 0 )
		return cb;

	switch ( plr->lrtyp )
		{
	case lrtypStart:
		return sizeof(LRSTART);

	case lrtypQuit:
	case lrtypRecoveryQuit1:
	case lrtypRecoveryQuit2:
		return sizeof(LRQUITREC);

	case lrtypRecoveryUndo1:
	case lrtypRecoveryUndo2:
	case lrtypFullBackup:
	case lrtypIncBackup:
		{
		LRLOGRESTORE *plrlogrestore = (LRLOGRESTORE *) plr;
		return sizeof(LRLOGRESTORE) + plrlogrestore->cbPath;
		}

	case lrtypCreateDB:
		{
		LRCREATEDB *plrcreatedb = (LRCREATEDB *)plr;
		Assert( plrcreatedb->cb != 0 );
		return sizeof(LRCREATEDB) + plrcreatedb->cb;
		}
	case lrtypAttachDB:
		{
		LRATTACHDB *plrattachdb = (LRATTACHDB *)plr;
		Assert( plrattachdb->cb != 0 );
		return sizeof(LRATTACHDB) + plrattachdb->cb;
		}
	case lrtypDetachDB:
		{
		LRDETACHDB *plrdetachdb = (LRDETACHDB *)plr;
		Assert( plrdetachdb->cb != 0 );
		return sizeof( LRDETACHDB ) + plrdetachdb->cb;
		}
	case lrtypSplit:
		{
		LRSPLIT *plrsplit = (LRSPLIT *) plr;
		return sizeof( LRSPLIT ) + plrsplit->cbKey + plrsplit->cbKeyMac +
				sizeof( BKLNK ) * plrsplit->cbklnk;
		}
	case lrtypMerge:
		{
		LRMERGE *plrmerge = (LRMERGE *) plr;
		return sizeof( LRMERGE ) + sizeof( BKLNK ) * plrmerge->cbklnk;
		}
	case lrtypInsertNode:
	case lrtypInsertItemList:
		{
		LRINSERTNODE *plrinsertnode = (LRINSERTNODE *) plr;
		return	sizeof(LRINSERTNODE) +
				plrinsertnode->cbKey + plrinsertnode->cbData;
		}
	case lrtypInsertItems:
		{
		LRINSERTITEMS *plrinsertitems = (LRINSERTITEMS *) plr;
		return	sizeof(LRINSERTITEMS) +
				plrinsertitems->citem * sizeof(ITEM);
		}
	case lrtypReplace:
	case lrtypReplaceC:
		{
		LRREPLACE *plrreplace = (LRREPLACE *) plr;
		return sizeof(LRREPLACE) + plrreplace->cb +
			( plrreplace->fOld ? plrreplace->cbOldData : 0 );
		}
	case lrtypLockRec:
		{
		LRLOCKREC *plrlockrec = (LRLOCKREC *) plr;
		return sizeof(LRLOCKREC) + plrlockrec->cbOldData;
		}
	default:
		Assert( fFalse );
		}
	}

