// 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/>.
#ifdef MODBUS_SUPPORT_ENABLED
#include <silecs-communication/interface/utility/SilecsLog.h>
#include <silecs-communication/interface/utility/StringUtilities.h>
#include <silecs-communication/interface/communication/SilecsConnection.h>
#include <silecs-communication/interface/equipment/SilecsPLC.h>
#include <silecs-communication/interface/communication/MBConnection.h>
#include <silecs-communication/interface/utility/SilecsException.h>
#include <silecs-communication/protocol/modbus/iemdb.h>

namespace Silecs
{

	MBConnection::MBConnection(PLC* thePLC) : Connection(thePLC)
	{
		LOG(ALLOC) << "MBConnection (create): " << thePLC->getName();
		readCtx_ = writeCtx_ = NULL;
	}


	MBConnection::~MBConnection()
	{
		//Close the connection before removing resources
		//disable(); must be done before removing resource
	}


	bool MBConnection::open(PLC* thePLC)
	{
		//Open read and write Modbus channels (using IP address to limit the naming-server accesses)
        int readErr  = IeMdbOpen(&readCtx_, NULL, (char *) thePLC->getIPAddress().c_str(), (int)thePLC->getBaseAddress());
        int writeErr = IeMdbOpen(&writeCtx_, NULL, (char *) thePLC->getIPAddress().c_str(), (int)thePLC->getBaseAddress());
        return ((readErr == 0) && (writeErr == 0));
	}


	bool MBConnection::close(PLC* thePLC)
	{
        int err = 0;
        err |= IeMdbClose(readCtx_);
        err |= IeMdbClose(writeCtx_);
        readCtx_ = writeCtx_ = NULL;
        return (err == 0);
	}


	int MBConnection::readData(PLC* thePLC, unsigned long address, unsigned long offset, unsigned long size, unsigned char* pBuffer)
	{
		//Schneider uses 16bit alignment memory. Block address is expressed in bytes, must be an even value!
		if (address % 2) throw SilecsException(__FILE__, __LINE__, PARAM_INCORRECT_BLOCK_ADDRESS, StringUtilities::toString(address));

	   /* . There is one read-channel per PLC connection. It must be protected against concurrent access.
		* . The write-channel is independent and can be accessed in parallel.
		* . The global action (open,close,etc.) must be protected from any concurrent access.
		* Attention!
		* Mutexes are defined with recursive option. It allows doOpen method re-calling readData
		* method by executing register synchronization if required.
		*/

		//(re)connect the PLC if needed and (re)synchronize the retentive registers
		if (doOpen(thePLC))
		{
			//connection is established then acquire data
			Lock lock(readMux_);
			//Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2)
			unsigned short addr = ((unsigned short)address + (unsigned short)offset)/2;

			//DATA topic makes sense with RECV one
			if (RECV & Log::topics_) LOG(DATA) << "Read data, address: %MW" << addr << ", byte-size: " << size;

            int err = IeMBreadData(readCtx_, addr, (unsigned short)size, pBuffer);
	        if(error)
	        	throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error)));
		}
		return 0;
	}


	int MBConnection::writeData(PLC* thePLC, unsigned long address, unsigned long offset, unsigned long size, unsigned char* pBuffer)
	{
		//Schneider uses 16bit alignment memory. Block address is expressed in bytes, must be an even value!
		if (address % 2) throw SilecsException(__FILE__, __LINE__, PARAM_INCORRECT_BLOCK_ADDRESS, StringUtilities::toString(address));

	   /* . There is one read-channel per PLC connection. It must be protected against concurrent access.
		* . The write-channel is independent and can be accessed in parallel.
		* . The global action (open,close,etc.) must be protected from any concurrent access.
		* Attention!
		* Mutexes are defined with recursive option. It allows doOpen method re-calling sendData
		* method by executing register synchronization if required.
		*/

		//(re)connect the PLC if needed and (re)synchronize the retentive registers
		if (doOpen(thePLC))
		{
			//connection is established then send data

			Lock lock(writeMux_);
			//Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2)
			unsigned short addr = ((unsigned short)address + (unsigned short)offset)/2;

			//DATA topic makes sense with SEND one
			if (SEND & Log::topics_) LOG(DATA) << "Write data, address: %MW" << addr << ", byte-size: " << size;

			int error = IeMBwriteData(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer);
	        if(error)
	        	throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error)));
		}
		return 0;
	}

} // namespace
#endif //MODBUS_SUPPORT_ENABLED