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

/*
 *	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 "pktio.h"
#include "baycomusb.h"

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

#define DRIVER_NAME	"BaycomUSB"
#define DRIVER_VERSION  "0.1"

#define RX_RUNNING   0
#define RX_RESTART   1
#define RX_TERMINATE 2

#define TX_NORMAL    0
#define TX_LOCKPKT   1
#define TX_TERMINATE 2

struct pkthandle {
	struct pktio *pkt;
	unsigned int refcnt;
};

static struct state {
	volatile unsigned int terminate;
	volatile unsigned int rxactive;
	struct pkthandle *pkt;
	struct configentry *cfg;

       	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;

	UINT calibtimer;
	unsigned int stopcalib;

	HANDLE hdevlife;
	HANDLE hpktmutex;

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

#define REGISTRYPATH "SOFTWARE\\FlexNet\\BaycomUSB"
#define REGHKEY      HKEY_LOCAL_MACHINE

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

int lprintf(unsigned vl, const char *format, ...)
{
        va_list ap;
        char buf[512];
        int r;

	va_start(ap, format);
        r = vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);
        OutputDebugString(buf);
        return r;
}

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

static struct pkthandle *pktget(void)
{
	struct pkthandle *r;

	WaitForSingleObject(state.hpktmutex, INFINITE);
	r = state.pkt;
	if (r)
		r->refcnt++;
	ReleaseMutex(state.hpktmutex);
	return r;
}

static void pktput(struct pkthandle *pkt)
{
	unsigned int r;

	if (!pkt)
		return;
	WaitForSingleObject(state.hpktmutex, INFINITE);
	if (pkt->refcnt)
		pkt->refcnt--;
	r = pkt->refcnt;
	ReleaseMutex(state.hpktmutex);
	if (r)
		return;
	pkt_close(pkt->pkt);
	free(pkt);
}

static void pktrelease(void)
{
	struct pkthandle *pkt;
	unsigned int r = 1;

	WaitForSingleObject(state.hpktmutex, INFINITE);
	pkt = state.pkt;
	state.pkt = NULL;
	if (pkt) {
		if (pkt->refcnt)
			pkt->refcnt--;
		r = pkt->refcnt;
	}
	ReleaseMutex(state.hpktmutex);
	if (r)
		return;
	pkt_close(pkt->pkt);
	free(pkt);
}

static void pktopen(void)
{
	struct pkthandle *pkt = malloc(sizeof(struct pkthandle));
	struct pkthandle *pkt2;

	if (!pkt)
		return;
	pkt->refcnt = 1;
	pkt->pkt = pkt_open(state.serial, 0);
	if (!pkt->pkt) {
		free(pkt);
		return;
	}
	WaitForSingleObject(state.hpktmutex, INFINITE);
	pkt2 = state.pkt;
	state.pkt = pkt;
	ReleaseMutex(state.hpktmutex);
	if (pkt2)
		pktput(pkt2);
}

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

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

	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;
	state.terminate = state.rxactive = 0;
        state.status = CH_DEAD;
	state.cfg = NULL;
	state.hdevlife = CreateEvent(NULL, FALSE, FALSE, "FlexNet Device State");
	state.hpktmutex = CreateMutex(NULL, FALSE, NULL);
        return 1;
}

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

void l1_exit(HKEY hKey)
{
	state.terminate = 1;
	while (state.rxactive)
		Sleep(10);
	pktrelease();
        l1_init_kanal(0, 0, MODE_off);
	RegSetValueEx(hKey, "SerialNumber", 0, REG_SZ, state.serial, strlen(state.serial)+1);
	CloseHandle(state.hdevlife);
	CloseHandle(state.hpktmutex);
}

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

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

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

static BOOL CALLBACK EdParmDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        HWND hcombo;
        HKEY regkey;
        LONG err;
        DWORD index;
        DWORD len;
        char buf[32];
	unsigned int restart, i;
        int cursel;

	switch (uMsg) {
	case WM_INITDIALOG:
                hcombo = GetDlgItem(hDlg, IDC_SERIAL);
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"Any");
                err = RegOpenKeyEx(REGHKEY, REGISTRYPATH, 0, KEY_READ, &regkey);
                if (err == ERROR_SUCCESS) {
                        cursel = state.serial[0] ? -1 : 0;
                        for (i = 1, index = 0;; index++) {
                                len = sizeof(buf);
                                if ((RegEnumKeyEx(regkey, index, buf, &len, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS)
                                        break;
                                if (buf[0] >= '0' && buf[0] <= '9') {       
                                        SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)buf);
                                        if (!strcmp(buf, state.serial))
                                                cursel = i;
                                        i++;
                                }
                        }
                        ComboBox_SetCurSel(hcombo, cursel);
                        RegCloseKey(regkey);
                }
		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 (buf[0] < '0' || buf[0] > '9')
                                buf[0] = 0;
                        restart = strcmp(buf, state.serial);
                        strncpy(state.serial, buf, sizeof(state.serial));
			EndDialog(hDlg, restart);
                        break;
                        
                default:	
                        break;
                }
                break;
	
	default:
		return FALSE;
	}
	return TRUE;
}

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

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

	if (restart)
		pktrelease();
	return restart;
}

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

u16 l1_get_ch_cnt(void)
{
	return 1;

}

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

byte get_txdelay(byte kanal)
{
        byte txd = 0;
        
	if (state.cfg)
		txd = state.cfg->chaccess.txdelay / 10;
        lprintf(10, "get_txdelay: %u\n", txd);
	return txd;
}

void set_txdelay(byte kanal, byte delay)
{
	if (state.cfg)
		state.cfg->chaccess.txdelay = 10 * delay;
}

u16 get_mode(byte kanal)
{
	return state.mode;
}

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)
{
#if 0
	if (state.active)
		pkt_setled(state.pkt, ledcode);
#endif
}

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

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)
{
	state.stopcalib = 1;
}

void l1_tx_calib(byte kanal, byte minutes)
{
	struct pkthandle *pkt = pktget();

	if (!pkt)
		return;
	if (state.calibtimer)
		KillTimer(NULL, state.calibtimer);
	state.stopcalib = 0;
	pkt_forceptt(pkt->pkt, 1);
	pktput(pkt);
	state.calibtimer = SetTimer(NULL, 0, minutes * 60000, stopcalib);
	if (!state.calibtimer)
		state.stopcalib = 1;
}


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

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

byte l1_tx_frame(void)
{
	struct pkthandle *pkt = pktget();
        unsigned int txd;
	int r;

	if (!pkt) {
		state.stat.io_error++;
		return 1;
	}
        txd = state.txframe.txdelay;
        if (!txd && state.cfg)
                txd = state.cfg->chaccess.txdelay / 10;
	r = pkt_transmit(pkt->pkt, txd, state.txframe.frame, state.txframe.len);
	pktput(pkt);
	if (r == -1) {
		pktrelease();
		state.stat.io_error++;
		return 1;
	}
	if (r < 0)
		return 0;
	state.stat.tx_frames++;
	return 1;
}

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

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

L1FRAME far *l1_rx_frame(void)
{
	struct pkthandle *pkt;
	unsigned int i;
        char buf[32];
	byte st;
	int r;

	state.rxactive = 1;
	for (;;) {
		if (state.terminate) {
			state.rxactive = 0;
			return NULL;
		}
		if (!(pkt = pktget())) {
			for (;;) {
				pktopen();
				if ((pkt = pktget()))
					break;
				state.status = CH_DEAD;
				SetEvent(state.hdevlife);
				for (i = 0; i < 20; i++) {
					if (state.terminate) {
						state.rxactive = 0;
						return NULL;
					}
					Sleep(50);
				}
			}
			pkt_getserial(pkt->pkt, buf, sizeof(buf));
			buf[sizeof(buf)-1] = 0;
			sprintf(state.devname, "BaycomUSB %s", buf);
			state.cfg = config_lookup(buf);
			r = pkt_getbitrate(pkt->pkt);
			if (r < 1)
				r = 1;
			state.scale = 614400 / r;
			state.baud = r / 100;
			state.status = 0;
			SetEvent(state.hdevlife);			
		}
		if (state.stopcalib) {
			state.stopcalib = 0;
			pkt_forceptt(pkt->pkt, 0);
		}
		/* update status */
		r = pkt_getstatus(pkt->pkt);
		if (r < 0) {
			pktput(pkt);
			pktrelease();
			state.stat.io_error++;
			state.status = CH_DEAD;
			SetEvent(state.hdevlife);
			state.rxactive = 0;
			return NULL;
		}
		st = 0;
		if (r & PKTSTAT_PTT)
			st |= CH_PTT;
		if (r & PKTSTAT_DCD)
			st |= CH_DCD;
		if (r & PKTSTAT_RXB)
			st |= CH_RXB;
		state.status = st;
		/* try to receive */
		if (st & CH_RXB) {
			r = pkt_receive(pkt->pkt, state.rxframe.frame, sizeof(state.rxframe.frame));
			if (r < 0) {
				pktput(pkt);
				pktrelease();
				state.stat.io_error++;
				state.status = CH_DEAD;
				SetEvent(state.hdevlife);
				state.rxactive = 0;
				return NULL;
			}
			if (r > 0) {
				pktput(pkt);
				state.rxframe.len = r;
				state.rxframe.kanal = 0;
				state.rxframe.txdelay = 0;
				state.rxactive = 0;
				return &state.rxframe;
			}
		}
		pktput(pkt);
		Sleep(10);
	}
}

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