/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program 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 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */

//==========================================================
// ORIGINAL COPYRIGHT FOLLOWS
//==========================================================
// Yes::Engine - Free C++ 3D engine
//
// Copyright (C) 2004-2005 Laurent Gomila
//
// 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.
//
// E-mail : laurent.gom@gmail.com
//==========================================================

#include "KeosMemoryManager.h"
#include "KeosException.h"
#include "KeosNoMemoryMacros.h"

namespace Keos
{
    //=======================================================================
    // CMemoryManager implementation
    //=======================================================================
    
    //-----------------------------------------------------------------------
    CMemoryManager::CMemoryManager() :
    m_File(KEOS_MEMLEAKS_FILE)
    {
        // The file is open ?
        if (!m_File)
            throw CLoadingFailed(KEOS_MEMLEAKS_FILE, "Impossible to write in file", "CMemoryManager::CMemoryManager");

        // Write header
        m_File << "=============================================" << std::endl;
        m_File << " Keos::Engine v" << KEOS_VERSION_MAJOR << "." << KEOS_VERSION_MINOR << 
            "." << KEOS_VERSION_PATCH << " - Memory leak tracker - " << std::endl;
        m_File << "=============================================" << std::endl << std::endl;
    
    }

    //-----------------------------------------------------------------------
    CMemoryManager::~CMemoryManager()
    {
        if (m_Blocks.empty())
        {
            // Aucune fuite
            m_File << std::endl;
            m_File << "==================" << std::endl;
            m_File << " No leak detected " << std::endl;
            m_File << "==================" << std::endl;
        }
        else
        {
            // Fuites mémoires
            m_File << std::endl;
            m_File << "===============================" << std::endl;
            m_File << " Some leaks have been detected " << std::endl;
            m_File << "===============================" << std::endl;
            m_File << std::endl;

            ReportLeaks();
        }
    }

    //-----------------------------------------------------------------------
    CMemoryManager& CMemoryManager::Instance()
    {
        static CMemoryManager sInst;

        return sInst;
    }

    //-----------------------------------------------------------------------
    void CMemoryManager::ReportLeaks()
    {
        // Détail des fuites
        size_t nTotalSize = 0;
        for (TBlockMap::iterator i = m_Blocks.begin(); i != m_Blocks.end(); ++i)
        {
            // Ajout de la taille du bloc au cumul
            nTotalSize += i->second.nSize;

            // Inscription dans le fichier des informations sur le bloc courant
            m_File << "-> 0x" << i->first
                   << " | "   << std::setw(7) << std::setfill(' ') << static_cast<int>(i->second.nSize) << " bytes"
                   << " | "   << i->second.File.Filename() << " (" << i->second.nLine << ")" << std::endl;

            // Libération de la mémoire
            free(i->first);
        }

        // Affichage du cumul des fuites
        m_File << std::endl << std::endl << "-- "
               << static_cast<int>(m_Blocks.size()) << " Blocks not released, "
               << static_cast<int>(nTotalSize)       << " bytes --"
               << std::endl;
    }

    //-----------------------------------------------------------------------
    void* CMemoryManager::Allocate(size_t nSize, const CFile& File, int nLine, bool bArray)
    {
        // Allocation de la mémoire
        void* pPtr = malloc(nSize);

        // Ajout du bloc à la liste des blocs alloués
        TBlock NewBlock;
        NewBlock.nSize  = nSize;
        NewBlock.File   = File;
        NewBlock.nLine  = nLine;
        NewBlock.bArray = bArray;
        m_Blocks[pPtr]  = NewBlock;

        // Loggization
        m_File << "++ Allocation    | 0x" << pPtr
               << " | " << std::setw(7) << std::setfill(' ') << static_cast<int>(NewBlock.nSize) << " bytes"
               << " | " << NewBlock.File.Filename() << " (" << NewBlock.nLine << ")" << std::endl;

        return pPtr;
    }

    //-----------------------------------------------------------------------
    void CMemoryManager::Free(void* pPtr, bool bArray)
    {
        // Recherche de l'adresse dans les blocs alloués
        TBlockMap::iterator It = m_Blocks.find(pPtr);

        // Si le bloc n'a pas été alloué, on génère une erreur
        if (It == m_Blocks.end())
        {
            // En fait ça arrive souvent, du fait que le delete surchargé est pris en compte même là où on n'inclue pas DebugNew.h,
            // mais pas la macro pour le new
            // Dans ce cas on détruit le bloc et on quitte immédiatement
            free(pPtr);
            return;
        }

        // Si le type d'allocation ne correspond pas, on génère une erreur
        if (It->second.bArray != bArray)
        {
            throw CBadDelete(pPtr, It->second.File.Filename(), It->second.nLine, !bArray);
        }

        // Finalement, si tout va bien, on supprime le bloc et on loggiz tout ça
        m_File << "-- Deallocation  | 0x" << pPtr
               << " | " << std::setw(7) << std::setfill(' ') << static_cast<int>(It->second.nSize) << " bytes"
               << " | " << m_DeleteStack.top().File.Filename() << " (" << m_DeleteStack.top().nLine << ")" << std::endl;
        m_Blocks.erase(It);
        m_DeleteStack.pop();
    
        // Libération de la mémoire
        free(pPtr);
    }

    //-----------------------------------------------------------------------
    void CMemoryManager::NextDelete(const CFile& File, int nLine)
    {
        TBlock Delete;
        Delete.File = File;
        Delete.nLine = nLine;

        m_DeleteStack.push(Delete);
    }

} // namespace Keos
