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

#pragma once

#include <optional>

#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;
	}

	explicit operator std::string ( ) const;

	auto operator <=> ( const symbol_or_epsilon & other ) const {
		return m_symbol <=> other.m_symbol;
	}

	bool operator == ( const symbol_or_epsilon & other ) const {
		return m_symbol == other.m_symbol;
	}

	auto operator <=> ( const SymbolType & other ) const {
		return m_symbol <=> other;
	}

	bool operator == ( const SymbolType & other ) const {
		return m_symbol == other;
	}

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

		return *this;
	}
};

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 */

