/*=============================================================================|
|  PROJECT SNAP7                                                         1.3.0 |
|==============================================================================|
|  Copyright (C) 2013, 2015 Davide Nardella                                    |
|  All rights reserved.                                                        |
|==============================================================================|
|  SNAP7 is free software: you can redistribute it and/or modify               |
|  it under the terms of the Lesser GNU General Public License as published by |
|  the Free Software Foundation, either version 3 of the License, or           |
|  (at your option) any later version.                                         |
|                                                                              |
|  It means that you can distribute your commercial software linked with       |
|  SNAP7 without the requirement to distribute the source code of your         |
|  application and without the requirement that your application be itself     |
|  distributed under LGPL.                                                     |
|                                                                              |
|  SNAP7 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               |
|  Lesser GNU General Public License for more details.                         |
|                                                                              |
|  You should have received a copy of the GNU General Public License and a     |
|  copy of Lesser GNU General Public License along with Snap7.                 |
|  If not, see  http://www.gnu.org/licenses/                                   |
|=============================================================================*/
#include "s7_client.h"

//---------------------------------------------------------------------------
TSnap7Client::TSnap7Client()
{
     FThread = 0;
     CliCompletion = 0;
	 EvtJob = NULL;
     EvtComplete = NULL;
	 FThread=NULL;
	 ThreadCreated = false;
}
//---------------------------------------------------------------------------
TSnap7Client::~TSnap7Client()
{
    Destroying=true;
    Disconnect();
    CliCompletion=NULL;
	if (ThreadCreated)
	{
	    CloseThread();
	    delete EvtComplete;
	    delete EvtJob;
		ThreadCreated=false;
	}
}
//---------------------------------------------------------------------------
void TSnap7Client::CloseThread()
{
     int Timeout;

     if (FThread)
     {
          FThread->Terminate();
          if (Job.Pending)
              Timeout=3000;
          else
              Timeout=1000;
          EvtJob->Set();
		  if (FThread->WaitFor(Timeout)!=WAIT_OBJECT_0)
              FThread->Kill();
          try {
             delete FThread;
          }
          catch (...){
          }

          FThread=0;
     }
}
//---------------------------------------------------------------------------
void TSnap7Client::OpenThread()
{
    FThread = new TClientThread(this);
    FThread->Start();
}
//---------------------------------------------------------------------------
int TSnap7Client::Reset(bool DoReconnect)
{
    bool WasConnected = Connected;
    if (ThreadCreated)
	{
		CloseThread();
		Disconnect();
		OpenThread();
	}
	else
		Disconnect();
	
	if (DoReconnect || WasConnected)
       return Connect();
    else
       return 0;
}
//---------------------------------------------------------------------------
void TSnap7Client::DoCompletion()
{
    if ((CliCompletion!=NULL) && !Destroying)
    {
      try{
          CliCompletion(FUsrPtr, Job.Op, Job.Result);
      }catch (...)
	  {
      }
    }
}
//---------------------------------------------------------------------------
int TSnap7Client::SetAsCallback(pfn_CliCompletion pCompletion, void * usrPtr)
{
    CliCompletion=pCompletion;
    FUsrPtr=usrPtr;
    return 0;
}
//---------------------------------------------------------------------------
int TSnap7Client::GetParam(int ParamNumber, void * pValue)
{
    // Actually there are no specific client params, maybe in future...
	return TSnap7MicroClient::GetParam(ParamNumber, pValue);
}
//---------------------------------------------------------------------------
int TSnap7Client::SetParam(int ParamNumber, void * pValue)
{
    // Actually there are no specific client params, maybe in future...
	return TSnap7MicroClient::SetParam(ParamNumber, pValue);
}
//---------------------------------------------------------------------------
bool TSnap7Client::CheckAsCompletion(int &opResult)
{
    if (!Job.Pending)
        opResult=Job.Result;
    else
        if (!Destroying)
            opResult=errCliJobPending; // don't set LastError here
        else
        {
            opResult=errCliDestroying;
            return true;
        }

    return !Job.Pending;
}
//---------------------------------------------------------------------------
int TSnap7Client::AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData)
{
     if (!Job.Pending)
     {
          Job.Pending  = true;
          Job.Op       = s7opReadArea;
          Job.Area     = Area;
          Job.Number   = DBNumber;
          Job.Start    = Start;
          Job.Amount   = Amount;
          Job.WordLen  = WordLen;
          Job.pData    = pUsrData;
          JobStart     = SysGetTick();
          StartAsyncJob();
          return 0;
     }
     else
          return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData)
{
    int ByteSize, TotalSize;

    if (!Job.Pending)
    {
        Job.Pending =true;
        Job.Op      =s7opWriteArea;
        Job.Area    =Area;
        Job.Number  =DBNumber;
        Job.Start   =Start;
        // Performs some check first to copy the data
        ByteSize=DataSizeByte(WordLen);
        TotalSize=ByteSize*Amount; // Total size in bytes
        if (ByteSize==0)
            return SetError(errCliInvalidWordLen);
        if ((TotalSize < 1) || (TotalSize > int(sizeof(opData))))
            return SetError(errCliInvalidParams);
        Job.Amount  =Amount;
        Job.WordLen =WordLen;
        // Doublebuffering
        memcpy(&opData, pUsrData, TotalSize);
        Job.pData =&opData;
        JobStart  =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsListBlocksOfType(int BlockType, PS7BlocksOfType pUsrData, int & ItemsCount)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opListBlocksOfType;
        Job.Area     =BlockType;
        Job.pData    =pUsrData;
        Job.pAmount  =&ItemsCount;
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsReadSZL(int ID, int Index, PS7SZL pUsrData, int & Size)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opReadSZL;
        Job.ID       =ID;
        Job.Index    =Index;
        Job.pData    =pUsrData;
        Job.pAmount  =&Size;
        Job.Amount   =Size;
        Job.IParam   =1; // Data has to be copied into user buffer
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsReadSZLList(PS7SZLList pUsrData, int &ItemsCount)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opReadSzlList;
        Job.pData    =pUsrData;
        Job.pAmount  =&ItemsCount;
        Job.Amount   =ItemsCount;
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsUpload(int BlockType, int BlockNum, void * pUsrData, int & Size)
{
    if (!Job.Pending)
     {
        Job.Pending  =true;
        Job.Op       =s7opUpload;
        Job.Area     =BlockType;
        Job.pData    =pUsrData;
        Job.pAmount  =&Size;
        Job.Amount   =Size;
        Job.Number   =BlockNum;
        Job.IParam   =0; // not full upload, only data
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsFullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opUpload;
        Job.Area     =BlockType;
        Job.pData    =pUsrData;
        Job.pAmount  =&Size;
        Job.Amount   =Size;
        Job.Number   =BlockNum;
        Job.IParam   =1; // full upload
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsDownload(int BlockNum, void * pUsrData, int Size)
{
    if (!Job.Pending)
    {
        // Checks the size : here we only need a size>0 to avoid problems during
        // doublebuffering, the real test of the block size will be done in
        // Checkblock.
        if (Size<1)
            return SetError(errCliInvalidBlockSize);
        Job.Pending  =true;
        Job.Op       =s7opDownload;
        // Doublebuffering
        memcpy(&opData, pUsrData, Size);
        Job.Number   =BlockNum;
        Job.Amount   =Size;
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsCopyRamToRom(int Timeout)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opCopyRamToRom;
        if (Timeout>0)
        {
          Job.IParam   =Timeout;
          JobStart     =SysGetTick();
          StartAsyncJob();
          return 0;
        }
        else
            return SetError(errCliInvalidParams);
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsCompress(int Timeout)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opCompress;
        if (Timeout>0)
        {
            Job.IParam   =Timeout;
            JobStart     =SysGetTick();
            StartAsyncJob();
            return 0;
        }
        else
            return SetError(errCliInvalidParams);
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsDBRead(int DBNumber, int Start, int Size, void * pUsrData)
{
    return AsReadArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsDBWrite(int DBNumber, int Start, int Size, void * pUsrData)
{
    return AsWriteArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsMBRead(int Start, int Size, void * pUsrData)
{
    return AsReadArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsMBWrite(int Start, int Size, void * pUsrData)
{
    return AsWriteArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsEBRead(int Start, int Size, void * pUsrData)
{
    return AsReadArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsEBWrite(int Start, int Size, void * pUsrData)
{
    return AsWriteArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsABRead(int Start, int Size, void * pUsrData)
{
    return AsReadArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsABWrite(int Start, int Size, void * pUsrData)
{
    return AsWriteArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsTMRead(int Start, int Amount, void * pUsrData)
{
    return AsReadArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsTMWrite(int Start, int Amount, void * pUsrData)
{
    return AsWriteArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsCTRead(int Start, int Amount, void * pUsrData)
{
    return AsReadArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsCTWrite(int Start, int Amount, void * pUsrData)
{
    return AsWriteArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsDBGet(int DBNumber, void * pUsrData, int &Size)
{
    if (!Job.Pending)
    {
        if (Size<=0)
            return SetError(errCliInvalidBlockSize);
        Job.Pending  =true;
        Job.Op       =s7opDBGet;
        Job.Number   =DBNumber;
        Job.pData    =pUsrData;
        Job.pAmount  =&Size;
        Job.Amount   =Size;
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
int TSnap7Client::AsDBFill(int DBNumber, int FillChar)
{
    if (!Job.Pending)
    {
        Job.Pending  =true;
        Job.Op       =s7opDBFill;
        Job.Number   =DBNumber;
        Job.IParam   =FillChar;
        JobStart     =SysGetTick();
        StartAsyncJob();
        return 0;
    }
    else
        return SetError(errCliJobPending);
}
//---------------------------------------------------------------------------
void TSnap7Client::StartAsyncJob()
{
    ClrError();
	if (!ThreadCreated)
	{
		EvtJob =  new TSnapEvent(false);
		EvtComplete = new TSnapEvent(false);
	    OpenThread();
		ThreadCreated=true;
	}
	EvtComplete->Reset(); // reset if previously was not called WaitAsCompletion
    EvtJob->Set();
}
//---------------------------------------------------------------------------
int TSnap7Client::WaitAsCompletion(unsigned long Timeout)
{
    if (Job.Pending)
    {
        if (ThreadCreated)
		{
			if (EvtComplete->WaitFor(Timeout)==WAIT_OBJECT_0)
				return Job.Result;
			else
			{  
				if (Destroying)
					return errCliDestroying;
				else
					return SetError(errCliJobTimeout);
			}
		}
		else
			return SetError(errCliJobTimeout);
    }
    else
        return Job.Result;
}
//---------------------------------------------------------------------------
void TClientThread::Execute()
{
     while (!Terminated)
     {
          FClient->EvtJob->WaitForever();
          if (!Terminated)
          {
               FClient->PerformOperation();
               FClient->EvtComplete->Set();
               // Notify the caller the end of job (if callback is set)
               FClient->DoCompletion();
          }
     };
}

