/*
This file is part of CanFestival, a library implementing CanOpen Stack. 

CanFestival Copyright (C): Edouard TISSERANT and Francis DUPIN
CanFestival Win32 port Copyright (C) 2007 Leonid Tochinski, ChattenAssociates, Inc.

See COPYING file for copyrights details.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// can_uvccm_win32 adapter (http://www.gridconnect.com)
// driver for CanFestival-3 Win32 port

#include <sstream>
#include <iomanip>
#if 0  // change to 1 if you use boost
#include <boost/algorithm/string/case_conv.hpp>
#else
#include <algorithm>
#endif

extern "C" {
#include "can_driver.h"
}
class can_uvccm_win32
   {
   public:
      class error
        {
        };
      can_uvccm_win32(s_BOARD *board);
      ~can_uvccm_win32();
      bool send(const Message *m);
      bool receive(Message *m);
   private:
      bool open_rs232(int port = 1, int baud_rate = 57600);
      bool close_rs232();
      bool get_can_data(const char* can_cmd_buf, long& bufsize, Message* m);
      bool set_can_data(const Message& m, std::string& can_cmd);
   private:
      HANDLE m_port;
      HANDLE m_read_event;
      HANDLE m_write_event;
      std::string m_residual_buffer;
   };

can_uvccm_win32::can_uvccm_win32(s_BOARD *board) : m_port(INVALID_HANDLE_VALUE),
      m_read_event(0),
      m_write_event(0)
   {
   if (strcmp( board->baudrate, "125K") || !open_rs232(1))
      throw error();
   }

can_uvccm_win32::~can_uvccm_win32()
   {
   close_rs232();
   }

bool can_uvccm_win32::send(const Message *m)
   {
   if (m_port == INVALID_HANDLE_VALUE)
      return true;

   // build can_uvccm_win32 command string
   std::string can_cmd;
   set_can_data(*m, can_cmd);

   OVERLAPPED overlapped;
   ::memset(&overlapped, 0, sizeof overlapped);
   overlapped.hEvent = m_write_event;
   ::ResetEvent(overlapped.hEvent);

   unsigned long bytes_written = 0;
   ::WriteFile(m_port, can_cmd.c_str(), (unsigned long)can_cmd.size(), &bytes_written, &overlapped);
   // wait for write operation completion
   enum { WRITE_TIMEOUT = 1000 };
   ::WaitForSingleObject(overlapped.hEvent, WRITE_TIMEOUT);
   // get number of bytes written
   ::GetOverlappedResult(m_port, &overlapped, &bytes_written, FALSE);

   bool result = (bytes_written == can_cmd.size());
   
   return false;
   }


bool can_uvccm_win32::receive(Message *m)
   {
   if (m_port == INVALID_HANDLE_VALUE)
      return false;

   long res_buffer_size = (long)m_residual_buffer.size();
   bool result = get_can_data(m_residual_buffer.c_str(), res_buffer_size, m);
   if (result)
      {
      m_residual_buffer.erase(0, res_buffer_size);
      return true;
      }

   enum { READ_TIMEOUT = 500 };

   OVERLAPPED overlapped;
   ::memset(&overlapped, 0, sizeof overlapped);
   overlapped.hEvent = m_read_event;
   ::ResetEvent(overlapped.hEvent);
   unsigned long event_mask = 0;

   if (FALSE == ::WaitCommEvent(m_port, &event_mask, &overlapped) && ERROR_IO_PENDING == ::GetLastError())
      {
      if (WAIT_TIMEOUT == ::WaitForSingleObject(overlapped.hEvent, READ_TIMEOUT))
         return false;
      }

   // get number of bytes in the input que
   COMSTAT stat;
   ::memset(&stat, 0, sizeof stat);
   unsigned long errors = 0;
   ::ClearCommError(m_port, &errors, &stat);
   if (stat.cbInQue == 0)
      return false;
   char buffer[3000];

   unsigned long bytes_to_read = min(stat.cbInQue, sizeof (buffer));

   unsigned long bytes_read = 0;
   ::ReadFile(m_port, buffer, bytes_to_read, &bytes_read, &overlapped);
   // wait for read operation completion
   ::WaitForSingleObject(overlapped.hEvent, READ_TIMEOUT);
   // get number of bytes read
   ::GetOverlappedResult(m_port, &overlapped, &bytes_read, FALSE);
   result = false;
   if (bytes_read > 0)
      {
      m_residual_buffer.append(buffer, bytes_read);
      res_buffer_size = (long)m_residual_buffer.size();
      result = get_can_data(m_residual_buffer.c_str(), res_buffer_size, m);
      if (result)
         m_residual_buffer.erase(0, res_buffer_size);
      }
   return result;
   }

bool can_uvccm_win32::open_rs232(int port, int baud_rate)
   {
   if (m_port != INVALID_HANDLE_VALUE)
      return true;

   std::ostringstream device_name;
   device_name << "COM" << port;

   m_port = ::CreateFile(device_name.str().c_str(),
                         GENERIC_READ | GENERIC_WRITE,
                         0,   // exclusive access
                         NULL,   // no security
                         OPEN_EXISTING,
                         FILE_FLAG_OVERLAPPED,   // overlapped I/O
                         NULL); // null template

   // Check the returned handle for INVALID_HANDLE_VALUE and then set the buffer sizes.
   if (m_port == INVALID_HANDLE_VALUE)
      return false;

   //  SetCommMask(m_hCom,EV_RXCHAR|EV_TXEMPTY|EV_CTS|EV_DSR|EV_RLSD|EV_BREAK|EV_ERR|EV_RING); //
   ::SetCommMask(m_port, EV_RXFLAG);

   COMMTIMEOUTS timeouts;
   ::memset(&timeouts, 0, sizeof (timeouts));
   timeouts.ReadIntervalTimeout = -1;
   timeouts.ReadTotalTimeoutConstant = 0;
   timeouts.ReadTotalTimeoutMultiplier = 0;
   timeouts.WriteTotalTimeoutConstant = 5000;
   timeouts.WriteTotalTimeoutMultiplier = 0;
   SetCommTimeouts(m_port, &timeouts); //

   ::SetupComm(m_port, 1024, 512); // set buffer sizes

   // Port settings are specified in a Data Communication Block (DCB). The easiest way to initialize a DCB is to call GetCommState to fill in its default values, override the values that you want to change and then call SetCommState to set the values.
   DCB dcb;
   ::memset(&dcb, 0, sizeof (dcb));
   ::GetCommState(m_port, &dcb);
   dcb.BaudRate = baud_rate;
   dcb.ByteSize = 8;
   dcb.Parity = NOPARITY;
   dcb.StopBits = ONESTOPBIT;
   dcb.fAbortOnError = TRUE;
   dcb.EvtChar = 0x0A; // '\n' character
   ::SetCommState(m_port, &dcb);

   ::PurgeComm(m_port, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR);

   m_read_event = ::CreateEvent(NULL, TRUE, FALSE, NULL);
   m_write_event = ::CreateEvent(NULL, TRUE, FALSE, NULL);

   return true;
   }

bool can_uvccm_win32::close_rs232()
   {
   if (m_port != INVALID_HANDLE_VALUE)
      {
      ::PurgeComm(m_port, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR);
      ::CloseHandle(m_port);
      m_port = INVALID_HANDLE_VALUE;
      ::CloseHandle(m_read_event);
      m_read_event = 0;
      ::CloseHandle(m_write_event);
      m_write_event = 0;
      m_residual_buffer.clear();
      }
   return true;
   }

bool can_uvccm_win32::get_can_data(const char* can_cmd_buf, long& bufsize, Message* m)
   {
   if (bufsize < 5)
      {
      bufsize = 0;
      return false;
      }

   Message msg;
   ::memset(&msg, 0 , sizeof (msg));
   char colon = 0, type = 0, request = 0;
   std::istringstream buf(std::string(can_cmd_buf, bufsize));
   buf >> colon >> type >> std::hex >> msg.cob_id >> request;
   if (colon != ':' || (type != 'S' && type != 'X'))
      {
      bufsize = 0;
      return false;
      }
   if (request == 'N')
      {
      msg.rtr = 0;
      for (msg.len = 0; msg.len < 8; ++msg.len)
         {
         std::string data_byte_str;
         buf >> std::setw(2) >> data_byte_str;
         if (data_byte_str[0] == ';')
            break;
         long byte_val = -1;
         std::istringstream(data_byte_str) >> std::hex >> byte_val;
         if (byte_val == -1)
            {
            bufsize = 0;
            return false;
            }
         msg.data[msg.len] = (UNS8)byte_val;
         }
      if (msg.len == 8)
         {
         char semicolon = 0;
         buf >> semicolon;
         if (semicolon != ';')
            {
            bufsize = 0;
            return false;
            }
         }

      }
   else if (request == 'R')
      {
      msg.rtr = 1;
      buf >> msg.len;
      }
   else
      {
      bufsize = 0;
      return false;
      }

   bufsize = buf.tellg();

   *m = msg;
   return true;
   }

bool can_uvccm_win32::set_can_data(const Message& m, std::string& can_cmd)
   {
   // build can_uvccm_win32 command string
   std::ostringstream can_cmd_str;
   can_cmd_str << ":S" << std::hex << m.cob_id;
   if (m.rtr == 1)
      {
      can_cmd_str << 'R' << (long)m.len;
      }
   else
      {
      can_cmd_str << 'N';
      for (int i = 0; i < m.len; ++i)
         can_cmd_str << std::hex << std::setfill('0') << std::setw(2) << (long)m.data[i];
      }
   can_cmd_str << ';';
   can_cmd = can_cmd_str.str();
#ifdef BOOST_VERSION
   boost::to_upper(can_cmd);
#else
   std::transform(can_cmd.begin(),can_cmd.end(),can_cmd.begin(),::toupper);
#endif
   return true;
   }


//------------------------------------------------------------------------
extern "C"
   UNS8 __stdcall canReceive_driver(CAN_HANDLE fd0, Message *m)
   {
   return (UNS8)(!(reinterpret_cast<can_uvccm_win32*>(fd0)->receive(m)));
   }

extern "C"
   UNS8 __stdcall canSend_driver(CAN_HANDLE fd0, Message const *m)
   {
   return (UNS8)reinterpret_cast<can_uvccm_win32*>(fd0)->send(m);
   }

extern "C"
   CAN_HANDLE __stdcall canOpen_driver(s_BOARD *board)
   {
   try
      {
      return (CAN_HANDLE) new can_uvccm_win32(board);
      }
   catch (can_uvccm_win32::error&)
      {
      return NULL;
      }
   }

extern "C"
   int __stdcall canClose_driver(CAN_HANDLE inst)
   {
   delete reinterpret_cast<can_uvccm_win32*>(inst);
   return 1;
   }

extern "C"
	UNS8 __stdcall canChangeBaudRate_driver( CAN_HANDLE fd, char* baud)
	{
	return 0;
	} 
