/**
 * uart.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Modem
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * 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.
 */

#include "yatemodem.h"

#include <string.h>

using namespace TelEngine;

// ETSI EN 300 659-1: 5.2
// Channel seizure signal: block of 300 continuous bits of alternating 0 and 1
// Use both values to detect the begining of an ETSI defined message:
//  the modem might loose the first bits
#define ETSI_CHANNEL_SEIZURE_1 0x55            // 01010101
#define ETSI_CHANNEL_SEIZURE_2 0xaa            // 10101010


// Convert a buffer to a short
static inline short net2short(unsigned char* buffer)
{
    return (buffer[0] << 8) | buffer[1];
}

// Get date and time from system of received param
// dt: month,day,hour,minute;
// Return false if invalid
static bool getDateTime(unsigned char dt[4], String* src = 0, char sep = ':')
{
    static int minDt[4] = {1,1,0,0};
    static int maxDt[4] = {12,31,23,59};

    if (!src) {
	// TODO: implement from system time
	return false;
    }

    ObjList* list = src->split(sep);
    int i = 0;
    for (; i < 4; i++) {
	String* s = static_cast<String*>((*list)[i]);
	int tmp = s ? s->toInteger(-1) : -1;
	if (tmp >= minDt[i] && tmp <= maxDt[i])
	    dt[i] = (unsigned char)tmp;
	else
	    i = 5;
    }
    delete list;
    return (i == 4);
}


// ETSI EN 300 659-3 5.4.4 Reason of caller absence
static TokenDict s_dict_callerAbsence[] = {
    {"unavailable", 0x4f},
    {"restricted",  0x50},
    {0,0}
};

// ETSI EN 300 659-3 5.4.8 Message identification
static TokenDict s_dict_mwiStatus[] = {
    {"removed",     0x00},
    {"reference",   0x55},               // Message reference only
    {"added",       0xff},
    {0,0}
};

// ETSI EN 300 659-3 5.4.12 Call type
static TokenDict s_dict_callType[] = {
    {"voice",          0x01},            // Normal (voice) Call
    {"ccbs-ccnr",      0x02},            // CCBS / CCNR
    {"callername",     0x03},            // Calling Name Delivery
    {"return",         0x04},            // Call Return
    {"alarm",          0x05},            // Alarm Call
    {"download",       0x06},            // Download Function
    {"reverse-charge", 0x07},            // Reverse Charging Call
    {"vpn_external",   0x10},            // External Call (VPN)
    {"vpn_internal",   0x11},            // Internal Call (VPN)
    {"monitoring",     0x50},            // Monitoring Call
    {"message",        0x81},            // Message Waiting Call
    {0,0}
};

// ETSI EN 300 659-3 5.4.16 Caller type
static TokenDict s_dict_callerType[] = {
    {"unknown",             0x00},       // Origination unknown or unavailable
    {"voice",               0x01},       // Voice Call
    {"text",                0x02},       // Text Call
    {"vpn",                 0x03},       // VPN (Virtual Private Network)
    {"mobile",              0x04},       // Mobile phone
    {"mobile-vpn",          0x05},       // Mobile phone + VPN
    {"fax",                 0x06},       // Fax Call
    {"video",               0x07},       // Video Call
    {"e-mail",              0x08},       // E-mail Call
    {"operator",            0x09},       // Operator Call
    {"ordinary-subscriber", 0x0a},       // Ordinary calling subscriber
    {"priority-subscriber", 0x0b},       // Calling subscriber with priority
    {"data",                0x0c},       // Data Call
    {"test",                0x0d},       // Test call
    {"telemetric",          0x0e},       // Telemetric Call
    {"payphone",            0x0f},       // Payphone
    {0,0}
};

// ETSI EN 300 659-3 5.4.15 Forwarded call reason
static TokenDict s_dict_ffwdReason[] = {
    {"unknown",             0x00},       // Unavailable or unknown forwarded call type
    {"busy",                0x01},       // Forwarded call on busy
    {"noanswer",            0x02},       // Forwarded call on no reply
    {"unconditional",       0x03},       // Unconditional forwarded call
    {"deflected-alerted",   0x04},       // Deflected call (after alerting)
    {"deflected-immediate", 0x05},       // Deflected call (immediate)
    {"mobile-not-found",    0x06},       // Forwarded call on inability to reach mobile subscriber
    {0,0}
};


/**
 * ETSIModem
 */
static TokenDict s_etsiState[] = {
    {"Error",    ETSIModem::StateError},
    {"FSKStart", ETSIModem::WaitFSKStart},
    {"Mark",     ETSIModem::WaitMark},
    {"Msg",      ETSIModem::WaitMsg},
    {"MsgLen",   ETSIModem::WaitMsgLen},
    {"Param",    ETSIModem::WaitParam},
    {"ParamLen", ETSIModem::WaitParamLen},
    {"Data",     ETSIModem::WaitData},
    {"Chksum",   ETSIModem::WaitChksum},
    {0,0}
};

TokenDict ETSIModem::s_msg[] = {
    {"CallSetup", ETSIModem::MsgCallSetup},
    {"MWI",       ETSIModem::MsgMWI},
    {"Charge",    ETSIModem::MsgCharge},
    {"SMS",       ETSIModem::MsgSMS},
    {0,0}
};

#define MAKE_NAME(x) { #x, ETSIModem::x }
TokenDict ETSIModem::s_msgParams[] = {
    {"datetime",              DateTime},
    {"caller",                CallerId},
    {"called",                CalledId},
    {"callerpres",            CallerIdReason},
    {"callername",            CallerName},
    {"callernamepres",        CallerNameReason},
    {"visualindicator",       VisualIndicator},
    {"message_status",        MessageId},
    {"message_caller",        LastMsgCLI},
    {"service_datetime",      CompDateTime},
    {"networkprovidedcaller", CompCallerId},
    {"calltype",              CallType},
    {"fwd_first",             FirstCalledId},
    {"message_count",         MWICount},
    {"fwd_calltype",          FwdCallType},
    {"callertype",            CallerType},
    {"fwd_last",              RedirNumber},
    {"charge",                Charge},
    {"additionalcharge",      AdditionalCharge},
    {"callduration",          Duration},
    {"netid",                 NetworkID},
    {"carrierid",             CarrierId},
    {"display",               Display},
    {"serviceinfo",           ServiceInfo},
    {"extension",             Extension},
    {"selectfunction",        SelectFunction},
    {0,0}
};

ETSIModem::ETSIModem(const NamedList& params, const char* name)
    : UART(UART::Idle,params,name),
    m_buffer(this),
    m_state(WaitFSKStart),
    m_waitSeizureCount(3),
    m_crtSeizureCount(0)
{
    reset();
}

ETSIModem::~ETSIModem()
{
}

void ETSIModem::reset()
{
    m_buffer.reset();
    m_crtMsg = m_crtParamLen = 0;
    m_chksum = 0;
    m_crtSeizureCount = 0;
    m_state = WaitFSKStart;
    UART::reset();
}

// Process accumulated byte in Idle state
// Return negative to stop, positive to change state to BitStart, 0 to continue
// See ETSI EN 300 659-1 for data transmission
// 1. Channel seizure signal: block of 300 continuous bits of alternating 0 and 1
// 1. Mark (stop bits) signal: 180(+/-25) or 80(+/-25) bits
// 3. Message transmission: START bit / DATA bits / STOP bit
int ETSIModem::idleRecvByte(unsigned char data)
{
    XDebug(this,DebugAll,"idleRecvByte(%u,0x%02x,'%c') ETSI state=%s [%p]",
	data,data,(data>=32)?(char)data:' ',lookup(m_state,s_etsiState),this);

    switch (m_state) {
	case WaitFSKStart:
	    if (data == ETSI_CHANNEL_SEIZURE_1 || data == ETSI_CHANNEL_SEIZURE_2) {
		m_crtSeizureCount++;
		if (m_crtSeizureCount == m_waitSeizureCount) {
		    DDebug(this,DebugInfo,"Received FSK start pattern [%p]",this);
		    changeState(WaitMark);
		}
	    }
	    else
		m_crtSeizureCount = 0;
	    return 0;
	case WaitMark:
	    if (data != 0xff)
		return 0;
	    DDebug(this,DebugInfo,"Received mark signal. Waiting message [%p]",this);
	    changeState(WaitMsg);
	    return 1;
	default: ;
    }
    return -1;
}

// Push a data byte into this UART. Reset this UART and call decode after validated a received message
// Return false to stop feeding data
bool ETSIModem::recvByte(unsigned char data)
{
    XDebug(this,DebugAll,"recvByte(%u,0x%02x,'%c') ETSI state=%s [%p]",
	data,data,(data>=32)?(char)data:' ',lookup(m_state,s_etsiState),this);

    switch (m_state) {
	case WaitData:
	    if (!m_crtParamLen) {
		Debug(this,DebugWarn,"Internal: received unexpected parameter data [%p]",this);
		break;
	    }
	    XDebug(this,DebugAll,"Received parameter data %u [%p]",data,this);
	    if (!m_buffer.accumulate(data))
		break;
	    m_chksum += data;
	    m_crtParamLen--;
	    if (!m_crtParamLen)
		changeState(m_buffer.free() ? WaitParam : WaitChksum);
	    return true;
	case WaitParam:
	    NDebug(this,DebugAll,"Received parameter start %u=%s [%p]",
		data,lookup(data,s_msgParams),this);
	    if (!m_buffer.accumulate(data))
		break;
	    m_chksum += data;
	    changeState(WaitParamLen);
	    return true;
	case WaitParamLen:
	    if (!data || data > m_buffer.free()) {
		Debug(this,DebugNote,
		    "Received invalid parameter length %u (buffer=%u free=%u) [%p]",
		    data,m_buffer.buffer().length(),m_buffer.free(),this);
		break;
	    }
	    NDebug(this,DebugAll,"Received parameter length %u [%p]",data,this);
	    if (!m_buffer.accumulate(data))
		break;
	    m_chksum += data;
	    m_crtParamLen = data;
	    changeState(WaitData);
	    return true;
	case WaitMsgLen:
	    if (data < 3) {
		Debug(this,DebugNote,"Received invalid message length %u [%p]",data,this);
		break;
	    }
	    m_buffer.reset(data);
	    m_chksum = m_crtMsg + data;
	    NDebug(this,DebugAll,"Received message length %u [%p]",data,this);
	    changeState(WaitParam);
	    return true;
	case WaitMsg:
	    if (!lookup(data,s_msg))
		return true;
	    m_crtMsg = data;
	    NDebug(this,DebugInfo,"Received message start: %s [%p]",lookup(m_crtMsg,s_msg),this);
	    changeState(WaitMsgLen);
	    return true;
	case WaitChksum:
	    if (data == (256 - (m_chksum & 0xff))) {
		NDebug(this,DebugAll,"Checksum OK for message %s [%p]",
		    lookup(m_crtMsg,s_msg),this);
		return decode((MsgType)m_crtMsg,m_buffer.buffer());
	    }
	    Debug(this,DebugNote,"Checksum failed for message (recv=%u crt=%u) %s [%p]",
		data,m_chksum,lookup(m_crtMsg,s_msg),this);
	    changeState(StateError);
	    return UART::error(UART::EChksum);
	case StateError:
	    return false;
	default:
	    DDebug(this,DebugNote,"Can't process data in state %s [%p]",
		lookup(m_state,s_etsiState),this);
	    return true;
    }
    changeState(StateError);
    return UART::error(UART::EInvalidData);
}

// Set a date time digits string
inline void setDateTime(String& dest, const char* data, unsigned int count)
{
    dest << data[0] << data[1];
    for (unsigned int i = 2; i < count; i += 2)
	dest << ':' << data[i] << data[i+1];
}

// Process (decode) a valid received buffer. Call recvParams() after decoding the message
// Return false to stop processing data
bool ETSIModem::decode(MsgType msg, const DataBlock& buffer)
{
    NamedList params("");
    DDebug(this,DebugAll,"Decoding message %s [%p]",lookup(msg,s_msg),this);

    unsigned char* data = (unsigned char*)buffer.data();
    for (unsigned int i = 0; i < buffer.length();) {
	unsigned char param = data[i++];                                // Param type
	const char* pname = lookup(param,s_msgParams);
	unsigned int len = data[i++];                                   // Param length (non 0)
	unsigned char* pdata = data + i;
	// End of buffer: Force index outside the end of buffer
	if (i < buffer.length())
	    i += data[i-1];
	else
	    i++;
	if (i > buffer.length()) {
	    Debug(this,DebugWarn,"Unexpected end of %s parameter [%p]",pname,this);
	    return UART::error(UART::EInvalidData);
	}

	String tmp;

#define CHECK_LEN(expected) \
	if (len != expected) { \
	    Debug(this,DebugNote,"Invalid len=%u (expected %u) for %s parameter [%p]",len,expected,pname,this); \
	    continue; \
	}
#define SET_PARAM_FROM_DATA(paramname) \
	tmp.assign((char*)pdata,len); \
	params.addParam(paramname,tmp);
#define SET_PARAM_FROM_DICT(paramname,dict) \
	tmp = lookup(*pdata,dict,"unknown"); \
	params.addParam(paramname,tmp);
	// Process parameters
	// References are the sections from ETSI EN 300 659-3
	switch (param) {
	    case CallerId:               // 5.4.2
		SET_PARAM_FROM_DATA("caller")
		break;
	    case CallerName:             // 5.4.5
		SET_PARAM_FROM_DATA("callername")
		break;
	    case CallerIdReason:         // 5.4.4
		CHECK_LEN(1)
		SET_PARAM_FROM_DICT("callerpres",s_dict_callerAbsence)
		break;
	    case CallerNameReason:       // 5.4.6
		CHECK_LEN(1)
		SET_PARAM_FROM_DICT("callernamepres",s_dict_callerAbsence)
		break;
	    case DateTime:               // 5.4.1
		CHECK_LEN(8)
		setDateTime(tmp,(char*)pdata,8);
		params.addParam("datetime",tmp);
		break;
	    case CompDateTime:           // 5.4.10
		if (param == CompDateTime && len != 8 && len != 10) {
		    Debug(this,DebugNote,
			"Invalid len=%u (expected 8 or 10) for %s parameter [%p]",len,pname,this);
		    continue;
		}
		setDateTime(tmp,(char*)pdata,len);
		params.addParam("service_datetime",tmp);
		break;
	    case CalledId:               // 5.4.3
		SET_PARAM_FROM_DATA("called")
		break;
	    case CallType:               // 5.4.12
		CHECK_LEN(1)
		SET_PARAM_FROM_DICT("calltype",s_dict_callType)
		break;
	    case CallerType:             // 5.4.16
		CHECK_LEN(1)
		SET_PARAM_FROM_DICT("originator_type",s_dict_callerType)
		break;
	    case VisualIndicator:        // 5.4.7
		CHECK_LEN(1)
		if (*pdata == 0 || *pdata == 255)
		    tmp = String::boolText(*pdata != 0);
		else
		    tmp = (int)(*pdata);
		params.addParam("visualindicator",tmp);
		break;
	    case MessageId:              // 5.4.8
		CHECK_LEN(3)
		SET_PARAM_FROM_DICT("message_status",s_dict_mwiStatus)
		params.addParam("message_ref",String(net2short(pdata + 1)));
		DDebug(this,DebugInfo,
		    "Decoded %s parameter (status=%s ref=%d) [%p]",
		    pname,tmp.c_str(),net2short(pdata + 1),this);
		continue;
	    case LastMsgCLI:             // 5.4.9
		SET_PARAM_FROM_DATA("message_caller")
		break;
	    case CompCallerId:           // 5.4.11
		SET_PARAM_FROM_DATA("caller_networkprovided")
		break;
	    case FirstCalledId:          // 5.4.13
		SET_PARAM_FROM_DATA("ffwd_first")
		break;
	    case MWICount:               // 5.4.14
		CHECK_LEN(1)
		tmp = (int)(*pdata);
		params.addParam("message_count",tmp);
		break;
	    case FwdCallType:            // 5.4.15
		CHECK_LEN(1)
		SET_PARAM_FROM_DICT("ffwd_reason",s_dict_ffwdReason)
		break;
	    case RedirNumber:            // 5.4.17
		SET_PARAM_FROM_DATA("ffwd_last")
		break;
	    case Charge:                 // 5.4.18
	        Debug(this,DebugStub,"Skipping %s parameter [%p]",pname,this);
		continue;
	    case AdditionalCharge:       // 5.4.19
	        Debug(this,DebugStub,"Skipping %s parameter [%p]",pname,this);
		continue;
	    case Duration:               // 5.4.20
		CHECK_LEN(6)
		setDateTime(tmp,(char*)pdata,6);
		params.addParam("duration",tmp);
		break;
	    case NetworkID:              // 5.4.21
		SET_PARAM_FROM_DATA("netid")
		break;
	    case CarrierId:              // 5.4.22
		SET_PARAM_FROM_DATA("carrierid")
		break;
	    case SelectFunction:         // 5.4.23
	        Debug(this,DebugStub,"Skipping %s parameter [%p]",pname,this);
		continue;
	    case Display:                // 5.4.24
	        Debug(this,DebugStub,"Skipping %s parameter [%p]",pname,this);
		continue;
	    case ServiceInfo:            // 5.4.25
		CHECK_LEN(1)
		if (*pdata > 1)
		    tmp = (int)(*pdata);
		else
		    tmp = *pdata ? "active" : "not-active";
		params.addParam("service_info",tmp);
		break;
	    case Extension:              // 5.4.26
	        Debug(this,DebugStub,"Skipping %s parameter [%p]",pname,this);
		continue;
	}
#undef SET_PARAM_FROM_DATA
#undef SET_PARAM_FROM_DICT
#undef CHECK_LEN

	DDebug(this,DebugAll,"Decoded %s=%s [%p]",pname,tmp.c_str(),this);
    }
    if (recvParams(msg,params))
	return true;
    return UART::error(EStopped);
}

// Append a parameter to a buffer
// Truncate it or set error if fail is true and parameter length exceeds maxLen
// Return: 0 if the parameter is missing
//         -1 if the parameter is too long
//         1 on success
int appendParam(ObjList& msg, NamedList& params, unsigned char value,
	unsigned char maxLen, bool fail)
{
    NamedString* ns = params.getParam(lookup(value,ETSIModem::s_msgParams));
    if (!ns)
	return 0;
    unsigned char len = ns->length();
    if (len > maxLen) {
	if (fail) {
	    params.setParam("error",ns->name() + "-too-long");
	    return -1;
	}
	len = maxLen;
    }
    DataBlock* data = new DataBlock;
    unsigned char a[2] = {value,len};
    FSKModem::addRaw(*data,a,sizeof(a));
    FSKModem::addRaw(*data,(void*)ns->c_str(),len);
    msg.append(data);
    return 1;
}

// Append a parameter to a buffer from a list or dictionary
void appendParam(ObjList& msg, NamedList& params, unsigned char value,
	TokenDict* dict, unsigned char defValue)
{
    unsigned char a[3] = {value,1};
    const char* name = lookup(value,ETSIModem::s_msgParams);
    a[2] = lookup(params.getValue(name),dict,defValue);
    msg.append(new DataBlock(a,sizeof(a)));
}

// Create a buffer containing the byte representation of a message to be sent
//  and another one with the header
bool ETSIModem::createMsg(NamedList& params, DataBlock& data)
{
    int type = lookup(params,s_msg);
    switch (type) {
	case MsgCallSetup:
	    break;
	case MsgMWI:
	case MsgCharge:
	case MsgSMS:
	    Debug(this,DebugStub,"Create message '%s' not implemented [%p]",
		params.c_str(),this);
	    return false;
	default:
	    Debug(this,DebugNote,"Can't create unknown message '%s' [%p]",
		params.c_str(),this);
	    return false;
    }

    ObjList msg;
    bool fail = !params.getBoolValue("force-send",true);

    // DateTime - ETSI EN 300 659-3 - 5.4.1
    String datetime = params.getValue("datetime");
    unsigned char dt[4];
    bool ok = false;
    if (datetime.isBoolean())
	if (datetime.toBoolean())
	    ok = getDateTime(dt);
	else ;
    else
	ok = getDateTime(dt,&datetime);
    if (ok) {
	DataBlock* dtParam = new DataBlock(0,10);
	unsigned char* d = (unsigned char*)dtParam->data();
	d[0] = DateTime;
	d[1] = 8;
	// Set date and time: %.2d%.2d%.2d%.2d month:day:hour:minute
	for (int i = 0, j = 2; i < 4; i++, j += 2) {
	    d[j] = '0' + dt[i] / 10;
	    d[j+1] = '0' + dt[i] % 10;
	}
	msg.append(dtParam);
    }
    else
	DDebug(this,DebugInfo,"Can't set datetime parameter from '%s' [%p]",
	    datetime.c_str(),this);

    // CallerId/CallerIdReason - ETSI EN 300 659-3 - 5.4.2: Max caller id 20
    // Parameter is missing: append reason (default caller absence: 0x4f: unavailable)
    int res = appendParam(msg,params,CallerId,20,fail);
    if (res == -1)
	return false;
    if (!res)
	appendParam(msg,params,CallerIdReason,s_dict_callerAbsence,0x4f);

    // CallerName/CallerNameReason - ETSI EN 300 659-3 - 5.4.5: Max caller name 50
    // Parameter is missing: append reason (default callername absence: 0x4f: unavailable)
    res = appendParam(msg,params,CallerName,50,fail);
    if (res == -1)
	return false;
    if (!res)
	appendParam(msg,params,CallerNameReason,s_dict_callerAbsence,0x4f);

    // Build message
    unsigned char len = 0;
    unsigned char hdr[2] = {type};
    data.assign(&hdr,sizeof(hdr));

    for (ObjList* o = msg.skipNull(); o; o = o->skipNext()) {
	DataBlock* msgParam = static_cast<DataBlock*>(o->get());
	if (len + msgParam->length() > 255) {
	    if (!fail) {
		Debug(this,DebugNote,"Trucating %s message length to %u bytes [%p]",
		    params.c_str(),data.length(),this);
		break;
	    }
	    params.setParam("error","message-too-long");
	    return false;
	}
	len += msgParam->length();
	data += *msgParam;
    }
    if (!len) {
	params.setParam("error","empty-message");
	return false;
    }

    unsigned char* buf = ((unsigned char*)(data.data()));
    buf[1] = len;
    m_chksum = 0;
    for (unsigned int i = 0; i < data.length(); i++)
	m_chksum += buf[i];
    unsigned char crcVal = 256 - (m_chksum & 0xff);
    FSKModem::addRaw(data,&crcVal,1);
    return true;
}

// Change the state of this ETSI modem
void ETSIModem::changeState(State newState)
{
    if (m_state == newState)
	return;
    XDebug(this,DebugInfo,"ETSI changed state from %s to %s [%p]",
	lookup(m_state,s_etsiState),lookup(newState,s_etsiState),this);
    m_state = newState;
}


/**
 * UART
 */
#ifdef XDEBUG
static TokenDict s_uartState[] = {
    {"Idle",   UART::Idle},
    {"Start",  UART::BitStart},
    {"Data",   UART::BitData},
    {"Parity", UART::BitParity},
    {"Stop",   UART::BitStop},
    {"Error",  UART::UARTError},
    {0,0}
};
#endif

TokenDict UART::s_errors[] = {
    {"framing",      UART::EFraming},
    {"parity",       UART::EParity},
    {"chksum",       UART::EChksum},
    {"invalid-data", UART::EInvalidData},
    {"unknown",      UART::EUnknown},
    {"terminated",   UART::EStopped},
    {"",             UART::ENone},
    {0,0}
};

UART::UART(State state, const NamedList& params, const char* name)
    : m_modem(params,this),
    m_state(Idle),
    m_error(ENone),
    m_parity(0),
    m_expectedParity(false),
    m_accumulator(8)
{
    debugName(name);

    unsigned char dataBits = params.getIntValue("databits",8);
    if (dataBits < 1 || dataBits > 8)
	dataBits = 8;
    m_accumulator.dataBits(dataBits);

    m_parity = params.getIntValue("parity");

    reset(state);
}

void UART::reset(State st)
{
    changeState(st);
    m_error = ENone;
    m_modem.reset();
    m_accumulator.reset();
}

// Push a bit of data into this UART
// Return false to stop feeding data
bool UART::recvBit(bool value)
{
#ifdef XDEBUG
    Debug(this,DebugAll,"recvBit(%c) state=%s [%p]",
	value?'1':'0',lookup(m_state,s_uartState),this);
#endif

    int res = 0;

    switch (m_state) {
	case Idle:
	    res = m_accumulator.accumulate(value);
	    if (res & 0xffffff00)
		return true;
	    res = idleRecvByte((unsigned char)res);
	    if (res < 0)
		return error(EUnknown);
	    if (res)
		changeState(BitStart);
	    break;
	case BitData:
	    res = m_accumulator.accumulate(value);
	    if (res & 0xffffff00)
		return true;
	    if (recvByte((unsigned char)res))
		if (!m_parity)
		    changeState(BitStop);
		else {
		    // TODO: get parity and set the expected one
		    changeState(BitParity);
		}
	    else
		return error(EUnknown);
	    break;
	case BitStart:
	    if (!value)
		changeState(BitData);
	    break;
	case BitStop:
	    if (value)
		changeState(BitStart);
	    else
		return error(EFraming);
	    break;
	case BitParity:
	    if (value == m_expectedParity)
		changeState(BitStop);
	    else
		return error(EParity);
	    break;
	default:
	    return false;
    }
    return true;
}

// Set error state
bool UART::error(Error e)
{
    changeState(UARTError);
    if (m_error == ENone) {
	m_error = e;
	if (m_error != EStopped)
	    Debug(this,DebugNote,"Error detected: %u '%s' [%p]",
		m_error,lookup(m_error,s_errors),this);
    }
    return false;
}

// Change the state of this UART
void UART::changeState(State newState)
{
    if (m_state == newState)
	return;
#ifdef XDEBUG
    Debug(this,DebugAll,"UART changed state from %s to %s [%p]",
	lookup(m_state,s_uartState),lookup(newState,s_uartState),this);
#endif
    m_state = newState;
}

/* vi: set ts=8 sw=4 sts=4 noet: */
