/*
    zserial_win32 - portable serial port API
	Module for WIN32 serial ports
    Copyright (C) 2012-2023 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

    UTF-8 detect: ěščřžýáíé
*/

#include <zserial.h>

#include <zdebug.h>
#include <zerror.h>
#include <zgptrarray.h>
#include <zthread.h>
#include <zmisc.h>
#include <zmsgbox.h>

#ifdef Z_MSC_MINGW

#ifdef Z_HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef Z_HAVE_WINDOWS_H
#include <windows.h>
#endif


static int zserial_win32_open(struct zserial *zser){
    char compath[MAX_PATH];
	char *settings;
	DCB dcb;
	int ret;

    if (zser->handle != INVALID_HANDLE_VALUE) return 0;

    g_snprintf(compath, MAX_PATH, "\\\\.\\%s", zser->filename);
    zser->handle = CreateFile(compath, GENERIC_READ|GENERIC_WRITE/*|FILE_FLAG_NO_BUFFERING*/, 0, 0, 
                         OPEN_EXISTING, 0, 0);
    if (zser->handle == INVALID_HANDLE_VALUE){
        g_string_printf(zser->errorstr, "Can't open device %s: ", zser->id);
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }
    
    settings = g_strdup_printf("%d,%c,%d,%d", zser->baudrate, zser->parity, zser->bits, zser->stopbits);
    //strcpy(settings, "9600,N,8,1");
    memset(&dcb, 0, sizeof(dcb));
    dcb.DCBlength=sizeof(dcb);

    ret = BuildCommDCB(settings, &dcb);
    g_free(settings);
    if (!ret){
        g_string_printf(zser->errorstr, "zserial_open: BuildCommDCB failed: ");
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }

    /* BuildCommDCB pod win98fe to nenastavuje */
    dcb.fDtrControl=1;
    dcb.fRtsControl=1;	

    /* kompatibilita s mttty */
    dcb.fBinary=1;
    dcb.XonChar=0x11;
    dcb.XoffChar=0x13;

    if (!SetCommState(zser->handle, &dcb)) {
        g_string_printf(zser->errorstr, "Can't set comm state for %s: ", zser->id);
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    } 

    zser->opened = 1;

    return 0;
}

static int zserial_win32_read(struct zserial *zser, void *data, int len, int timeout_ms){
	DWORD dwRead;
	COMMTIMEOUTS timeouts;

    timeouts.ReadIntervalTimeout = timeout_ms; 
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = timeout_ms;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;

    if (!SetCommTimeouts(zser->handle, &timeouts)){
        g_string_printf(zser->errorstr, "zserial_read: SetCommTimeouts failed: ");
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }

    if (!ReadFile(zser->handle, data, len, &dwRead, NULL)){
        g_string_printf(zser->errorstr, "Can't read from %s: ", zser->id);
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }
    return dwRead;
}

static int zserial_win32_write(struct zserial *zser, void *data, int len){
    int ret;
	DWORD written;

    ret = WriteFile(zser->handle, data, len, &written, NULL);
    if (!ret){ 
        g_string_printf(zser->errorstr, "Can't write to %s: ", zser->id);
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }
    if (!FlushFileBuffers(zser->handle)){
        g_string_printf(zser->errorstr, "zserial_write: FlushFileBuffers failed: ");
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }
    return written;
}

static int zserial_win32_close(struct zserial *zser){
	MUTEX_LOCK(zser->close3);
	if (zser->handle != INVALID_HANDLE_VALUE) CloseHandle(zser->handle);
	zser->handle = INVALID_HANDLE_VALUE;
	MUTEX_UNLOCK(zser->close3);
	return 0;
}

static int zserial_win32_dtr(struct zserial *zser, int on){
    if (!EscapeCommFunction(zser->handle, on ? SETDTR : CLRDTR)){
        g_string_printf(zser->errorstr, "Can't set DTR on %s: ", zser->id);
        z_lasterror(zser->errorstr);
        return -1;
    }
    return 0;
}

static int zserial_win32_rts(struct zserial *zser, int on){
    if (!EscapeCommFunction(zser->handle, on ? SETRTS : CLRRTS)){
        g_string_printf(zser->errorstr, "Can't set RTS on %s: ", zser->id);
        z_lasterror(zser->errorstr);
        return -1;
    }
    return 0;
}


static gint z_compare_port (gconstpointer a, gconstpointer b){
	struct zserial_port **ca = (struct zserial_port**)a;
	struct zserial_port **cb = (struct zserial_port**)b;
		
	if (strlen((*ca)->filename) < 4) return 0;
	if (strlen((*cb)->filename) < 4) return 0;
	
	return atoi((*ca)->filename + 3) - atoi((*cb)->filename + 3);
}


#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

int zserial_win32_detect(struct zserial *zser){
	DWORD dwType, dwCount,ii;
	int rc, n, i;
	char *regbase;

	HKEY hKey;
    CHAR     achKey[MAX_KEY_LENGTH];   // buffer for subkey name
    DWORD    cbName;                   // size of name string 
    CHAR     achClass[MAX_PATH] = "";  // buffer for class name 
    DWORD    cchClassName = MAX_PATH;  // size of class string 
    DWORD    cSubKeys=0;               // number of subkeys 
    DWORD    cbMaxSubKey;              // longest subkey size 
    DWORD    cchMaxClass;              // longest class string 
    DWORD    cValues;              // number of values for key 
    DWORD    cchMaxValue;          // longest value name 
    DWORD    cbMaxValueData;       // longest value data 
    DWORD    cbSecurityDescriptor; // size of security descriptor 
    FILETIME ftLastWriteTime;      // last write time 
 
    DWORD  retCode; 
 
    CHAR  achValue[MAX_VALUE_NAME]; 
    DWORD cchValue = MAX_VALUE_NAME; 
 

	rc=RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey);
	if (rc != ERROR_SUCCESS){
		return zser->ports->len;
	}

    // Get the class name and the value count. 
    retCode = RegQueryInfoKey(
        hKey,                    // key handle 
        achClass,                // buffer for class name 
        &cchClassName,           // size of class string 
        NULL,                    // reserved 
        &cSubKeys,               // number of subkeys 
        &cbMaxSubKey,            // longest subkey size 
        &cchMaxClass,            // longest class string 
        &cValues,                // number of values for this key 
        &cchMaxValue,            // longest value name 
        &cbMaxValueData,         // longest value data 
        &cbSecurityDescriptor,   // security descriptor 
        &ftLastWriteTime);       // last write time 
 
    // Enumerate the subkeys, until RegEnumKeyEx fails.
    
    if (cSubKeys)
    {
        dbg("Number of subkeys: %d\n", cSubKeys);
        for (ii=0; ii<cSubKeys; ii++) { 
            cbName = MAX_KEY_LENGTH;
            retCode = RegEnumKeyEx(hKey, ii, achKey, &cbName, NULL, NULL, NULL, &ftLastWriteTime); 
            if (retCode == ERROR_SUCCESS) {
                dbg( "(%d) %s\n", ii+1, achKey);
            }
        }
    } 
 
    // Enumerate the key values. 

    if (cValues) 
    {
        dbg( "\nNumber of values: %d\n", cValues);
        for (ii=0, retCode=ERROR_SUCCESS; ii<cValues; ii++) { 
            cchValue = MAX_VALUE_NAME; 
            achValue[0] = '\0'; 
            retCode = RegEnumValue(hKey, ii, achValue, &cchValue, NULL, NULL, NULL, NULL);
            if (retCode == ERROR_SUCCESS ) { 
				LONG lResult = RegQueryValueEx(hKey, achValue, NULL, &dwType, NULL, &dwCount);
				if (lResult == ERROR_SUCCESS){
					char *strValue;

					if (dwType != REG_SZ) continue;
					
					strValue = g_new0(char, dwCount + 1);
					lResult = RegQueryValueEx(hKey, achValue, NULL, &dwType, (LPBYTE)strValue, &dwCount);
					if (lResult == ERROR_SUCCESS){
						struct zserial_port *port = g_new0(struct zserial_port, 1);
						port->filename = g_strdup(strValue);
						port->desc = g_strdup(achValue);
						g_ptr_array_add(zser->ports, port);
					}
				}
            } 
        }
    }

	RegCloseKey(hKey);
	//qsort (comport, comports, sizeof(*comport), compare_comport);
	zg_ptr_array_qsort(zser->ports, z_compare_port);
	


	/** sériové porty **/
	
	regbase = "System\\CurrentControlSet\\Services\\Serenum\\Enum";
	n = zreg_getint(HKEY_LOCAL_MACHINE, regbase, "Count");
	/*if (n <= 0){
		regbase = "System\\CurrentControlSet\\Services\\Serial\\Enum";
		n = zreg_getint(HKEY_LOCAL_MACHINE, regbase, "Count");

	} */
	for (i = 0; i < n; i++){
		char key[256];
		char *s, *t, *u;
		unsigned j;

		g_snprintf(key, sizeof(key), "%d", i);
		s = zreg_getstr(HKEY_LOCAL_MACHINE, regbase, key);
		t = g_strdup_printf("System\\CurrentControlSet\\Enum\\%s\\Device Parameters", s);
		u = zreg_getstr(HKEY_LOCAL_MACHINE, t, "PortName");
		if (u == NULL) continue;
		
		for (j = 0; j < zser->ports->len; j++){
			struct zserial_port *port = (struct zserial_port *)g_ptr_array_index(zser->ports, j);
			if (strcmp(u, port->filename)) continue;

			t = g_strdup_printf("System\\CurrentControlSet\\Enum\\%s", s);

			g_free(u);
			u = zreg_getstr(HKEY_LOCAL_MACHINE, t, "FriendlyName");

			if (u != NULL){
				g_free(port->desc);
				port->desc = u; // optimized port->desc=g_strdup(u); g_free(u);
				break;
			}

			u = zreg_getstr(HKEY_LOCAL_MACHINE, t, "DeviceDesc");
			g_free(port->desc);
			port->desc = u; // optimized port->desc=g_strdup(u); g_free(u);
			break; 
		} 
		g_free(s);
		g_free(t);

	}

    return zser->ports->len;
}

#endif

struct zserial *zserial_init_win32(const char *filename){
#ifdef Z_MSC_MINGW
    struct zserial *zser = zserial_init();
	zser->type = ZSERTYPE_WIN32;
	zser->id = g_strdup(filename);
	zser->filename = g_strdup(filename);

    zser->zs_open = zserial_win32_open;
    zser->zs_read = zserial_win32_read;
    zser->zs_write = zserial_win32_write;
    zser->zs_close = zserial_win32_close;
    zser->zs_dtr = zserial_win32_dtr;
    zser->zs_rts = zserial_win32_rts;
	zser->zs_detect = zserial_win32_detect;
    return zser;
#else
	return NULL;
#endif
}


