/*****************************************************************************/

/*
 *	bayusb.c  --  FlexNet driver for Baycom USB modem.
 *
 *	Copyright (C) 1999-2000
 *          Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program 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 General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *  History:
 *   0.1  23.06.1999  Created
 *   0.2  07.01.2000  Expanded to usbdevfs capabilities
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>

#include "flexdrv.h"
#include "resource.h"

/* --------------------------------------------------------------------- */

#include "trx.h"

#define VERBOSE

#ifdef VERBOSE
#define verbose(x) x
#else
#define verbose(x)
#endif

static CORBA_ORB trx_orb;

static inline Trx_Control ior_to_obj(const char *ior)
{
	CORBA_Environment ev;
	Trx_Control obj;
	Trx_TrxDescription *desc;

	CORBA_exception_init(&ev);
	obj = CORBA_ORB_string_to_object(trx_orb, (char *)ior, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		verbose(fprintf(stderr, "Error: trx_resolve_byindex: %s %s\n", ior, CORBA_exception_id(&ev)));
		CORBA_exception_free(&ev);
		return NULL;
	}
	Trx_Control_GetDescription(obj, &desc, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		verbose(fprintf(stderr, "Error: trx_resolve_byindex: (GetDescription) %s %s\n", ior, CORBA_exception_id(&ev)));
		CORBA_exception_free(&ev);
		CORBA_Object_release(obj, &ev);
		return NULL;
	}
	verbose(fprintf(stderr, "trx_resolve_byindex: %s mfg %s model %s instid %s\n", 
			ior, desc->manufacturer, desc->model, desc->instid));
	CORBA_free(desc);
	CORBA_exception_free(&ev);
	return obj;
}

static inline void runserver(void)
{
        static unsigned int inited = 0;
        char name[512], cmdname[512], cmdline[512], *cp;
        STARTUPINFO sti;
        PROCESS_INFORMATION pi;

        if (inited)
                return;
        inited = 1;
        if (!GetModuleFileName(GetModuleHandle(NULL), name, sizeof(name))) {
                fprintf(stderr, "Cannot determine executable name\n");
                return;
        }
        if ((cp = strrchr(name, '\\')))
                *cp = 0;
        else
                name[0] = 0;
        snprintf(cmdname, sizeof(cmdname), "%s\\baycomusbserv.exe", name);
	/*
	 * Database calls (getXbyY) seem to hang for a very looooong time under
	 * w9x if DNS is not or misconfigured. So we set the binding address to
	 * 127.0.0.1, which only accepts connections from 127.0.0.1
	 * and does no database calls
	 */
        snprintf(cmdline, sizeof(cmdline), "%s -v 9 -s -ORBBindAddr=127.0.0.1", cmdname);
        GetStartupInfo(&sti);
        if (!CreateProcess(cmdname, cmdline, NULL, NULL, FALSE,
                           DETACHED_PROCESS | CREATE_DEFAULT_ERROR_MODE | HIGH_PRIORITY_CLASS,
                           NULL, NULL, &sti, &pi)) {
                fprintf(stderr, "Cannot start command %s, Error %lu\n", cmdname, GetLastError());
        }        
}

static Trx_Control trx_resolve_byindex(unsigned int index)
{
	Trx_Control obj;
	HKEY key, key2;
        DWORD err, i, bufsz, dt;
	CORBA_Environment ev;
	char buf[512];

        runserver();
	if ((err = RegOpenKeyEx(IORREGHKEY, IORREGPATH, 0, KEY_ENUMERATE_SUB_KEYS, &key)) != ERROR_SUCCESS) {
                fprintf(stderr, "RegOpenKeyEx(" IORREGPATH ") returned 0x%lx\n", err);
                return CORBA_OBJECT_NIL;
        }
	for (i = 0;; i++) {
		bufsz = sizeof(buf);
		err =  RegEnumKeyEx(key, i, buf, &bufsz, NULL, NULL, NULL, NULL);
		if (err != ERROR_SUCCESS) {
			RegCloseKey(key);
			return NULL;
		}
		if (RegOpenKeyEx(key, buf, 0, KEY_ALL_ACCESS, &key2) != ERROR_SUCCESS)
			continue;
		bufsz = sizeof(buf);
		if (RegQueryValueEx(key2, "IOR", NULL, &dt, buf, &bufsz) != ERROR_SUCCESS) {
			RegCloseKey(key2);
			continue;
		}
                RegCloseKey(key2);
		if (dt != REG_SZ) {
			continue;
		}
		if ((obj = ior_to_obj(buf))) {
                        if (index) {
                                index--;
                                CORBA_exception_init(&ev);
                                CORBA_Object_release(obj, &ev);
                                CORBA_exception_free(&ev);
                                continue;
                        }
			RegCloseKey(key);
			return obj;
		}
	}
}

int trx_initorb(int *argc, char **argv)
{
	CORBA_Environment ev;

        CORBA_exception_init(&ev);
	trx_orb = CORBA_ORB_init(argc, argv, "orbit-local-orb", &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		verbose(fprintf(stderr, "Error: trx_initorb: %s\n", CORBA_exception_id(&ev)));
		CORBA_exception_free(&ev);
		return -1;
	}
	return 0;
}

/* --------------------------------------------------------------------- */

#define DRIVER_NAME	"BaycomUSB"
#define DRIVER_VERSION  "0.2"

static struct state {
	unsigned int terminate;
	Trx_Control obj;
	HANDLE hobjmutex;
	HANDLE hdevlife;

       	char serial[16];   /* device serial number, or NULL string for any device */
	char devname[32];

        byte baud;
	byte mode;
	byte status;

	unsigned int scale;
	unsigned int txdelay;
	unsigned int fullduplex;

	UINT calibtimer;

	Trx_FlexNetPollState *poll;
	unsigned int pollidx;

	L1FRAME txframe;
	L1FRAME rxframe;
	L1_STATISTICS stat;
} state = { 0, };

/* ----------------------------------------------------------------------- */

/*
 * Treiber-Init. Sofern mehrfach aufgerufen, kommt vorher jeweils l1_exit()
 * Hier also alle Ressourcen allokieren, aber noch nicht starten sofern
 * dazu die Parameter gebraucht werden. Die kommen spaeter per l1_init_kanal()
 */

int init_device(HKEY hKey)
{
	DWORD regtype, reglen, regval;
	int argc = 0;
	char *argv[1] = { NULL };
	static int orbinit = 1;
	

	verboselevel = ~0;
	syslogmsg = 1;

	/* initialize ORB */
	if (orbinit && trx_initorb(&argc, argv))
                return 0;
	orbinit = 0;
	/* get serial from registry */
	reglen = sizeof(state.serial);
	if (RegQueryValueEx(hKey, "SerialNumber", NULL, &regtype, state.serial, &reglen) != ERROR_SUCCESS ||
	    regtype != REG_SZ)
		state.serial[0] = 0;
	state.serial[sizeof(state.serial)-1] = 0;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "TxDelay", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS ||
	    regtype != REG_DWORD)
		regval = 5;
	state.txdelay = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "FullDuplex", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS ||
	    regtype != REG_DWORD)
		regval = 0;
	state.fullduplex = regval;
	state.terminate = 0;
        state.status = CH_DEAD;
	state.obj = CORBA_OBJECT_NIL;
	state.poll = NULL;
	state.hdevlife = CreateEvent(NULL, FALSE, FALSE, "FlexNet Device State");
	state.hobjmutex = CreateMutex(NULL, FALSE, NULL);
        return 1;
}

/* ------------------------------------------------------------------------- */

void l1_exit(HKEY hKey)
{
        CORBA_Environment ev;
	DWORD regval;
        
	WaitForSingleObject(state.hobjmutex, INFINITE);
	state.terminate = 1;
        if (state.obj) {
                CORBA_exception_init(&ev);
                Trx_Control_FlexNetUnlock(state.obj, &ev);
                CORBA_exception_free(&ev);
                CORBA_Object_release(state.obj, &ev);
                CORBA_exception_free(&ev);
                state.obj = CORBA_OBJECT_NIL;
        }
        if (state.poll)
                CORBA_free(state.poll);
        state.poll = NULL;
        ReleaseMutex(state.hobjmutex);
        l1_init_kanal(0, 0, MODE_off);
	RegSetValueEx(hKey, "SerialNumber", 0, REG_SZ, state.serial, strlen(state.serial)+1);
	regval = state.txdelay;
	RegSetValueEx(hKey, "TxDelay", 0, REG_DWORD, (void *)&regval, sizeof(regval));
	regval = state.fullduplex;
	RegSetValueEx(hKey, "FullDuplex", 0, REG_DWORD, (void *)&regval, sizeof(regval));	
	CloseHandle(state.hdevlife);
	CloseHandle(state.hobjmutex);
}

/* ------------------------------------------------------------------------- */

byte *config_info(byte kanal)
{
	return state.devname;
}

/* ------------------------------------------------------------------------- */

static BOOL CALLBACK EdParmDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        CORBA_Environment ev;
        Trx_TrxDescription *desc;
        Trx_Control obj;
        HWND hcombo;
        char buf[32];
	unsigned int restart, i, index;
        int cursel;

	switch (uMsg) {
	case WM_INITDIALOG:
                hcombo = GetDlgItem(hDlg, IDC_SERIAL);
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"Any");
		cursel = state.serial[0] ? -1 : 0;
		WaitForSingleObject(state.hobjmutex, INFINITE);
		CORBA_exception_init(&ev);
		for (index = 0, i = 1;; index++) {
	                if (!(obj = trx_resolve_byindex(index)))
				break;
			Trx_Control_GetDescription(obj, &desc, &ev);
			if (ev._major == CORBA_NO_EXCEPTION) {
				SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)desc->instid);
				if (!strcmp(desc->instid, state.serial))
					cursel = i;
				CORBA_free(desc);
				i++;
			}
                        CORBA_exception_free(&ev);
                        CORBA_Object_release(obj, &ev);
                        CORBA_exception_free(&ev);
                }
		ReleaseMutex(state.hobjmutex);
		ComboBox_SetCurSel(hcombo, cursel);
                snprintf(buf, sizeof(buf), "%d", state.txdelay);
                SetDlgItemText(hDlg, IDC_TXDELAY, buf);
                CheckDlgButton(hDlg, IDC_FULLDUPLEX, state.fullduplex ? BST_CHECKED : BST_UNCHECKED);
		SetWindowText(hDlg, DRIVER_NAME" Configuration");
		break;
                
        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDCANCEL:
                        EndDialog(hDlg, 0);
                        break;

                case IDOK:
                        GetDlgItemText(hDlg, IDC_SERIAL, buf, sizeof(buf));
			if (!strcmp(buf, "Any"))
				buf[0] = 0;
                        restart = strcmp(buf, state.serial);
                        strncpy(state.serial, buf, sizeof(state.serial));
			GetDlgItemText(hDlg, IDC_TXDELAY, buf, sizeof(buf));
                        state.txdelay = strtoul(buf, NULL, 0);
                        state.fullduplex = IsDlgButtonChecked(hDlg, IDC_FULLDUPLEX) == BST_CHECKED;
		EndDialog(hDlg, restart);
                        break;
                        
                default:	
                        break;
                }
                break;
	
	default:
		return FALSE;
	}
	return TRUE;
}

/* ------------------------------------------------------------------------- */

int config_device(byte max_channels, HWND hDlg, byte channel)
{
        CORBA_Environment ev;
	int restart = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hDlg, EdParmDlgProc, 0);

	if (restart) {
		WaitForSingleObject(state.hobjmutex, INFINITE);
		CORBA_exception_init(&ev);
		if (state.obj) {
                        Trx_Control_FlexNetUnlock(state.obj, &ev);
                        CORBA_exception_free(&ev);
			CORBA_Object_release(state.obj, &ev);
                }
		CORBA_exception_free(&ev);
		state.obj = CORBA_OBJECT_NIL;
		if (state.poll)
			CORBA_free(state.poll);
		state.poll = NULL;
		ReleaseMutex(state.hobjmutex);
	}
	return restart;
}

/* ------------------------------------------------------------------------- */

u16 l1_get_ch_cnt(void)
{
	return 1;

}

/* ------------------------------------------------------------------------- */

byte get_txdelay(byte kanal)
{
	return state.txdelay / 10;
}

void set_txdelay(byte kanal, byte delay)
{
	state.txdelay = 10 * delay;
}

u16 get_mode(byte kanal)
{
	return (state.mode & ~MODE_d) | (state.fullduplex ? MODE_d : 0);
}

u16 get_baud(byte kanal)
{
	return state.baud;
}

/* ------------------------------------------------------------------------- */

byte l1_init_kanal(byte kanal, u16 chbaud, u16 chmode)
{
	state.mode = chmode;
	return 1;
}

/* ------------------------------------------------------------------------- */

byte l1_ch_active(byte kanal)
{
	return 1;
}

/* ------------------------------------------------------------------------- */

char far *l1_ident(byte kanal)
{
	return DRIVER_NAME;
}

/* ------------------------------------------------------------------------- */

char far *l1_version(byte kanal)
{
	return DRIVER_VERSION;
}

/* ------------------------------------------------------------------------- */

L1_STATISTICS far *l1_stat(byte kanal, byte delete)
{
	L1_STATISTICS *p = &state.stat;
	
	if (delete)
		p->tx_error = p->rx_overrun = p->rx_bufferoverflow =
		p->tx_frames = p->rx_frames = p->io_error = 0;
	return p;
}

/* ------------------------------------------------------------------------- */

void set_led(byte kanal, byte ledcode)
{
        CORBA_Environment ev;

	WaitForSingleObject(state.hobjmutex, INFINITE);
	if (state.obj) {
		CORBA_exception_init(&ev);
		Trx_Control_FlexNetSetLEDS(state.obj, !!(ledcode & LED_CON), !!(ledcode & LED_STA), &ev);
		CORBA_exception_free(&ev);
	}
	ReleaseMutex(state.hobjmutex);
}

/* ------------------------------------------------------------------------- */

byte l1_ch_state(byte kanal)
{
	return state.status;
}

/* ------------------------------------------------------------------------- */

u16 l1_scale(byte kanal)
{
	return state.scale;
}

/* ------------------------------------------------------------------------- */

static void CALLBACK stopcalib(HWND hwnd, UINT umsg, UINT idevent, DWORD dwtime)
{
        CORBA_Environment ev;

	WaitForSingleObject(state.hobjmutex, INFINITE);
	if (state.obj) {
		CORBA_exception_init(&ev);
		Trx_Control_SetPTT(state.obj, CORBA_FALSE, &ev);
		CORBA_exception_free(&ev);
	}
	ReleaseMutex(state.hobjmutex);
}

void l1_tx_calib(byte kanal, byte minutes)
{
        CORBA_Environment ev;

	if (state.calibtimer)
		KillTimer(NULL, state.calibtimer);
	WaitForSingleObject(state.hobjmutex, INFINITE);
	if (state.obj) {
		CORBA_exception_init(&ev);
		Trx_Control_SetPTT(state.obj, CORBA_TRUE, &ev);
		CORBA_exception_free(&ev);
	}
	ReleaseMutex(state.hobjmutex);
	state.calibtimer = SetTimer(NULL, 0, minutes * 60000, stopcalib);
	if (!state.calibtimer)
		stopcalib(NULL, 0, 0, 0);
}


/* ------------------------------------------------------------------------- */

L1FRAME far *l1_get_framebuf(byte kanal)
{
	return &state.txframe;
}

byte l1_tx_frame(void)
{
        CORBA_Environment ev;
	Trx_FlexNetPacket pkt = { 0, };
	byte ret = 0;

        pkt.txdelay = state.txframe.txdelay;
        if (!pkt.txdelay)
                pkt.txdelay = state.txdelay / 10;
	pkt.data._buffer = state.txframe.frame;
	pkt.data._length = pkt.data._maximum = state.txframe.len;
	WaitForSingleObject(state.hobjmutex, INFINITE);
	if (!state.obj) {
		ReleaseMutex(state.hobjmutex);
		state.stat.io_error++;
		return 1;
	}
	CORBA_exception_init(&ev);
	Trx_Control_FlexNetSendPacket(state.obj, &pkt, &ev);
	if (ev._major == CORBA_NO_EXCEPTION) {
		ret = 1;
		state.stat.tx_frames++;
	}
	CORBA_exception_free(&ev);
	ReleaseMutex(state.hobjmutex);
	return ret;
}

/* ------------------------------------------------------------------------- */

/*
 * RX-Thread. Wartet auf RX-Bytes, verarbeitet sie und returned wenn
 * Handle zu oder Paket komplett
 */

L1FRAME far *l1_rx_frame(void)
{
        CORBA_Environment ev;
	CORBA_unsigned_long bitrate;
        Trx_TrxDescription *desc;
        Trx_Control obj;
	Trx_FlexNetPacket *pkt;
	unsigned int i;
        byte st;
        
	WaitForSingleObject(state.hobjmutex, INFINITE);
	for (;;) {
		if (state.terminate) {
			ReleaseMutex(state.hobjmutex);
			return NULL;
		}
		if (!state.obj) {
			state.status = CH_DEAD;
			SetEvent(state.hdevlife);
			for (i = 0; i < 20; i++) {
				if (state.terminate)
					break;
				ReleaseMutex(state.hobjmutex);
				Sleep(50);
				WaitForSingleObject(state.hobjmutex, INFINITE);
			}
			if (state.terminate)
				continue;
			for (i = 0;; i++) {
				CORBA_exception_init(&ev);
				if (!(obj = trx_resolve_byindex(i)))
					break;
				Trx_Control_GetDescription(obj, &desc, &ev);
				if (ev._major == CORBA_NO_EXCEPTION && 
				    (!state.serial[0] || !strcmp(desc->instid, state.serial)))
					break;
				CORBA_exception_free(&ev);
				CORBA_Object_release(obj, &ev);
				CORBA_exception_free(&ev);
			}
			if (!obj)
				goto wait;
			snprintf(state.devname, sizeof(state.devname), "%s (%s)", desc->model, desc->instid);
			CORBA_free(desc);
                        if (!Trx_Control_FlexNetLock(obj, &ev) || ev._major != CORBA_NO_EXCEPTION) {
                                CORBA_exception_free(&ev);
                                CORBA_Object_release(obj, &ev);
				CORBA_exception_free(&ev);
                                goto wait;        
                        }
                        Trx_Control_FlexNetGetBitrate(obj, &bitrate, &ev);
			if (ev._major != CORBA_NO_EXCEPTION)
				bitrate = 1;
			CORBA_exception_free(&ev);
			if (bitrate < 1)
				bitrate = 1;
			state.scale = 614400 / bitrate;
			state.baud = bitrate / 100;
			state.status = 0;
			SetEvent(state.hdevlife);
			state.obj = obj;
                }
		if (!state.poll) {
			CORBA_exception_init(&ev);
			Trx_Control_FlexNetPoll(state.obj, &state.poll, &ev);
			if (ev._major != CORBA_NO_EXCEPTION) {
				CORBA_exception_free(&ev);
                                Trx_Control_FlexNetUnlock(state.obj, &ev);
                                CORBA_exception_free(&ev);
				CORBA_Object_release(state.obj, &ev);
				CORBA_exception_free(&ev);
				state.obj = CORBA_OBJECT_NIL;
				state.poll = NULL;
				goto wait;
			}
			CORBA_exception_free(&ev);
			state.pollidx = 0;
                        st = state.status;
			if (state.poll->ptt)
				state.status |= CH_PTT;
			else
				state.status &= ~CH_PTT;
			if (state.poll->dcd)
				state.status |= CH_DCD;
			else
				state.status &= ~CH_DCD;
			if (state.poll->pkt._length > 0)
				state.status |= CH_RXB;
			else
				state.status &= ~CH_RXB;
                        if (state.poll->dead)
				state.status |= CH_DEAD;
			else
				state.status &= ~CH_DEAD;
                        if ((state.status ^ st) & CH_DEAD)
                                SetEvent(state.hdevlife);
                        if (!state.poll->pkt._length) {
                                CORBA_free(state.poll);
				state.poll = NULL;
                                goto wait;
                        }
		}
		if (state.pollidx < state.poll->pkt._length) {
			pkt = &state.poll->pkt._buffer[state.pollidx++];
                        state.rxframe.kanal = 0;
			state.rxframe.txdelay = pkt->txdelay;
			state.rxframe.len = pkt->data._length;
			memcpy(state.rxframe.frame, pkt->data._buffer, state.rxframe.len);
			if (state.pollidx >= state.poll->pkt._length) {
				state.status &= ~CH_RXB;
				CORBA_free(state.poll);
				state.poll = NULL;
			}
			ReleaseMutex(state.hobjmutex);
			return &state.rxframe;
		}
          wait:
		ReleaseMutex(state.hobjmutex);
		Sleep(10);
		WaitForSingleObject(state.hobjmutex, INFINITE);
	}
}

/* ------------------------------------------------------------------------- */

/* KB Q164787 */

void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
	lprintf(0, "Hello World!\ncmdline: %s\n", lpszCmdLine);
	printf("Hello World!\ncmdline: %s\n", lpszCmdLine);
	Sleep(30000);
	lprintf(0, "End\n");
} 

/* ------------------------------------------------------------------------- */
