/*
 * Copyright (c) 2001 Tommy Bohlin <tommy@gatespace.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/* commsrv.c
 *
 * 2do:
 *   obj2=iasSrvNewObject(ias,"IrLPT");
 *   iasObjNewInteger(obj2,"IrDA:IrLMP:LsapSel", ...);
 *   lap->flags|=HINT_PRINTER;
 *   HINT_MODEM?
 *   HINT_FAX?
 *   HINT_TELEPHONY?
 */

#include <irda.h>
#include <comm.h>

/**********************************************************************
 * Constants
 **********************************************************************/

static const char id_server[]="comm server";
static const char id_port[]="comm port";

static const u_char commParameters[] = {
  PI_SERVICE_TYPE, 1, COMM_ST_3WIRE|COMM_ST_9WIRE|COMM_ST_CENTRONICS,
  PI_PORT_TYPE,    1, COMM_PT_SERIAL|COMM_PT_PARALLEL
};

/**********************************************************************
 * Data structures
 **********************************************************************/

typedef struct COMMPortPrivate {
  COMMPort port;
  struct COMMPortPrivate* next;
  struct COMMServerPrivate* server;
  LSAP* lsap;
  int type;
  Connection* con;
  Object* iasObject;
} COMMPortPrivate;

typedef struct COMMServerPrivate {
  COMMServer comm;
  LAP* lap;
  IASServer* ias;
  COMMPortPrivate* ports;
} COMMServerPrivate;

/**********************************************************************
 * Internal functions
 **********************************************************************/

static int parseControl(COMMPortPrivate* cpp, const u_char* buf, int len)
{
  int i,len1;

  if(len<1) return 0;
  len1=buf[0]+1;
  if(len1>len) {
    if(cpp->server->comm.debug&COMM_DEBUG_INFO)
      birda_log("comm control data longer than packet");
    return -1;
  }
  i=1;
  while(i+1<len1) {
    int pi=buf[i];
    int pl=buf[i+1];
    int pv=pl<=4 ? getBEVariable(buf+i+2,pl) : 0;

    if(i+2+pl>len1) {
      if(cpp->server->comm.debug&COMM_DEBUG_INFO)
	birda_log("comm control entry too long");
      return -1;
    }

    switch(pi) {
    case PI_SERVICE_TYPE:
      cpp->type=pv;
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("service type %x\n",pv);
      break;
    case PI_PORT_TYPE:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("port type %x\n",pv);
      break;
    case PI_FIXED_PORT_NAME:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("fixed port name %.*s\n",pl,buf+i+2);
      break;
    case PI_DATA_RATE:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("data rate %d\n",pv);
      break;
    case PI_DATA_FORMAT:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("data format %x\n",pv);
      break;
    case PI_FLOW_CONTROL:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("flow control %x\n",pv);
      break;
    case PI_XON_XOFF:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("xon/xoff %x\n",pv);
      break;
    case PI_ENQ_ACK:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("enq/ack %x\n",pv);
      break;
    case PI_DTE:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("DTE %x\n",pv);
      break;
    case PI_DCE:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("DCE %x\n",pv);
      break;
    case PI_POLL_LINE_SETTINGS:
      birda_log("poll line settings is NYI\n");
    default:
      if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("pi=%x pl=%d\n",pi,pl);
      break;
    }

    i+=2+pl;
  }
  if(i!=len1) {
    if(cpp->server->comm.debug&COMM_DEBUG_INFO)
      birda_log("trailing garbage in comm control data");
    return -1;
  }
  return len1;
}

static void commData(Connection* con, void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  COMMPortPrivate* cpp=(COMMPortPrivate*)con->handle;
  int len1=parseControl(cpp,buf,len);

  if(len1>=0) {
    if(cpp->port.data) cpp->port.data(cpp->port.handle,buf+len1,len-len1);
  } else {
    birda_log("bad comm control data\n");
  }
}

static void commStatus(Connection* con, int event, void* buf, int len)
{
  COMMPortPrivate* cpp=(COMMPortPrivate*)con->handle;

  if(event==CONN_CLOSED) {
    if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("comm connection closed\n");
    cpp->con=0;
    connClose(con);
  }
}

static bool accept(LSAP* lsap, Connection* con, void* buf, int len)
{
  COMMPortPrivate* cpp=(COMMPortPrivate*)lsap->handle;

  if(cpp->con) {
    birda_log("port already open\n");
    return FALSE;
  }

  cpp->type=0;

  if(parseControl(cpp,buf,len)<0) {
    birda_log("bad comm connection request\n");
    return FALSE;
  }

  if(!(cpp->type&(COMM_ST_3WIRE|COMM_ST_9WIRE))) {
    birda_log("port type does not match\n");
    return FALSE;
  }

  if(cpp->port.status)
    (cpp->port.status)(&cpp->port, COMM_CONNECTED);

  if(cpp->server->comm.debug&COMM_DEBUG_INFO) birda_log("comm connection accepted\n");
  con->handle=cpp;
  con->status=commStatus;
  con->data=commData;

  cpp->con=con;
    
  return TRUE;
}

/**********************************************************************
 * External functions
 **********************************************************************/

void commPortWrite(COMMPort* cp, const void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  COMMPortPrivate* cpp=(COMMPortPrivate*)cp;
  int k=connGetSendDataSize(cpp->con);
  int n,i=0;
  u_char cbuf[1];

  cbuf[0]=0;

  while((n=len-i)>0) {
    if(cpp->type!=COMM_ST_3WIRE_RAW) {
      if(n>=k) n=k-1;
      connWrite2(cpp->con,cbuf,1,buf+i,n);
    } else {
      if(n>k) n=k;
      connWrite(cpp->con,buf+i,n);
    }
    i+=n;
  }
}

void commPortClose(COMMPort* cp)
{
  COMMPortPrivate* cpp=(COMMPortPrivate*)cp;
  COMMPortPrivate** ch=&cpp->server->ports;
  COMMPortPrivate* c;

  while((c=*ch)) {
    if(c==cpp) {
      *ch=c->next;
      break;
    } else {
      ch=&c->next;
    }
  }

  /* ### NYI
   * kill the current connection ...
   * Unregister from IAS
   * lsapClose(comp->lsap);
   * iasObjDelete(comp->iasObject);
   */
  birda_log("commPortClose is NYI\n");
  
  freeMem(cpp);
}

COMMPort* commSrvNewPort(COMMServer* cs)
{
  COMMServerPrivate* csp=(COMMServerPrivate*)cs;
  COMMPortPrivate* cpp=allocMem(id_port,sizeof(COMMPortPrivate));

  cpp->port.debug=0;
  cpp->port.handle=0;
  cpp->port.status=0;
  cpp->port.data=0;

  cpp->server=csp;
  cpp->next=csp->ports;
  csp->ports=cpp;

  cpp->lsap=lapNewLSAP(csp->lap,LM_TINY_TP);
  cpp->lsap->handle=cpp;
  cpp->lsap->accept=accept;
  cpp->con=0;

  cpp->iasObject=iasSrvNewObject(csp->ias,"IrDA:IrCOMM");
  iasObjNewInteger(cpp->iasObject,"IrDA:TinyTP:LsapSel",lsapGetSelector(cpp->lsap));
  iasObjNewOctets(cpp->iasObject,"Parameters",commParameters,sizeof commParameters);
  iasObjNewString(cpp->iasObject,"IrDA:IrLMP:InstanceName",CHARSET_ASCII,"com1",4);

  return &cpp->port;
}

void commSrvClose(COMMServer* comm)
{
  COMMServerPrivate* commp=(COMMServerPrivate*)comm;

  while(commp->ports) commPortClose(&commp->ports->port);
  freeMem(commp);
}

COMMServer* createCOMMServer(LAP* lap, IASServer* ias)
{
  COMMServerPrivate* commp=allocMem(id_server,sizeof(COMMServerPrivate));
  
  commp->comm.debug=0;
  commp->lap=lap;
  commp->ias=ias;
  commp->ports=0;

  lap->flags|=HINT_IRCOMM;

  return &commp->comm;
}
