/*
 * Object.h
 *
 *  Created on: Apr 10, 2013
 *      Author: Martin Zak
 */

#ifndef OBJECT_H_
#define OBJECT_H_

#include <alib/variant>
#include <object/AnyObject.h>
#include <object/Void.h>

namespace object {

/**
 * Wrapper around object.
 */
class Object {
	/**
	 * The wrapped data.
	 */
	ext::cow_shared_ptr < AnyObjectBase > m_data;

	/**
	 * \brief
	 * Unifies the shared pointers. For internal use when two pointer eval equal.
	 */
	void unify ( Object & other ) {
		if ( this->m_data.use_count ( ) > other.m_data.use_count ( ) )
			other.m_data = this->m_data;
		else
			this->m_data = other.m_data;
	}

	/**
	 * \brief
	 * Helper method to detect variant content and call make method on the object from inside of the variant.
	 */
	template < class Variant >
	static Object processVariant ( Variant && data ) {
		auto visitor = [] ( auto && element ) {
			return Object ( std::forward < decltype ( element ) > ( element ) );
		};

		return ext::visit ( visitor, std::forward < Variant > ( data ) );
	}

	/**
	 * Constructor that wraps raw pointer. Takes ownership of the pointer.
	 */
	explicit Object ( AnyObjectBase * data ) : m_data ( data ) {
	}

	/**
	 * Sets the wrapped object from raw pointer. Takes ownership of the pointer.
	 *
	 * Internally handles situation like storing the same value that is already wrapped. Usefull in normalisation.
	 */
	void setData ( AnyObjectBase * data ) {
		if ( & getData ( ) == data )
			return;

		this->m_data = ext::cow_shared_ptr < AnyObjectBase > ( data );
	}

public:
	/**
	 * \brief
	 * Specialisation of the make method for c-strings.
	 */
	explicit Object ( const char * string ) : Object ( ( std::string ) string ) {
	}

	/**
	 * \brief
	 * Specialisation of the make method for objects that are not from the object hierarchy of Algorithms library
	 */
	template < class Type, typename Enable = std::enable_if < ! std::is_same_v < std::decay_t < Type >, Object >, void > >
	explicit Object ( Type && data ) : Object ( object::AnyObject < typename std::decay < Type >::type > ( std::forward < Type > ( data ) ) ) {
	}

	/**
	 * \brief
	 * Specialisation of the make method for variants.
	 *
	 * \details
	 * The resulting object is not constructed from the variant itself but from the value stored inside. If the value stored inside the variant is variant again, the process is repeated.
	 */
	template < class ... Types >
	explicit Object ( ext::variant < Types ... > && data ) : Object ( processVariant ( std::move ( data ) ) ) {
	}

	/**
	 * \brief
	 * Specialisation of the make method for variants.
	 *
	 * \details
	 * The resulting object is not constructed from the variant itself but from the value stored inside. If the value stored inside the variant is variant again, the process is repeated.
	 */
	template < class ... Types >
	explicit Object ( const ext::variant < Types ... > & data ) : Object ( processVariant ( data ) ) {
	}

	/**
	 * Constructor that wraps an object given by a constant reference. Uses clone of the parameter internally.
	 */
	template < class Type >
	explicit Object ( const AnyObject < Type > & data ) : m_data ( data.clone ( ) ) {
	}

	/**
	 * Constructor that wraps an object given by a reference. Uses clone of the parameter internally.
	 */
	template < class Type >
	explicit Object ( AnyObject < Type > & data ) : m_data ( data.clone ( ) ) {
	}

	/**
	 * Constructor that wraps an object given by an r-value reference. Uses clone of the parameter internally.
	 */
	template < class Type >
	explicit Object ( AnyObject < Type > && data ) : m_data ( std::move ( data ).clone ( ) ) {
	}

	/**
	 * Constructor that wraps an object given by constant reference. Uses clone of the parameter internally.
	 */
	explicit Object ( const AnyObject < object::Object > & data ) : Object ( data.getData ( ) ) {
	}

	/**
	 * Constructor that wraps an object given by r-value reference. Uses clone of the parameter internally.
	 */
	explicit Object ( AnyObject < object::Object > && data ) : Object ( std::move ( data ).getData ( ) ) {
	}

	/**
	 * Gets the wrapped object.
	 *
	 * \returns wrapped object.
	 */
	const AnyObjectBase & getData ( ) const {
		return * m_data;
	}

	/**
	 * Gets the wrapped object.
	 *
	 * \returns wrapped object.
	 */
	AnyObjectBase & getData ( ) {
		return * m_data;
	}

	/**
	 * Sets the wrapped object from constant reference. Uses clone of the parameter internally.
	 */
	void setData ( const AnyObjectBase & data ) {
		setData ( data.clone ( ) );
	}

	/**
	 * Sets the wrapped object from r-value reference. Uses clone of the parameter internally.
	 */
	void setData ( AnyObjectBase && data ) {
		setData ( std::move ( data ).clone ( ) );
	}

	/**
	 * Determines whether this object is bigger or equal in relation to the other object.
	 *
	 * \returns true if this object is bigger or equal than other object are not equal, false othervise
	 */
	bool operator >=( const Object & other ) const {
		return this->compare ( other ) >= 0;
	}

	/**
	 * Determines whether this object is smaller or equal in relation to the other object.
	 *
	 * \returns true if this object is smaller or equal than other object are not equal, false othervise
	 */
	bool operator <=( const Object & other ) const {
		return this->compare ( other ) <= 0;
	}

	/**
	 * Determines whether this object is strictly bigger in relation to the other object.
	 *
	 * \returns true if this object is strictly bigger than other object are not equal, false othervise
	 */
	bool operator >( const Object & other ) const {
		return this->compare ( other ) > 0;
	}

	/**
	 * Determines whether this object is strictly smaller in relation to the other object.
	 *
	 * \returns true if this object is strictly smaller than other object are not equal, false othervise
	 */
	bool operator <( const Object & other ) const {
		return this->compare ( other ) < 0;
	}

	/**
	 * Compares this object with other for inequality.
	 *
	 * \returns true if this and other objects are not equal, false othervise
	 */
	bool operator !=( const Object & other ) const {
		return this->compare ( other ) != 0;
	}

	/**
	 * Compares this object with other for equality.
	 *
	 * \returns true if this and other objects are equal, false othervise
	 */
	bool operator ==( const Object & other ) const {
		return this->compare ( other ) == 0;
	}

	/**
	 * \brief Compares this wrapper with another, establishing relationship between them.
	 *
	 * \returns result of actual comparison of wrapped objects returning:
	 *          value < 0 if this < other
	 *          value == 0 if this == other
	 *          value > 0 if this > other
	 */
	int compare ( const Object & other ) const {
		if ( this->m_data.get ( ) == other.m_data.get ( ) ) return 0;

		int res = ( * this->m_data ).compare ( * other.m_data );

		if ( res == 0 ) const_cast < Object * > ( this )->unify ( const_cast < Object & > ( other ) );

		return res;
	}

	/**
	 * Print the wrapped object as raw representation to ostream.
	 *
	 * \param os ostream where to print
	 * \param instance wrapper to print
	 *
	 * \returns modified output stream
	 */
	friend std::ostream & operator <<( std::ostream & os, const Object & instance ) {
		instance.getData ( ) >> os;
		return os;
	}

	/**
	 * Casts the wrapped object to as compact as possible string representation.
	 *
	 * \returns string representation of the wrapped object
	 */
	explicit operator std::string ( ) const {
		return ( std::string ) * m_data;
	}

	/**
	 * \brief
	 * Increments the unique counter of the object by one. Prefix version.
	 *
	 * \return this instance
	 */
	Object & operator ++ ( ) {
		this->getData ( ).increment ( 1 );
		return *this;
	}

	/**
	 * \brief
	 * Increments the unique counter of the object by one. Postfix version.
	 *
	 * \return this instance
	 */
	Object operator ++ ( int ) {
		Object res = * this;
		++ * this;
		return res;
	}

	/**
	 * \brief
	 * Increments the unique counter of the object.
	 *
	 * \param by how much to increment
	 */
	Object operator += ( unsigned by ) {
		this->getData ( ).increment ( by );
		return *this;
	}

	/**
	 * Getter of unique identifier
	 *
	 * \return the unique identifier
	 */
	unsigned getId ( ) const {
		return this->getData ( ).getId ( );
	}
};

} /* namespace object */

namespace ext {

/**
 * Helper for comparing everything that inherits from WrapperBaseBase.
 */
template < >
struct compare < object::Object > {

	/**
	 * Compare operator that determines relation between first and second object.
	 *
	 * \returns value < 0 if first < second
	 *          value == 0 if first == second
	 *          value > 0 if first > second
	 */
	int operator ()( const object::Object & first, const object::Object & second ) const {
		return first.compare ( second );
	}

};

} /* namespace ext */

#endif /* OBJECT_H_ */