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

#pragma once

#include <alib/variant>
#include <object/AnyObject.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 ( ) );
	}

	/**
	 * The three way comparison implementation
	 *
	 * \param other the other object to compare with.
	 *
	 * \returns the strong ordering between this object and the @p other.
	 */
	std::strong_ordering operator <=> ( const Object & other ) const {
		if ( this->m_data.get ( ) == other.m_data.get ( ) )
			return std::strong_ordering::equal;

		std::strong_ordering res = ( * this->m_data ) <=> ( * other.m_data );
		if ( res == 0 )
			const_cast < Object * > ( this )->unify ( const_cast < Object & > ( other ) );

		return res;
	}

	/**
	 * The equality comparison implementation.
	 *
	 * \param other the other object to compare with.
	 *
	 * \returns true if this and other objects are equal, false othervise
	 */
	bool operator ==( const Object & other ) const {
		if ( this->m_data.get ( ) == other.m_data.get ( ) )
			return true;

		bool res = ( * this->m_data ) == ( * other.m_data );
		if ( res )
			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 */

