/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-01-29
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <stdlib.h>
#include <stdio.h>

#ifdef MEMORY_CHECKER

#include "MemoryCheckerPrivate.h"
#include "QGpCoreToolsDLLExport.h"

namespace QGpCoreTools {

#ifdef DO_MEMORY_CHECK

// defines for new,... must be removed because they are defined here as functions
#undef new
#undef isBlockDamaged
#undef printBlockInfo

/*!
  \class MemoryCheckerPrivate MemoryCheckerPrivate.h
  \brief Checking for memory leaks and over writing
 
  This class is internal, use only MemoryChecker
*/

Mutex MemoryCheckerPrivate::_mutex;
bool MemoryCheckerPrivate::_verbose=false;
bool MemoryCheckerPrivate::_enabled=true;
bool MemoryCheckerPrivate::_record=false;
BlockInfo * MemoryCheckerPrivate::_infos=nullptr;

inline bool MemoryCheckerPrivate::testNoMansLand(unsigned char * ptr)
{
  for(unsigned int i=0;i < NOMANSLAND_SIZE;i++ ) {
    if(ptr[ i ]!=0xFD)
      return false;
  }
  return true;
}

void MemoryCheckerPrivate::printHeader(FILE * f, const char * prefix, unsigned char * ptr)
{
  BlockInfo * infoBlock=*(BlockInfo ** ) ptr;
  fprintf (f, "\n%s%p :\n", prefix, infoBlock);
  ptr+= sizeof(BlockInfo ** );
  fprintf(f, "%s", prefix);
  for(unsigned int i=0;i < NOMANSLAND_SIZE;i++ ) {
    fprintf (f, "%.2X", ptr[ i ] );
    if((i+1)%4==0) {
      if((i+1)%16==0) {
        fprintf(f, "\n%s", prefix);
      } else {
        fprintf (f, " " );
      }
    }
  }
  fprintf(f, "\n" );
  if(_verbose) {  // Try to access infoblock even if not correct
    fprintf(f, "%sINFOBLOCK: address=%p/%p\n", prefix, infoBlock->blockAddress, (unsigned char *)infoBlock->blockAddress+HEADER_SIZE);
    fprintf(f, "%sINFOBLOCK: size=%u\n", prefix, infoBlock->sz);
    fprintf(f, "%sINFOBLOCK: file=%.20s\n", prefix, infoBlock->fName);
    fprintf(f, "%sINFOBLOCK: line=%i\n", prefix, infoBlock->fLine);
  }
}

/*!
  \fn setEnabled(bool e)
  Use this function with care, changing this state when blocks are already allocated with memory checker
  lead to uncontrolled results. Used only be PluginApplication to swith this memory checker for applications
  when the main trunc is not using this memory checker (loading the plugin with enabled checker may lead to
  crash).
*/

/*!
  Insert one block description at the head of the meminfo linked list
*/
inline BlockInfo * MemoryCheckerPrivate::addBlock(void * blockAddress, unsigned int sz,
                                                  const char * fName, int fLine)
{
  BlockInfo * b=(BlockInfo * ) malloc (sizeof(BlockInfo));
  b->blockAddress=blockAddress;
  b->sz=sz;
  b->record=_record;
  b->fName=fName;
  b->fLine=fLine;
  b->previous=nullptr;
  if(_record) {   // Memory checker is initialized with record to false, before initialization of QApplication cannot use Mutex
    _mutex.MUTEX_LOCK;
    b->next=_infos;
    if(_infos)
      _infos->previous=b;
    _infos=b;
    _mutex.unlock();
  } else {
    b->next=_infos;
    if(_infos)
      _infos->previous=b;
    _infos=b;
  }
  return b;
}

/*!
  Delete one block description from the meminfo linked list
*/
inline void MemoryCheckerPrivate::delBlock(BlockInfo * b)
{
  if(_record) {   // Memory checker is initialized with record to false, before initialization of QApplication cannot use Mutex
    _mutex.MUTEX_LOCK;
    if(b->previous)
      b->previous->next=b->next;
    else
      _infos=b->next;
    if(b->next)
      b->next->previous=b->previous;
    _mutex.unlock();
  } else {
    if(b->previous)
      b->previous->next=b->next;
    else
      _infos=b->next;
    if(b->next)
      b->next->previous=b->previous;
  }
  free(b);
}

inline BlockInfo * MemoryCheckerPrivate::findBlock(void * blockAddress)
{
  BlockInfo * b;
  if(_record) {   // Memory checker is initialized with record to false, before initialization of QApplication cannot use Mutex
    _mutex.MUTEX_LOCK;
    b=_infos;
    while(b && b->blockAddress!=blockAddress)
      b=b->next;
    _mutex.unlock();
  } else {
    b=_infos;
    while(b && b->blockAddress!=blockAddress)
      b=b->next;
  }
  return b;
}

inline void MemoryCheckerPrivate::srcLocation(const char * fName, int fLine)
{
  if(fName)
    fprintf (stderr, "in file %s, at line %i\n", fName, fLine);
  else
    fprintf (stderr, "in libraries\n" );
}

inline void MemoryCheckerPrivate::srcLocation(BlockInfo * b)
{
  fprintf(stderr, "allocated ");
  srcLocation(b->fName, b->fLine);
}

inline void* MemoryCheckerPrivate::allocateBlock(std::size_t sz, const char* file, int line)
{
  unsigned char * p_header=(unsigned char * ) malloc (sz + HEADER_SIZE + TAIL_SIZE);
  unsigned char * p_tail=(unsigned char * ) p_header + sz + HEADER_SIZE;
  if( !p_header) {
    fprintf (stderr, "malloc returned 0 (block size=%i) ", (int)sz);
    srcLocation(file, line);
    abort();
  }
  *(BlockInfo ** ) (p_header)=addBlock(p_header, sz, file, line);

  unsigned char * p_header_noman=p_header+sizeof(BlockInfo **);
  for(unsigned int i=0;i < NOMANSLAND_SIZE;i++ ) {
    p_header_noman[ i ]=0xFD;
    p_tail[ i ]=0xFD;
  }
  if(_verbose) fprintf(stderr, "Allocating block %p\n", (void *)(p_header+HEADER_SIZE));
  return (void * ) (p_header + HEADER_SIZE);
}

inline void MemoryCheckerPrivate::freeBlock(void* v)
{
  if(v) {
    if(_verbose) fprintf(stderr, "Freeing block %p\n",v);
    unsigned char * p_header=(unsigned char * ) v - HEADER_SIZE;
    BlockInfo * infoBlock=nullptr;
    if(testNoMansLand(p_header+sizeof(BlockInfo **))) {
      infoBlock=*(BlockInfo ** ) p_header;
      if(infoBlock && p_header!=infoBlock->blockAddress) {
        fprintf (stderr,  "*** WARNING *** : info for block %p damaged\n", v);
        fprintf (stderr,  "                  or ptr to info damaged looking for a matching info\n" );
        infoBlock=findBlock(p_header);
#ifdef ABORT_ON_WARNING
        abort();
#endif
      }
    } else {
      fprintf (stderr, "*** WARNING *** : header damaged block at %p", v);
      printHeader(stderr, "                  ", p_header);
      infoBlock=findBlock(p_header);
      if(infoBlock) {
        fprintf (stderr, ", ");
        srcLocation(infoBlock);
      } else {
        fprintf (stderr, "\n");
      }
#ifdef ABORT_ON_WARNING
      abort();
#endif
    }
    if(infoBlock) {
      unsigned int sz=infoBlock->sz;
      unsigned char * p_tail=((unsigned char * ) p_header + sz + HEADER_SIZE);
      if( !testNoMansLand(p_tail)) {
        fprintf (stderr,  "*** WARNING *** : tail damaged block at %p, ", v);
        srcLocation(infoBlock);
#ifdef ABORT_ON_WARNING
        abort();
#endif
      }
      delBlock(infoBlock);
      free(p_header);
    } else {
      fprintf(stderr, "Block not found in info list %p\n", v);
#ifdef ABORT_ON_WARNING
      abort();
#endif
    }
  }
}

void MemoryCheckerPrivate::checkBlocks(bool showLib, bool showUndeleted, const char* file, int line)
{
  if(!showUndeleted || file) {
    fprintf (stderr,  "checkBlocks() called from file %s, at line %i\n", file, line);
  }
  _mutex.MUTEX_LOCK;
  BlockInfo * b=_infos;
  while(b) {
    unsigned int sz=b->sz;
    unsigned char * p_header=(unsigned char * ) b->blockAddress;
    unsigned char * p_tail=((unsigned char * ) p_header + sz + HEADER_SIZE);
    if( !testNoMansLand(p_header+sizeof(BlockInfo **))) {
      fprintf (stderr,  "*** WARNING *** : header damaged block at %p, ",
                (unsigned char * ) p_header + HEADER_SIZE);
      srcLocation(b);
    }
    if( !testNoMansLand(p_tail)) {
      fprintf (stderr,  "*** WARNING *** : tail damaged block at %p, ",
                (unsigned char * ) p_header + HEADER_SIZE);
      srcLocation(b);
    }
    if(*(BlockInfo **)p_header!=b) {
      fprintf(stderr,  "*** WARNING *** : ptr to info damaged at %p, ",
              (unsigned char *)p_header+HEADER_SIZE);
      srcLocation(b);
    } else if(showUndeleted && (showLib || b->fName) && b->record) {
      fprintf(stderr, "Undeleted block at %p, ", (void *)((unsigned char *)b->blockAddress+HEADER_SIZE));
      srcLocation(b);
    }
    b=b->next;
  }
  _mutex.unlock();
}

bool MemoryCheckerPrivate::isBlockDamaged(void* v, const char* file, int line)
{
  if(v) {
    fprintf (stderr,  "isBlockDamaged(%p) called from file %s, at line %i\n", v, file, line);
    unsigned char * p_header=(unsigned char * ) v - HEADER_SIZE;
    BlockInfo * infoBlock=findBlock(p_header);
    if(infoBlock) {
      unsigned int sz=infoBlock->sz;
      unsigned char * p_tail=((unsigned char * ) p_header + sz + HEADER_SIZE);
      if( !testNoMansLand(p_header+sizeof(BlockInfo **))) {
        fprintf (stderr,  "*** WARNING *** : header damaged block at %p, ", v);
        srcLocation(infoBlock);
        return true;
      }
      if( !testNoMansLand(p_tail)) {
        fprintf (stderr,  "*** WARNING *** : tail damaged block at %p, ", v);
        srcLocation(infoBlock);
        return true;
      }
    } else {
      fprintf(stderr, "Block not found in info list %lx\n", (long) v);
      return true;
    }
  }
  return false;
}

void MemoryCheckerPrivate::printBlockInfo(void * ptr, const char* file, int line)
{
  if(ptr) {
    fprintf (stderr,  "printBlockInfo(%p) called from file %s, at line %i\n", ptr, file, line);
    unsigned char * p_header=(unsigned char * ) ptr - HEADER_SIZE;
    BlockInfo * infoBlock=findBlock(p_header);
    if(infoBlock) {
      unsigned int sz=infoBlock->sz;
      fprintf (stderr,  "  Block of %i bytes allocated ",sz);
      srcLocation(infoBlock);
      unsigned char * p_tail=((unsigned char * ) p_header + sz + HEADER_SIZE);
      if( !testNoMansLand(p_header+sizeof(BlockInfo **))) {
        fprintf (stderr,  "  *** WARNING *** : header damaged block at %p\n", ptr);
      }
      if( !testNoMansLand(p_tail)) {
        fprintf (stderr,  "  *** WARNING *** : tail damaged block at %p\n", ptr);
      }
    } else
      fprintf (stderr,  "  Block not found in info list %p\n", ptr);
  }
}

#endif  // DO_MEMORY_CHECK

} // namespace QGpCoreTools

QGPCORETOOLS_EXPORT void* operator new(std::size_t sz)
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    return QGpCoreTools::MemoryCheckerPrivate::allocateBlock(sz, nullptr, 0);
  else
#endif
    return malloc(sz);
}

QGPCORETOOLS_EXPORT void* operator new [] (std::size_t sz)
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    return QGpCoreTools::MemoryCheckerPrivate::allocateBlock(sz, nullptr, 0);
  else
#endif
    return malloc(sz);
}

QGPCORETOOLS_EXPORT void* operator new(std::size_t sz, const char* file, int line)
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    return QGpCoreTools::MemoryCheckerPrivate::allocateBlock(sz, file, line);
  else
    return malloc(sz);
#else
  Q_UNUSED(file)
  Q_UNUSED(line)
  return malloc (sz);
#endif
}

QGPCORETOOLS_EXPORT void* operator new [] (std::size_t sz, const char* file, int line)
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    return QGpCoreTools::MemoryCheckerPrivate::allocateBlock(sz, file, line);
  else
    return malloc(sz);
#else
  Q_UNUSED(file)
  Q_UNUSED(line)
  return malloc (sz);
#endif
}

QGPCORETOOLS_EXPORT void operator delete(void* v) noexcept
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    QGpCoreTools::MemoryCheckerPrivate::freeBlock(v);
  else
#endif
    free(v);
}

QGPCORETOOLS_EXPORT void operator delete [] (void* v) noexcept
{
#ifdef DO_MEMORY_CHECK
  if(QGpCoreTools::MemoryCheckerPrivate::isEnabled())
    QGpCoreTools::MemoryCheckerPrivate::freeBlock(v);
  else
#endif
    free(v);
}

#endif //MEMORY_CHECKER
