//
// MessagePack for C++ memory pool
//
// Copyright (C) 2008 FURUHASHI Sadayuki
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
//
#ifndef MSGPACK_ZONE_HPP__
#define MSGPACK_ZONE_HPP__
#include <iostream>

#include "msgpack/object.hpp"
#include <string.h>
#include <stdlib.h>
#include <stdexcept>

#ifndef MSGPACK_ZONE_CHUNK_SIZE
#define MSGPACK_ZONE_CHUNK_SIZE 1024
#endif

namespace msgpack {


static const size_t ZONE_CHUNK_SIZE = MSGPACK_ZONE_CHUNK_SIZE;


class zone {
public:
	zone() : m_used(0) { }
	~zone() { clear(); }

public:
	template <typename T>
	void push_finalizer(void (*func)(void* obj, void* user), T* obj, void* user);

public:
	object_nil*    nnil   () { return new (alloc()) object_nil();   }
	object_true*   ntrue  () { return new (alloc()) object_true();  }
	object_false*  nfalse () { return new (alloc()) object_false(); }
	object_u8*     nu8    (uint8_t  v) { return new (alloc()) object_u8(v);     }
	object_u16*    nu16   (uint16_t v) { return new (alloc()) object_u16(v);    }
	object_u32*    nu32   (uint32_t v) { return new (alloc()) object_u32(v);    }
	object_u64*    nu64   (uint64_t v) { return new (alloc()) object_u64(v);    }
	object_i8*     ni8    (int8_t   v) { return new (alloc()) object_i8(v);     }
	object_i16*    ni16   (int16_t  v) { return new (alloc()) object_i16(v);    }
	object_i32*    ni32   (int32_t  v) { return new (alloc()) object_i32(v);    }
	object_i64*    ni64   (int64_t  v) { return new (alloc()) object_i64(v);    }
	object_float*  nfloat (float    v) { return new (alloc()) object_float(v);  }
	object_double* ndouble(double   v) { return new (alloc()) object_double(v); }

	object_raw_ref* nraw_ref(void* ptr, uint32_t len)
		{ return new (alloc()) object_raw_ref(ptr, len); }

	object_const_raw_ref* nraw_ref(const void* ptr, uint32_t len)
		{ return new (alloc()) object_const_raw_ref(ptr, len); }

	object_raw_ref* nraw_copy(const void* ptr, uint32_t len)
		{
			void* copy = malloc(len);
			if(!copy) { throw std::bad_alloc(); }
			object_raw_ref* o;
			try {
				o = new (alloc()) object_raw_ref(copy, len);
				push_finalizer<void>(&zone::finalize_free, NULL, copy);
			} catch (...) {
				free(copy);
				throw;
			}
			memcpy(copy, ptr, len);
			return o;
		}

	object_array* narray()
		{ return new (alloc()) object_array(); }

	object_array* narray(size_t reserve_size)
		{ return new (alloc()) object_array(reserve_size); }

	object_map* nmap()
		{ return new (alloc()) object_map(); }

<% GENERATION_SIZE = 16 %>
<% 1.upto(GENERATION_SIZE) {|i| %>
	object_array* narray(<% 1.upto(i-1) {|n| %>object o<%=n%>, <% } %>object o<%=i%>)
		{ object_array* a = new (alloc()) object_array(<%=i%>);
			<% 1.upto(i) {|n| %>a->push_back(o<%=n%>);
			<% } %>return a; }
<% } %>

<% 1.upto(GENERATION_SIZE) {|i| %>
	object_map* nmap(<% 1.upto(i-1) {|n| %>object k<%=n%>, object v<%=n%>, <% } %>object k<%=i%>, object v<%=i%>)
		{ object_map* m = new (alloc()) object_map();
			<% 1.upto(i) {|n| %>m->store(k<%=n%>, v<%=n%>);
			<% } %>return m; }
<% } %>

public:
	void clear();
	bool empty() const;

private:
	void* alloc();

private:
	size_t m_used;

	static const size_t MAX_OBJECT_SIZE =
		sizeof(object_raw_ref) > sizeof(object_array)
			? ( sizeof(object_raw_ref) > sizeof(object_map)
					? sizeof(object_raw_ref)
					: sizeof(object_map)
			  )
			: ( sizeof(object_array) > sizeof(object_map)
					? sizeof(object_array)
					: sizeof(object_map)
			  )
		;

	struct cell_t {
		char data[MAX_OBJECT_SIZE];
	};

	typedef std::vector<cell_t*> pool_t;
	pool_t m_pool;


	class finalizer {
	public:
		finalizer(void (*func)(void*, void*), void* obj, void* user) :
			m_obj(obj), m_user(user), m_func(func) {}
		void call() { (*m_func)(m_obj, m_user); }
	private:
		void* m_obj;
		void* m_user;
		void (*m_func)(void*, void*);
	};

	typedef std::vector<finalizer> user_finalizer_t;
	user_finalizer_t m_user_finalizer;

private:
	void expand_chunk();

public:
	static void finalize_free(void* obj, void* user)
		{ free(user); }

private:
	zone(const zone&);
};


template <typename T>
inline void zone::push_finalizer(void (*func)(void* obj, void* user), T* obj, void* user)
{
	m_user_finalizer.push_back( finalizer(
			func, reinterpret_cast<void*>(obj),
			user) );
}

inline bool zone::empty() const
{
	return m_used == 0 && m_user_finalizer.empty();
}

inline void* zone::alloc()
{
	if(m_pool.size() <= m_used/ZONE_CHUNK_SIZE) {
		expand_chunk();
	}
	void* data = m_pool[m_used/ZONE_CHUNK_SIZE][m_used%ZONE_CHUNK_SIZE].data;
	++m_used;
	return data;
}


}  // namespace msgpack

#endif /* msgpack/zone.hpp */