// datataker.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "datataker.h"
#include <chrono>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

using namespace std;

BEGIN_MESSAGE_MAP(DataTakerApp, CWinApp)
  //ON_BN_CLICKED(IDC_BUTTON1, &DataTakerApp::OnBnClickedButton1)
END_MESSAGE_MAP()

// The one and only application object
DataTakerApp theApp;

//dialog box item for showing block info
DtDialog gui;

//ODBC_CHECK_RETURN macro, continue if there is an error
//#define ODBC_CHECK_RETURN2(nRet, handle) \
//  handle.ValidateReturnValue(nRet);

//Another flavour of an ODBC_CHECK_RETURN macro
//#define ODBC_CHECK_RETURN3(nRet, handle) \
//  handle.ValidateReturnValue(nRet); \
//  if ((nRet != SQL_SUCCESS) && (nRet != SQL_SUCCESS_WITH_INFO)) \
//  { \
//    return nRet; \
//  }

//update wrapper for tellerstand
class CdboTellerStandUpdateAccessor
{
public:

    DEFINE_ODBC_COMMAND( CdboTellerStandUpdateAccessor, 
                         _T("UPDATE %s SET %s = %s"))
		
	  //You may wish to call this function if you are inserting a record and wish to
	  //initialize all the fields, if you are not going to explicitly set all of them.
	  void ClearRecord()
	  {
		  memset(this, 0, sizeof(*this));
	  }	
};

//class used for updating block numbering
class CdboTellerStandUpdate : public CODBC::CAccessor<CdboTellerStandUpdateAccessor>
{

  public:
    //Methods
	  SQLRETURN Update(CODBC::CConnection* pDbConnect, CString Table, CString Column, CString ColVal)
    {

      char dbgMsg[4096];

      //Validate our parameters
      ATLASSUME(pDbConnect);

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

		  //Prepare the count statement
      SQLTCHAR *tmp = GetDefaultCommand();
      SQLTCHAR sql[4096];
      sprintf( (char *)&sql, (char *)tmp, Table.GetBuffer(), Column.GetBuffer(), ColVal.GetBuffer());
		  nRet = statement.Prepare( sql);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Bind the parameters 
      nRet = BindParameters(statement);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Execute the statement
      nRet = statement.Execute();
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      return nRet;
    }

    //Update counter in table with one
    //NOTE: table is assumed to have only only one record, only in the first record a clounm is incremented
	  SQLRETURN AddOne(CODBC::CConnection* pDbConnect, CString Table, CString Column, unsigned short *sCount, char *InsertStatement)
    {

      char dbgMsg[4096];

      //Validate our parameters
      ATLASSUME(pDbConnect);

      *sCount = 0;

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Prepare the select statement
      SQLTCHAR sql[4096];
      sprintf( (char *)&sql, "SELECT %s FROM %s", Column.GetBuffer(), Table.GetBuffer());
		  nRet = statement.Prepare( sql);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      nRet = statement.Execute();
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;
      
      //Bind the columns
      nRet = BindColumns(statement);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Only the first record is updated
      //ClearRecord();
      nRet = statement.FetchNext();
      //ODBC_CHECK_RETURN2(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);

      if (nRet != SQL_SUCCESS)
      {

        //close pending statement
        statement.Close();
        nRet = statement.Create(*pDbConnect);
        //ODBC_CHECK_RETURN3(nRet, statement);
        theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
        if (nRet != SQL_SUCCESS) return nRet;

        //select failed, insert the firstr record
        sprintf( (char *)&sql, InsertStatement, Table.GetBuffer());
		    nRet = statement.Prepare( sql);
        //ODBC_CHECK_RETURN3(nRet, statement);
        theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
        if (nRet != SQL_SUCCESS) return nRet;

        nRet = statement.Execute();
        //ODBC_CHECK_RETURN3(nRet, statement);
        theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
        if (nRet != SQL_SUCCESS) return nRet;

        return SQL_SUCCESS;
      }

      //get the counter vlaue
      short s1;
      nRet = statement.GetData( 1, SQL_C_SSHORT,  &s1, sizeof(s1), NULL);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //increment counter an dconvert to string
      char val[80];
      if (!(++s1)) s1++;
      *sCount = s1;
      sprintf( val, "'%d'", s1);

      //select statement finished, close statement
      statement.Close();
      nRet = statement.Create(*pDbConnect);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

		  //Prepare the update statement
      SQLTCHAR *tmp = GetDefaultCommand();
      sprintf( (char *)&sql, (char *)tmp, Table.GetBuffer(), Column.GetBuffer(), val);
		  nRet = statement.Prepare( sql);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Bind the parameters 
      nRet = BindParameters(statement);
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      if (nRet != SQL_SUCCESS) return nRet;

      //Execute the statement
      nRet = statement.Execute();
      //ODBC_CHECK_RETURN3(nRet, statement);
      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC TellerStandUpdate Error", dbgMsg);
      return nRet;
    }
};


// ODBC accessor class used for  inserting a row
class CdboBlokInfoInsertAccessor
{
public:
  //Parameter values  
	TCHAR m_sColumn[10][51];

  BEGIN_ODBC_PARAM_MAP(CdboBlokInfoInsertAccessor)
	  SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
	  ODBC_PARAM_ENTRY( 1, m_sColumn[1])
	  ODBC_PARAM_ENTRY( 2, m_sColumn[2])
	  ODBC_PARAM_ENTRY( 3, m_sColumn[3])
	  ODBC_PARAM_ENTRY( 4, m_sColumn[4])
	  ODBC_PARAM_ENTRY( 5, m_sColumn[5])
	  ODBC_PARAM_ENTRY( 6, m_sColumn[6])
  END_ODBC_PARAM_MAP()

  //DEFINE_ODBC_COMMAND(CdboBlokInfoInsertAccessor, _T("INSERT INTO [datataker].[dbo].[blokdata] ([pers],[datum_tijd],[hoogte],[gewicht],[temp],[anodeteller]) VALUES (?,CONVERT(datetime, ?, 105),?,?,?,?)"))
  DEFINE_ODBC_COMMAND(CdboBlokInfoInsertAccessor, _T("INSERT INTO %s %s VALUES %s"))

	//You may wish to call this function if you are inserting a record and wish to
	//initialize all the fields, if you are not going to explicitly set all of them.
	void ClearRecord()
	{
		memset(this, 0, sizeof(*this));
	}	
};


//class used for  inserting a row 
class CdboBlokInfoInsert : public CODBC::CAccessor<CdboBlokInfoInsertAccessor>
{
public:

  int iCommandBuild;
  char table[1024];
  char columns[1024];
  char parameters[1024];

  //Parameter values  
	//TCHAR m_sColumn[10][51];

  CdboBlokInfoInsert()
  {
   iCommandBuild = -1;
   //sODBCcommand = "INSERT INTO %s %s VALUES %s";
  }

//Methods
  void BuildCommand(CIniFile *AppIniFile)
  {
  
    strcpy( table, (char *)AppIniFile->GetString( "TblBlokInfo", "DATABASE", "[datataker].[dbo].[dfdadsfa]"));
    strcpy( columns, (char *)AppIniFile->GetString( "BlokInfoColumns", "DATABASE", "[datataker].[dbo].[dfdadsfa]"));
    strcpy( parameters, (char *)AppIniFile->GetString( "BlokInfoParameters", "DATABASE", "[datataker].[dbo].[dfdadsfa]"));
    iCommandBuild = 0;
  }

	SQLRETURN Insert(CODBC::CConnection* pDbConnect, DT_SLAVE *dtSlave)
  {

    if (iCommandBuild) return -1;

    char dbgMsg[4096];

    //Validate our parameters
    ATLASSUME(pDbConnect);

    //Create the statement object
    CODBC::CStatement statement;
    SQLRETURN nRet = statement.Create(*pDbConnect);
    //ODBC_CHECK_RETURN3(nRet, statement);
    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC BlokInfoInsert Error", dbgMsg);
    if (nRet != SQL_SUCCESS) return nRet;


    //Prepare the select statement
    SQLTCHAR *tmp = GetDefaultCommand();
    SQLTCHAR sql[4096];
    sprintf( (char *)&sql, (char *)tmp, table, columns, parameters); 
    nRet = statement.Prepare( sql);
    //ODBC_CHECK_RETURN3(nRet, statement);
    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC BlokInfoInsert Error", dbgMsg);
    if (nRet != SQL_SUCCESS) return nRet;

    _tcscpy(m_sColumn[1], _T(dtSlave->name));
    _tcscpy(m_sColumn[2], _T((const char *)dtSlave->date_time)); //CONVERT(datetime, inserted.[Tijd], 105)
    _stprintf(m_sColumn[3], "%d", dtSlave->height);
    _stprintf(m_sColumn[4], "%d", dtSlave->weight);
    _stprintf(m_sColumn[5], "%d", dtSlave->temperature);
    _stprintf(m_sColumn[6], "%d", dtSlave->anode);

    nRet = BindParameters(statement);
    //ODBC_CHECK_RETURN3(nRet, statement);
    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC BlokInfoInsert Error", dbgMsg);
    if (nRet != SQL_SUCCESS) return nRet;

    //Execute the statement
    nRet = statement.Execute();
    //ODBC_CHECK_RETURN3(nRet, statement);
    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC BlokInfoInsert Error", dbgMsg);
    return nRet;
  }
};


//dialogbpx with block info is handled in separate thread
DWORD WINAPI ModelessThreadFunc(LPVOID)
{

  gui.Create(DtDialog::IDD);
  gui.SetDefaults();

  //check if we need to show the info dialog
  gui.ShowWindow(SW_SHOW);
  
  MSG msg;

  //message loop for dialog box
  while(::GetMessage(&msg, NULL, 0, 0))
  {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  //close datataker application
  gui.CloseWindow();
  gui.DestroyWindow();
  exit(0);
  return 0;
}

//===========================================================================================
// Command handler for console
//===========================================================================================
static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType)
{

  BOOL bRet = FALSE;


  switch (dwCtrlType)
  {
  case CTRL_C_EVENT: // Ctrl+C
    theApp.DataTakerExit( TRUE);
    bRet = TRUE;
    break;

  case CTRL_BREAK_EVENT: // Ctrl+Break
    theApp.DataTakerExit( TRUE);
    bRet = TRUE;
    break;

  case CTRL_CLOSE_EVENT: // Closing the console window
    theApp.DataTakerExit( TRUE);
    bRet = TRUE;
    break;

  case CTRL_LOGOFF_EVENT: // User logs off. Passed only to services!
    break;

  case CTRL_SHUTDOWN_EVENT: // System is shutting down. Passed only to services!
    break;
  }

  // Return TRUE if handled this message, further handler functions won't be called.
  // Return FALSE to pass this message to further handlers until default handler calls ExitProcess().
  return bRet;
}


//===========================================================================================
// MAIN function for the datataker application
//===========================================================================================
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	HMODULE hModule = ::GetModuleHandle(NULL);

	if (hModule != NULL)
	{
		// initialize MFC and print and error on failure
		if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
		{
			// TODO: change error code to suit your needs
			_tprintf(_T("Fatal Error: MFC initialization failed\n"));
			nRetCode = 1;
		}
		else
		{

      // TODO: code your application's behavior here.
      theApp.InitInstance();

      //create our serial port object
      SerialPort MyCOMport( ((CString)(theApp.m_pszAppName)).GetBuffer());

      // create thread for the modeless dialog
      if (theApp.GetShowBlockInfo())
        CreateThread(NULL, 0, ModelessThreadFunc, NULL, 0, NULL);

      //hide console window if requested from the ini file
      ShowWindow( GetConsoleWindow(), theApp.GetShowConsole());

      //Devices on the COM port are polled, to grant exclusive use to the port by just one thread
      while (!theApp.StopDataTaker())
      {
        //chack all connected devices/datataker
        theApp.PollDevices( &MyCOMport);
      }
		}
	}
	else
	{
		// TODO: change error code to suit your needs
		_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
		nRetCode = 1;
	}

	return nRetCode;
}


DataTakerApp::DataTakerApp()
{

  DataTakerExit( FALSE);
  SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
  MaxSlaves = 99;
  strConnectString = "";
  ODBC_SetConnected( 0);
}

DataTakerApp::~DataTakerApp()
{

  ODBCDisConnect();
}

BOOL DataTakerApp::InitInstance()
{

  CString sSlave;
  polltime = 100; //polltime in msec, default is 100 msec
  timepast = 0;
  //rx_len = 0;
  char *tmpbuf;
  CExecImageVersion MyVersion;

  //Settings will be stored in the registry
  SetRegistryKey( MyVersion.GetLegalTrademarks());

  CString mIniFile = m_pszAppName;
  mIniFile.Append( ".ini");
  DtIniFile = new CIniFile( mIniFile.GetBuffer());

  //debug settings for the datataker application, global setting
  dbgview.SetLog2DbgViewer( DtIniFile->GetInteger( "Log2DbgViewer", "DATATAKER", 1));
  dbgview.SetLog2Screen( DtIniFile->GetInteger( "Log2Screen", "DATATAKER", 0));
  dbgview.SetLog2File( DtIniFile->GetInteger( "Log2File", "DATATAKER", 0));
  //dbgview.SetLog2EventViewer( DtIniFile->GetInteger( "Log2EventViewer", "DATATAKER", 0));
  dbgview.SetLog2EventViewer( 0);
  dbgview.SetTraceLevel( DtIniFile->GetInteger( "Tracelevel", "DATATAKER", 0));

  //event viewer: enable logging
  dbgview.SetEventLogging( (char *)m_pszAppName);
  
  if ( dbgview.GetLog2Screen()) 
    ShowConsole = 1;
  else
    ShowConsole = DtIniFile->GetInteger( "CMD-Window", "DATATAKER", 1);

  ShowConsole  = ShowConsole ? SW_SHOW : SW_HIDE;
  ShowBlockInfo = DtIniFile->GetInteger( "DLG-Window", "DATATAKER", 1);

  dbgview.CodeTrace( "DataTaker: InitInstance function called, ini file [%s] read", 
                      6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                      mIniFile.GetBuffer());

  //get the defualt init string for the dialog
  SetStartString( DtIniFile->GetString( "StartString", "DATATAKER", "R90S"));

  //get the maximum slave number
  MaxSlaves = DtIniFile->GetInteger( "MaxSlave", "DATATAKER", 1);

  //first assume all slave addresses are invalid
  memset( (void *)aDT_slave, 0, sizeof(aDT_slave));
  for( int i = 0; i <= MaxSlaves; i++)
  {

    aDT_slave[ i].address = -1; 
    aDT_slave[ i].status = 0; 
    aDT_slave[ i].iBlokCurrentDay = 0;      //Number of day for the current block 
    aDT_slave[ i].iBlokPreviousDay = 0;     //Number of day for the previous block
  }

  //read all the slaves from the ini file, just check if there is an address available
  //for slaves in the address range 1 - 99.
  for( int i = 0; i <= MaxSlaves; i++)
  {

    //keyword for the ini is slave followed by a number
    sSlave.Format( "SLAVE%d", i);

    //check if there is a slave
    int iSlave = DtIniFile->GetInteger( "ADDRESS", sSlave.GetBuffer(), -1);
    if ( ( iSlave > 0) && ( iSlave < MaxSlaves)) 
    {

      //set the slave number
      aDT_slave[ iSlave].address = iSlave;

      //fill in description, identical to column name in 'tellerstanden'
      strcpy( aDT_slave[ iSlave].name, DtIniFile->GetString( "NAME", sSlave.GetBuffer(), "???"));

      aDT_slave[ iSlave].date_time[ 0] = '\0';     /* date + time of m easurement */
      aDT_slave[ iSlave].weight = 0;               /* weight */
      aDT_slave[ iSlave].height = 0;               /* height */
      aDT_slave[ iSlave].temperature = 0;          /* temperature */
      aDT_slave[ iSlave].anode = 0;                /* anode id */

      //fill in the communication strings
      strcpy( aDT_slave[ iSlave].pInitString, DtIniFile->GetString( "InitString", sSlave.GetBuffer(), "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0"));
      strcpy( aDT_slave[ iSlave].pDateString, DtIniFile->GetString( "DateString", sSlave.GetBuffer(), "D=%d"));
      strcpy( aDT_slave[ iSlave].pTimeString, DtIniFile->GetString( "TimeString", sSlave.GetBuffer(), "T=%s"));
      strcpy( aDT_slave[ iSlave].pStartString, DtIniFile->GetString( "StartString", sSlave.GetBuffer(), "R1-E"));
      strcpy( aDT_slave[ iSlave].pReadString, DtIniFile->GetString( "ReadString", sSlave.GetBuffer(), "D T 1+..17+V"));

      CString strTerm = DtIniFile->GetString( "TermString", sSlave.GetBuffer(), "\r\n");
      strTerm.Replace("\\n", "\n"); strTerm.Replace("\\r", "\r");
      strcpy( aDT_slave[ iSlave].pTermString, strTerm.GetBuffer());
      strcpy( aDT_slave[ iSlave].pClearString, DtIniFile->GetString( "ClearString", sSlave.GetBuffer(), "CLAST"));
      strcpy( aDT_slave[ iSlave].pScanString, DtIniFile->GetString( "ScanString", sSlave.GetBuffer(), "U"));
      strcpy( aDT_slave[ iSlave].pAbortString, DtIniFile->GetString( "AbortString", sSlave.GetBuffer(), "/Q"));
      strcpy( aDT_slave[ iSlave].pSlaveString, DtIniFile->GetString( "SlaveString", sSlave.GetBuffer(), "#%d"));

    }
  }

  //Setup the ODBC environment
  char dbgMsg[4096];

  nRet = env.Create();
  //env.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  nRet = env.SetAttr(SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3);
  //env.ValidateReturnValue(nRet);  
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  nRet = env.SetAttrU(SQL_ATTR_CONNECTION_POOLING, SQL_CP_OFF);
  //env.ValidateReturnValue(nRet);  
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);
      
  nRet = env.GetAttr(SQL_ATTR_ODBC_VERSION, nVersion);
  //env.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  nRet = env.GetAttrU(SQL_ATTR_CONNECTION_POOLING, nPooling);
  //env.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  nRet = env.CommitTran();
  //env.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  nRet = env.RollbackTran();
  //env.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  //Setup the connection
  nRet = con.Create(env);
  //con.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  //Set login timeout to 5 seconds
  nRet = con.SetAttrU(SQL_ATTR_LOGIN_TIMEOUT, 5);
  //con.ValidateReturnValue(nRet);
  ODBCValidateReturnValue( nRet, 1, "ODBC Environment Error", dbgMsg);

  CString strTemp;
  //get the database name
  strConnectString.Format( "Driver=%s;", (char *)DtIniFile->GetString( "DRIVER", "DATABASE", "DATATAKER"));
  strTemp.Format( "Server=%s;", (char *)DtIniFile->GetString( "SERVER", "DATABASE", "DATATAKER"));
  strConnectString += strTemp;
  strTemp.Format( "Database=datataker;", (char *)DtIniFile->GetString( "DBNAME", "DATABASE", "localhost"));
  strConnectString += strTemp;
  strTemp.Format( "UID=%s;", (char *)DtIniFile->GetString( "UID", "DATABASE", "sa"));
  strConnectString += strTemp;
  strTemp.Format( "PWD=%s;", (char *)DtIniFile->GetString( "PWD", "DATABASE", "Qw1234"));
  strConnectString += strTemp;

  //get the table name for TellerStand
  strTemp = DtIniFile->GetString( "tblTellerStand", "DATABASE", "[datataker].[dbo].[tellerstand]");
  strcpy( tblTellerStand, strTemp.GetBuffer());

  //get the insert statmeent for TellerStand
  strTemp = DtIniFile->GetString( "TellerStandInsert", "DATABASE", "INSERT INTO %s VALUES (0,0)");
  strcpy( insTellerStand, strTemp.GetBuffer());
  
  //Connect to the datasource _T("Driver={SQL Server};Server=BBS5\\SQLEXPRESS;Database=datataker;UID=dt2005;PWD=Qw1234;")
  ODBCConnect();

  //create the wrapper objects
  tblTellerstand = new CdboTellerStandUpdate;
  tblBlokInfo = new CdboBlokInfoInsert;

  //build command for block info
  tblBlokInfo->BuildCommand(DtIniFile);

  //get the communication strings from the ini file 
  tmpbuf = (char *)DtIniFile->GetString( "Delimiter", "PROTOCOL", ",");
  pDelimiter = tmpbuf[ 0];

  tmpbuf = (char *)DtIniFile->GetString( "Decimal", "PROTOCOL", ".");
  pDecimal = tmpbuf[ 0];

  tmpbuf = (char *)DtIniFile->GetString( "Terminator", "PROTOCOL", "\4");
  pTerminator = tmpbuf[ 0];

//  time_t t = time(0);   // get time now
//    struct tm * timeinfo = localtime( & t );
//    tm timeinfo = {};
//  timeinfo.tm_year = 2014 - 1900;
//  timeinfo.tm_mday = 16;
//  timeinfo.tm_mon = 2;
//  mktime(&timeinfo);
//  int dayNumber = timeinfo->tm_yday;

  return true;
}


void DataTakerApp::ODBCConnect( int ExitMessage)
{
  char dbgMsg[4096];

  //Connect to the datasource _T("Driver={SQL Server};Server=BBS5\\SQLEXPRESS;Database=datataker;UID=dt2005;PWD=Qw1234;")
  nRet = con.DriverConnect(reinterpret_cast<SQLTCHAR*>(strConnectString.GetBuffer()), sFullInitString);
  ODBCValidateReturnValue( nRet, ExitMessage, "ODBC Connection Error", dbgMsg);

  //con.ValidateReturnValue(nRet, 1, "ODBC Connection Error", dbgMsg);
  if ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO)) {

    //mark as connected to database
    ODBC_SetConnected( 1);
  }
  
}


void DataTakerApp::ODBCDisConnect( void)
{
  char dbgMsg[4096];

  //Connect to the datasource _T("Driver={SQL Server};Server=BBS5\\SQLEXPRESS;Database=datataker;UID=dt2005;PWD=Qw1234;")
  nRet = con.Disconnect();
  ODBCValidateReturnValue( nRet, 0, "ODBC DisConnect Error", dbgMsg);

  //mark as disconnected from database
  ODBC_SetConnected( 0);
}


void DataTakerApp::ODBCValidateReturnValue(SQLRETURN nRet, int ExitMessage, CString ErrCaption, char *dbgMsg)
{

  //connection object is always present, so call the validatereturn value function
  con.ValidateReturnValue( nRet, ExitMessage, ErrCaption, dbgMsg);

  //if we have an error log a message
  if ((nRet != SQL_SUCCESS) && (nRet != SQL_SUCCESS_WITH_INFO)) {

    theApp.dbgview.CodeTrace("DataTaker ODBC: %s", 7, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, dbgMsg);

    //just quit if we are asked to do
    if (ExitMessage) exit(nRet);
  }
  else nRet = SQL_SUCCESS;
}


void DataTakerApp::PollDevices( SerialPort *DTComPort)
{
  unsigned short sCount = 0;
  char cLast='\0';


  //there are several steps which will to do before we can query a device
  //software assumes a device can always send a datastring, we try to respond to this string correctly
  /* the order in communication is:
     1. send init command string
        complete string slave 2 = "#2/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0  D=75 T=12:39:21 R1-E D T 1+..17+V\0xd\0xa"
        init string = "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0 "
        date string = " D=75" //number of days in this year
        time string = " T=12:39:21" //current time
        start string = "R1-E"
        read string = " D T 1+..17+V"
        terminator string = \0xd\0xa"
     2. keep silent for 10 seconds
     3. send clear command for device
     4. send ask for data command
     5. wait 5 seconds, and go to step 4

     7. check for start of new day: send date + time + terminator string

     9. check for received data, and try to evaluate
  */
  /*
InitString = "/c/e/k/L/m/n/O/r/t/u P22=44 P24=4 P39=0 "
ReadString = " D T 1+..17+V" + Chr(13) + Chr(10)
ClearString = "CLAST"
AbortString = "/Q"
ScanString = "U"

  */
  CString strSend;
  CString strTemp;

  typedef std::chrono::high_resolution_clock Clock;
  typedef std::chrono::milliseconds milliseconds;
  Clock::time_point t0 = Clock::now();

  //open COM port
  if ( !DTComPort->IsOpened()) DTComPort->OpenPort();

  //fail if comport is not opened
  if ( !DTComPort->IsOpened())
  {

    //first of all release the com port, make sure for at least one second the port is not opened
    Sleep( 4500);

    //process all the slaves
    for (int i = 0; i < MaxSlaves; i++)
    {

      //process only present slaves
      if (aDT_slave[ i].address >= 0)
        aDT_slave[ i].status = 0; //reset defined slaves
    }

    //wait for polltime and update the time past
    UpdateTime( 0);

    return;
  }

  int tx_error = SER_GOOD; //only send errors are handled to close the port
  int com_error = SER_GOOD;

  //process all the slaves
  for (int i = 0; i < MaxSlaves; i++)
  {

    //save the last transmit error
    if ( tx_error  != SER_GOOD) com_error = tx_error;

    //process only present slaves
    if (aDT_slave[ i].address >= 0)
    {



      switch (aDT_slave[ i].status)
      {

        case 0: //start with reset
          //DtString = " D=" + Trim(Str(DatePart("y", Date))) + " T=" + Time$
          //Call DisplayOutput(Slave(AktSlave) + InitString + DtString + " " + IniString.Text + ReadString)

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pInitString;
          strTemp.Format( aDT_slave[ i].pDateString, GetYearDay()+1);
          strSend += " ";
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( aDT_slave[ i].pTimeString, GetTimeNow());
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pStartString);
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pReadString);
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pTermString);
          strSend += strTemp;

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 1;
          aDT_slave[ i].msec = 0;
          break;

        case 1: //wait 10 seconds

          if ( aDT_slave[ i].msec >= 10000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }

          break;

        case 2: //ask data from slave

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pScanString;
          strTemp.Format( "%s", aDT_slave[ i].pTermString);
          strSend += strTemp;

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 3;
          aDT_slave[ i].msec = 0;
          break;

        case 3: //wait 4 seconds for data, if received go to 4, otherwise abort and go to 2

          if ( aDT_slave[ i].msec >= 4000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            //send abort string
            strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
            strSend += aDT_slave[ i].pAbortString;
            strTemp.Format( "%s", aDT_slave[ i].pTermString);
            strSend += strTemp;

            tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }

          break;

        case 4: //data received and handled, send clear

          dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                             6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                             aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

          strSend.Format( aDT_slave[ i].pSlaveString, aDT_slave[ i].address);
          strSend += aDT_slave[ i].pClearString;
          strTemp.Format( "%s", aDT_slave[ i].pTermString);
          strSend += strTemp;

          tx_error = DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

          //update status and time elapsed
          aDT_slave[ i].status = 5;
          aDT_slave[ i].msec = 0;
          break;

        case 5: //wait 5 seconds and ask for data

          if ( aDT_slave[ i].msec >= 4000)
          {

            dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                               6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                               aDT_slave[ i].address, aDT_slave[ i].name, aDT_slave[ i].status);

            aDT_slave[ i].status = 2;
            aDT_slave[ i].msec = 0;
          }
          break;

        default:
          break;
      }
    }
  }


  sCount = 1; 

  int rx_error = SER_GOOD;
  unsigned short sSlave = 0;

  //try receving one character at a time, keep receiving while there is data
  while ( sCount)
  {

    rx_error = DTComPort->Receive( &sCount, &cLast);

    if ( rx_error && (rx_error != SER_E_RTIMEOUT))
    {

      com_error = rx_error;

      //continue to receive
      sCount = 0;
      continue;
    }

    switch (cLast)
    {
      case 4:

        //there is data, save it in the database
        sSlave = UseData( DTComPort->RX_GetBuffer(), DTComPort->RX_GetLength());
		    DTComPort->RX_EmptyBuffer();

        //sync time with dtataker unit
        SyncTimeDT( DTComPort, sSlave);

        break;

      case 13:
        //check for 'empty' string
        //"empty" + Chr(13)
        if ( strstr( (char *)DTComPort->RX_GetBuffer(), "empty\x00d") != NULL)
        {

          unsigned int i = 0;
          char SlaveId[20];
          char *SlaveBuf = (char *)DTComPort->RX_GetBuffer();
          SlaveId[0] = '\0';

          while (i <= DTComPort->RX_GetLength() && i <= 20 && SlaveBuf[ i] != ',')
          {

            SlaveId[ i] = SlaveBuf[ i];
            SlaveId[ ++i] = '\0';
          }

          int SlaveNr = atoi( SlaveId);

          if ( SlaveNr > 0) //this is a real slave number
          {

            //perform a reset
            aDT_slave[ SlaveNr].status = 0;
          }

          //clear receive buffer
          DTComPort->RX_Clear();
        }
        break;

      default:
        break;
    }
  }

  //fake a received message
/*  unsigned int tp = GetTimePast();

  if ( ( tp >= 35000))
  {
    CString Ctest = "3,21,08:11:28, 7.368, 1022.0, 1030.4, 1026.8, 1027.2, 9.896, 10.112, 1031.6, 1028.8, 10.136, 10.532, 1032.4, 318.0, 14.252, 14.344, 14.196, 14.412, 317.2, 14.660, 14.584, 319.6,-50.64, 322.4, 17.712, 16.316, 326.4, 17.136, 14.196, 13.968, 13.208, 17.252, 331.2, 322.8\4";
    Ctest = "3,96,13:55:22, 814.4,-303.6, 536.0,-120.16, 339.6,-45.72, 179.28, 1.616, 46.28,-0.468,-80.40, 76.36,-130.24, 53.32,-143.12, 50.80,-213.20,-2.020,-214.48,-0.220,-230.08,-7.292,-216.28,-1.216,-231.36,-12.708,-211.16, 4.028,-207.88, 5.092,-193.40, 26.60,-242.16.\4";
    theApp.UseData( (unsigned char *)Ctest.GetBuffer(), Ctest.GetLength());
    timepast = 0;
  }
*/

  Clock::time_point t1 = Clock::now();
  milliseconds ms = std::chrono::duration_cast<milliseconds>(t1 - t0);
  __int64 ms_correction = ms.count();


  //wait for polltime and update the time past
  UpdateTime( (int)ms_correction);

  //check the transmit errors, if there is one close the COM port
  if (com_error != SER_GOOD)
  {

    //clear buffers
    DTComPort->RX_Clear();
    DTComPort->TX_Clear();

    //close the com port
    DTComPort->ClosePort();

    //reset all slaves
    //process all the slaves
    for (int i = 0; i < MaxSlaves; i++)
    {

      //process only present slaves
      if (aDT_slave[ i].address >= 0)
        aDT_slave[ i].status = 0; //reset defined slaves
    }

    //wait 2 seconds
    Sleep( 2000);
  }

}

//update the time used for polling, i.e update actual time past with the poll time, and perform a sleep
//note that the time past is aproximate, we don't need the exact time! Only used to calculate timeouts.
void DataTakerApp::UpdateTime( unsigned int ms_correction)
{

  unsigned int sleepms = (ms_correction < polltime ? polltime - ms_correction : 0);

  Sleep( (DWORD)sleepms);

  //process all the slaves
  for (int i = 0; i < MaxSlaves; i++)
  {

    //process only present slaves, update slave time value
    if (aDT_slave[ i].address >= 0)
      aDT_slave[ i].msec += sleepms + ms_correction;
  }

  timepast++;
}

//Sunc the system time with the DataTaker
void DataTakerApp::SyncTimeDT( SerialPort *DTComPort, int iSlave)
{

  //return if slave id is invalid
  if (( iSlave == 0) || ( iSlave > MaxSlaves)) return;
  
  if ( aDT_slave[ iSlave].iBlokPreviousDay == 0) 
  {
    aDT_slave[ iSlave].iBlokPreviousDay = aDT_slave[ iSlave].iBlokCurrentDay;
    return; //no need for time sync
  }

  if (( aDT_slave[ iSlave].iBlokCurrentDay != aDT_slave[ iSlave].iBlokPreviousDay) && ( aDT_slave[ iSlave].iBlokPreviousDay > 0))
  {

    CString strSend;
    CString strTemp;

    //perform time sync
    dbgview.CodeTrace( "DataTaker: Slave = %d [name=%s], Status = %d", 
                       6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                       aDT_slave[ iSlave].address, aDT_slave[ iSlave].name, aDT_slave[ iSlave].status);

    strSend.Format( aDT_slave[ iSlave].pSlaveString, aDT_slave[ iSlave].address);
    strTemp.Format( aDT_slave[ iSlave].pDateString, GetYearDay()+1);
    strSend += strTemp;
    strSend += " ";
    strTemp.Format( aDT_slave[ iSlave].pTimeString, GetTimeNow());
    strSend += strTemp;
    strTemp.Format( "%s", aDT_slave[ iSlave].pTermString);
    strSend += strTemp;

    DTComPort->Transmit( strSend.GetLength(), (unsigned char *)strSend.GetBuffer());

    aDT_slave[ iSlave].iBlokPreviousDay = aDT_slave[ iSlave].iBlokCurrentDay;
  }
}

//process received data, if successfull the slave id is returned
//on failure the returned slave id is zero
unsigned short DataTakerApp::UseData( unsigned char *ubuf, unsigned short ulen)
{
  
  int idx = 0;
  double fTemp;
  int i, k;
  unsigned short sWeight;
  unsigned short sTemperature;
  unsigned short sHeight;
  unsigned short sSlave;

//Private Sub VerwerkRegel(Buffer, Lengte)
//Dim I, HJaar, HDag, Dag, Slv As Integer
//Dim Dat, Char As String
//RDataString = ""
//Index = 0

//I = 1
  //Haal alle elementen op gescheiden door een komma tot EOT (4)
  for (i = 0, k = 0; i < ulen; i++)
  {
    switch (ubuf[ i])
    {

      case ' ': //strip spaces
      case  10: //LF
      case  13: //CR
        break;

      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '.':
      case '-':
      case ':':

        rx_data[ idx][ k++] = ubuf[ i];
        break;

      case '\4': //end of message
      case ',':
        
        rx_data[ idx++][ k++] = '\0';
        //if (idx == 2) 
        //{
        //  //save time string
        //  memccpy( dt_time, val_buf, '\0', k);
        //  dt_time[ k] = '\0';
        //}
        //else 
        //{
        //  rx_data[ idx++] = atoi( (const char *)val_buf);
        //}

        k = 0;
        break;

      default:
        break;
    }
  }

  //slave id
  sSlave = atoi( (const char *)rx_data[ 0]);

  //check slave id, id between 0 and max
  if ( ( sSlave < 0) || ( sSlave > MaxSlaves))
  {
  
    //slave id invalid, skip all oter data
    return 0;
  }
  else
  {

    //check if found address is registered
    if ( aDT_slave[ sSlave].address == -1) return 0;
  }

  //date + time string
  //we have the time from the datataker, and the day of year
  time_t t = time(0);   // get time now
  struct tm * timenow = localtime( & t );
  mktime(timenow);

  tm timeinfo = {};
  memset( (void *)&timeinfo, 0, sizeof( tm));
  timeinfo.tm_year = timenow->tm_year;
  timeinfo.tm_mday = atoi( (const char *)rx_data[ 1]);
  mktime(&timeinfo);
  
  //Number of day for the current block 
  aDT_slave[ sSlave].iBlokCurrentDay = atoi( (const char *)rx_data[ 1]);  

  //build date + time
  char sDateTime[80];
  memset( (void *)sDateTime, 0, sizeof(sDateTime));
  strcpy( sDateTime, _itoa( timeinfo.tm_mday, sDateTime, 10));
  strcat( sDateTime, "-");
  _itoa( timeinfo.tm_mon+1, &sDateTime[strlen( sDateTime)], 10);
  strcat( sDateTime, "-");
  _itoa( timeinfo.tm_year+1900, &sDateTime[strlen( sDateTime)], 10);
  strcat( sDateTime, " ");
  strcat( sDateTime, (const char *)rx_data[ 2]);
  strcpy( (char *)rx_data[ 2], sDateTime);

  //weight
  for ( i = 0, sWeight = 0; i <= 11; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 3]);
    if ( fTemp < 400) sWeight |= (0x1 << i);
  }

  sWeight = bcdToDec( sWeight);

  //temperature
  for ( i = 0, sTemperature = 0; i <= 9; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 15]);
    if ( fTemp > 200) sTemperature |= (0x1 << i);
  }

  sTemperature = bcdToDec( sTemperature);

  //height
  for ( i = 0, sHeight = 0; i <= 10; i++)
  {
    fTemp = atof( (const char *)rx_data[ i + 25]);
    if ( fTemp > 200) sHeight |= (0x1 << i);
  }

  sHeight = bcdToDec( sHeight);

  //update the slave record
  aDT_slave[ sSlave].weight = sWeight;
  aDT_slave[ sSlave].height = sHeight;
  aDT_slave[ sSlave].temperature = sTemperature;
  strcpy( (char *)aDT_slave[ sSlave].date_time, (char *)rx_data[ 2]);

  //Increment anode counter in database table
  unsigned short sCounter = 0;
  CString strTmp;
  strTmp.Format( "[%s]", aDT_slave[ sSlave].name);

  char dbgMsg[4096];

  if (!ODBC_IsConnected()) ODBCConnect();

  nRet = tblTellerstand->AddOne( &con, tblTellerStand, strTmp.GetBuffer(), &sCounter, insTellerStand);
  if (nRet != SQL_SUCCESS)
  {

    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC tblTellerstand->AddOne", dbgMsg);
    //disconnect, and try again
    ODBCDisConnect();
    ODBCConnect( 0);

    nRet = tblTellerstand->AddOne( &con, tblTellerStand, strTmp.GetBuffer(), &sCounter, insTellerStand);
    if (nRet != SQL_SUCCESS)
    {

      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC tblTellerstand->AddOne", dbgMsg);
      ODBCDisConnect();
    }
  }

  //update the anode counter
  aDT_slave[ sSlave].anode = sCounter;

  //add record in the local datataker database
  if (!ODBC_IsConnected()) ODBCConnect();

  nRet = tblBlokInfo->Insert( &con, &aDT_slave[ sSlave]);
  if (nRet != SQL_SUCCESS)
  {

    theApp.ODBCValidateReturnValue( nRet, 0, "ODBC tblBlokInfo->Insert Error", dbgMsg);
    //disconnect, and try again
    ODBCDisConnect();
    ODBCConnect( 0);

    nRet = tblBlokInfo->Insert( &con, &aDT_slave[ sSlave]);
    if (nRet != SQL_SUCCESS)
    {

      theApp.ODBCValidateReturnValue( nRet, 0, "ODBC tblBlokInfo->Insert Error", dbgMsg);
      ODBCDisConnect();
    }
  }
  
  //update slave status
  if ( aDT_slave[ sSlave].status)
    aDT_slave[ sSlave].status = 4; //send clear

  //update dialog box with block data
  gui.UpdateContent( &aDT_slave[ sSlave]);

  //return current slave id
  return sSlave;
}




