/*
  ZynAddSubFX - a software synthesizer

  Allocator.cpp - RT-Safe Memory Allocator
  Copyright (C) 2016 Mark McCurry

  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.
*/
#include <cstddef>
#include <cstdlib>
#include <cassert>
#include <utility>
#include <cstdio>
#include "tlsf/tlsf.h"
#include "Allocator.h"

namespace zyncarla {

//Used for dummy allocations
DummyAllocator DummyAlloc;

//recursive type class to avoid void *v = *(void**)v style casting
struct next_t
{
    next_t *next;
    size_t pool_size;
};

void *data(next_t *n)
{
    return n+sizeof(next_t);
}


struct AllocatorImpl
{
    void *tlsf = 0;

    //singly linked list of memory pools
    //XXX this may violate alignment on some platforms if malloc doesn't return
    //nice values
    next_t *pools = 0;
    unsigned long long totalAlloced = 0;
};

Allocator::Allocator(void) : transaction_active()
{
    impl = new AllocatorImpl;
    size_t default_size = 10*1024*1024;
    impl->pools = (next_t*)malloc(default_size);
    impl->pools->next = 0x0;
    impl->pools->pool_size = default_size;
    size_t off = tlsf_size() + tlsf_pool_overhead() + sizeof(next_t);
    //printf("Generated Memory Pool with '%p'\n", impl->pools);
    impl->tlsf = tlsf_create_with_pool(((char*)impl->pools)+off, default_size-2*off);
    //printf("Allocator(%p)\n", impl);
}

Allocator::~Allocator(void)
{
    next_t *n = impl->pools;
    while(n) {
        next_t *nn = n->next;
        free(n);
        n = nn;
    }
    delete impl;
}

void *AllocatorClass::alloc_mem(size_t mem_size)
{
    impl->totalAlloced += mem_size;
    void *mem = tlsf_malloc(impl->tlsf, mem_size);
    //printf("Allocator.malloc(%p, %d) = %p\n", impl, mem_size, mem);
    //void *mem = malloc(mem_size);
    //printf("Allocator result = %p\n", mem);
    return mem;
}
void AllocatorClass::dealloc_mem(void *memory)
{
    //printf("dealloc_mem(%d)\n", tlsf_block_size(memory));
    tlsf_free(impl->tlsf, memory);
    //free(memory);
}

bool AllocatorClass::lowMemory(unsigned n, size_t chunk_size) const
{
    //This should stay on the stack
    void *buf[n];
    for(unsigned i=0; i<n; ++i)
        buf[i] = tlsf_malloc(impl->tlsf, chunk_size);
    bool outOfMem = false;
    for(unsigned i=0; i<n; ++i)
        outOfMem |= (buf[i] == nullptr);
    for(unsigned i=0; i<n; ++i)
        if(buf[i])
            tlsf_free(impl->tlsf, buf[i]);

    return outOfMem;
}


void AllocatorClass::addMemory(void *v, size_t mem_size)
{
    next_t *n = impl->pools;
    while(n->next) n = n->next;
    n->next = (next_t*)v;
    n->next->next = 0x0;
    n->next->pool_size = mem_size;
    //printf("Inserting '%p'\n", v);
    off_t off = sizeof(next_t) + tlsf_pool_overhead();
    void *result =
        tlsf_add_pool(impl->tlsf, ((char*)n->next)+off,
                //0x0eadbeef);
            mem_size-off-sizeof(size_t));
    if(!result)
        printf("FAILED TO INSERT MEMORY POOL\n");
};//{(void)mem_size;};

#ifndef INCLUDED_tlsfbits
//From tlsf internals
typedef struct block_header_t
{
	/* Points to the previous physical block. */
	struct block_header_t* prev_phys_block;

	/* The size of this block, excluding the block header. */
	size_t size;

	/* Next and previous free blocks. */
	struct block_header_t* next_free;
	struct block_header_t* prev_free;
} block_header_t;
static const size_t block_header_free_bit = 1 << 0;
#endif

void Allocator::beginTransaction() {
    // TODO: log about unsupported nested transaction when a RT compliant
    // logging is available and transaction_active == true
    transaction_active = true;
    transaction_alloc_index = 0;
}

void Allocator::endTransaction() {
    // TODO: log about invalid end of transaction when a RT copmliant logging
    // is available and transaction_active == false
    transaction_active = false;
}

bool Allocator::memFree(void *pool) const
{
    size_t bh_shift = sizeof(next_t)+sizeof(size_t);
    //Assume that memory is free to start with
    bool isFree = true;
    //Get the block header from the pool
    block_header_t &bh  = *(block_header_t*)((char*)pool+bh_shift);
    //The first block must be free
    if((bh.size&block_header_free_bit) == 0)
        isFree = false;
    block_header_t &bhn = *(block_header_t*)
        (((char*)&bh)+((bh.size&~0x3)+bh_shift-2*sizeof(size_t)));
    //The next block must be 'non-free' and zero length
    if((bhn.size&block_header_free_bit) != 0)
        isFree = false;
    if((bhn.size&~0x3) != 0)
        isFree = false;

    return isFree;
}

int Allocator::memPools() const
{
    int i = 1;
    next_t *n = impl->pools;
    while(n->next) {
        i++;
        n = n->next;
    }
    return i;
}

int Allocator::freePools() const
{
    int i = 0;
    next_t *n = impl->pools->next;
    while(n) {
        if(memFree(n))
            i++;
        n = n->next;
    }
    return i;
}


unsigned long long Allocator::totalAlloced() const
{
    return impl->totalAlloced;
}

void Allocator::rollbackTransaction() {

    // if a transaction is active
    if (transaction_active) {

        // deallocate all allocated memory within this transaction
        for (size_t temp_idx = 0;
             temp_idx < transaction_alloc_index; ++temp_idx) {
            dealloc_mem(transaction_alloc_content[temp_idx]);
        }

    }

    transaction_active = false;
}

/*
 * Notes on tlsf internals
 * - TLSF consists of blocks linked by block headers and these form a doubly
 *   linked list of free segments
 * - Original memory is [control_t pool]
 *              base    sentinal
 *   Pools are [block_t block_t blocks ...]
 *   Blocks are [memory block_t](??)
 * - These are stored in the control_t structure in an order dependent on the
 *   size that they are
 *   it's a bit unclear how collisions are handled here, but the basic premise
 *   makes sense
 * - Additional structure is added before the start of each pool to define the
 *   pool size and the next pool in the list as this information is not
 *   accessible in O(good) time
 */

}
