#ifndef OBJECT_H
#define OBJECT_H

#include <string>
#include <memory>
#include <boost/intrusive_ptr.hpp>
#include <iostream>

#include "util/math/log-double.H"
#include "computation/type_constant.H"

class Object 
{
    mutable int _refs = 0; 

    friend inline void intrusive_ptr_release(Object* pThis)
	{
	    if (--pThis->_refs == 0 ) { 
		delete pThis;
	    }
	}
  
    friend inline void intrusive_ptr_add_ref(Object* pThis)
	{
	    pThis->_refs++;
	}
  
    friend inline void intrusive_ptr_release(const Object* pThis)
	{
	    if(--const_cast<Object*>(pThis)->_refs == 0 ) { 
		delete const_cast<Object*>(pThis);
	    }
	}
  
    friend inline void intrusive_ptr_add_ref(const Object* pThis)
	{
	    const_cast<Object*>(pThis)->_refs++;
	}

public:

    virtual Object* clone() const =0;

    virtual bool operator==(const Object& O) const
	{
	    if (this == &O)
		return true;

	    if (typeid(*this) != typeid(O)) return false;

	    std::abort();
	}

    bool operator !=(const Object& o) const
	{
	    return not operator==(o);
	}

    int ref_count() const {return _refs;}

    virtual type_constant type() const {return unknown_type;}

    virtual std::string print() const;

    Object& operator=(const Object&) {return *this;}

    Object() {}
    Object(const Object&):_refs(0) {}
    virtual ~Object() {}
    void swap(Object&) {}
};

// What type of smart pointer are we using to point to Objects?
template <typename T>
using object_ptr = boost::intrusive_ptr<T>;

template<typename T>
class Box: public Object, public T
{
public:
    Box<T>* clone() const {return new Box<T>(*this);}

    Box<T>& operator=(const Box<T>& t) = default;
    Box<T>& operator=(Box<T>&& t) = default;

    Box<T>& operator=(const T& t)
	{
	    T::operator=(t);
	    return *this;
	}

    Box<T>& operator=(T&& t)
	{
	    T::operator=(std::move(t));
	    return *this;
	}

    std::string print() const {return Object::print();}

    const T& value() const {return *(const T*)this;}

    using T::T;

    Box() = default;
    Box(const Box<T>&) = default;
    Box(Box<T>&&) = default;
    Box(const T& t):T(t) {}
    Box(T&& t):T(std::move(t)) {}
};

template<typename T>
using PtrBox = Box<std::shared_ptr<const T>>;

/***************** Make log_double_t count as an arithmetic type **************/

typedef Box<std::string> String;

template<> 
inline std::string Box<std::string>::print() const
{
    return "\""+(*this)+"\"";
}

#include "util/matrix.H"

template<> std::string Box<Matrix>::print() const;

template <typename T>
bool unshare(object_ptr<T>& ptr)
{
    if (ptr.unique()) return false;

    ptr = object_ptr<T>(ptr->clone());

    return true;
}

template <typename T>
using Vector = Box<std::vector<T>>;

template <typename T1,typename T2>
using Pair = Box<std::pair<T1,T2>>;

template <typename T>
object_ptr<T> ptr(const T& t) {return object_ptr<T>(t.clone());}
template <typename T>
object_ptr<const T> const_ptr(const T& t) {return object_ptr<const T>(t.clone());}

template<typename T>
inline std::ostream& operator<<(std::ostream& o,const object_ptr<T>& R)
{
    if (R)
	return o<<R->print();
    else
	return o<<"[NULL]";
}

inline std::ostream& operator<<(std::ostream& o,const Object& R)
{
    return o<<R.print();
}

// In util.cc
std::string demangle(const std::string&);

template <typename T>
const T* convert_and_check(const Object* o)
{
    assert(o);
    const T* converted = dynamic_cast<const T*>(o);
    if (not converted)
	throw myexception()<<"Cannot convert '"<<o->print()<<"' from type "<<demangle(typeid(*o).name())<<" to type "<<demangle(typeid(T).name());
    return converted;
}

template <typename T1, typename T2>
std::shared_ptr<const T2> poly_convert_and_check(const Object* o)
{
    auto* converted1 = convert_and_check<PtrBox<T1>>(o);
    std::shared_ptr<const T2> converted2 = std::dynamic_pointer_cast<const T2>(*converted1);
    if (not converted2)
	throw myexception()<<"Cannot convert '"<<o->print()<<"' from type "<<demangle(typeid(T1).name())<<" to type "<<demangle(typeid(T2).name());
    return converted2;
}

template <typename T>
object_ptr<T> convert_and_check(const object_ptr<const Object>& o)
{
    assert(o);
    object_ptr<T> converted =  boost::dynamic_pointer_cast<const T>(o);
    if (not converted)
    {
	const Object* oo = o.get();
	throw myexception()<<"Cannot convert '"<<o->print()<<"' from type "<<demangle(typeid(*oo).name())<<" to type "<<demangle(typeid(T).name());
    }
    return converted;
}

template <typename T1, typename T2>
std::shared_ptr<const T2> poly_convert_and_check(const object_ptr<const Object>& o)
{
    assert(o);
    auto* converted1 = convert_and_check<PtrBox<T1>>(o);
    std::shared_ptr<const T2> converted2 = std::dynamic_pointer_cast<const T2>(*converted1);
    if (not converted2)
    {
	throw myexception()<<"Cannot convert '"<<o->print()<<"' from type "<<demangle(typeid(T1).name())<<" to type "<<demangle(typeid(T2).name());
    }
    return converted2;
}

template <typename T>
const T* convert(const Object* o)
{
#ifdef NDEBUG
    return static_cast<const T*>(o);
#else
    return convert_and_check<T>(o);
#endif
}

template <typename T1, typename T2>
std::shared_ptr<const T2> poly_convert(const Object* o)
{
#ifdef NDEBUG
    auto* converted1 = convert<PtrBox<T1>>(o);
    return std::static_pointer_cast<const T2>(*converted1);
#else
    return poly_convert_and_check<T1,T2>(o);
#endif
}

template <typename T>
object_ptr<T> convert(const object_ptr<const Object>& o)
{
#ifdef NDEBUG
    return boost::static_pointer_cast<T>(o);
#else
    return convert_and_check<T>(o);
#endif
}

template <typename T1, typename T2>
std::shared_ptr<T2> poly_convert(const object_ptr<const Object>& o)
{
#ifdef NDEBUG
    std::shared_ptr<const T1> converted1 = convert_and_check<PtrBox<T1>>(o);
    std::shared_ptr<const T2> converted2 = std::static_pointer_cast<const T2>(converted1);
    return converted2;
#else
    return poly_convert_and_check<T1,T2>(o);
#endif
}

#endif
