// Copyright 2016 CERN and GSI
//
// 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/>.

#include <silecs-communication/interface/equipment/SilecsPLC.h>

#include <silecs-communication/interface/core/Diagnostic.h>
#include <silecs-communication/interface/core/SilecsService.h>
#include <silecs-communication/interface/core/WrapperAction.h>
#include <silecs-communication/interface/equipment/SilecsCluster.h>
#include <silecs-communication/interface/equipment/SilecsDevice.h>
#include <silecs-communication/interface/equipment/SilecsBlock.h>
#include <silecs-communication/interface/equipment/SilecsRegister.h>
#include <silecs-communication/interface/equipment/PLCBlock.h>
#include <silecs-communication/interface/equipment/PLCRegister.h>
#include <silecs-communication/interface/utility/SilecsException.h>
#include <silecs-communication/interface/utility/SilecsLog.h>
#include <silecs-communication/interface/utility/StringUtilities.h>
#include <silecs-communication/interface/utility/XMLParser.h>

//Supported protocol
#include <silecs-communication/interface/communication/MBConnection.h>
#include <silecs-communication/interface/communication/SNAP7Connection.h>

#include <silecs-communication/interface/communication/CNVConnection.h>
#include <silecs-communication/interface/equipment/CNVBlock.h>

#include <fstream>

namespace Silecs
{
        connMapType PLC::connMap_;
		PLC::PLC(Cluster* theCluster, std::string plcName, std::string plcIPaddr, string parameterFile) :
				theCluster_ (theCluster), IPaddr_(plcIPaddr), parameterFile_(parameterFile)
		{
			//Force PLC hostname lower-case
			name_ = plcName;
			StringUtilities::toLower(name_);

			LOG(ALLOC) << "PLC (create): " << name_ << "/ " << IPaddr_;

			// Upload the parameters file and build the PLC configuration
			extractDatabase();

			 //Do not synchronize retentive Registers by default (overwritten at connection time)
			synchroMode_  = NO_SYNCHRO;

			// Start the PLC Thread
			pThread_ = new Thread<WrapperAction>();

			// create default contexts for all-devices transaction (normal and for synchronization)
			allDevicesTransaction_ 		= new Context(NULL/*no Device: for all devices)*/, false);
			allDevicesSynchronization_	= new Context(NULL/*no Device: for all devices)*/, true);

            // Instantiate the communication interface
			plcConn_ = NULL;
			isSharedConn_ = false; // No shared connection by default and for CNV protocol (PXI platform).
		}


		PLC::~PLC()
		{
			LOG(ALLOC) << "PLC (delete): " << name_;

            //Before removing the PLC resources, we need to interrupt all the related accesses (if connection is not shared)
			if (isSharedConn_ == false)	disconnect();

            // Remove PLC communication resources
            if (pThread_ != NULL) { delete pThread_; pThread_ = NULL; }
            if ((plcConn_ != NULL) && (isSharedConn_ == false)) { delete plcConn_; plcConn_ = NULL; }

			//theHeader object and the shared-connection objects (if any) must not be delete from the PLC since
            //it is shared between all the PLC instances. It will be removed by the Service destructor at the end.

			delete allDevicesTransaction_;
			delete allDevicesSynchronization_;

			// Remove the Device instances related to this PLC
			deviceVectorType::iterator pDeviceIter;
			for(pDeviceIter = deviceCol_.begin(); pDeviceIter != deviceCol_.end(); ++pDeviceIter) delete pDeviceIter->second;
			deviceCol_.clear();

			// Remove the Block instances related to this PLC
			blockVectorType::iterator pBlockIter;
			for(pBlockIter = blockCol_.begin(); pBlockIter != blockCol_.end(); ++pBlockIter)
			{	delete *pBlockIter;
			}
		}

		void PLC::connect(SynchroMode synchroMode, bool connectNow, bool compareChecksums)
		{
		    //At first, Instantiate the PLC connection object if needed
			LOG((COMM|DIAG)) << "Attempt to establish connection ... ";
		    /*plcConn_ =*/ getConnection();
			//Check PLC is not already Enabled
			std::string connStr = name_ + "(" + IPaddr_ + ")" + ":" + theCluster_->getClassName() + "/v" + theCluster_->getClassVersion();
			LOG(ERROR) << connStr;

	        if ( (connectNow == false) && plcConn_->isEnabled() )
	        {   //Trying to connect a connected PLC should not be necessary an ERROR
				//throw SilecsException(__FILE__, __LINE__, COMM_ALREADY_ENABLED, connStr);
	        	LOG(ERROR) << "This PLC connection is already enabled: " << connStr;
	        	return;
	        }

	        LOG(COMM) << (connectNow ? "Open" : "Enable") << " Connection request for " << connStr;

			//Store the synchonization mode for this particular PLC/Cluster connection request (can change at any connect() call.
            synchroMode_  = synchroMode;

			// Whatever the outcome of the connection that will follow, the PLC must be Enabled in any case.
			// Thus, even if the PLC is off and rejects the first connection to upload the Header (see updateHeader),
			// we will automatically attempt to reconnect later at the next PLC accesses (send/recv) ... again and again.
			plcConn_->enable(this, connectNow);

			if( compareChecksums )
			{
		        /* PLC memory could have change during a disconnection time (downloading or other).
		           The Header registers must be reloaded for consistency checking.
		           Do not continue the connection sequence if it is not even possible to load the header.
				*/
				LOG(COMM) << "getting checksum";
				remoteChecksum_ = theHeader_->getRegister("_checksum")->getValULong();
				if (remoteChecksum_ != localChecksum_)
				{   //Client and PLC mapping are not consistent ... no transaction are possible!
					disconnect();
					std::ostringstream message;
					message << "Checksum comparison failed. Checksum in parameter file: '" << localChecksum_ << "' Checksum in PLC: '" << remoteChecksum_<<  "' Disconnecting PLC now.";
					throw SilecsException(__FILE__, __LINE__, CONFIG_CLIENT_PLC_NOT_CONSISTENT, message.str());
				}
			}
		};


		void PLC::disconnect()
		{
		    LOG(COMM) << "Disconnection request for " << name_ << ":" << theCluster_->getClassName() << "/v" << theCluster_->getClassVersion();
			//close the connection (also close the connection if shared with other cluster on the same PLC!)
			getConnection()->disable(this);
            synchroMode_  = NO_SYNCHRO; //Do not synchronize retentive Registers by default
		};

		bool PLC::isEnabled() { return ((plcConn_ == NULL) ? false : plcConn_->isEnabled()); /*do not use getConnection() here*/}
		bool PLC::isConnected(bool connectNow)
		{
			return (connectNow ? getConnection()->doOpen(this): getConnection()->isConnected());
		}


		unsigned int PLC::getThreadPriority()
		{
			LOG(SETUP) << "Thread RT-priority of PLC " << name_ << " is: " << pThread_->getPriority();
			return pThread_->getPriority();
		}


		void PLC::setThreadPriority(unsigned int rtprio)
		{
			LOG(SETUP) << "Set Thread RT-priority of PLC " << name_ << ": " << rtprio;
			return pThread_->setPriority(rtprio);
		}


		const std::string PLC::getParamsFileName()
		{
			return parameterFile_;
		}


		Device* PLC::getDevice(std::string deviceName)
		{
			deviceVectorType::iterator iter;

			for(iter = deviceCol_.begin(); iter != deviceCol_.end(); ++iter)
			{
				if (DEBUG & Log::topics_) LOG(COMM) << "getDevice - deviceName: '" << iter->first <<  "'";
			}

			for(iter = deviceCol_.begin(); iter != deviceCol_.end(); ++iter)
			{
				if (iter->first == deviceName)
				{
					return iter->second;
				}
			}

	        throw SilecsException(__FILE__, __LINE__, PARAM_UNKNOWN_DEVICE_NAME, deviceName+"/"+name_);
	        return NULL;//to suppress warning
		}


		std::vector<std::string> PLC::getDeviceList()
		{
			std::vector<std::string> deviceList;
			deviceVectorType::iterator pDeviceIter;
			for(pDeviceIter = deviceCol_.begin(); pDeviceIter != deviceCol_.end(); ++pDeviceIter)
			{
				deviceList.push_back(pDeviceIter->first);
			}
			return deviceList;
		}


		std::string PLC::getName() { return name_; }
		std::string PLC::getIPAddress() { return IPaddr_; }
		std::string PLC::getDomain() { return domain_; }
		std::string PLC::getBrand() { return brand_; }
        std::string PLC::getSystem() { return system_; }
		std::string PLC::getModel() { return model_; }
		std::string PLC::getProtocolType() { return protocolType_; }
		std::string PLC::getProtocolMode() { return protocolMode_; }
		unsigned long PLC::getBaseAddress() { return baseAddr_; }

		deviceVectorType& PLC::getDeviceMap() { return deviceCol_; }
		std::string PLC::getLocalRelease() { return localRelease_; }
		std::string PLC::getLocalOwner() { return localOwner_; }
		std::string PLC::getLocalDate() { return localDate_; }
		unsigned long PLC::getLocalChecksum() { return localChecksum_; }

		void PLC::updateLocalData()
		{
			blockVectorType::iterator blockIter;
			for (blockIter = blockCol_.begin();blockIter!= blockCol_.end(); blockIter ++)
			{
				if ((*blockIter)->hasInputAccess())
				{
					deviceVectorType::iterator pDeviceIter;
					for(pDeviceIter = deviceCol_.begin(); pDeviceIter != deviceCol_.end(); ++pDeviceIter)
					{
						if( pDeviceIter->second->hasBlock((*blockIter)->getName()))
						{
							LOG(COMM) << "Updating block '" << (*blockIter)->getName() << "' for device '" << pDeviceIter->second->getLabel()<< "'";
							pDeviceIter->second->recv((*blockIter)->getName());
						}
					}
				}
			}
		}

        int PLC::recvUnitCode(UnitCodeType& dataStruct)
        {
            return getConnection()->readUnitCode(this, dataStruct);
        }


        int PLC::recvUnitStatus(UnitStatusType& dataStruct)
        {
            return getConnection()->readUnitStatus(this, dataStruct);
        }


        int PLC::recvCPUInfo(CPUInfoType& dataStruct)
        {
            return getConnection()->readCPUInfo(this, dataStruct);
        }


        int PLC::recvCPInfo(CPInfoType& dataStruct)
        {
            return getConnection()->readCPInfo(this, dataStruct);
        }


		int PLC::recv(std::string blockName)
		{
			//Synchronous data receive

			//Execute the receive action for all the PLC devices (from the current thread)
			return getBlock(blockName, Input)->getTask()->execute(allDevicesTransaction_);
		}


		int  PLC::send(std::string blockName)
		{
			//Synchronous data send

			//Execute the send action for all the PLC devices (from the current thread)
			return getBlock(blockName, Output)->getTask()->execute(allDevicesTransaction_);
		}


		void PLC::recvAsync(std::string blockName)
		{
			//Asynchronous data receive (used from Cluster level only)

		    //Schedule task using asynchronous call for parallelism (submit to thread)
			pThread_->schedule(getBlock(blockName, Input)->getTask(), allDevicesTransaction_);
		}


		void PLC::sendAsync(std::string blockName)
		{
			//Asynchronous data send (used from Cluster level only)

		    //Schedule task using asynchronous call for parallelism (submit to thread)
			pThread_->schedule(getBlock(blockName, Output)->getTask(), allDevicesTransaction_);
		}

		void PLC::waitAsync()
		{
			pThread_->waitForTaskCompletion();
		}

		void PLC::extractDatabase()
		{
			LOG(SETUP) << "Extract SILECS configuration from: " << parameterFile_;
			XMLParser xmlParser(parameterFile_,true);

			// Extract general information ===========================================
			ElementXML rootNode = xmlParser.getFirstElementFromXPath("/SILECS-Param");
			localRelease_ = rootNode.getAttribute("silecs-version");

			ElementXML ownerNode = xmlParser.getFirstElementFromXPath("/SILECS-Param/Mapping-Info/Owner");
			localOwner_ = ownerNode.getAttribute("user-login");

			ElementXML generationNode = xmlParser.getFirstElementFromXPath("/SILECS-Param/Mapping-Info/Generation");
			localDate_ = generationNode.getAttribute("date");

			ElementXML deploymentNode = xmlParser.getFirstElementFromXPath("/SILECS-Param/Mapping-Info/Deployment");
			StringUtilities::fromString(localChecksum_,deploymentNode.getAttribute("checksum") );

			// Extract PLC general configuration ======================================
			ElementXML mappingNode = xmlParser.getFirstElementFromXPath("/SILECS-Param/SILECS-Mapping");
			//name_ = pEl->getStringAttribute("name"); already done before
			domain_ = mappingNode.getAttribute("domain");
			brand_ = mappingNode.getAttribute("plc-brand");
            system_ = mappingNode.getAttribute("plc-system");
            protocolMode_ = mappingNode.getAttribute("protocol");
			brandID_ = whichPLCBrand(brand_);
            systemID_ = whichPLCSystem(system_);
			protocolModeID_ = whichProtocolMode(protocolMode_);
			protocolTypeID_ = whichProtocolType(system_);

			switch(protocolTypeID_)
			{
				case MBProtocol: 	protocolType_ = "MODBUS-TCP"; break;
				case S7Protocol: protocolType_ = "SNAP7-TCP"; break;
				case CNVProtocol: 	protocolType_ = "CNV-TCP"; break;
			};

			model_ = mappingNode.getAttribute("plc-model");
            modelID_ = whichPLCModel(model_);
			StringUtilities::fromString(baseAddr_,mappingNode.getAttribute("address") );
			usedMem_ = mappingNode.getAttribute("used-mem");

			std::vector< boost::shared_ptr<ElementXML> > classNodes = mappingNode.childList_;
			std::vector< boost::shared_ptr<ElementXML> >::const_iterator classIter;
			for(classIter = classNodes.begin(); classIter != classNodes.end(); classIter++)
			{
				std::string className = (*classIter)->getAttribute("name");

				boost::ptr_vector<ElementXML> blockNodes = xmlParser.getElementsFromXPath_throwIfEmpty("/SILECS-Param/SILECS-Mapping/SILECS-Class[@name='"+ className + "']/Block");
				boost::ptr_vector<ElementXML>::const_iterator blockIter;

				boost::ptr_vector<ElementXML> instanceNodes = xmlParser.getElementsFromXPath_throwIfEmpty("/SILECS-Param/SILECS-Mapping/SILECS-Class[@name='"+ className + "']/Instance");
				boost::ptr_vector<ElementXML>::const_iterator instanceIter;
				for(instanceIter = instanceNodes.begin(); instanceIter != instanceNodes.end(); instanceIter++)
				{
					std::string deviceName = (*instanceIter).getAttribute("label");
					std::string deviceAddress = (*instanceIter).getAttribute("address");
					Device* pDevice = new Device(this, deviceName, deviceAddress, blockNodes);
					deviceCol_.push_back(std::make_pair(deviceName, pDevice));
					if(deviceName == "SilecsHeader")
						theHeader_ = pDevice;
				}

				for(blockIter = blockNodes.begin(); blockIter != blockNodes.end(); blockIter++)
				{
					std::string blockName = (*blockIter).getAttribute("name");
					AccessType accessType = Block::whichAccessType((*blockIter).getAttribute("mode"));
					LOG((DIAG)) << "The block '" << blockName << " will be created with access-type: '" << accessType << "'.";

					Block* pBlock = 0;
					// Instantiate Input blocks ------------------------------------------------------
					if (Service::withInputAccess(accessType)) //Input or InOut access
					{  	//Instantiate the block, forcing it to Input access for InOut case

						if(whichPLCBrand(brand_)==Ni)
#ifdef NI_SUPPORT_ENABLED
							pBlock = new CNVInputBlock(this, (*pBlockElCol)[i], accessType);
#else
							throw SilecsException(__FILE__, __LINE__, "Support for NI-Devices is disabled");
#endif
						else
							pBlock = new InputBlock(this,*blockIter, Input);
						blockCol_.push_back(pBlock); // FIXME: Currently a block is instatiated twice if it is READ+WRITE

					}
					// Instantiate Output blocks ------------------------------------------------------
					if (Service::withOutputAccess(accessType))  //Output or InOut accessprotocol
					{   //Instantiate the block, forcing it to Output access for InOut case


						if(whichPLCBrand(brand_)==Ni)
#ifdef NI_SUPPORT_ENABLED
							pBlock = new CNVOutputBlock(this, (*pBlockElCol)[i], accessType);
#else
						throw SilecsException(__FILE__, __LINE__, "Support for NI-Devices is disabled");
#endif
						else
							pBlock = new OutputBlock(this, *blockIter, Output);
						blockCol_.push_back(pBlock); // FIXME: Currently a block is instatiated twice if it is READ+WRITE
					}



				} //for blocks
			} //for classes
		}


        Connection* PLC::createConnection()
        {
            Connection* theConn = NULL;
            try
            {
                switch(protocolTypeID_)
                {
                    case S7Protocol:
                        theConn = new SNAP7Connection(this); break;
                    case MBProtocol:
                        theConn = new MBConnection(this); break;
                    case CNVProtocol:
#ifdef NI_SUPPORT_ENABLED
                    	theConn = new CNVConnection(this);break;
#else
                    	throw SilecsException(__FILE__, __LINE__, "Support for NI-Devices is disabled");
#endif
                    default:
                    	throw SilecsException(__FILE__, __LINE__, "Unknown protocolTypeID_");
                }
            }
            catch (const SilecsException& ex)
            {   //Something has failed. Nothing to do, we trust the re-open mechanism from now.
            	LOG(ERROR) << ex.what();
            	throw ex;
            }

            if (DEBUG & Log::topics_) LOG(COMM) << "PLC::createConnection(): " << connID_ << ", isShared: " << (isSharedConn_? "true":"false");
            return theConn;
        }


        Connection* PLC::getConnection()
        {
            if (plcConn_ == NULL)
            {
            	LOG((COMM|DIAG)) << "No connection found. Attempt to establish connection ... ";
                //Connection not instantiated yet. Check if it will be shared or not?
                if (protocolTypeID_ != CNVProtocol)
                {
                    /*TODO: get from the param file (extractDatabase): Major release??*/
                    isSharedConn_ =  theCluster_->configuration.sharedConnection;
                }
                connID_ = (isSharedConn_) ? name_+"SharedConnection" : name_+theCluster_->getClassName();

                if (isSharedConn_)
                {
                	LOG((COMM|DIAG)) << "Creating shared connection";
                    //Create/re-used the shared connection
                    connMapType::iterator iter = connMap_.find(connID_);
                    if (iter == connMap_.end()) //one shared-connection per PLC
                    {   Connection* theConn = createConnection();
                        connMap_.insert(std::make_pair(connID_, theConn));
                        plcConn_ = theConn;
                    }
                    else
                    {   plcConn_ = iter->second;
                    }
                }
                else
                {   //Create the specific PLC/Cluster connection
                	LOG((COMM|DIAG)) << "Creating non-shared connection";
                    plcConn_ = createConnection();
                }
            }
            return plcConn_;
        }


        void PLC::deleteConnection()
        {
            // Remove all shared-connection if any (one per PLC)
            connMapType::iterator iter;
            for(iter = connMap_.begin(); iter != connMap_.end(); ++iter)
            {   delete iter->second;
            }
            connMap_.clear();
        }


        Block* PLC::getBlock(const std::string blockName, AccessType accessType)
		{
        	blockVectorType::iterator block;
        	for( block =  blockCol_.begin(); block != blockCol_.end(); block++)
        	{
        		if( (*block)->getName() == blockName && (*block)->getAccessType() == accessType)
            			return *block;
        	}
        	std::ostringstream error;
        	error << "The block '" << blockName << "' was not found for the access type: '" << Block::whichAccessType(accessType) << "'";
			throw SilecsException(__FILE__, __LINE__, error.str());
		}


		blockVectorType& PLC::getBlockCol() { return blockCol_; }


		PLCBrand PLC::whichPLCBrand(std::string brand)
		{
			StringUtilities::toLower(brand);
			if (brand == "siemens")			return Siemens;
			else if (brand == "schneider")	return Schneider;
            else if (brand == "beckhoff")   return Beckhoff;
			else if (brand == "rabbit")		return Digi;
			else if (brand == "ni")			return Ni;
			else throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PLC_MANUFACTURER, brand);
		}

        PLCModel PLC::whichPLCModel(std::string model)
        {
            StringUtilities::toLower(model);
            if (model == "simatic_et-200s")    return ET200S;
            else if (model == "simatic_s7-300")    return S7300;
            else if (model == "simatic_s7-400")  return S7400;
            else if (model == "simatic_s7-1200")   return S71200;
            else if (model == "simatic_s7-1500")         return S71500;
            else if (model == "premium")         return Premium;
            else if (model == "quantum")         return Quantum;
            else if (model == "m340")         return M340;
            else if (model == "bc9020")         return BC9020;
            else if (model == "cx9020")         return CX9020;
            else if (model == "rcm_4010")         return RCM4010;
            else if (model == "rcm_2000")         return RCM2000;
            else if (model == "compact_rio")         return CompactRIO;
            else if (model == "pxi_rt")             return PXIRT;
            else if (model == "pxi_windows")         return PXIWindows;
            else if (model == "pc_windows")         return PCWindows;
            else if (model == "other_support_cnv")         return OtherSupportCNV;
            else throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PLC_MODEL, model);
        }

	   PLCSystem PLC::whichPLCSystem(std::string system)
       {
	       StringUtilities::toLower(system);
           if (system == "step-7")          return Step7;
           if (system == "tia-portal")      return TiaPortal;
           else if (system == "unity pro")  return Unity;
           else if (system == "twincat")    return TwinCat;
           else if (system == "standard-c") return StdC;
           else if (system == "labview")    return Labview;
           else throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PLC_SYSTEM, system);
       }


		ProtocolType PLC::whichProtocolType(std::string system)
		{
            StringUtilities::toLower(system);
            if (system == "step-7")          return S7Protocol;
            if (system == "tia-portal")      return S7Protocol;
            else if (system == "unity pro")  return MBProtocol;
            else if (system == "twincat")    return MBProtocol;
            else if (system == "standard-c") return MBProtocol;
            else if (system == "labview")    return CNVProtocol;
            else throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PLC_SYSTEM, system);
		}


		ProtocolMode PLC::whichProtocolMode(std::string mode)
		{
			StringUtilities::toLower(mode);
			if (mode == "block_mode") return BlockMode;
			else if (mode == "device_mode") return DeviceMode;
			else throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PROTOCOL_MODE, mode);
		}


		void PLC::updateStatus()
		{
			status_.connStatus_ = (getConnection()->isConnected()) ? Connected : Disconnected;
			status_.plcStatus_ = (getConnection()->isAlive()) ? On : Off;
			//#FLO problem: qui remet Undefined/Unknown pour ces 2 variables le cas echeant?

			if (DEBUG & Log::topics_) LOG(COMM) << "PLC Status:\n\t" <<
				getName() << " mode: " << status_.getPLCStatusAsString() << "\n\t" <<
				theCluster_->getHostName() << "/" << getName() << "/" << theCluster_->getClassName() << " connection: " << status_.getConnectionStatusAsString();
		}

		void PLC::copyInToOut(const std::string blockName)
		{
			deviceVectorType::iterator pDeviceIter;
			for(pDeviceIter = deviceCol_.begin(); pDeviceIter != deviceCol_.end(); ++pDeviceIter)
				pDeviceIter->second->copyInToOut(blockName);
		}

        void PLC::setReadBlockAttributes(const std::string blockName, const unsigned long customAddress, const unsigned long customOffset, const unsigned long customSize)
	    {
            //Set the custom offset/size of the referred Input block if any.
            //Will throw an exception if that block-name does not exist.
            getBlock(blockName, Input)->setCustomAttributes(customAddress, customOffset, customSize);
	    }

        void PLC::resetReadBlockAttributes(const std::string blockName)
        {
            getBlock(blockName, Input)->resetCustomAttributes();
        }

        void PLC::setWriteBlockAttributes(const std::string blockName, const unsigned long customAddress, const unsigned long customOffset, const unsigned long customSize)
        {
            //Set the custom address/offset/size of the referred Output block if any.
            //Will throw an exception if that block-name does not exist.
            getBlock(blockName, Output)->setCustomAttributes(customAddress, customOffset, customSize);
        }

        void PLC::resetWriteBlockAttributes(const std::string blockName)
        {
            getBlock(blockName, Output)->resetCustomAttributes();
        }

} // namespace