//
// MessagePack for C++ static resolution routine
//
// Copyright (C) 2008-2009 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_OBJECT_HPP__
#define MSGPACK_OBJECT_HPP__

#include "msgpack/object.h"
#include "msgpack/pack.hpp"
#include <stdint.h>
#include <string.h>
#include <stdexcept>
#include <typeinfo>
#include <limits>
#include <ostream>

namespace msgpack {


class type_error : public std::bad_cast { };


namespace type {
	enum object_type {
		NIL					= 0x01,
		BOOLEAN				= 0x02,
		POSITIVE_INTEGER	= 0x03,
		NEGATIVE_INTEGER	= 0x04,
		DOUBLE				= 0x05,
		RAW					= 0x06,
		ARRAY				= 0x07,
		MAP					= 0x08,
	};
}


struct object;
struct object_kv;

struct object_array {
	uint32_t size;
	object* ptr;
};

struct object_map {
	uint32_t size;
	object_kv* ptr;
};

struct object_raw {
	uint32_t size;
	const char* ptr;
};

struct object {
	union union_type {
		bool boolean;
		uint64_t u64;
		int64_t  i64;
		double   dec;
		object_array array;
		object_map map;
		object_raw raw;
		object_raw ref;  // obsolete
	};

	type::object_type type;
	union_type via;

	bool is_nil() const { return type == type::NIL; }

	template <typename T>
	T as() const;

	template <typename T>
	void convert(T* v) const;

	object();
	object(msgpack_object obj);
	operator msgpack_object();

private:
	struct implicit_type;

public:
	implicit_type convert() const;
};

struct object_kv {
	object key;
	object val;
};

bool operator==(const object x, const object y);
bool operator!=(const object x, const object y);

std::ostream& operator<< (std::ostream& s, const object o);


template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T& v);

template <typename T>
T& operator>> (object o, T& v);


struct object::implicit_type {
	implicit_type(object o) : obj(o) { }
	~implicit_type() { }

	template <typename T>
	operator T() { return obj.as<T>(); }

private:
	object obj;
};


template <typename Type>
class define : public Type {
public:
	typedef Type msgpack_type;
	typedef define<Type> define_type;

	define() {}
	define(const msgpack_type& v) : msgpack_type(v) {}

	template <typename Packer>
	void msgpack_pack(Packer& o) const
	{
		o << static_cast<const msgpack_type&>(*this);
	}

	void msgpack_unpack(object o)
	{
		o >> static_cast<msgpack_type&>(*this);
	}
};


template <typename Stream>
template <typename T>
inline packer<Stream>& packer<Stream>::pack(const T& v)
{
	*this << v;
	return *this;
}

inline object& operator>> (object o, object& v)
{
	v = o;
	return v;
}

template <typename T>
inline T& operator>> (object o, T& v)
{
	v.msgpack_unpack(o.convert());
	return v;
}

template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T& v)
{
	v.msgpack_pack(o);
	return o;
}


inline bool operator!=(const object x, const object y)
{ return !(x == y); }


inline object::object() { }

inline object::object(msgpack_object obj)
{
	// FIXME beter way?
	::memcpy(this, &obj, sizeof(obj));
}

inline object::operator msgpack_object()
{
	// FIXME beter way?
	msgpack_object obj;
	::memcpy(&obj, this, sizeof(obj));
	return obj;
}


inline object::implicit_type object::convert() const
{
	return implicit_type(*this);
}

template <typename T>
inline void object::convert(T* v) const
{
	*this >> *v;
}

template <typename T>
inline T object::as() const
{
	T v;
	convert(&v);
	return v;
}


// obsolete
template <typename T>
inline void convert(T& v, object o)
{
	o.convert(&v);
}

// obsolete
template <typename Stream, typename T>
inline void pack(packer<Stream>& o, const T& v)
{
	o.pack(v);
}

// obsolete
template <typename Stream, typename T>
inline void pack_copy(packer<Stream>& o, T v)
{
	pack(o, v);
}


template <typename Stream>
packer<Stream>& operator<< (packer<Stream>& o, const object& v)
{
	switch(v.type) {
	case type::NIL:
		o.pack_nil();
		return o;

	case type::BOOLEAN:
		if(v.via.boolean) {
			o.pack_true();
		} else {
			o.pack_false();
		}
		return o;

	case type::POSITIVE_INTEGER:
		if(v.via.u64 <= (uint64_t)std::numeric_limits<uint16_t>::max()) {
			if(v.via.u64 <= (uint16_t)std::numeric_limits<uint8_t>::max()) {
				o.pack_uint8(v.via.u64);
			} else {
				o.pack_uint16(v.via.u64);
			}
		} else {
			if(v.via.u64 <= (uint64_t)std::numeric_limits<uint32_t>::max()) {
				o.pack_uint32(v.via.u64);
			} else {
				o.pack_uint64(v.via.u64);
			}
		}
		return o;

	case type::NEGATIVE_INTEGER:
		if(v.via.i64 >= (int64_t)std::numeric_limits<int16_t>::min()) {
			if(v.via.i64 >= (int64_t)std::numeric_limits<int8_t>::min()) {
				o.pack_int8(v.via.i64);
			} else {
				o.pack_int16(v.via.i64);
			}
		} else {
			if(v.via.i64 >= (int64_t)std::numeric_limits<int32_t>::min()) {
				o.pack_int64(v.via.i64);
			} else {
				o.pack_int64(v.via.i64);
			}
		}
		return o;

	case type::RAW:
		o.pack_raw(v.via.raw.size);
		o.pack_raw_body(v.via.raw.ptr, v.via.raw.size);
		return o;

	case type::ARRAY:
		o.pack_array(v.via.array.size);
		for(object* p(v.via.array.ptr),
				* const pend(v.via.array.ptr + v.via.array.size);
				p < pend; ++p) {
			o << *p;
		}
		return o;

	case type::MAP:
		o.pack_map(v.via.map.size);
		for(object_kv* p(v.via.map.ptr),
				* const pend(v.via.map.ptr + v.via.map.size);
				p < pend; ++p) {
			o << p->key;
			o << p->val;
		}
		return o;

	default:
		throw type_error();
	}
}


}  // namespace msgpack

#include "msgpack/type.hpp"

#endif /* msgpack/object.hpp */