/*
 * 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.
 */
/* iasclt.c
 */

#include <irda.h>
#include <ias.h>

#include <string.h>

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

static const char id_client[]="ias client";
static const char id_inbuf[]="ias client inbuf";

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

typedef struct IASClientPrivate {
  IASClient ias;
  Connection* con;

  bool busy;
  int op;

  int outOfs;
  int outLength;
  u_char outBuf[256];

  int inOfs;
  int inLength;
  int inMax;
  u_char* inBuf;
} IASClientPrivate;

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

static void sendACK(Connection* con, int op)
{
  u_char hdr[1];

  hdr[0]=op|IAS_ACK|IAS_LAST;
  connWrite(con,hdr,1);
}

static void status(Connection* con, int event, void* buf, int len)
{
  IASClientPrivate* iasp=(IASClientPrivate*)con->handle;

  if(event==CONN_CLOSED) {
    if(iasp->ias.debug&IAS_DEBUG_INFO) birda_log("ias connection closed\n");
    connClose(con);
    iasp->con=0;

    if(iasp->busy) {
      iasp->busy=FALSE;
      freeMem(iasp->inBuf);
      iasp->inBuf=0;
      if(iasp->ias.status) iasp->ias.status(&iasp->ias,IAS_QUERY_FAILED);
    }
  }
}

static void sendChunk(IASClientPrivate* iasp)
{
  int n=iasp->outLength-iasp->outOfs;
  int k=connGetSendDataSize(iasp->con)-1;
  u_char hdr[1];
  if(k>n) k=n;
  hdr[0]=iasp->op;
  if(k==n) hdr[0]|=IAS_LAST;
  connWrite2(iasp->con,hdr,1,iasp->outBuf,k);
  iasp->outOfs+=k;
}

static void data(Connection* con, void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  IASClientPrivate* iasp=(IASClientPrivate*)con->handle;
  u_char op;

  /* Just ignore null input, OK? */
  if(len<1) return;
  op=buf[0]&~(IAS_LAST|IAS_ACK);

  if(!iasp->busy || op!=iasp->op) {
    status(con,CONN_CLOSED,0,0);
    return;
  }

  if(iasp->outOfs<iasp->outLength) {
    if((buf[0]&IAS_ACK) && op==iasp->op) sendChunk(iasp);
    else {
      status(con,CONN_CLOSED,0,0);
    }
    return;
  }
  if(buf[0]&IAS_ACK) return;

  while(iasp->inMax<iasp->inLength+len-1) {
    iasp->inMax*=2;
    iasp->inBuf=growMem(iasp->inBuf,iasp->inMax);
  }      

  memcpy(iasp->inBuf+iasp->inLength,buf+1,len-1);
  iasp->inLength+=len-1;

  if(buf[0]&IAS_LAST) {
    iasp->busy=FALSE;

    iasp->inOfs=3;
    if(iasp->inLength>0 && iasp->inBuf[0]!=IAS_SUCCESS) iasp->inLength=0;

    if(iasp->ias.status) iasp->ias.status(&iasp->ias,IAS_QUERY_COMPLETE);
  } else {
    sendACK(con,op);
  }
}

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

bool iasCltGetValueByClass(IASClient* ias, const char* class, const char* name)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  if(iasp->con && !iasp->busy) {
    int clen=strlen(class);
    int nlen=strlen(name);
    if(clen>60) clen=60;
    if(nlen>60) nlen=60;
    iasp->outBuf[0]=clen;
    memcpy(iasp->outBuf+1,class,clen);
    iasp->outBuf[1+clen]=nlen;
    memcpy(iasp->outBuf+2+clen,name,nlen);

    iasp->busy=TRUE;
    iasp->op=OP_GetValueByClass;
    iasp->outOfs=0;
    iasp->outLength=2+clen+nlen;
    iasp->inLength=0;

    sendChunk(iasp);
    return TRUE;
  }
  return FALSE;
}

void iasCltClose(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  if(iasp->con) connClose(iasp->con);
  if(iasp->inBuf) freeMem(iasp->inBuf);
  freeMem(iasp);
}

void iasCltShowResult(IASClient* ias)
{
  int type,id;

  while((type=iasCltResType(ias))!=IAS_NONE) {
    id=iasCltResId(ias);
    switch(type) {
    case IAS_INT:
      birda_log("  %d. int %d\n",id,iasCltResInt(ias));
      break;
    case IAS_OCTETS:
      birda_log("  %d. octets ",id);
      showBytes(iasCltResPtr(ias),iasCltResLength(ias));
      break;
    case IAS_STRING:
      birda_log("  %d. string %.*s\n",id,
	      iasCltResLength(ias),(char*)iasCltResPtr(ias));
      break;
    }
    iasCltResNext(ias);
  }
}

int iasCltResType(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  if(iasp->busy || !iasp->inBuf) return IAS_NONE;
  for(;;) {
    int i=iasp->inOfs;
    int n=iasp->inLength-i;

    if(n<3) return IAS_NONE;
    switch(iasp->inBuf[i+2]) {
    case IAS_NONE:
      iasp->inOfs+=3;
      continue;
    case IAS_INT:
      if(n>=7) return IAS_INT;
      break;
    case IAS_OCTETS:
      if(n>=5 && n>=getBEShort(iasp->inBuf+i+3)+5) return IAS_OCTETS;
      break;
    case IAS_STRING:
      if(n>=5 && n>=iasp->inBuf[i+4]+5) return IAS_STRING;
      break;
    }
    iasp->inOfs=iasp->inLength;
    return IAS_NONE;    
  }
}

int iasCltResId(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  return getBEShort(iasp->inBuf+iasp->inOfs);
}

int iasCltResInt(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  return getBELong(iasp->inBuf+iasp->inOfs+3);
}

int iasCltResCharSet(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  return iasp->inBuf[iasp->inOfs+3];
}

int iasCltResLength(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  switch(iasp->inBuf[iasp->inOfs+2]) {
  case IAS_INT:    return 4;
  case IAS_OCTETS: return getBEShort(iasp->inBuf+iasp->inOfs+3);
  case IAS_STRING: return iasp->inBuf[iasp->inOfs+4];
  default:         return 0;
  }
}

void* iasCltResPtr(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;

  return iasp->inBuf+iasp->inOfs+5;
}

void iasCltResNext(IASClient* ias)
{
  IASClientPrivate* iasp=(IASClientPrivate*)ias;
  int i=iasp->inOfs;

  switch(iasp->inBuf[i+2]) {
  case IAS_INT:
    iasp->inOfs+=7;
    break;
  case IAS_OCTETS:
    iasp->inOfs+=5+getBEShort(iasp->inBuf+i+3);
    break;
  case IAS_STRING:
    iasp->inOfs+=5+iasp->inBuf[i+4];
    break;
  }
}

IASClient* createIASClient(LAP* lap)
{
  IASClientPrivate* iasp;
  Connection* con=lapNewConnection(lap,IAS_LSAPSEL,0,0,0);
  if(!con) return 0;

  iasp=allocMem(id_client,sizeof(IASClientPrivate));
  iasp->ias.handle=0;
  iasp->ias.status=0;
  iasp->con=con;
  iasp->con->handle=iasp;
  iasp->con->status=status;
  iasp->con->data=data;
  iasp->busy=FALSE;
  iasp->outOfs=0;
  iasp->outLength=0;
  iasp->inLength=0;
  iasp->inMax=256;
  iasp->inBuf=allocMem(id_inbuf,iasp->inMax);

  return &iasp->ias;
}
