/* Copyright (C) 2003 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

//----------------------------------------------------------------
// REDOLOGFILEREADER
// Reads a redo log file and checks it for errors and/or prints
// the file in a human readable format.
//
// Usage: redoLogFileReader <file> [-noprint] [-nocheck] 
//        [-mbyte <0-15>] [-mbyteHeaders] [-pageHeaders] 
//	
//----------------------------------------------------------------


#include <ndb_global.h>

#include "records.hpp"

#define RETURN_ERROR 1
#define RETURN_OK 0

#define FROM_BEGINNING 0

void usage(const char * prg);
Uint32 readRecordOverPageBoundary (Uint32 *, Uint32 , Uint32 , Uint32);
Uint32 readFromFile(FILE * f, Uint32 *toPtr, Uint32 sizeInWords);
void readArguments(int argc, const char** argv);
void doExit();

FILE * f= 0;
char fileName[256];
bool thePrintFlag = true;
bool theCheckFlag = true;
bool onlyPageHeaders = false;
bool onlyMbyteHeaders = false;
bool onlyFileDesc = false;
bool firstLap = true;
Uint32 startAtMbyte = 0;
Uint32 startAtPage = 0;
Uint32 startAtPageIndex = 0;
Uint32 *redoLogPage;

NDB_COMMAND(redoLogFileReader,  "redoLogFileReader", "redoLogFileReader", "Read a redo log file", 16384) { 
  Uint32 pageIndex = 0;
  Uint32 oldPageIndex = 0;
  Uint32 recordType = 1234567890;

  PageHeader *thePageHeader;
  CompletedGCIRecord *cGCIrecord;
  PrepareOperationRecord *poRecord;
  NextLogRecord *nlRecord;
  FileDescriptor *fdRecord;
  CommitTransactionRecord *ctRecord;
  InvalidCommitTransactionRecord *ictRecord;
  NextMbyteRecord *nmRecord;
  AbortTransactionRecord *atRecord;
  
  readArguments(argc, argv);
 
  f = fopen(fileName, "rb");
  if(!f){
    perror("Error: open file");
    exit(RETURN_ERROR);
  }
  
  Uint32 tmpFileOffset = startAtMbyte * PAGESIZE * NO_PAGES_IN_MBYTE * sizeof(Uint32);
  if (fseek(f, tmpFileOffset, FROM_BEGINNING)) {
    perror("Error: Move in file");
    exit(RETURN_ERROR);
  }

  redoLogPage = new Uint32[PAGESIZE*NO_PAGES_IN_MBYTE];

  // Loop for every mbyte.
  for (Uint32 j = startAtMbyte; j < NO_MBYTE_IN_FILE; j++) {
    readFromFile(f, redoLogPage, PAGESIZE*NO_PAGES_IN_MBYTE);
 
    if (firstLap) {
      pageIndex = startAtPageIndex;
      firstLap = false;
    } else
      pageIndex = 0;

    // Loop for every page.
    for (int i = startAtPage; i < NO_PAGES_IN_MBYTE; i++) {

      if (pageIndex == 0) {
	thePageHeader = (PageHeader *) &redoLogPage[i*PAGESIZE];
	// Print out mbyte number, page number and page index.
	ndbout << j << ":" << i << ":" << pageIndex << endl 
	       << " " << j*32 + i << ":" << pageIndex << " ";
	if (thePrintFlag) ndbout << (*thePageHeader);
	if (theCheckFlag) {
	  if(!thePageHeader->check()) {
	    doExit();
	  }

	  Uint32 checkSum = 37;
	  for (int ps = 1; ps < PAGESIZE; ps++)
	    checkSum = redoLogPage[i*PAGESIZE+ps] ^ checkSum;

	  if (checkSum != redoLogPage[i*PAGESIZE]){
	    ndbout << "WRONG CHECKSUM: checksum = " << redoLogPage[i*PAGESIZE]
		   << " expected = " << checkSum << endl;
	    doExit();
	  }
	  else
	    ndbout << "expected checksum: " << checkSum << endl;

	}
	pageIndex += thePageHeader->getLogRecordSize();
      }

      if (onlyMbyteHeaders) {
	// Show only the first page header in every mbyte of the file.
	break;
      }

      if (onlyPageHeaders) {
	// Show only page headers. Continue with the next page in this for loop.
	pageIndex = 0;
	continue;
      }

      do {
	// Print out mbyte number, page number and page index.
	ndbout << j << ":" << i << ":" << pageIndex << endl 
	       << " " << j*32 + i << ":" << pageIndex << " ";
	recordType = redoLogPage[i*PAGESIZE + pageIndex];
	switch(recordType) {
	case ZFD_TYPE:
	  fdRecord = (FileDescriptor *) &redoLogPage[i*PAGESIZE + pageIndex];
	  if (thePrintFlag) ndbout << (*fdRecord);
	  if (theCheckFlag) {
	    if(!fdRecord->check()) {
	      doExit();
	    }
	  }
	  if (onlyFileDesc) {
	    delete [] redoLogPage;
	    exit(RETURN_OK);
	  }
	  pageIndex += fdRecord->getLogRecordSize();
	  break;
    
	case ZNEXT_LOG_RECORD_TYPE:
	  nlRecord = (NextLogRecord *) (&redoLogPage[i*PAGESIZE] + pageIndex);
	  pageIndex += nlRecord->getLogRecordSize(pageIndex);
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*nlRecord);
	    if (theCheckFlag) {
	      if(!nlRecord->check()) {
		doExit();
	      }
	    }
	  }
	  break;

	case ZCOMPLETED_GCI_TYPE:
	  cGCIrecord = (CompletedGCIRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  pageIndex += cGCIrecord->getLogRecordSize();
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*cGCIrecord);
	    if (theCheckFlag) {
	      if(!cGCIrecord->check()) {
		doExit();
	      }
	    }
	  }
	  break;

	case ZPREP_OP_TYPE:
	  poRecord = (PrepareOperationRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  pageIndex += poRecord->getLogRecordSize();
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*poRecord);
	    if (theCheckFlag) {
	      if(!poRecord->check()) {
		doExit();
	      }
	    }
	  }
	  else {
	    oldPageIndex = pageIndex - poRecord->getLogRecordSize();
	  }
	  break;

	case ZCOMMIT_TYPE:
	  ctRecord = (CommitTransactionRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  pageIndex += ctRecord->getLogRecordSize();
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*ctRecord);
	    if (theCheckFlag) {
	      if(!ctRecord->check()) {
		doExit();
	      }
	    }
	  }
	  else {
	    oldPageIndex = pageIndex - ctRecord->getLogRecordSize();
	  }	
	  break;
      
	case ZINVALID_COMMIT_TYPE:
	  ictRecord = (InvalidCommitTransactionRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  pageIndex += ictRecord->getLogRecordSize();
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*ictRecord);
	    if (theCheckFlag) {
	      if(!ictRecord->check()) {
		doExit();
	      }
	    }
	  }
	  else {
	    oldPageIndex = pageIndex - ictRecord->getLogRecordSize();
	  }	
	  break;

	case ZNEXT_MBYTE_TYPE:
	  nmRecord = (NextMbyteRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  if (thePrintFlag) ndbout << (*nmRecord);
	  i = NO_PAGES_IN_MBYTE;
	  break;
	
	case ZABORT_TYPE:
	  atRecord = (AbortTransactionRecord *) &redoLogPage[i*PAGESIZE + pageIndex];
	  pageIndex += atRecord->getLogRecordSize();
	  if (pageIndex <= PAGESIZE) {
	    if (thePrintFlag) ndbout << (*atRecord);
	    if (theCheckFlag) {
	      if(!atRecord->check()) {
		doExit();
	      }
	    }
	  }
	  break;

	case ZNEW_PREP_OP_TYPE: 
	case ZFRAG_SPLIT_TYPE:
	  ndbout << endl << "Record type = " << recordType << " not implemented." << endl;
	  doExit();

	default:
	  ndbout << " ------ERROR: UNKNOWN RECORD TYPE------" << endl;

	  // Print out remaining data in this page
	  for (int j = pageIndex; j < PAGESIZE; j++){
	    Uint32 unknown = redoLogPage[i*PAGESIZE + j];

	    ndbout_c("%-30d%-12u%-12x", j, unknown, unknown);
	  }
	  
	  doExit();
	}
      } while(pageIndex < PAGESIZE && i < NO_PAGES_IN_MBYTE);

      if (pageIndex > PAGESIZE) {
	// The last record overlapped page boundary. Must redo that record.
	pageIndex = readRecordOverPageBoundary(&redoLogPage[i*PAGESIZE], 
				 pageIndex, oldPageIndex, recordType);
      } else {
	pageIndex = 0;
      }
      ndbout << endl;
    }//for  
    ndbout << endl;
    if (startAtMbyte != 0) {
      break;
    }
  }//for
  fclose(f);
  delete [] redoLogPage;
  exit(RETURN_OK);
}

//----------------------------------------------------------------
// 
//----------------------------------------------------------------

Uint32 readFromFile(FILE * f, Uint32 *toPtr, Uint32 sizeInWords) {
  Uint32 noOfReadWords;
  if ( !(noOfReadWords = fread(toPtr, sizeof(Uint32), sizeInWords, f)) ) {
    ndbout << "Error reading file" << endl;
    doExit();
  } 

  return noOfReadWords;
}


//----------------------------------------------------------------
// 
//----------------------------------------------------------------

Uint32  readRecordOverPageBoundary(Uint32 *pagePtr, Uint32 pageIndex, Uint32 oldPageIndex, Uint32 recordType) {
  Uint32 pageHeader[PAGEHEADERSIZE];
  Uint32 tmpPages[PAGESIZE*10];
  PageHeader *thePageHeader;
  Uint32 recordSize = 0;

  PrepareOperationRecord *poRecord;
  CommitTransactionRecord *ctRecord;
  InvalidCommitTransactionRecord *ictRecord;

  memcpy(pageHeader, pagePtr + PAGESIZE, PAGEHEADERSIZE*sizeof(Uint32));
  memcpy(tmpPages, pagePtr + oldPageIndex, (PAGESIZE - oldPageIndex)*sizeof(Uint32));
  memcpy(tmpPages + PAGESIZE - oldPageIndex , 
	 (pagePtr + PAGESIZE + PAGEHEADERSIZE), 
	 (PAGESIZE - PAGEHEADERSIZE)*sizeof(Uint32));

  switch(recordType) {
  case ZPREP_OP_TYPE:
    poRecord = (PrepareOperationRecord *) tmpPages;
    recordSize = poRecord->getLogRecordSize();
    if (recordSize < (PAGESIZE - PAGEHEADERSIZE)) {
      if (theCheckFlag) {
	if(!poRecord->check()) {
	  doExit();
	}
      } 
      if (thePrintFlag) ndbout << (*poRecord);
    } else {
      ndbout << "Error: Record greater than a Page" << endl;
    }
    break;

  case ZCOMMIT_TYPE:
    ctRecord = (CommitTransactionRecord *) tmpPages;
    recordSize = ctRecord->getLogRecordSize();
    if (recordSize < (PAGESIZE - PAGEHEADERSIZE)) {
      if (theCheckFlag) {
	if(!ctRecord->check()) {
	  doExit();
	}
      }
      if (thePrintFlag) ndbout << (*ctRecord);
    } else {
      ndbout << endl << "Error: Record greater than a Page" << endl;
    }
    break;

  case ZINVALID_COMMIT_TYPE:
   ictRecord = (InvalidCommitTransactionRecord *) tmpPages;
    recordSize = ictRecord->getLogRecordSize();
    if (recordSize < (PAGESIZE - PAGEHEADERSIZE)) {
      if (theCheckFlag) {
	if(!ictRecord->check()) {
	  doExit();
	}
      }
      if (thePrintFlag) ndbout << (*ictRecord);
    } else {
      ndbout << endl << "Error: Record greater than a Page" << endl;
    }
    break;

  case ZNEW_PREP_OP_TYPE: 
  case ZABORT_TYPE:
  case ZFRAG_SPLIT_TYPE:
  case ZNEXT_MBYTE_TYPE:
    ndbout << endl << "Record type = " << recordType << " not implemented." << endl;
    return 0;

  default:
    ndbout << endl << "Error: Unknown record type. Record type = " << recordType << endl;
    return 0;
  }

  thePageHeader = (PageHeader *) (pagePtr + PAGESIZE);
  if (thePrintFlag) ndbout << (*thePageHeader);

  return PAGEHEADERSIZE - PAGESIZE + oldPageIndex + recordSize;
}

//----------------------------------------------------------------
// 
//----------------------------------------------------------------


void usage(const char * prg){
  ndbout << endl << "Usage: " << endl << prg 
	 << " <Binary log file> [-noprint] [-nocheck] [-mbyte <0-15>] "
	 << "[-mbyteheaders] [-pageheaders] [-filedescriptors] [-page <0-31>] "
	 << "[-pageindex <12-8191>]" 
	 << endl << endl;
  
}
void readArguments(int argc, const char** argv)
{
  if(argc < 2 || argc > 9){
    usage(argv[0]);
    doExit();
  }

  strcpy(fileName, argv[1]);
  argc--;

  int i = 2;
  while (argc > 1)
    {
      if (strcmp(argv[i], "-noprint") == 0) {
	thePrintFlag = false;
      } else if (strcmp(argv[i], "-nocheck") == 0) {
	theCheckFlag = false;
      } else if (strcmp(argv[i], "-mbyteheaders") == 0) {
	onlyMbyteHeaders = true;
      } else if (strcmp(argv[i], "-pageheaders") == 0) {
	onlyPageHeaders = true;
      } else if (strcmp(argv[i], "-filedescriptors") == 0) {
	onlyFileDesc = true;
      } else if (strcmp(argv[i], "-mbyte") == 0) {
	startAtMbyte = atoi(argv[i+1]);
	if (startAtMbyte > 15) {
	  usage(argv[0]);
	  doExit();
	}
	argc--;
	i++;
      } else if (strcmp(argv[i], "-page") == 0) {
	startAtPage = atoi(argv[i+1]);
	if (startAtPage > 31) {
	  usage(argv[0]);
	  doExit();
	}
	argc--;
	i++;
      } else if (strcmp(argv[i], "-pageindex") == 0) {
	startAtPageIndex = atoi(argv[i+1]);
	if (startAtPageIndex > 8191 || startAtPageIndex < 12) {
	  usage(argv[0]);
	  doExit();
	}
	argc--;
	i++;
      } else {
	usage(argv[0]);
	doExit();
      }
      argc--;
      i++;
    }
  
}

void doExit() {
  ndbout << "Error in redoLogReader(). Exiting!" << endl;
  if (f) fclose(f);
  delete [] redoLogPage;
  exit(RETURN_ERROR);
}