// Clemens Groepl, 2009-12-10
// Praxis des Programmierens, WS 2009/10, Uni Greifswald

// An illustration of the factory design pattern

#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
#include <cstdlib>


void parseCmdline ( int argc, char **argv, std::vector<std::string> &args )
{
	args.clear();
	args.reserve(argc);
	for ( int i = 0; i < argc; ++i ) { args.push_back(argv[i]); }
	return;
}


/// Macro to construct an instance of MyException with appropriate context information.
/// __PRETTY_FUNCTION__ is a GNU extension but very useful
#define MYEXCEPTION(a) throw MyException(__FILE__,__LINE__,__PRETTY_FUNCTION__,(a));

/// simple exception class, e.g. throw MYEXCEPTION("bla");
/** This implementation aims to be as simple and useful as possible */
class MyException : public std::exception
{
  std::string message;

public:

	/// Ctor with context info
  MyException( char const * const file, int line, char const * const pretty_function, const std::string& rhs ) : message()
  {
    std::ostringstream msg_buf;
    msg_buf << file;
    msg_buf << ':';
    msg_buf << line;
    msg_buf << ":  ";
    msg_buf << pretty_function;
    msg_buf << ":  ";
    msg_buf << rhs;
    message = msg_buf.str();
  }

	/// Copy ctor
  MyException( const MyException& rhs ) : message(rhs.message) {}

	/// As specified by the C++ standard.
  const char* what() const throw () { return message.c_str(); }

	/// Dtor
  ~MyException() throw () {}

};


/// Base class for binary operations
class BinaryOperation
{
	public:

	/// Applies the binary operation.  Implemented in derived classes.
	virtual double apply ( double lhs, double rhs ) const = 0;

	/// Construct a derived class by its name.
	static BinaryOperation * create ( std::string const & name );

	/// The name of the derived class.
	virtual char const * name () const = 0;

	/// virtual dtor
	virtual ~BinaryOperation() {};

	protected:

		/// Ctor is protected, use create() instead to get instances of derived classes.
		BinaryOperation() {}

		/// "declared away" (intentionally not implemented)
		BinaryOperation( const BinaryOperation & );

};

/// An adder, derived from BinaryOperation.  It is "registered" in the BinaryOperation::create() method.
class Adder : public BinaryOperation
{
	public:

	/// add the numbers
	double apply ( double lhs, double rhs ) const
	{
		return lhs + rhs;
	}

	/// returns a new instance of this class
	static BinaryOperation * create () { return new Adder(); }

	/// the name of this class
	char const * name () const { return "add"; }

};

/// A multiplier, derived from BinaryOperation.  It is "registered" in the BinaryOperation::create() method.
class Multiplier : public BinaryOperation
{
	public:

	/// multiply the numbers
	double apply ( double lhs, double rhs ) const
	{
		return lhs * rhs;
	}

	/// returns a new instance of this class
	static BinaryOperation * create () { return new Multiplier(); }

	/// the name of this class
	char const * name () const { return "multiply"; }

};

/// Use this to get instances of classes derived from BinaryOperation.
BinaryOperation * BinaryOperation::create( std::string const & name )
{

	// if ( name = "add" ) { return an instance of Adder; }
	// else if ( name = "multiply" ) { return an instance of Multiplier; }
	// else { error; }

	typedef std::map < std::string, BinaryOperation*(*)() > CreatorMapType;
	static CreatorMapType	creators;

	// take care that the creator map is initialized
	if ( creators.empty() )
	{
		// Note: when you have implemented yet another class, you need to add exactly 1 line here!
		creators[Adder().name()] = &Adder::create;
		creators[Multiplier().name()] = &Multiplier::create;
	}

	// find the creator by name
	CreatorMapType::const_iterator c = creators.find(name);
	if ( c == creators.end() )
	{
		MYEXCEPTION( std::string("I don't know how to '") + name + "'.");
		return 0;
	}

	// call the creator	
	return c->second();
}


int main ( int argc, char ** argv )
{

	std::vector<std::string> args;
	parseCmdline ( argc, argv, args );

	if ( args.size() < 4 )
	{
		MYEXCEPTION("Usage: " + args[0] + " <op> <number> <number>");
		return 1;
	}

	BinaryOperation * binop = BinaryOperation::create( args[1] );
	
	std::cout << "binop: " << binop << std::endl;

	double result = binop->apply(atof(args[2].c_str()), atof(args[3].c_str()));

	std::cout << binop->name() << ' ' << args[2] << ' ' << args [3] << ":\n";

	std::cout << "result: " << result << std::endl;

	delete binop;

	return 0;
}






