/*
    zserial_proc_win32 - portable serial port API
	Module for WIN32 process
    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.

*/

#include <zserial.h>

#include <zdebug.h>
#include <zerror.h>

#ifdef Z_MSC_MINGW

static int zserial_proc_win32_open(struct zserial *zser){

	HANDLE tmpstdin, tmpstdout, my;
	SECURITY_ATTRIBUTES sa;
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

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

    my = GetCurrentProcess();

    sa.nLength= sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    
    //if (!CreatePipe(&zser->procstdin, &tmpstdin, &sa, 0)) zinternal("Can't create pipe for %s", zser->id);
    //if (!DuplicateHandle(

    if (!CreatePipe(&tmpstdout, &si.hStdOutput, &sa, 0)) zinternal("Can't create pipe for %s", zser->id);

    // stdout -> stderr							
    if (!DuplicateHandle(my, si.hStdOutput, my, &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
        g_string_printf(zser->errorstr, "Can't duplicate stdout for %s: ", zser->id);
        z_lasterror(zser->errorstr);
        goto cleanup;
    }
    // vstup stdoutu duplikovat, protoze jinak by se dedil
    if (!DuplicateHandle(my, tmpstdout, my, &zser->procstdout, 0, FALSE, DUPLICATE_SAME_ACCESS)) zinternal("Can't duplicate tmpstdout for %s", zser->id);
    CloseHandle(tmpstdout);

    // vystup stdinu duplikovat, protoze jinak by se dedil
    if (!CreatePipe(&si.hStdInput, &tmpstdin, &sa, 0)) zinternal("Can't create pipe for %s", zser->id);
    if (!DuplicateHandle(my, tmpstdin, my, &zser->procstdin, 0, FALSE, DUPLICATE_SAME_ACCESS)) zinternal("Can't duplicate tmpstdin for %s", zser->id);
    CloseHandle(tmpstdin);

    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    if (!CreateProcess(NULL, zser->cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)){
        g_string_printf(zser->errorstr, "Can't run %s: ", zser->id);
        z_lasterror(zser->errorstr);
        zserial_close(zser);
        return -1;
    }
    zser->prochandle = pi.hProcess;
    // zavrit vsechny handly, co mu drzime
    CloseHandle(si.hStdInput);
    CloseHandle(si.hStdOutput);
    CloseHandle(si.hStdError);
    zser->opened = 1;
cleanup:;
    return 0;
}

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

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

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

    ret = WriteFile(zser->procstdin, 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;
    }
    return written;
}

static int zserial_proc_win32_close(struct zserial *zser){
	int ret = 0;
	
    if (zser->prochandle != INVALID_HANDLE_VALUE){
		TerminateProcess(zser->prochandle, 1);
		ret |= !CloseHandle(zser->prochandle);
		zser->prochandle = INVALID_HANDLE_VALUE;
	}
	if (zser->procstdin != INVALID_HANDLE_VALUE) ret |= !CloseHandle(zser->procstdin);
	zser->procstdin = INVALID_HANDLE_VALUE;
	if (zser->procstdout != INVALID_HANDLE_VALUE) ret |= !CloseHandle(zser->procstdout);
	zser->procstdout = INVALID_HANDLE_VALUE;
	return 0;
}
#endif

struct zserial *zserial_init_proc_win32(const char *cmd, const char *arg){
#ifdef Z_MSC_MINGW
	char *c;
	struct zserial *zser = zserial_init();
	zser->type = ZSERTYPE_PROC_WIN32;
	zser->id = g_strdup(cmd);
	c = strchr(zser->id, ' ');
	if (c != NULL) *c = '\0';
	zser->cmd = g_strdup(cmd);
    zser->arg = g_strdup(arg);
    //dbg("zserial_init_process_win32 (%s)\n", cmd);

    zser->zs_open = zserial_proc_win32_open;
    zser->zs_read = zserial_proc_win32_read;
    zser->zs_write = zserial_proc_win32_write;
    zser->zs_close = zserial_proc_win32_close;
    return zser;
#else
	return NULL;
#endif
}

