// 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

// The one and only application object
DataTakerApp theApp;

//BEGIN_MESSAGE_MAP(DataTakerApp, CWinApp)
	//{{AFX_MSG_MAP(DataTakerApp)
	//}}AFX_MSG
//END_MESSAGE_MAP()
  DtDialog gui;


DWORD WINAPI ModelessThreadFunc(LPVOID)
{

  gui.Create(DtDialog::IDD);
  gui.SetDefaults();
  gui.ShowWindow(SW_SHOW);

  
  //DtDialog *m_pTestDlg= NULL;
  //m_pTestDlg = new DtDialog();
  //m_pMainWnd = m_pTestDlg;
  //m_pTestDlg->Create(IDD_FORMVIEW, NULL);

  HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, "CloseDatatakerDialog");

  MSG msg;
  while(WaitForSingleObject(hEvent, 0) != WAIT_OBJECT_0)
  {
    while(::GetMessage(&msg, NULL, 0, 0))
    {
      ::TranslateMessage(&msg);
      ::DispatchMessage(&msg);
    }

    
  }

  // event cleanup
  CloseHandle(hEvent);

  hEvent = CreateEvent(NULL, TRUE, TRUE, "DatatakerDialogIsClosed");
  gui.CloseWindow();
  gui.DestroyWindow();
  return 0;
}

// Handler function will be called on separate thread!
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;
}



//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));
	  }	
};


//test class used for demonstrating updating a row in AdventureWorks2008.Production.ProductCategory table
class CdboTellerStandUpdate : public CODBC::CAccessor<CdboTellerStandUpdateAccessor>
{

  public:
    //Methods
	  SQLRETURN Update(CODBC::CConnection* pDbConnect, CString Table, CString Column, CString ColVal)
    {

      //Validate our parameters
      ATLASSUME(pDbConnect);

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      ODBC_CHECK_RETURN3(nRet, statement);

		  //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);

      //Bind the parameters 
      nRet = BindParameters(statement);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Execute the statement
      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      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)
    {

      //Validate our parameters
      ATLASSUME(pDbConnect);

      *sCount = 0;

      //Create the statement object
      CODBC::CStatement statement;
      SQLRETURN nRet = statement.Create(*pDbConnect);
      ODBC_CHECK_RETURN3(nRet, statement);

      //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);

      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      
      //Bind the columns
      nRet = BindColumns(statement);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Only the first record is updated
      //ClearRecord();
      nRet = statement.FetchNext();
      ODBC_CHECK_RETURN2(nRet, statement);
      if (nRet != SQL_SUCCESS)
      {

        //close pending statement
        statement.Close();
        nRet = statement.Create(*pDbConnect);
        ODBC_CHECK_RETURN3(nRet, statement);

        //select failed, insert the firstr record
        sprintf( (char *)&sql, InsertStatement, Table.GetBuffer());
		    nRet = statement.Prepare( sql);
        ODBC_CHECK_RETURN3(nRet, statement);

        nRet = statement.Execute();
        ODBC_CHECK_RETURN3(nRet, statement);

        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);

      //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);

		  //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);

      //Bind the parameters 
      nRet = BindParameters(statement);
      ODBC_CHECK_RETURN3(nRet, statement);

      //Execute the statement
      nRet = statement.Execute();
      ODBC_CHECK_RETURN3(nRet, statement);
      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),?,?,?,?)"))
*/

  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;

    //Validate our parameters
    ATLASSUME(pDbConnect);

    //Create the statement object
    CODBC::CStatement statement;
    SQLRETURN nRet = statement.Create(*pDbConnect);
    ODBC_CHECK_RETURN3(nRet, statement);

    //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);

    //Bind the parameters
//  #ifdef _tcscpy_s
//    _tcscpy_s(m_sProductCategory, sizeof(m_sProductCategory)/sizeof(TCHAR), _T("Unicycles"));
//  #else
//    _tcscpy(m_sProductCategory, _T("Unicycles"));
//  #endif

    _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);

    //Execute the statement
    nRet = statement.Execute();
    ODBC_CHECK_RETURN3(nRet, statement);
    return nRet;
  }
};







using namespace std;

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();

      //get the name of the ini file for our application
      CString mIniFile = theApp.m_pszAppName;
     
      //create our serial port object
      SerialPort MyCOMport( mIniFile.GetBuffer());

      //Get the COM port settings from the ini file
      //MyCOMport.OpenPort();
      //CString testtx = "data naar comport";
      
      //MyCOMport.Transmit( testtx.GetLength(), (unsigned char *)testtx.GetBuffer());


      //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";

      //theApp.UseData( (unsigned char *)Ctest.GetBuffer(), Ctest.GetLength());

      //open dialog
      
      //hide console window
      //HWND hwnd = GetConsoleWindow();
      //ShowWindow( hwnd, SW_HIDE);

        // create thread for the modeless dialog
  CreateThread(NULL, 0, ModelessThreadFunc, NULL, 0, NULL);

      //Devices on the COM port are polled, to grant exclusive use to the port by just one thread
      while (!theApp.StopDataTaker())
      {
        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;
}

DataTakerApp::~DataTakerApp()
{

// wait for the modeless dialog to close itself
  //HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, "CloseDatatakerDialog");
  //SetEvent( hEvent);

  //HANDLE hEvent2 = CreateEvent(NULL, TRUE, FALSE, "DatatakerDialogIsClosed");
  //while(WaitForSingleObject(hEvent2, 0) != WAIT_OBJECT_0)
  //{
    // do other job
  //}

  // event cleanup
  //CloseHandle(hEvent2);
}

BOOL DataTakerApp::InitInstance()
{

  CString sSlave;
  polltime = 100; //polltime in msec, default is 100 msec
  timepast = 0;
  //rx_len = 0;
  char *tmpbuf;


  CExecImageVersion MyVersion;
  //CIniFile MyINI("datataker.ini");

  //Settings will be stored in the registry
  //CString mijntest = MyVersion.GetLegalTrademarks();
  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.SetTraceLevel( DtIniFile->GetInteger( "Tracelevel", "DATATAKER", 0));

  dbgview.CodeTrace( "DataTaker: InitInstance function called, ini file [%s] read", 
                      6, EVENTLOG_INFORMATION_TYPE, TRACE_TEXT, NULL, 0, 
                      mIniFile.GetBuffer());

  //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; 
  }

  //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
  //CODBC::CEnvironment env;
  nRet = env.Create();
  env.ValidateReturnValue(nRet);
  nRet = env.SetAttr(SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3);
  env.ValidateReturnValue(nRet);  
  
  nRet = env.SetAttrU(SQL_ATTR_CONNECTION_POOLING, SQL_CP_OFF);
  env.ValidateReturnValue(nRet);  
      
  nRet = env.GetAttr(SQL_ATTR_ODBC_VERSION, nVersion);
  env.ValidateReturnValue(nRet);

  nRet = env.GetAttrU(SQL_ATTR_CONNECTION_POOLING, nPooling);
  env.ValidateReturnValue(nRet);

  nRet = env.CommitTran();
  env.ValidateReturnValue(nRet);
  nRet = env.RollbackTran();
  env.ValidateReturnValue(nRet);

  //Setup the connection
  nRet = con.Create(env);
  con.ValidateReturnValue(nRet);

  //Set login timeout to 5 seconds
  nRet = con.SetAttrU(SQL_ATTR_LOGIN_TIMEOUT, 5);
  con.ValidateReturnValue(nRet);

  CString strConnectString, 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;")
  nRet = con.DriverConnect(reinterpret_cast<SQLTCHAR*>(strConnectString.GetBuffer()), sFullInitString);
  con.ValidateReturnValue(nRet);

  //create the wrapper objects
  tblTellerstand = new CdboTellerStandUpdate;
  tblBlokInfo = new CdboBlokInfoInsert;

  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;


//  DtDialog *m_pTestDlg= NULL;
//  m_pTestDlg = new DtDialog();
//  m_pMainWnd = m_pTestDlg;
//  m_pTestDlg->Create(IDD_FORMVIEW, NULL);
  //m_pMainWnd = m_pTestDlg;
  
//  m_pTestDlg->Attach( GetConsoleWindow());
//  m_pMainWnd->ShowWindow( 3);

  //DtDialog MyDiag;
  //MyDiag.DoModal();
  //MyDiag.ShowWindow( SW_SHOW);



  return true;
}

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());
          strSend += " ";
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( aDT_slave[ i].pTimeString, GetTimeNow());
          strSend += strTemp;
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pStartString);
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pReadString);
          strSend += " ";
          strTemp.Format( "%s", aDT_slave[ i].pTermString);

          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;

          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;

            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;

          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; 
  //try receving one character at a time, keep receiving while there is data
  while ( sCount)
  {
    DTComPort->Receive( &sCount, &cLast);

    switch (cLast)
    {
      case 4:

        //there is data, save it in the database
        UseData(DTComPort->RX_GetBuffer(), DTComPort->RX_GetLength());
        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++;
}


void 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]);

  //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);
  
  //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);
  tblTellerstand->AddOne( &con, tblTellerStand, strTmp.GetBuffer(), &sCounter, insTellerStand);

  //update the anode counter
  aDT_slave[ sSlave].anode = sCounter;

  //add record in the local datataker database
  tblBlokInfo->Insert( &con, &aDT_slave[ sSlave]);
  
  //update slave status
  if ( aDT_slave[ sSlave].status)
    aDT_slave[ sSlave].status = 4; //send clear

  gui.UpdateContent( &aDT_slave[ sSlave]);
}

BEGIN_MESSAGE_MAP(DataTakerApp, CWinApp)
  //ON_BN_CLICKED(IDC_BUTTON1, &DataTakerApp::OnBnClickedButton1)
END_MESSAGE_MAP()


