/*
 * symbol_or_epsilon.h
 *
 *  Created on: Apr 10, 2013
 *      Author: Jan Travnicek
 */

#ifndef SYMBOL_OR_EPSILON_HPP_
#define SYMBOL_OR_EPSILON_HPP_

#include <alib/tuple>

#include <core/normalize.hpp>

#include <common/DefaultSymbolType.h>

#include <exception/CommonException.h>

namespace common {

/**
 * Represents symbol in an std.
 */
template < class SymbolType = DefaultSymbolType >
class symbol_or_epsilon {
	std::optional < SymbolType > m_symbol;

public:
	/**
	 * Creates new symbol with given name and rank.
	 * @param symbol name of the symbol
	 * @param rank of the symbol
	 */
	explicit symbol_or_epsilon ( SymbolType symbol ) : m_symbol ( std::move ( symbol ) ) {
	}

	explicit symbol_or_epsilon ( ) = default;

	/**
	 * @return name of the symbol
	 */
	const SymbolType & getSymbol ( ) const & {
		if ( ! m_symbol )
			throw exception::CommonException ( "The symbol is epsilon" );
		else
			return m_symbol.value ( );
	}

	/**
	 * @return name of the symbol
	 */
	SymbolType && getSymbol ( ) && {
		if ( ! m_symbol )
			throw exception::CommonException ( "The symbol is epsilon" );
		else
			return std::move ( m_symbol.value ( ) );
	}

	bool is_epsilon ( ) const {
		return ! m_symbol;
	}

	int compare ( const symbol_or_epsilon & other ) const;

	explicit operator std::string ( ) const;

	bool operator < ( const symbol_or_epsilon & other ) const {
		return compare ( other ) < 0;
	}

	bool operator > ( const symbol_or_epsilon & other ) const {
		return compare ( other ) > 0;
	}

	bool operator <= ( const symbol_or_epsilon & other ) const {
		return compare ( other ) <= 0;
	}

	bool operator >= ( const symbol_or_epsilon & other ) const {
		return compare ( other ) >= 0;
	}

	bool operator == ( const symbol_or_epsilon & other ) const {
		return compare ( other ) == 0;
	}

	bool operator != ( const symbol_or_epsilon & other ) const {
		return compare ( other ) != 0;
	}

	symbol_or_epsilon < SymbolType > & operator ++ ( ) {
		++ m_symbol.value ( );

		return *this;
	}
};

template < class SymbolType >
bool operator < ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	if ( first.is_epsilon ( ) )
		return true;

	static ext::compare<SymbolType> comp;
	return comp ( first.getSymbol ( ), second ) < 0;
}

template < class SymbolType >
bool operator > ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	return second < first;
}

template < class SymbolType >
bool operator <= ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	return ! ( first > second );
}

template < class SymbolType >
bool operator >= ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	return ! ( first < second );
}

template < class SymbolType >
bool operator == ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	if ( first.is_epsilon ( ) )
		return false;

	static ext::compare<SymbolType> comp;
	return comp ( first.getSymbol ( ), second ) == 0;
}

template < class SymbolType >
bool operator != ( const symbol_or_epsilon < SymbolType > & first, const SymbolType & second ) {
	return ! ( first == second );
}

template < class SymbolType >
bool operator < ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	if ( second.is_epsilon ( ) )
		return false;

	static ext::compare<SymbolType> comp;
	return comp ( first, second.getSymbol ( ) ) < 0;
}

template < class SymbolType >
bool operator > ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	return second < first;
}

template < class SymbolType >
bool operator <= ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	return ! ( first > second );
}

template < class SymbolType >
bool operator >= ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	return ! ( first < second );
}

template < class SymbolType >
bool operator == ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	if ( first.is_epsilon ( ) )
		return false;

	static ext::compare<SymbolType> comp;
	return comp ( first.getSymbol ( ), second ) == 0;
}

template < class SymbolType >
bool operator != ( const SymbolType & first, const symbol_or_epsilon < SymbolType > & second ) {
	return ! ( first == second );
}

template < class SymbolType >
int symbol_or_epsilon < SymbolType >::compare(const symbol_or_epsilon& other) const {
	if ( ! m_symbol && ! other.m_symbol )
		return 0;
	if ( m_symbol && ! other.m_symbol )
		return 1;
	if ( ! m_symbol && other.m_symbol )
		return -1;

	static ext::compare<SymbolType> comp;
	return comp ( m_symbol.value ( ), other.m_symbol.value ( ) );
}

template < class SymbolType >
symbol_or_epsilon < SymbolType >::operator std::string () const {
	if ( ! m_symbol )
		return "#E";
	else
		return ext::to_string ( m_symbol.value ( ) );
}

template < class SymbolType >
std::ostream & operator << ( std::ostream & out, const common::symbol_or_epsilon < SymbolType > & symbol ) {
	out << "(symbol_or_epsilon ";
	if ( symbol.is_epsilon ( ) )
		out << "#E";
	else
		out << symbol.getSymbol ( );
	out << ")";
	return out;
}

} /* namespace common */

namespace ext {

template < class T >
struct compare < common::symbol_or_epsilon < T > > {
	int operator ()( const common::symbol_or_epsilon < T > & first, const common::symbol_or_epsilon < T > & second ) const {
		return first.compare ( second );
	}

};

} /* namespace ext */

#endif /* SYMBOL_OR_EPSILON_HPP_ */
