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

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

#include <silecs-communication/interface/communication/SilecsConnection.h>
#include <silecs-communication/interface/equipment/CNVBlock.h>
#include <silecs-communication/interface/equipment/SilecsBlock.h>
#include <silecs-communication/interface/equipment/SilecsPLC.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/protocol/core/silecs.h>

namespace Silecs
{

	CNVConnection::CNVConnection(PLC* thePLC) : Connection(thePLC)
	{
		LOG(ALLOC) << "CNVConnection (create): " << thePLC->getName();
	}


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

	/* Check for errors */
	int CNVConnection::errChk(PLC* thePLC, int code)// throw (std::string*)
	{
        if(code != 0)
        {   LOG(DEBUG) << "CNV error occurred: " << code << ": " << CNVGetErrorDescription(code);
            //Do not close the connection if it's only for data missing (when deleting empty Data object)
            if (code != CNVInvalidDataHandleError)
            {
                LOG(COMM) << "Transaction failure with PXI: " << thePLC->getName() << ". CNV[" << code << "]: " << CNVGetErrorDescription(code);
                doClose(thePLC, /*withLock =*/true);
			    throw SilecsException(__FILE__, __LINE__, CNV_INTERNAL_ERROR, std::string(CNVGetErrorDescription(code))+" in CNVConnection");
            }
		}
        return code;
	}

	/* Read Data */
    int CNVConnection::readData(PLC* thePLC, CNVBufferedSubscriber *handle,CNVData *data)
    {
        CNVBufferDataStatus status;

        /* Buffer is used only for performance reason.
         * Always return the last value in the buffer discarding the oldest ones.
         */

        //read the oldest data in the buffer
        int errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status));

        if ((errorCode == 0) && (status != CNVStaleData))
        {
            if (status != CNVNoData)
            { //buffer was not empty, try to read again to get the very last data if any
              errChk(thePLC, CNVDisposeData(*data)); //CNV requires to free the buffer on every CNVGetDataFromBuffer
              errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status));
            }
            else
            {   LOG(DEBUG) << "CNVConnection::readData() : buffer is empty (variables may not be initialized properly)";
                errorCode = CNVEmptyDataError;
            }
        }
        return errorCode;
    }

	/* Write Data */
	int CNVConnection::writeData(PLC* thePLC, const char *networkVariablePathname, CNVData data)
	{
		CNVWriter writer;

		int errorCode = errChk(thePLC, CNVCreateWriter(networkVariablePathname , NULL, NULL, CNVWaitForever , 0, &writer));

		if (errorCode ==0)
		{   CNVWrite (writer, data, 0);
            CNVDispose(writer);
		}
		return errorCode;
	}

	bool CNVConnection::open(PLC* thePLC)
	{
        //Controller is reachable at this stage (ping done from doOpen)
        bool isOK = true; //all subscription is fine a priori

        LOG(DEBUG) << "CNVConnection::open(): Subscribe to all input variables";

        // Create all the subscriptions
        blockMapType::iterator pBlockIter;
        for(pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter)
        {
            //Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object)
            if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos)
            { if (static_cast<CNVInputBlock*>(pBlockIter->second)->doSubscribe() == false)
                {  isOK = false;
                   break;
                }
            }
        }
        return isOK;
	}


	bool CNVConnection::close(PLC* thePLC)
	{
        LOG(DEBUG) << "CNVConnection::close(): unSubscribe all input variables";

		// Delete all the subscription
		blockMapType::iterator pBlockIter;
		for(pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter)
		{
			//Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object)
			if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos)
			{ static_cast<CNVInputBlock*>(pBlockIter->second)->unSubscribe();
			}
		}
		isConnected_ = false;
		return true;
	}

} // namespace

#endif //NI_SUPPORT_ENABLED