// $Id$
//
//  PLP - An implementation of the PSION link protocol
//
//  Copyright (C) 1999  Philip Proudman
//  Modifications for plptools:
//    Copyright (C) 1999 Fritz Elfert <felfert@to.com>
//
//  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  e-mail philip.proudman@btinternet.com

#include <stream.h>
#include <string.h>
#include <time.h>

#include "bool.h"
#include "defs.h"
#include "ncp.h"
#include "linkchan.h"
#include "bufferstore.h"
#include "link.h"
#include "bufferarray.h"

#define NCP_SENDLEN 250

ncp::ncp(const char *fname, int baud, IOWatch & iow)
{
	l = new link(fname, baud, iow);
	failed = false;
	verbose = 0;
 	protocolVersion = PV_SERIES_5; // until detected on receipt of INFO

	// init channels
	for (int i = 0; i < MAX_CHANNEL; i++)
		channelPtr[i] = NULL;
}

ncp::~ncp()
{
	delete l;
}

void ncp::
reset() {
	for (int i = 0; i < MAX_CHANNEL; i++) {
		if (channelPtr[i])
			channelPtr[i]->terminateWhenAsked();
		channelPtr[i] = NULL;
	}
	failed = false;
	lChan = NULL;
	l->reset();
}

short int ncp::
getVerbose()
{
	return verbose;
}

void ncp::
setVerbose(short int _verbose)
{
	verbose = _verbose;
}

short int ncp::
getLinkVerbose()
{
	return l->getVerbose();
}

void ncp::
setLinkVerbose(short int _verbose)
{
	l->setVerbose(_verbose);
}

short int ncp::
getPktVerbose()
{
	return l->getPktVerbose();
}

void ncp::
setPktVerbose(short int _verbose)
{
	l->setPktVerbose(_verbose);
}

short int ncp::
getProtocolVersion()
{
	return protocolVersion;
}

void ncp::
poll()
{
	bufferArray res(l->poll());
	if (!res.empty()) {
		do {
			bufferStore s = res.pop();
			if (s.getLen() > 1) {
				int channel = s.getByte(0);
				s.discardFirstBytes(1);
				if (channel == 0) {
					decodeControlMessage(s);
				} else {
					/* int remChan = */ s.getByte(0);
					int allData = s.getByte(1);
					s.discardFirstBytes(2);
					if (channelPtr[channel] == NULL) {
						cerr << "ncp: Got message for unknown channel\n";
					} else {
						messageList[channel].addBuff(s);
						if (allData == LAST_MESS) {
							channelPtr[channel]->ncpDataCallback(messageList[channel]);
							messageList[channel].init();
						} else if (allData != NOT_LAST_MESS) {
							cerr << "ncp: bizarre third byte!\n";
						}
					}
				}
			} else {
				cerr << "Got null message\n";
			}
		} while (!res.empty());
	}
}

void ncp::
controlChannel(int chan, enum interControllerMessageType t, bufferStore & command)
{
	bufferStore open;
	open.addByte(0);	// control

	open.addByte(chan);
	open.addByte(t);
	open.addBuff(command);
	if (verbose & NCP_DEBUG_LOG)
		cout << "ncp: >> " << ctrlMsgName(t) << " " << chan << endl;
	l->send(open);
}

void ncp::
decodeControlMessage(bufferStore & buff)
{
	int remoteChan = buff.getByte(0);
	short int ver;
	interControllerMessageType imt = (interControllerMessageType) buff.getByte(1);
	buff.discardFirstBytes(2);
	if (verbose & NCP_DEBUG_LOG)
		cout << "ncp: << " << ctrlMsgName(imt) << " " << remoteChan;
	switch (imt) {
		case NCON_MSG_CONNECT_TO_SERVER:
			{
				if (verbose & NCP_DEBUG_LOG) {
					if (verbose & NCP_DEBUG_DUMP)
						cout << " [" << buff << "]";
					cout << endl;
				}
				int localChan;
				bufferStore b;

				// Ack with connect response
				localChan = getFirstUnusedChan();
				b.addByte(remoteChan);
				b.addByte(0x0);
				controlChannel(localChan, NCON_MSG_CONNECT_RESPONSE, b);

				// NOTE: we don't allow connections from the
				// Psion to any local "processes" other than
				// LINK.* - Matt might need to change this for
				// his NCP<->TCP/IP bridge code...

				if (!strcmp(buff.getString(0), "LINK.*")) {
					if (lChan)
						failed = true;
					if (verbose & NCP_DEBUG_LOG)
						cout << "ncp: Link UP" << endl;
					channelPtr[localChan] = lChan = new linkChan(this);
					lChan->setNcpChannel(localChan);
					lChan->ncpConnectAck();
					lChan->setVerbose(verbose);
				} else {
					if (verbose & NCP_DEBUG_LOG)
						cout << "ncp: Link DOWN" << endl;
					bufferStore b;
					b.addByte(remoteChan);
					controlChannel(localChan, NCON_MSG_CHANNEL_DISCONNECT, b);
				}
			}
			break;
		case NCON_MSG_CONNECT_RESPONSE:
			{
				int forChan = buff.getByte(0);
				if (verbose & NCP_DEBUG_LOG)
					cout << " ch=" << forChan << " stat=";
				if (buff.getByte(1) == 0) {
					if (verbose & NCP_DEBUG_LOG)
						cout << "OK" << endl;
					if (channelPtr[forChan]) {
						remoteChanList[forChan] = remoteChan;
						channelPtr[forChan]->ncpConnectAck();
					} else {
						if (verbose & NCP_DEBUG_LOG)
							cout << "ncp: message for unknown channel" << endl;
					}
				} else {
					if (verbose & NCP_DEBUG_LOG)
						cout << "Unknown " << (int) buff.getByte(1) << endl;
					channelPtr[forChan]->ncpConnectNak();
				}
			}
			break;
		case NCON_MSG_NCP_INFO:
			ver = buff.getByte(0);
			// Series 3c returns '3', as does mclink. PsiWin 1.1
			// returns version 2. We return whatever version we're
			// sent, which is rather crude, but works for Series 3
			// and probably 5. If Symbian have changed EPOC Connect
			// for the Series 5mx/7, this may need to change.
			// 
			if (ver == PV_SERIES_5 || ver == PV_SERIES_3) {
				bufferStore b;
				protocolVersion = ver;
				if (verbose & NCP_DEBUG_LOG) {
					if (verbose & NCP_DEBUG_DUMP)
						cout << " [" << buff << "]";
					cout << endl;
				}
				// Fake NCP version 2 for a Series 3 (behave like PsiWin 1.1)
				if (ver == PV_SERIES_3)
					ver = 2;
				b.addByte(ver);
				// Do we send a time of 0 or a real time?
				// The Psion uses this to determine whether to
				// restart. (See protocol docs for details)
				// b.addDWord(0);
				b.addDWord(time(NULL));
				controlChannel(0, NCON_MSG_NCP_INFO, b);
			} else
				cout << "ALERT!!!! Unexpected Protocol Version!! (No Series 5/3?)!" << endl;
			break;
		case NCON_MSG_CHANNEL_DISCONNECT:
			if (verbose & NCP_DEBUG_LOG)
				cout << " ch=" << (int) buff.getByte(0) << endl;
			disconnect(buff.getByte(0));
			break;
		case NCON_MSG_DATA_XOFF:
		case NCON_MSG_DATA_XON:
		case NCON_MSG_CHANNEL_CLOSED:
		case NCON_MSG_NCP_END:
		default:
			if (verbose & NCP_DEBUG_LOG) {
				if (verbose & NCP_DEBUG_DUMP)
					cout << " [" << buff << "]";
				cout << endl;
			}
	}
}

int ncp::
getFirstUnusedChan()
{
	for (int cNum = 1; cNum < MAX_CHANNEL; cNum++) {
		if (channelPtr[cNum] == NULL) {
				if (verbose & NCP_DEBUG_LOG)
					cout << "ncp: getFirstUnusedChan=" << cNum << endl;
			return cNum;
		}
	}
	return 0;
}

void ncp::
RegisterAck(int chan)
{
	if (verbose & NCP_DEBUG_LOG)
		cout << "ncp: RegisterAck: chan=" << chan << endl;
	for (int cNum = 1; cNum < MAX_CHANNEL; cNum++) {
		channel *ch = channelPtr[cNum];
		if (ch->getNcpChannel() == chan) {
			ch->ncpRegisterAck();
			return;
		}
	}
	cerr << "ncp: RegisterAck: no channel to deliver" << endl;
}

void ncp::
Register(channel * ch)
{
	if (lChan)
		lChan->Register(ch);
	else
		cerr << "ncp: Register without established lChan" << endl;
}

int ncp::
connect(channel * ch)
{
	// look for first unused chan
	int cNum = ch->getNcpChannel();
	if (cNum == 0)
		cNum = getFirstUnusedChan();
	if (cNum > 0) {
		channelPtr[cNum] = ch;
		ch->setNcpChannel(cNum);
		bufferStore b;
		b.addString(ch->getNcpConnectName());
		b.addString(".*");
		b.addByte(0);
		controlChannel(cNum, NCON_MSG_CONNECT_TO_SERVER, b);
		return cNum;
	}
	return -1;
}

void ncp::
send(int channel, bufferStore & a)
{
	bool last;
	do {
		last = true;

		if (a.getLen() > NCP_SENDLEN)
			last = false;

		bufferStore out;
		out.addByte(remoteChanList[channel]);
		out.addByte(channel);

		if (last) {
			out.addByte(LAST_MESS);
		} else {
			out.addByte(NOT_LAST_MESS);
		}

		out.addBuff(a, NCP_SENDLEN);
		a.discardFirstBytes(NCP_SENDLEN);
		l->send(out);
	} while (!last);
}

void ncp::
disconnect(int channel)
{
	channelPtr[channel]->terminateWhenAsked();
	if (verbose & NCP_DEBUG_LOG)
		cout << "ncp: disconnect: channel=" << channel << endl;
	channelPtr[channel] = NULL;
	bufferStore b;
	b.addByte(remoteChanList[channel]);
	controlChannel(channel, NCON_MSG_CHANNEL_DISCONNECT, b);
}

bool ncp::
stuffToSend()
{
	return l->stuffToSend();
}

bool ncp::
hasFailed()
{
	if (failed)
		return true;
	return l->hasFailed();
}

bool ncp::
gotLinkChannel()
{
	return (lChan != NULL);
}

char *ncp::
ctrlMsgName(unsigned char msgType)
{
	switch (msgType) {
		case NCON_MSG_DATA_XOFF:
			return "NCON_MSG_DATA_XOFF";
		case NCON_MSG_DATA_XON:
			return "NCON_MSG_DATA_XON";
		case NCON_MSG_CONNECT_TO_SERVER:
			return "NCON_MSG_CONNECT_TO_SERVER";
		case NCON_MSG_CONNECT_RESPONSE:
			return "NCON_MSG_CONNECT_RESPONSE";
		case NCON_MSG_CHANNEL_CLOSED:
			return "NCON_MSG_CHANNEL_CLOSED";
		case NCON_MSG_NCP_INFO:
			return "NCON_MSG_NCP_INFO";
		case NCON_MSG_CHANNEL_DISCONNECT:
			return "NCON_MSG_CHANNEL_DISCONNECT";
		case NCON_MSG_NCP_END:
			return "NCON_MSG_NCP_END";
	}
	return "NCON_MSG_UNKNOWN";
}