#include "ofSerial.h"



//---------------------------------------------
#ifdef TARGET_WIN32
//---------------------------------------------
//some static code for windows serial enumeration
//ripped from: http://www.codeproject.com/system/serial_portsenum_fifo.asp

#include <setupapi.h>
#include <dbt.h>
#include <string.h>

#define MAX_NAME_PORTS 7
#define RegDisposition_OpenExisting (0x00000001)   // open key only if exists
#define CM_REGISTRY_HARDWARE        (0x00000000)


typedef DWORD WINAPI
  (* CM_Open_DevNode_Key)(DWORD, DWORD, DWORD, DWORD, ::PHKEY, DWORD);


//-----------------------------------------------------------------------
HANDLE EnumeratePorts(VOID);
HANDLE EnumeratePorts(VOID){
 
  BOOL guidTest=FALSE;
  DWORD RequiredSize=0;
  int j;
  HDEVINFO DeviceInfoSet;
  char* buf;

        guidTest=SetupDiClassGuidsFromNameA(
        "Ports",0,0,&RequiredSize);
        if(RequiredSize < 1)return HANDLE(-1);

        buf=(char *)malloc(RequiredSize*sizeof(GUID));

        guidTest=SetupDiClassGuidsFromNameA(
        "Ports",(GUID *)buf,RequiredSize*sizeof(GUID),&RequiredSize);

        if(!guidTest)return HANDLE(-1);


           DeviceInfoSet=SetupDiGetClassDevs(
             (GUID *)buf,NULL,NULL,DIGCF_PRESENT);
             if(DeviceInfoSet == INVALID_HANDLE_VALUE)return (HANDLE)-1;

           free(buf);

	return DeviceInfoSet;
}

//-----------------------------------------------------------------------
BOOL EnumeratePortsNext(HANDLE DeviceInfoSet, LPTSTR lpBuffer);
BOOL EnumeratePortsNext(HANDLE DeviceInfoSet, LPTSTR lpBuffer){

	static CM_Open_DevNode_Key OpenDevNodeKey=NULL;
	static HINSTANCE CfgMan;

	int res1;
	char DevName[MAX_NAME_PORTS]={0};
	static int numDev=0;
	int numport;

	SP_DEVINFO_DATA DeviceInfoData={0};
	DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA);


	if(!DeviceInfoSet || !lpBuffer)return -1;
	if(!OpenDevNodeKey)
	   {
	        CfgMan=LoadLibrary("cfgmgr32");
	        if(!CfgMan)return FALSE;
	        OpenDevNodeKey=(CM_Open_DevNode_Key)GetProcAddress(CfgMan,"CM_Open_DevNode_Key");
	        if(!OpenDevNodeKey)
	               {
	                FreeLibrary(CfgMan);
	                return FALSE;
	               }
	   }

	while(TRUE)
	     {

	        HKEY KeyDevice;
	        DWORD len;
	        res1=SetupDiEnumDeviceInfo(
	        DeviceInfoSet,numDev,&DeviceInfoData);

	        if(!res1)
	          {
	          SetupDiDestroyDeviceInfoList(DeviceInfoSet);
	          FreeLibrary(CfgMan);
	          OpenDevNodeKey=NULL;
	          return FALSE;
	                }


	        res1=OpenDevNodeKey(DeviceInfoData.DevInst,KEY_QUERY_VALUE,0,
	            RegDisposition_OpenExisting,&KeyDevice,CM_REGISTRY_HARDWARE);
	        if(res1 != ERROR_SUCCESS)return NULL;
	        len=MAX_NAME_PORTS;

	        res1=RegQueryValueEx(
	                KeyDevice,	// handle of key to query
	                "portname",	// address of name of value to query
	               NULL,	// reserved
	               NULL,	// address of buffer for value type
	                (unsigned char *)DevName,	// address of data buffer
	                &len 	// address of data buffer size
	                );

	        RegCloseKey(KeyDevice);
	        if(res1 != ERROR_SUCCESS)return NULL;
	        numDev++;
	        if(memcmp(DevName, "COM", 3))continue;
	        
	        numport=atoi(DevName+3);
	        if(numport > 0)
	         {
	          strcpy(lpBuffer,DevName);
	          return TRUE;
	         }

	        FreeLibrary(CfgMan);
	        OpenDevNodeKey=NULL;
	        return FALSE;
	   }
}
//---------------------------------------------
#endif
//---------------------------------------------


//----------------------------------------------------------------
ofSerial::ofSerial(){

	
}

//----------------------------------------------------------------
void ofSerial::enumerateDevices(){
	
	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
	//---------------------------------------------

		//----------------------------------------------------
		//We will find serial devices by listing the directory
		
		DIR *dir;
		struct dirent *entry;
		dir = opendir("/dev");
		string str			= "";
		string device		= "";
			

		if (dir == NULL){
			printf("ofSerial: error listing devices in /dev\n");
		} else {
			printf("ofSerial: listing devices\n");
			while ((entry = readdir(dir)) != NULL){	
				str = (char *)entry->d_name;
				if( str.substr(0,3) == "cu." ){
					printf("device %i - %s  \n", count, str.c_str());
					count++;
				}
			}
		}
		
	//---------------------------------------------
    #endif
    //---------------------------------------------

	//---------------------------------------------
	#ifdef TARGET_WIN32
	//---------------------------------------------

		int count = 0;
		char buff[20];
		HANDLE res = EnumeratePorts();
		printf("ofSerial: listing devices\n");
		while( EnumeratePortsNext(res, buff) ){
			string str = buff;
			printf("device %i - %s\n", count, str.c_str());
			count++;
		}
		
	//---------------------------------------------
    #endif
    //---------------------------------------------

}
		
		
		
//----------------------------------------------------------------		
bool ofSerial::setup(){
	return setup(0,9600);
}

//----------------------------------------------------------------
bool ofSerial::setup(int deviceNumber, int baud){
	
	
	int count = 0;
	
	string str			= "";
	string device		= "";
	bool deviceFound	= false;
		
	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
	//---------------------------------------------

		//----------------------------------------------------
		//We will find serial devices by listing the directory
		
		DIR *dir;
		struct dirent *entry;
		dir = opendir("/dev");

		if (dir == NULL){
			printf("ofSerial: error listing devices in /dev\n");
		}
		
		while ((entry = readdir(dir)) != NULL){	
			str = (char *)entry->d_name;
			if( str.substr(0,3) == "cu." ){
				if(count == deviceNumber){
					device = "/dev/"+str;
					deviceFound = true;
					printf("ofSerial device %i - /dev/%s  <--selected\n", count, str.c_str());
				}else printf("ofSerial device %i - /dev/%s\n", count, str.c_str());
				count++;
			}
		}
		
		
	//---------------------------------------------
    #endif
    //---------------------------------------------

	//---------------------------------------------
	#ifdef TARGET_WIN32
	//---------------------------------------------

		char buff[20];
		HANDLE res = EnumeratePorts();
		
		while( EnumeratePortsNext(res, buff) ){
			string str = buff;
			
			if(count == deviceNumber){
				printf("ofSerial device %i - %s  <--selected\n", count, str.c_str());
				deviceFound = true;
				device = str;
			}else printf("ofSerial device %i - %s\n", count, str.c_str());
			
			count++;
		}
	
	//---------------------------------------------
    #endif
    //---------------------------------------------

	if(deviceFound){
		return setup(device, baud);
	}else{
		printf("ofSerial: could not find device %i - only %i devices found\n", deviceNumber, count);
		return false;
	}

}

//----------------------------------------------------------------
bool ofSerial::setup(string portName, int baud){

	bInited = false;

	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
	//---------------------------------------------
	
	    printf("ofSerialInit: opening port %s @ %d bps\n", portName.c_str(), baud);
		fd = open(portName.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
		if(fd == -1){
			printf("ofSerial: unable to open port\n");
			return false;
		}

		struct termios options;
		tcgetattr(fd,&options);

		switch(baud){
		   case 300: 	cfsetispeed(&options,B300);
						cfsetospeed(&options,B300);
						break;
		   case 1200: 	cfsetispeed(&options,B1200);
						cfsetospeed(&options,B1200);
						break;		   
		   case 2400: 	cfsetispeed(&options,B2400);
						cfsetospeed(&options,B2400);
						break;		
		   case 4800: 	cfsetispeed(&options,B4800);
						cfsetospeed(&options,B4800);
						break;	
		   case 9600: 	cfsetispeed(&options,B9600);
						cfsetospeed(&options,B9600);
						break;
		   case 14400: 	cfsetispeed(&options,B14400);
						cfsetospeed(&options,B14400);
						break;						
		   case 19200: 	cfsetispeed(&options,B19200);
						cfsetospeed(&options,B19200);
						break;
		   case 28800: 	cfsetispeed(&options,B28800);
						cfsetospeed(&options,B28800);
						break;
		   case 38400: 	cfsetispeed(&options,B38400);
						cfsetospeed(&options,B38400);
						break;
		   case 57600:  cfsetispeed(&options,B57600);
						cfsetospeed(&options,B57600);
						break;		
		   case 115200: cfsetispeed(&options,B115200);
						cfsetospeed(&options,B115200);
						break;								
									
			default:	cfsetispeed(&options,B9600);
						cfsetospeed(&options,B9600);
						printf("ofSerialInit: cannot set %i baud setting baud to 9600\n", baud); 
						break;
		}

		options.c_cflag |= (CLOCAL | CREAD);
		options.c_cflag &= ~PARENB;
		options.c_cflag &= ~CSTOPB;
		options.c_cflag &= ~CSIZE;
		options.c_cflag |= CS8;
		tcsetattr(fd,TCSANOW,&options);

		bInited = true;
		printf("sucess in opening serial connection\n");

	    return true;
	//---------------------------------------------
    #endif
    //---------------------------------------------


    //---------------------------------------------
	#ifdef TARGET_WIN32
	//---------------------------------------------

	// open the serial port:
	// "COM4", etc...

	hComm=CreateFileA(portName.c_str(),GENERIC_READ|GENERIC_WRITE,0,0,
					OPEN_EXISTING,0,0);

	if(hComm==INVALID_HANDLE_VALUE){
		printf("ofSerial: unable to open port\n");
		return false;
	}

	// now try the settings:
	COMMCONFIG cfg;
	DWORD cfgSize;
	char  buf[80];
	
	cfgSize=sizeof(cfg);
	GetCommConfig(hComm,&cfg,&cfgSize);
	int bps = baud;
	sprintf(buf,"baud=%d parity=N data=8 stop=1",bps);
	
	// BuildCommDCB on visual studio seems to need this
	// because BuildCommDCB expects unicode input

	#if (_MSC_VER)       // microsoft visual studio
		wchar_t bufWide[80];
		mbstowcs(bufWide, buf, strlen(buf));	
		if(!BuildCommDCB(bufWide,&cfg.dcb)){
			printf("ofSerial: unable to build comm dcb; (%s) \n",buf);
		}
	#else
		if(!BuildCommDCB(buf,&cfg.dcb)){
			printf("ofSerial: Can't build comm dcb; %s\n",buf);
		}
	#endif

	
	// Set baudrate and bits etc.
	// Note that BuildCommDCB() clears XON/XOFF and hardware control by default
	
	if(!SetCommState(hComm,&cfg.dcb)){
		printf("ofSerial: Can't set comm state\n");
	}
	//printf(buf,"bps=%d, xio=%d/%d\n",cfg.dcb.BaudRate,cfg.dcb.fOutX,cfg.dcb.fInX);

	// Set communication timeouts (NT)
	COMMTIMEOUTS tOut;
	GetCommTimeouts(hComm,&tOut);
	// Make timeout so that:
	// - return immediately with buffered characters
	tOut.ReadIntervalTimeout=MAXDWORD;
	tOut.ReadTotalTimeoutMultiplier=0;
	tOut.ReadTotalTimeoutConstant=0;
	SetCommTimeouts(hComm,&tOut);

	bInited = true;
	return true;
	//---------------------------------------------
	#endif
	//---------------------------------------------
}


//----------------------------------------------------------------
int ofSerial::writeBytes(string str){

	if (!bInited){
		printf("ofSerial: serial not inited\n");
		return 0;
	}

	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
	    int len = str.length();
	    int numWritten = write(fd, str.c_str(), len);
		if(numWritten == 0 && len > 0) printf("ofSerial: Can't write to com port");
		// verbose
		//else printf("ofSerial: numWritten %i \n", numWritten);
	    return numWritten;
    #endif
    //---------------------------------------------

    //---------------------------------------------
	#ifdef TARGET_WIN32
		DWORD written;
		int len = str.length();
		if(!WriteFile(hComm, str.c_str(), len, &written,0)){
			 printf("ofSerial: Can't write to com port");
			 return 0;
		}else{
			// verbose
			//printf("ofSerial: numWritten %i \n", (int)written);
		}
		return (int)written;
	#endif
	//---------------------------------------------

}

//----------------------------------------------------------------
string ofSerial::readBytes(int length){

	if (!bInited){
		printf("ofSerial: serial not inited\n");
		return "";
	}

	char * tmpBuff = new char[length];
	memset(tmpBuff, 0, length);
	string str = ""; 
	
	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
		int nRead = read(fd, tmpBuff,length);	
		if(nRead == 0)printf("ofSerial: trouble reading from port\n");

    #endif
    //---------------------------------------------

    //---------------------------------------------
	#ifdef TARGET_WIN32
		DWORD nRead;
		if (!ReadFile(hComm,tmpBuff,length,&nRead,0)){
		printf("ofSerial: trouble reading from port\n");
		}
	#endif
	//---------------------------------------------
	
	str = tmpBuff;
	delete tmpBuff;
	return str;
}

//----------------------------------------------------------------
bool ofSerial::writeByte(unsigned char singleByte){

	if (!bInited){
		printf("ofSerial: serial not inited\n");
		return 0;
	}

	unsigned char tmpByte[10];
	tmpByte[0] = singleByte;

	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
	    int len = 1;
	    int numWritten = 0;
	    numWritten = write(fd, tmpByte, len);
		if(numWritten == 0 && len > 0){
			 printf("ofSerial: Can't write to com port");
		} else {
			// verbose
			//printf("ofSerial: written byte \n");
	    }
	    return numWritten;
    #endif
    //---------------------------------------------

    //---------------------------------------------
	#ifdef TARGET_WIN32
		DWORD written;
		int len = 1;
		if(!WriteFile(hComm, tmpByte, len, &written,0)){
			 printf("ofSerial: Can't write to com port");
			 return 0;
		}else{
			//verbose, non verbose
			//printf("ofSerial: written byte \n");
		}
		return (int)written;
	#endif
	//---------------------------------------------

}

//----------------------------------------------------------------
unsigned char ofSerial::readByte(){

	if (!bInited){
		printf("ofSerial: serial not inited\n");
		return 0;
	}

	unsigned char tmpByte[10];
	
	//---------------------------------------------
	#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
		int nRead = read(fd, tmpByte, 1);	
		if(nRead == 0)printf("ofSerial: trouble reading from port\n");

    #endif
    //---------------------------------------------

    //---------------------------------------------
	#ifdef TARGET_WIN32
		DWORD nRead;
		if (!ReadFile(hComm, tmpByte, 1, &nRead, 0)){
		printf("ofSerial: trouble reading from port\n");
		}
	#endif
	//---------------------------------------------
	
	return tmpByte[0];
}



