/**
* @file		pinimpl.h
* @brief	Helper classes to implement pins
* @author	Cesar Mauri Loba (cesar at crea-si dot com)
*
* -------------------------------------------------------------------------
*
* Copyright:   (C) 2010 Cesar Mauri Loba - CREA Software Systems
* 
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPCORE_PINIMPL_H
#define SPCORE_PINIMPL_H

#include "spcore/basetype.h"
#include "spcore/pin.h"
#include "spcore/coreruntime.h"

#include <string>
#include <vector>
#include <algorithm>
#include <boost/thread/shared_mutex.hpp>
#include <boost/thread/locks.hpp>

namespace spcore {


// Forward declaration for COutputPin
template<class> class SimpleType;

/**
	@brief Adapter class to help to implement input pin classes.
*/
class CInputPinAdapter : public IInputPin {
protected:
	/**
		@brief Constructor
		@param name Name the input pin will be given.
		@param type Type name of the input pin.

		If the type does not exists throws an exception.
	*/
	CInputPinAdapter(const char * name, const char * type) {
		m_name= name;
		m_typeID= getSpCoreRuntime()->ResolveTypeID(type);
		assert (m_typeID!= TYPE_INVALID);
		if (m_typeID== TYPE_INVALID)
			throw std::runtime_error("type not found while constructing input pin");
	}

	virtual ~CInputPinAdapter() {}

public:
	virtual int GetTypeID() const { return m_typeID; }

	virtual const char* GetName() const { return m_name.c_str(); }

	virtual int Send(SmartPtr<const CTypeAny> message) { return -1; }

	virtual SmartPtr<const CTypeAny> Read() const { return SmartPtr<const CTypeAny>(); }

	virtual void Rename (const char * name) { m_name= name;	}

	virtual int ChangeType (const char * type_name) {
		int typeId= getSpCoreRuntime()->ResolveTypeID(type_name);
		if (typeId== TYPE_INVALID) return -2;
		if (m_typeID!= TYPE_ANY && typeId!= m_typeID) return -1;	
		m_typeID= typeId;
		return 0;
	}

private:
	int m_typeID;
	std::string m_name;
};


/**
	@brief General implementation for output pins.
*/
class COutputPin : public IOutputPin {
	template <class, class> friend class SimpleTypeBasicOperations;
	friend class CCoreRuntime;

protected:
	/**
		@brief Constructor
		@param name Name the output pin will be given.
		@param type Type name of the output pin.

		If the type does not exists throws an exception.
	*/
	COutputPin(const char * name, const char * type) {
		m_name= name;
		m_typeID= getSpCoreRuntime()->ResolveTypeID(type);
		assert (m_typeID!= TYPE_INVALID);
		if (m_typeID== TYPE_INVALID)
			throw std::runtime_error("type not found while constructing output pin");
	}
	
	virtual ~COutputPin() {}

public:
   
	virtual int GetTypeID() const { return m_typeID; }
    
	virtual const char* GetName() const { return m_name.c_str(); }
    
    virtual int Connect(IInputPin & consumer) {
		if (!CanConnect(consumer)) return -1;	
		if (std::find<std::vector<IInputPin *>::iterator, IInputPin*> 
			(m_consumers.begin(), m_consumers.end(), &consumer)== m_consumers.end())
			m_consumers.push_back (&consumer);  

		// If pins already connected ignore request
		return 0;
	}

    virtual void Disconnect(const IInputPin & consumer) {
		std::vector<IInputPin*>::iterator it= find (m_consumers.begin(), m_consumers.end(), &consumer);
		if (it!= m_consumers.end()) m_consumers.erase(it);
		// If not connected do nothing
	}

	virtual bool CanConnect(const IInputPin & dst) const {
		// Check that iterator matches with destination pin type		
		if (GetTypeID()== dst.GetTypeID()) return true;
		if (dst.GetTypeID()== TYPE_ANY) return true;
		if (GetTypeID()== TYPE_ANY) return true;
		return false;
	}

	virtual int Send(SmartPtr<const CTypeAny> message) {
		/** @todo check if is really necessary the checks (receiver
			pin will also revise message) */
  		int myType= this->GetTypeID();
		if (myType!= TYPE_ANY && myType!= message->GetTypeID()) return -1;
  		size_t size= m_consumers.size();  	
		IInputPin* dst;
  		for (size_t i= 0; i< size; ++i)	{
  			dst= m_consumers[i];
  			// Pass message when
  			//	- Reveiver pin typeId coincides with message type
  			//	- Receiver pin typeId is ANY
	  
  			int consumer_type= dst->GetTypeID();
  			if (consumer_type== TYPE_ANY || consumer_type== message->GetTypeID())
  				dst->Send(message);
  			// In other cases consumer does not get called
  		}
		return 0;
	}

	virtual unsigned int GetNumComsumers() const { return m_consumers.size(); }

	virtual void Rename (const char * name) { m_name= name; }

	virtual int ChangeType (const char * type_name) {
		int typeId= getSpCoreRuntime()->ResolveTypeID(type_name);
		if (typeId== TYPE_INVALID) return -2;

		if (m_typeID!= TYPE_ANY && typeId!= m_typeID) return -1;
		
		// Is connected to a pin of different type
		std::vector<IInputPin *>::const_iterator it= m_consumers.begin();

		for (; it!= m_consumers.end(); ++it) {
			int consumerType= (*it)->GetTypeID();
			if (consumerType!= TYPE_ANY && typeId!= consumerType) return -1;
		}

		m_typeID= typeId;

		return 0;
	}

protected:
	std::vector<IInputPin *> const * GetConsumers() const { return &m_consumers; }

private:
	int m_typeID;
	std::vector<IInputPin *> m_consumers;
	std::string m_name;	
};

/**
	@brief Thread-safe output pin implementation.
*/
class COutputPinLock : public COutputPin {
	template <class, class> friend class SimpleTypeBasicOperations;
	friend class CCoreRuntime;

protected:
	/**
		@brief Constructor
		@param name Name the output pin will be given.
		@param type Type name of the output pin.

		If the type does not exists throws an exception.
	*/
	COutputPinLock (const char * name, const char * type) 
	: COutputPin(name, type)
	{}
	virtual ~COutputPinLock() { }

	virtual int Connect(IInputPin & consumer) {
		boost::unique_lock< boost::shared_mutex > lock(m_sharedMutex);
		return COutputPin::Connect(consumer);
	}
   
	virtual void Disconnect(const IInputPin & consumer) {
		boost::unique_lock< boost::shared_mutex > lock(m_sharedMutex);
		COutputPin::Disconnect (consumer);
	}

	virtual int Send(SmartPtr<const CTypeAny> message) {
		boost::shared_lock< boost::shared_mutex > lock(m_sharedMutex);
		return COutputPin::Send (message);
	}
private:
	boost::shared_mutex m_sharedMutex;
};

/**
	@brief Template class helper to implement pins that refer to some other
	object (usually the IComponent that contains it).
	@tparam TCOMPONENT Type which this class will hold a pointer to.
*/
template<class TCOMPONENT>
class CInputPinComponentRef : public CInputPinAdapter {
public:
	/**
		@brief Constructor
		@param name Name the pin will be given.
		@param type Type name of the output pin.
		@param component Component to point to.

		If the type does not exists throws an exception.
	*/
	CInputPinComponentRef(const char * name, const char * type, TCOMPONENT & component)
	: CInputPinAdapter(name, type)
	, m_component(&component)
	{}	

protected:
	TCOMPONENT* m_component;
};

/**
	@brief Template class helper to implement a read-only input pins.
	@tparam TDATA Compile time type class of the pin.
	@tparam TCOMPONENT Type which this class will hold a pointer to.
*/
template<class TDATA, class TCOMPONENT>
class CInputPinReadOnly : public CInputPinComponentRef<TCOMPONENT> {
public:
	/**
		@brief Constructor
		@param name Name the pin will be given.
		@param component Component to point to.

		Note that the type of the pin is provided as a template argument.
	*/
	CInputPinReadOnly(const char * name, TCOMPONENT & component) 
	: CInputPinComponentRef<TCOMPONENT> (name, TDATA::getTypeName(), component) 
	{}

	virtual unsigned int GetProperties() const {
		return (unsigned int) IInputPin::ALLOW_READ;
	}

	virtual SmartPtr<const CTypeAny> Read() const {
		return this->DoRead();
	}

protected:
	/**
		@brief Method the derived class should provide to do the actual read.
		@return Smart pointer to a type instance.
	*/
	virtual SmartPtr<TDATA> DoRead() const = 0;
};

/**
	@brief Template class helper to implement a write-only input pins.
	@tparam TDATA Compile time type class of the pin.
	@tparam TCOMPONENT Type which this class will hold a pointer to.
*/
template<class TDATA, class TCOMPONENT>
class CInputPinWriteOnly : public CInputPinComponentRef<TCOMPONENT> {
public:
	/**
		@brief Constructor
		@param name Name the pin will be given.
		@param component Component to point to.

		Note that the type of the pin is provided as a template argument.
	*/
	CInputPinWriteOnly(const char * name, TCOMPONENT & component) 
	: CInputPinComponentRef<TCOMPONENT> (name, TDATA::getTypeName(), component) 
	{}

	virtual unsigned int GetProperties() const {
		return (unsigned int) IInputPin::ALLOW_WRITE;
	}

	virtual int Send(SmartPtr<const CTypeAny> message) {
		// Check message type
		int myType= this->GetTypeID();
		if (myType!= TYPE_ANY && myType!= message->GetTypeID()) return -1;
	  
  		return this->DoSend(*static_cast<const TDATA *>(message.get()));
	}

protected:
	/**
		@brief Method the derived class should provide to do the actual send.
		@param message
		@return 0 -> message successfully sent, -1 -> type mismatch
	*/
	virtual int DoSend(const TDATA & message) = 0;
};

/**
	@brief Template class helper to implement a read-write input pins.
	@tparam TDATA Compile time type class of the pin.
	@tparam TCOMPONENT Type which this class will hold a pointer to.
*/
template<class TDATA, class TCOMPONENT>
class CInputPinReadWrite : public CInputPinComponentRef<TCOMPONENT> {
public:
	/**
		@brief Constructor
		@param name Name the pin will be given.
		@param component Component to point to.

		Note that the type of the pin is provided as a template argument.
	*/
	CInputPinReadWrite(const char * name, TCOMPONENT & component)
	: CInputPinComponentRef<TCOMPONENT> (name, TDATA::getTypeName(), component) 
	{}

	virtual unsigned int GetProperties() const {
		return (unsigned int) (IInputPin::ALLOW_READ | IInputPin::ALLOW_WRITE);
	}

	virtual SmartPtr<const CTypeAny> Read() const {
		return this->DoRead();
	}

	virtual int Send(SmartPtr<const CTypeAny> message){
		// Check message type
		int myType= this->GetTypeID();
		if (myType!= TYPE_ANY && myType!= message->GetTypeID()) return -1;
	  
  		return this->DoSend(*static_cast<const TDATA *>(message.get()));
	}

protected:
	/**
		@brief Method the derived class should provide to do the actual read.
		@return Smart pointer to a type instance.
	*/
	virtual SmartPtr<TDATA> DoRead() const = 0;

	/**
		@brief Method the derived class should provide to do the actual send.
		@param message
		@return 0 -> message successfully sent, -1 -> type mismatch
	*/
	virtual int DoSend(const TDATA & message) = 0;
};

} // namespace spcore
#endif
