/*
 * Low level functions for communication with Alcatel One Touch phones.
 *
 * This code implements the protocol used for synchronisation with PC.
 */
#include "../../gsmstate.h"

#if defined(GSM_ENABLE_ALCABUS)

#include <stdio.h>
#include <string.h>

#include "../../gsmcomon.h"
#include "alcabus.h"

static GSM_Error ALCABUS_WriteMessage (GSM_StateMachine *s, unsigned char *data, int len, unsigned char type)
{
	GSM_Protocol_ALCABUSData 	*d = &s->Protocol.Data.ALCABUS;
	static unsigned char 		buffer[1024];
	static int 			size = 0;
	int 				i = 0, checksum = 0;

	if ((type == 0) && (len == 0)) return GE_NONE;

	buffer[0] = ALCATEL_HEADER;
	buffer[1] = type;
	switch (type) {
		case ALCATEL_CONNECT:
				buffer[2] 	= 0x0A;
				buffer[3] 	= 0x04;
				buffer[4] 	= 0x00;
				size 		= 5;
				d->next_frame 	= ALCATEL_CONNECT_ACK;
				d->busy 	= true;
				break;
		case ALCATEL_DISCONNECT:
				size 		= 2;
				d->next_frame 	= ALCATEL_DISCONNECT_ACK;
				d->busy 	= true;
				break;
		case ALCATEL_DATA:
				 buffer[2] = d->out_counter;

				/* Increase outgoing packet counter */
				if (d->out_counter == ALCATEL_MAX_COUNTER) d->out_counter = 0;
				else d->out_counter++;

				buffer[3] 	= '\0';
				buffer[4] 	= len;
				memcpy(buffer+5, data, len);
				size 		= 5 + len;
				d->next_frame 	= ALCATEL_ACK;
				d->busy 	= true;
				break;
		case ALCATEL_ACK:
				buffer[2] = d->in_counter;
				if (d->in_counter == 0) d->in_counter = 1;
				size 		= 3;
				d->next_frame 	= ALCATEL_DATA;
				break;
		default:
				/* In fact, other types probably can came just from mobile... */
				smprintf(s,"WARNING: Wanted to send some unknown packet (%02X)\n", type);
				return GE_NOTIMPLEMENTED;
	}

	/* Calculate packet checksum */
	for (i=0; i<size; i++) checksum ^= buffer[i];

	buffer[size] = checksum;
	size ++;

	GSM_DumpMessageLevel2(s, buffer, size, type);
	GSM_DumpMessageLevel3(s, buffer, size, type);
	for (i=0;i<size;i++) {
		if (s->Device.Functions->WriteDevice(s,buffer+i,1)!=1) return GE_DEVICEWRITEERROR;
	}

	if (type == ALCATEL_CONNECT || type == ALCATEL_DISCONNECT) {
		/* For connect and disconnect we need a bit larger delay */
		my_sleep(10);
		while (d->busy) {
				GSM_ReadDevice(s,true);
				my_sleep(1);
				i++;
				if (i == 10) return GE_TIMEOUT;
		}
	}
	return GE_NONE;
}

static GSM_Error ALCABUS_StateMachine(GSM_StateMachine *s, unsigned char rx_byte)
{
	GSM_Protocol_ALCABUSData 	*d = &s->Protocol.Data.ALCABUS;
	int				i;
	int				checksum = 0;

	if (d->Msg.BufferUsed < d->Msg.Length + 1) {
		d->Msg.BufferUsed	= d->Msg.Length + 1;
		d->Msg.Buffer 	= (unsigned char *)realloc(d->Msg.Buffer,d->Msg.BufferUsed);
	}

	/* Check for header */
	if ((d->Msg.Length == 0) && (rx_byte != ALCATEL_HEADER)) {
		smprintf(s,"WARNING: Expecting alcatel header (%02X) but got (%02X)\n", ALCATEL_HEADER, rx_byte);
		return GE_UNKNOWNRESPONSE;
	/* Check for packet type */
	} else if (d->Msg.Length == 1){
		d->Msg.Type = rx_byte;
		/* Was it unexpected packet? */
		if ((rx_byte != d->next_frame) && (rx_byte != ALCATEL_CONTROL)) {
				smprintf(s,"WARNING: Expecting alcatel packet type (%02X) but got (%02X)\n", d->next_frame, rx_byte);
		}
		/* Determine packet size */
		switch (rx_byte) {
			case ALCATEL_ACK:
				d->expected_size = 4;
				break;
			case ALCATEL_DATA:
			/* Packet length is in it's header */
				d->expected_size = -1;
				break;
			case ALCATEL_CONTROL:
				d->expected_size = 4;
				break;
			case ALCATEL_CONNECT_ACK:
				d->expected_size = 6;
				break;
			case ALCATEL_DISCONNECT_ACK:
				d->expected_size = 3;
				break;
			default:
				smprintf(s,"WARNING: Something went wrong, unknown packet received (%02X)\n", rx_byte);
				return GE_UNKNOWNRESPONSE;
		}
	/* Check counter, we can probably ignore error here ;-) */
	} else if ((d->Msg.Length == 2) && (d->Msg.Type == ALCATEL_DATA)) {
		if (rx_byte != d->in_counter) {
			smprintf(s,"WARNING: Unexpected packet number, ignoring (expected %02X, received %02X)\n", d->in_counter, rx_byte);
			d->in_counter = rx_byte;
		}
		/* Increase incoming packet counter */
		if (d->in_counter == ALCATEL_MAX_COUNTER) d->in_counter = 0;
		else d->in_counter++;
	/* Read size for data packet */
	} else if ((d->Msg.Length == 4) && (d->Msg.Type == ALCATEL_DATA)) {
	/* Header till now + checksum */
		d->expected_size = (int)rx_byte + 6;
	}

	/* Write received byte into buffer */
	d->Msg.Buffer[d->Msg.Length++] = rx_byte;

	/* Did we received whole packet? */
	if (d->expected_size == d->Msg.Length) {
		/* Check checksum */
		for (i=0; i< (d->Msg.Length - 1); i++) checksum ^= d->Msg.Buffer[i];
		if (checksum != d->Msg.Buffer[d->Msg.Length - 1]) {
			/* We can only warn, as we don't know what should happend now... */
			smprintf(s,"WARNING: Ignoring incorrect packet checksum!\n");
		}
	
		/* Was it data? */
		if (d->Msg.Type == ALCATEL_DATA) {
			/* Dispatch message */
			s->Phone.Data.RequestMsg	= &d->Msg;
			s->Phone.Data.DispatchError	= s->Phone.Functions->DispatchMessage(s);
			/* Send ack */
			ALCABUS_WriteMessage (s, 0, 0, ALCATEL_ACK);
			/* Reset message length */
			d->Msg.Length = 0;
		/* Was it ack? */
		} else if ((d->Msg.Type == ALCATEL_ACK) ||
				(d->Msg.Type == ALCATEL_CONTROL) || 
				(d->Msg.Type == ALCATEL_CONNECT_ACK) || 
				(d->Msg.Type == ALCATEL_DISCONNECT_ACK)) {
			/* TODO: check counter of ack? */
			if (s->di.dl==DL_TEXT || s->di.dl==DL_TEXTALL ||
				s->di.dl==DL_TEXTDATE || s->di.dl==DL_TEXTALLDATE) {
				smprintf(s, "Received %s ack ", 
						(d->Msg.Type == ALCATEL_ACK) ? "normal" :
						(d->Msg.Type == ALCATEL_CONTROL) ? "control" :
						(d->Msg.Type == ALCATEL_CONNECT_ACK) ? "connect" :
						(d->Msg.Type == ALCATEL_DISCONNECT_ACK) ? "disconnect" :
						"BUG");
				smprintf(s, "0x%02x / 0x%04x", d->Msg.Type, d->Msg.Length);
				DumpMessage(s->di.df, d->Msg.Buffer, d->Msg.Length);
				fflush(s->di.df);
			}
			if (s->di.dl==DL_BINARY) {
				smprintf(s,"%c",0x02);	/* Receiving */
				smprintf(s,"%c",d->Msg.Type);
				smprintf(s,"%c",d->Msg.Length/256);
				smprintf(s,"%c",d->Msg.Length%256);
				for (i=0;i<d->Msg.Length;i++) smprintf(s,"%c",d->Msg.Buffer[i]);
			}
			if (d->Msg.Type != ALCATEL_CONTROL) {
				d->next_frame 	= ALCATEL_DATA;
				d->busy 	= false;
			}
			/* Reset message length */
			d->Msg.Length = 0;
		}

		/* Was it unexpected type? */
		if ((d->Msg.Type != d->next_frame) && (d->Msg.Type != ALCATEL_CONTROL)) {
				return GE_FRAMENOTREQUESTED;
		}
	} /* Last byte of packet */

	return GE_NONE;
}

static GSM_Error ALCABUS_Initialise(GSM_StateMachine *s)
{
	GSM_Protocol_ALCABUSData *d = &s->Protocol.Data.ALCABUS;

	/* Initialise some variables */
	d->Msg.BufferUsed	= 0;
	d->Msg.Buffer		= NULL;
	d->Msg.Length		= 0;
	d->Msg.Type		= 0;
	d->in_counter		= 1;
	d->out_counter		= 0;
	d->busy			= false;


	/* Initialise protocol */
	dbgprintf ("Initializing binary mode\n");
	return ALCABUS_WriteMessage (s, 0, 0, ALCATEL_CONNECT);
}

static GSM_Error ALCABUS_Terminate(GSM_StateMachine *s)
{
	/* Terminate protocol */
	dbgprintf ("Closing binary mode\n");
	return ALCABUS_WriteMessage (s, 0, 0, ALCATEL_DISCONNECT);
}

GSM_Protocol_Functions ALCABUSProtocol = {
	ALCABUS_WriteMessage,
	ALCABUS_StateMachine,
	ALCABUS_Initialise,
	ALCABUS_Terminate
};

#endif

/* How should editor hadle tabs in this file? Add editor commands here.
 * vim: noexpandtab sw=8 ts=8 sts=8:
 */
