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

Copyright (C): Edouard TISSERANT and Francis DUPIN
Modified by: Jaroslav Fojtik

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

#if defined(WIN32) && !defined(__CYGWIN__)
#define usleep(micro) Sleep(micro%1000 ? (micro/1000) + 1 : (micro/1000))
#else
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#endif

#include "cancfg.h"
#include "can_driver.h"
#include "def.h"


#define VERSION_2

/* dummy implementation for older version. */
#ifndef VERSION_2
void CAN_SetRcvEvent(HANDLE hEventx)
{
  SetEvent(hEventx);
}
#endif




#ifndef extra_PCAN_init_params
	#define extra_PCAN_init_params /**/
#else
	long int print_getenv(const char* pcanparam)
	{
		char* param=NULL;
		long int res=0;

		param = getenv(pcanparam);
		if(param != NULL){
			res = strtol(param,NULL,0);
		}
		else
			printf("Environment variable %s not defined !\n", pcanparam);
		printf("Found environment variable %s : %ld\n", pcanparam ,res);
		return res;
	}
	#define extra_PCAN_init_params\
		,print_getenv("PCANHwType")\
		,print_getenv("PCANIO_Port")\
		,print_getenv("PCANInterupt")
#endif


static s_BOARD *first_board = NULL;
//Create the Event for the first board
HANDLE hEvent1 = NULL;
CRITICAL_SECTION InitLock1;
CRITICAL_SECTION InitLock2;


#ifdef PCAN2_HEADER_
  static s_BOARD *second_board = NULL;
  HANDLE hEvent2 = NULL;
#endif

// Define for rtr CAN message
#define CAN_INIT_TYPE_ST_RTR MSGTYPE_STANDARD | MSGTYPE_RTR

/***************************************************************************/
static int TranslateBaudeRate(char* optarg)
{
	if(!strcmp( optarg, "1M")) return CAN_BAUD_1M;
	if(!strcmp( optarg, "500K")) return CAN_BAUD_500K;
	if(!strcmp( optarg, "250K")) return CAN_BAUD_250K;
	if(!strcmp( optarg, "125K")) return CAN_BAUD_125K;
	if(!strcmp( optarg, "100K")) return CAN_BAUD_100K;
	if(!strcmp( optarg, "50K")) return CAN_BAUD_50K;
	if(!strcmp( optarg, "20K")) return CAN_BAUD_20K;
	if(!strcmp( optarg, "10K")) return CAN_BAUD_10K;
	if(!strcmp( optarg, "5K")) return CAN_BAUD_5K;
	if(!strcmp( optarg, "none")) return 0;
	return 0x0000;
}

static UNS8 canInit (s_BOARD *board)
{
	int baudrate;
	int ret = 0;

#ifdef PCAN2_HEADER_
	// if not the first handler
	if(second_board == (s_BOARD *)board) {
		if(hEvent2==NULL)
		{	/* Create the Event for the first board */
		  hEvent2 = CreateEvent(NULL, // lpEventAttributes
							FALSE,  // bManualReset
							FALSE,  // bInitialState
							"");    // lpName
		  InitializeCriticalSection(&InitLock2);
		}

		EnterCriticalSection(&InitLock2);
		if(baudrate = TranslateBaudeRate(board->baudrate))
		{
		  ret = CAN2_Init(baudrate, CAN_INIT_TYPE_ST extra_PCAN_init_params);
		  if(ret != CAN_ERR_OK)
		  {
		    LeaveCriticalSection(&InitLock2);
		    return 0;
		  }
		}
		//Set Event Handle for CANReadExt
		CAN2_SetRcvEvent(hEvent2);
		LeaveCriticalSection(&InitLock2);
	}
	else
#endif
	if(first_board == (s_BOARD *)board) {
		//Create the Event for the first board
		if(hEvent1==NULL)
		{
		  hEvent1 = CreateEvent(NULL, // lpEventAttributes
							FALSE,  // bManualReset
							FALSE,  // bInitialState
							"");    // lpName
		  InitializeCriticalSection(&InitLock1);
                }

		EnterCriticalSection(&InitLock1);
		if(baudrate = TranslateBaudeRate(board->baudrate))
		{
		  ret = CAN_Init(baudrate, CAN_INIT_TYPE_ST extra_PCAN_init_params);
		  if(ret != CAN_ERR_OK)
		  {
		    LeaveCriticalSection(&InitLock1);
		    return 0;
		  }
		}
		//Set Event Handle for CANReadExt
		CAN_SetRcvEvent(hEvent1);
		LeaveCriticalSection(&InitLock1);
	}
	return 1;
}

/********* functions which permit to communicate with the board ****************/
UNS8 LIBAPI canReceive_driver(CAN_HANDLE fd0, Message * m)
{
static int HeavyCounter = 0;
	int ret=0;
	UNS8 data;
	TPCANMsg peakMsg;
#ifdef CAN_READ_EX
	TPCANTimestamp peakRcvTime;
#endif
	DWORD Res;
	DWORD result;
	// loop until valid message or fatal error
	do{
#ifdef PCAN2_HEADER_
		// if not the first handler
		if(second_board == (s_BOARD *)fd0) {
			//wait for CAN msg...
			result = WaitForSingleObject(hEvent2, INFINITE);
			if (result == WAIT_OBJECT_0)
				Res = CAN2_ReadEx(&peakMsg, &peakRcvTime);
				// Exit receive thread when handle is no more valid
				if(Res & CAN_ERRMASK_ILLHANDLE)
					return 1;
		}
		else
#endif

		// We read the queue looking for messages.
		if(first_board == (s_BOARD *)fd0) 
		{
#ifdef VERSION_2
			result = WaitForSingleObject(hEvent1, INFINITE);
			if (result == WAIT_OBJECT_0)
#endif
			{
#ifdef CAN_READ_EX
				Res = CAN_ReadEx(&peakMsg, &peakRcvTime);
#else
				Res = CAN_Read(&peakMsg);
#endif
				// Exit receive thread when handle is no more valid
#ifdef CAN_ERRMASK_ILLHANDLE
				if(Res & CAN_ERRMASK_ILLHANDLE) return 1;
#else
				if(Res & CAN_ERR_ILLHANDLE) return 1;
#endif				

#ifndef VERSION_2
				if(Res != CAN_ERR_OK) 
					result = WaitForSingleObject(hEvent1, 1);  	//pooling
#endif
			}
		}
#ifdef VERSION_2
		else
			Res = CAN_ERR_BUSOFF;
#endif
		
		// A message was received : we process the message(s)
		if(Res == CAN_ERR_OK)
		{			
		  switch(peakMsg.MSGTYPE)
		  {
		    case MSGTYPE_STATUS:
			  switch(peakMsg.DATA[3])
		          {
			    case CAN_ERR_BUSHEAVY: 
				      break;
		            case CAN_ERR_BUSOFF: 
			              printf ("Peak board read BUSOFF: re-init!!!\n");
				      canInit((s_BOARD*)fd0);
				      usleep(33);
				      break;
			  }
			  return peakMsg.DATA[3];	/* if something different that 11bit or rtr... problem */

		    case MSGTYPE_STANDARD:		/* bits of MSGTYPE_ */
		    case MSGTYPE_EXTENDED:
			  m->rtr = 0;
			  break;

		    case MSGTYPE_RTR:			/* bits of MSGTYPE_ */
			  m->rtr = 1;
			  break;

		    default: return CAN_ERR_OVERRUN;	/* If status, return status if 29bit, return overrun. */
		    
		    }

		   m->cob_id = peakMsg.ID;				
		   if (peakMsg.MSGTYPE == CAN_INIT_TYPE_ST)  /* bits of MSGTYPE_ */
                     m->rtr = 0;
		   else
		     m->rtr = 1;
		   m->len = peakMsg.LEN;		/* count of data bytes (0..8) */
		   for (data = 0; data < peakMsg.LEN; data++)
			m->data[data] = peakMsg.DATA[data];	/* data bytes, up to 8 */
#if defined DEBUG_MSG_CONSOLE_ON
			MSG("in : ");
			print_message(m);
#endif
		}
		else
		{
			// not benign error => fatal error
			if (!(Res & CAN_ERR_QRCVEMPTY
					|| Res & CAN_ERR_BUSLIGHT
					|| Res & CAN_ERR_BUSHEAVY))
			{
				printf ("canReceive returned error (%d)\n", Res);
				return 1;
			}
		}
	} while(Res != CAN_ERR_OK);
	return 0;
}

/***************************************************************************/
UNS8 LIBAPI canSend_driver(CAN_HANDLE fd0, Message const *m)
{
	UNS8 data;
	TPCANMsg peakMsg;
	peakMsg.ID = m->cob_id;	/* 11/29 bit code */
	if (m->rtr == 0)
	{
	  if(peakMsg.ID > 0x7FF)
		peakMsg.MSGTYPE = MSGTYPE_EXTENDED;	/* bits of MSGTYPE_ */
	  else
		peakMsg.MSGTYPE = MSGTYPE_STANDARD;	/* bits of MSGTYPE_ */
	}
	else	
		peakMsg.MSGTYPE = MSGTYPE_RTR;		/* bits of MSGTYPE_ */
	
	peakMsg.LEN = m->len;
	/* count of data bytes (0..8) */
	for (data = 0; data < m->len; data++)
		peakMsg.DATA[data] = m->data[data];	/* data bytes, up to 8 */

	do
	{
#ifdef PCAN2_HEADER_
		// if not the first handler
		if(second_board == (s_BOARD *)fd0)
		{
			errno = CAN2_Write (&peakMsg);
		}
		else
#endif
		if(first_board == (s_BOARD *)fd0)
			{
				errno = CAN_Write (&peakMsg);
			}
		else
			goto fail;
		if (errno)
		{
			if (errno == CAN_ERR_BUSOFF)
			{
				printf ("!!! Peak board write : re-init\n");
				canInit((s_BOARD*)fd0);
				usleep (10000);
			}
			usleep (1000);
		}
	}
	while (errno != CAN_ERR_OK);
#if defined DEBUG_MSG_CONSOLE_ON
	MSG("out : ");
	print_message(m);
#endif
	return 0;
fail:
	return 1;
}

/***************************************************************************/
UNS8 LIBAPI canChangeBaudRate_driver(CAN_HANDLE fd, char* baud)
{
	printf("canChangeBaudRate not yet supported by this driver\n");
	return 0;
}

/***************************************************************************/
LIBPUBLIC CAN_HANDLE LIBAPI canOpen_driver(s_BOARD * board)
{
  char* pEnd;
  int ret;

  //printf ("Board Busname=%d.\n",strtol(board->busname, &pEnd,0));
  if (strtol(board->busname, &pEnd,0) == 0)
  {
      first_board = board;
      //printf ("First Board selected\n");
      ret = canInit(board);
      if(ret)
    	  return (CAN_HANDLE)board;
  }
  #ifdef PCAN2_HEADER_
  if (strtol(board->busname, &pEnd,0) == 1)
  {
      second_board = board;
      //printf ("Second Board selected\n");
      ret = canInit(board);
      if(ret)
    	  return (CAN_HANDLE)board;
  }
  #endif
  return NULL;
}


/***************************************************************************/
int LIBAPI canClose_driver(CAN_HANDLE fd0)
{
#ifdef PCAN2_HEADER_
	// if not the first handler
	if(second_board == (s_BOARD *)fd0)
	{
		CAN2_SetRcvEvent(NULL);
		CAN2_Close ();		
		if(hEvent2)
		{
		  SetEvent(hEvent2);
		  CloseHandle(hEvent2);		
		  hEvent2 = NULL;
		}
		second_board = (s_BOARD *)NULL;
	}else
#endif
	if(first_board == (s_BOARD *)fd0)
	{
		CAN_SetRcvEvent(NULL);
		CAN_Close ();		
		if(hEvent1) 
		{
                  SetEvent(hEvent1);
		  CloseHandle(hEvent1);
		  hEvent1 = NULL;
                }
		first_board = (s_BOARD *)NULL;
	}
	return 0;
}
