// memory.cpp
#include "../../include/lock.hpp"     // locking
#include <new>          // std::bad_alloc
#include <cstdlib>      // malloc
#include <cstring>      // memset
#include <fstream>      // ofstream
#include <sstream>      // stringstream
#include <cassert>      // assert
#include <iomanip>      // setiosflags

/*********************************************************************

To use MemoryTracker merely add this file to your project
No need to instantiate anything

If your app is multi threaded define YASSL_THREAD_SAFE

*********************************************************************/


// locals
namespace {

class MemoryTracker {
    std::ofstream log_;
public:
    MemoryTracker();
    ~MemoryTracker();
private:
    MemoryTracker(const MemoryTracker&);             // hide copy
    MemoryTracker& operator=(const MemoryTracker&);  // and assign

    void LogStats();
};


struct alloc_node {
    alloc_node* left_;
    alloc_node* right_;
  
    alloc_node() : left_(0), right_(0) {}
};


alloc_node* Root = 0;

size_t Allocs    = 0;
size_t DeAllocs  = 0;
size_t Bytes     = 0;


struct size_tracker {
    size_t size_;
    size_t count_;
};

size_tracker sizes[] = 
{
    {0,0},
    {2,0},
    {4,0},
    {8,0},
    {16,0},
    {32,0},
    {64,0},
    {128,0},
    {256,0},
    {512,0},
    {1024,0},
    {2048,0},
    {4096,0},
    {8192,0},
};

const size_t size_elements(sizeof(sizes) / sizeof(size_tracker));

bool Tracking(false);

using   yaSSL::Mutex;
typedef Mutex::Lock Lock;

Mutex mutex;

MemoryTracker theTracker;


bool lookup(alloc_node*& find, void* key, alloc_node*& prev)
{
    bool found(false);

    while (find) {
        if (find == key) {
            found = true;
            break;
        }
        prev = find;
        if (key < find)
            find = find->left_;
        else
            find = find->right_;
    }
    return found;
}


// iterative insert
void insert(alloc_node* entry)
{
    if (!Root) {
        Root = entry;
        return;
    }
       
    alloc_node* tmp  = Root;
    alloc_node* prev = 0;

    if (lookup(tmp, entry, prev)) 
        assert(0); // duplicate

    if (entry < prev)
        prev->left_  = entry;
    else
        prev->right_ = entry;
}


alloc_node* predecessorSwap(alloc_node* del)
{
    alloc_node* pred = del->left_;
    alloc_node* predPrev = del;

    while (pred->right_) {
        predPrev = pred;
        pred = pred->right_;
    }
    if (predPrev == del)
        predPrev->left_  = pred->left_;
    else
        predPrev->right_ = pred->left_;

    pred->left_  = del->left_;
    pred->right_ = del->right_;

    return pred;
}


// iterative remove
void remove(void* ptr)
{
    alloc_node* del  = Root;
    alloc_node* prev = 0;
    alloc_node* replace = 0;

    if ( lookup(del, ptr, prev) == false)
        assert(0); // oops, not there

    if (del->left_ && del->right_)          // two children
        replace = predecessorSwap(del);
    else if (!del->left_ && !del->right_)   // no children
        replace = 0;
    else                                    // one child
        replace = (del->left_) ? del->left_ : del->right_;

    if (del == Root)
        Root = replace;
    else if (prev->left_ == del)
        prev->left_  = replace;
    else
        prev->right_ = replace;
}


typedef void (*fp)(alloc_node*, void*);

void applyInOrder(alloc_node* root, fp f, void* arg)
{
    if (root == 0)
        return;
    
    applyInOrder(root->left_,  f, arg);
    f(root, arg);
    applyInOrder(root->right_, f, arg);
}


void show(alloc_node* ptr, void* arg)
{
    std::ofstream* log = static_cast<std::ofstream*>(arg);
    *log << ptr << '\n';
}


MemoryTracker::MemoryTracker() : log_("memory.log")
{
#ifdef __GNUC__
    // Force pool allocator to cleanup at exit
    setenv("GLIBCPP_FORCE_NEW", "1", 0);
#endif

#ifdef _MSC_VER
    // msvc6 needs to create Facility for ostream before main starts, otherwise
    // if another ostream is created and destroyed in main scope, log stats
    // will access a dead Facility reference (std::numput)
    int msvcFac = 6;
    log_ << "MSVC " << msvcFac << "workaround" << std::endl; 
#endif


    Tracking = true;
}


MemoryTracker::~MemoryTracker()
{
    // stop tracking before log (which will alloc on output)
    Tracking = false;
    LogStats();

    //assert(Allocs == DeAllocs);
    //assert(Root == 0);
}


void MemoryTracker::LogStats()
{
    log_ << "Number of Allocs:     " << Allocs    << '\n';
    log_ << "Number of DeAllocs:   " << DeAllocs  << '\n';
    log_ << "Number of bytes used: " << Bytes     << '\n';

    log_ << "Alloc size table:\n";
    log_ << " Bytes " << '\t' << "   Times\n";

    for (size_t i = 0; i < size_elements; ++i) {
        log_ << " " << sizes[i].size_  << "  " << '\t';
        log_ << std::setiosflags(std::ios::right) << std::setw(8);
        log_ << sizes[i].count_ << '\n';
    }

    if (Allocs != DeAllocs) {
        log_<< "Showing new'd allocs with no deletes" << '\n';
        applyInOrder(Root, show, &log_);
    }
    log_.flush();
}


// return power of 2 up to size_tracker elements
size_t powerOf2(size_t sz)
{
    size_t shifts = 0;

    if (sz)
        sz -= 1;
    else
        return 0;
	   
    while (sz) {
        sz >>= 1;
        ++shifts;
    }

    return shifts < size_elements ? shifts : size_elements;
}


} // namespace local


void* operator new(size_t sz)
{
    // put alloc node in front of requested memory
    void* ptr = malloc(sz + sizeof(alloc_node));
    if (ptr) {
        if (Tracking) {
            Lock l(mutex);
            ++Allocs;
            Bytes += sz;
            ++sizes[powerOf2(sz)].count_;
            insert(new (ptr) alloc_node);
        }
        return static_cast<char*>(ptr) + sizeof(alloc_node);
    }
    else
        assert(0);
}


void operator delete(void* ptr)
{
    if (ptr) {
        ptr = static_cast<char*>(ptr) - sizeof(alloc_node);  // correct offset
        if (Tracking) {
            Lock l(mutex);
            ++DeAllocs;
            remove(ptr);
        }
        free(ptr);
    }
}


void* operator new[](size_t sz)
{
    return ::operator new(sz);
}


void operator delete[](void* ptr)
{
    ::operator delete(ptr);
}
