/***************************************************************************
 *   (c) Jürgen Riegel (juergen.riegel@web.de) 2006                        *   
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License (LGPL)   *
 *   as published by the Free Software Foundation; either version 2 of     *
 *   the License, or (at your option) any later version.                   *
 *   for detail see the LICENCE text file.                                 *
 *                                                                         *
 *   FreeCAD 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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with FreeCAD; if not, write to the Free Software        * 
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
 *   USA                                                                   *
 *                                                                         *
 *   Juergen Riegel 2006                                                   *
 ***************************************************************************/

/** \defgroup MemDebug Memory debugging
 *  \ingroup BASE
 * \section Overview
 * In C++ applications there are a lot of ways to handle memory allocation and deallocation.
 * As many ways to do it wrong or simply forget to free memory. One way to overcome
 * this problem is e.g. usage of handle classes (like OpenCASCADE it does) or use a lot of factories.
 * But all of them has drawbacks or performance penalties. One good way to get memory
 * problems hunted down is the MSCRT Heap debugging facility. This set of functions
 * opens the possibility to track and locate all kind of memory problems, e.g.
 * memory leaks.
 *
 * \section Implementation
 * The FreeCAD memory debugging is located in the Base::MemDebug class.
 */

#include "PreCompiled.h"

#ifndef _PreComp_
# ifdef _MSC_VER
#  include <cstdio>
#  include <time.h>
#  include <windows.h>
#  include <crtdbg.h>
# endif
#endif


/// Here the FreeCAD includes sorted by Base,App,Gui......
#include "MemDebug.h"

using namespace Base;

#if defined(_MSC_VER)


/** Memory debugging class
 * This class is an interface to the Windows CRT debugging
 * facility. If the define MemDebugOn in the src/FCConfig.h is 
 * set the class get intatiated 
 * globally and tracks all memory allocations on the heap. The 
 * result get written in the MemLog.txt in the active directory.
 *  \par
 * NOTE: you must not instaciate this class! 
 *  
 *
 * \author Juergen Riegel
 */
class MemDebug
{
public:
  /// Construction
  MemDebug();
  /// Destruction
  virtual ~MemDebug();

protected:
  static FILE *logFile;

  /** @name static callbacks for the Crt */
  //@{
  static void __cdecl sDumpClientHook(void * pUserData, size_t nBytes);
  static int  __cdecl sAllocHook(int nAllocType, void* pvData, size_t nSize,int nBlockUse,long lRequest,const unsigned char * szFileName,int nLine);
  static int          sReportHook(int   nRptType,char *szMsg,int  *retVal);
  //@}
};

// the one and only MemDebug instance. 
#ifdef MemDebugOn
MemDebug cSingelton;
#endif


#define  SET_CRT_DEBUG_FIELD(a)   _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#define  CLEAR_CRT_DEBUG_FIELD(a) _CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))

FILE *MemDebug::logFile = NULL;

//**************************************************************************
// Construction/Destruction


MemDebug::MemDebug()
{
   //_CrtMemState checkPt1;
   char timeStr[15], dateStr[15];         // Used to set up log file


   // Send all reports to STDOUT, since this example is a console app
   _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR );
   _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR );
   _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR );
 
   // Set the debug heap to report memory leaks when the process terminates,
   // and to keep freed blocks in the linked list.
   SET_CRT_DEBUG_FIELD( _CRTDBG_LEAK_CHECK_DF | _CRTDBG_DELAY_FREE_MEM_DF );

   // Open a log file for the hook functions to use 
   if ( logFile != NULL )
     throw "Base::MemDebug::MemDebug():38: Dont call the constructor by your self!";
#if (_MSC_VER >= 1400)
   fopen_s( &logFile, "MemLog.txt", "w" );
   if ( logFile == NULL )
     throw "Base::MemDebug::MemDebug():41: File IO Error. Canot open log file...";
   _strtime_s( timeStr, 15 );
   _strdate_s( dateStr, 15 );
#elif (_MSC_VER >= 1200)
   logFile = fopen( "MemLog.txt", "w" );
   if ( logFile == NULL )
     throw "Base::MemDebug::MemDebug():41: File IO Error. Canot open log file...";
   _strtime( timeStr );
   _strdate( dateStr );
#endif
   fprintf( logFile,
            "Memory Allocation Log File for FreeCAD, run at %s on %s.\n",
            timeStr, dateStr );
   fputs( "-------------------------------------------------------------------\n", logFile );

   // Install the hook functions
   _CrtSetDumpClient( sDumpClientHook );
   _CrtSetAllocHook( sAllocHook );
   _CrtSetReportHook( sReportHook );

}

MemDebug::~MemDebug()
{
  _CrtMemDumpAllObjectsSince( NULL );
  //_CrtCheckMemory( );

  // This fflush needs to be removed...
  fflush( logFile );
  fclose( logFile );
}


//**************************************************************************
// separator for other implemetation aspects


/* REPORT HOOK FUNCTION
   --------------------
   Again, report hook functions can serve a very wide variety of purposes.
   This one logs error and assertion failure debug reports in the
   log file, along with 'Damage' reports about overwritten memory.

   By setting the retVal parameter to zero, we are instructing _CrtDbgReport
   to return zero, which causes execution to continue. If we want the function
   to start the debugger, we should have _CrtDbgReport return one.
*/
int MemDebug::sReportHook(int   nRptType,char *szMsg,int  *retVal)
{
   char *RptTypes[] = { "Warning", "Error", "Assert" };

   if ( ( nRptType > 0 ) || ( strstr( szMsg, "HEAP CORRUPTION DETECTED" ) ) )
      fprintf( logFile, "%s: %s", RptTypes[nRptType], szMsg );

   retVal = 0;

   return( 7 );         // Allow the report to be made as usual (True = 7, False = 0)
}

/* ALLOCATION HOOK FUNCTION
   -------------------------
   An allocation hook function can have many, many different
   uses. This one simply logs each allocation operation in a file.
*/
int __cdecl MemDebug::sAllocHook(
   int      nAllocType,
   void   * pvData,
   size_t   nSize,
   int      nBlockUse,
   long     lRequest,
   const unsigned char * szFileName,
   int      nLine
   )
{
  char *operation[] = { "       :", "Alloc  :", "Realloc:", "Free   :" };
   char *blockType[] = { "Free", "Normal", "CRT", "Ignore", "Client" };

   if ( nBlockUse == _CRT_BLOCK )   // Ignore internal C runtime library allocations
      return( 7 ); // (True = 7, False = 0)

   _ASSERT( ( nAllocType > 0 ) && ( nAllocType < 4 ) );
   _ASSERT( ( nBlockUse >= 0 ) && ( nBlockUse < 5 ) );

   if( nBlockUse !=4 )
     return(7);
   
   fprintf( logFile, 
            "%s (#%7d) %12ld byte (%s) in %s line %d",
            operation[nAllocType],lRequest, nSize, blockType[nBlockUse],szFileName, nLine);
   if ( pvData != NULL )
      fprintf( logFile, " at %p\n", pvData );
   else
     fprintf( logFile, "\n", pvData );

   return( 7 );         // Allow the memory operation to proceed (True = 7, False = 0)
}


/* CLIENT DUMP HOOK FUNCTION
   -------------------------
   A hook function for dumping a Client block usually reports some
   or all of the contents of the block in question.  The function
   below also checks the data in several ways, and reports corruption
   or inconsistency as an assertion failure.
*/
void __cdecl MemDebug::sDumpClientHook(
   void * pUserData,
   size_t nBytes
   )
{
   long requestNumber=0;
  _CrtIsMemoryBlock(pUserData,(unsigned int)nBytes,&requestNumber,NULL,NULL);
  fprintf( logFile, "Leak   : (#%7d) %12ld bytes (%p)  \n", requestNumber, nBytes, pUserData );

}

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

MemCheck::MemCheck()
{
    // Store a memory checkpoint in the s1 memory-state structure
    _CrtMemCheckpoint( &s1 );
}

MemCheck::~MemCheck()
{
    // Store a 2nd memory checkpoint in s2
    _CrtMemCheckpoint( &s2 );
    if ( _CrtMemDifference( &s3, &s1, &s2 ) )
        _CrtMemDumpStatistics( &s3 );
}

void MemCheck::setNextCheckpoint()
{
    // Store a 2nd memory checkpoint in s2
    _CrtMemCheckpoint( &s2 );
    if ( _CrtMemDifference( &s3, &s1, &s2 ) )
        _CrtMemDumpStatistics( &s3 );

    // Store a memory checkpoint in the s1 memory-state structure
    _CrtMemCheckpoint( &s1 );
}

bool MemCheck::checkMemory()
{
    return _CrtCheckMemory() ? true : false;
}

bool MemCheck::dumpLeaks()
{
    return _CrtDumpMemoryLeaks() ? true : false;
}

bool MemCheck::isValidHeapPointer(const void* userData)
{
    return _CrtIsValidHeapPointer(userData) ? true : false;
}

#endif
