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

#include <irda.h>
#include <obex.h>

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

static const char id_client[]="obex client";

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

typedef struct OBEXClientPrivate {
  OBEXClient obex;
  LAP* lap;
  int state;
#define STATE_QUERY_OBEX              1
#define STATE_QUERY_XFER              2
#define STATE_CONNECTING              3
#define STATE_LIVE                    4
#define STATE_CLOSED                  0
  IASClient* ias;
  Connection* con;

  int maxSendSize;

  int chunkLength;
  int chunkCode;
  int outOfs;
  int outLength;
  const u_char* outBuf;
} OBEXClientPrivate;

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

static void sendConnect(Connection* con)
{
  u_char hdr[7];

  hdr[0]=OP_CONNECT|OBEX_LAST;
  putBEShort(hdr+1,7);
  hdr[3]=OBEX_VERSION;
  hdr[4]=0;

  /* NB: Stick to max frame size for now. We may want to increase
   *     it later if it works for other devices. Max OBEX size is 65535
   */
  putBEShort(hdr+5,connGetRecvDataSize(con));

  connWrite(con,hdr,7);
}

static void sendBadRequest(Connection* con)
{
  u_char hdr[3];

  hdr[0]=RES_BAD_REQUEST|OBEX_LAST;
  putBEShort(hdr+1,3);
  connWrite(con,hdr,3);
}

static int headerLength(OBEXClientPrivate* ocp, int ofs)
{
  int len=ocp->outLength-ofs;

  switch(ocp->outBuf[ofs]&TYPE_MASK) {
  case TYPE_UNICODE:
    if(len>2) {
      int l1=getBEShort(ocp->outBuf+ofs+1);
      if(l1<5) l1=5;
      if(len>l1) len=l1;
    }
    break;
  case TYPE_BYTES:
    if(len>2) {
      int l1=getBEShort(ocp->outBuf+ofs+1);
      if(l1<3) l1=3;
      if(len>l1) len=l1;
    }
    break;
  case TYPE_INT1:
    if(len>2) len=2;
    break;
  case TYPE_INT4:
    if(len>5) len=5;
    break;
  }
  return len;
}

static void sendChunk(Connection* con, OBEXClientPrivate* ocp)
{
  u_char hdr[6];

  if(ocp->chunkLength>0) {
    int l=ocp->outOfs+ocp->chunkLength;
    while(ocp->outLength-l>=3) {
      int l1=l+headerLength(ocp,l);
      if(l1-ocp->outOfs+6>ocp->maxSendSize) break;
      l=l1;
    }
    if(l-ocp->outOfs+6>ocp->maxSendSize) {
      hdr[0]=OP_PUT;
      putBEShort(hdr+1,ocp->maxSendSize);
      hdr[3]=ocp->chunkCode==(HI_END_OF_BODY|TYPE_BYTES) ? (HI_BODY|TYPE_BYTES) : ocp->chunkCode;
      putBEShort(hdr+4,ocp->maxSendSize-3);
      connWrite2(con,hdr,6,ocp->outBuf+ocp->outOfs,ocp->maxSendSize-6);

      ocp->chunkLength-=ocp->maxSendSize-6;
      ocp->outOfs+=ocp->maxSendSize-6;
    } else {
      hdr[0]=OP_PUT;
      if(l==ocp->outLength) hdr[0]|=OBEX_LAST;
      putBEShort(hdr+1,l-ocp->outOfs+6);
      hdr[3]=ocp->chunkCode;
      putBEShort(hdr+4,ocp->chunkLength+3);
      connWrite2(con,hdr,6,ocp->outBuf+ocp->outOfs,l-ocp->outOfs);

      ocp->chunkLength=0;
      ocp->outOfs=l;
    }
  } else {
    int l=ocp->outOfs;
    while(ocp->outLength-l>=3) {
      int l1=l+headerLength(ocp,l);
      if(l1-ocp->outOfs+3>ocp->maxSendSize) break;
      l=l1;
    }
    if(l>ocp->outOfs) {
      hdr[0]=OP_PUT;
      if(l==ocp->outLength) hdr[0]|=OBEX_LAST;
      putBEShort(hdr+1,l-ocp->outOfs+3);
      connWrite2(con,hdr,3,ocp->outBuf+ocp->outOfs,l-ocp->outOfs);

      ocp->outOfs=l;
    } else if(ocp->outLength-ocp->outOfs>=3) {
      ocp->chunkCode=ocp->outBuf[ocp->outOfs];
      ocp->chunkLength=headerLength(ocp,ocp->outOfs)-3;
      ocp->outOfs+=3;

      hdr[0]=OP_PUT;
      putBEShort(hdr+1,ocp->maxSendSize);
      hdr[3]=ocp->chunkCode==(HI_END_OF_BODY|TYPE_BYTES) ? (HI_BODY|TYPE_BYTES) : ocp->chunkCode;
      putBEShort(hdr+4,ocp->maxSendSize-3);
      connWrite2(con,hdr,6,ocp->outBuf+ocp->outOfs,ocp->maxSendSize-6);

      ocp->chunkLength-=ocp->maxSendSize-6;
      ocp->outOfs+=ocp->maxSendSize-6;
    } else {
      hdr[0]=OP_PUT|OBEX_LAST;
      putBEShort(hdr+1,ocp->outLength-ocp->outOfs+3);
      connWrite2(con,hdr,3,ocp->outBuf+ocp->outOfs,ocp->outLength-ocp->outOfs);

      ocp->outOfs=ocp->outLength;
    }
  }
}

static void shutDown(OBEXClientPrivate* ocp)
{
  ocp->outLength=0;
  birda_log("shutdown is NYI\n");
}

static void connData(Connection* con, void* buf0, int len)
{
  int op,len1;
  u_char* buf=(u_char*)buf0;
  OBEXClientPrivate* ocp=(OBEXClientPrivate*)con->handle;

  if(len<3) {
    birda_log("short obex packet\n");
    sendBadRequest(ocp->con);
    shutDown(ocp);
    return;
  }
  len1=getBEShort(buf+1);
  if(len1>len) {
    birda_log("bad length in OBEX packet\n");
    sendBadRequest(ocp->con);
    shutDown(ocp);
    return;
  }
  if(!(buf[0]&OBEX_LAST)) {
    birda_log("did not expect long OBEX packet\n");
    sendBadRequest(ocp->con);
    shutDown(ocp);
    return;
  }

  op=buf[0]&~OBEX_LAST;
  switch(op) {
  case RES_CONTINUE:
    sendChunk(con,ocp);
    break;
  case OP_DISCONNECT:
    birda_log("obex disconnect is NYI\n");
    break;
  case RES_SUCCESS:
    if(ocp->state==STATE_CONNECTING) {
      ocp->state=STATE_LIVE;
      ocp->maxSendSize=connGetSendDataSize(con);
      if(len1>=7) {
	int maxlen=getBEShort(buf+5);
	if(ocp->maxSendSize>maxlen) ocp->maxSendSize=maxlen;
      }
      if(ocp->outOfs<ocp->outLength) sendChunk(con,ocp);
    } else {
      if(ocp->obex.status) ocp->obex.status(&ocp->obex,OBEX_SUCCESS);
    }
    break;
  case RES_BAD_REQUEST:
    birda_log("received bad request response\n");
    shutDown(ocp);
    break;
  default:
    birda_log("unexpected obex opcode %x NYI\n",op);
    break;
  }
}

static void connStatus(Connection* con, int event, void* buf, int len)
{
  /* OBEXClient* oc=(OBEXClient*)con->handle; */

  if(event==CONN_CLOSED) {

    birda_log("connStatus disconnect is NYI\n");

  }
}

static void iasStatus(IASClient* ic, int event)
{
  OBEXClientPrivate* ocp=(OBEXClientPrivate*)ic->handle;
  
  if(event==IAS_QUERY_COMPLETE) {
    int type,lsap=0;
    
    while((type=iasCltResType(ocp->ias))!=IAS_NONE) {
      if(type==IAS_INT) {
	lsap=iasCltResInt(ocp->ias);
	break;
      }
      iasCltResNext(ocp->ias);
    }

    if(lsap>0) {
      iasCltClose(ocp->ias);
      ocp->ias=0;

      ocp->state=STATE_CONNECTING;
      ocp->con=lapNewConnection(ocp->lap,lsap,LM_TINY_TP,0,0);
      if(ocp->con) {
	ocp->con->handle=ocp;
	ocp->con->status=connStatus;
	ocp->con->data=connData;

	sendConnect(ocp->con);
      } else {
	birda_log("New obex connection failed is NYI\n");
      }
      return;
    }
  }

  if(ocp->state==STATE_QUERY_OBEX) {
    ocp->state=STATE_QUERY_XFER;
    if(iasCltGetValueByClass(ocp->ias,"OBEX:IrXfer","IrDA:TinyTP:LsapSel")) {
      return;
    }
  }

  iasCltClose(ocp->ias);
  ocp->ias=0;

  birda_log("ias query failed is NYI\n");
}

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

bool obexCltPut(OBEXClient* oc, const void* buf, int len)
{
  OBEXClientPrivate* ocp=(OBEXClientPrivate*)oc;  

  if(ocp->state!=STATE_CLOSED &&
     ocp->outOfs>=ocp->outLength &&
     buf &&
     len>0) {
    ocp->chunkLength=0;
    ocp->outOfs=0;
    ocp->outLength=len;
    ocp->outBuf=buf;
    if(ocp->state==STATE_LIVE) sendChunk(ocp->con,ocp);
    return TRUE;
  } else {
    return FALSE;
  }
}

void obexCltClose(OBEXClient* oc)
{
  OBEXClientPrivate* ocp=(OBEXClientPrivate*)oc;

  if(ocp->ias) iasCltClose(ocp->ias);
  if(ocp->con) connClose(ocp->con);
  freeMem(ocp);
}

OBEXClient* createOBEXClient(LAP* lap)
{
  OBEXClientPrivate* ocp;
  IASClient* ias=createIASClient(lap);

  if(!ias) return 0;

  ocp=allocMem(id_client,sizeof(OBEXClientPrivate));
  ocp->obex.handle=0;
  ocp->obex.status=0;
  ocp->lap=lap;
  ocp->state=STATE_QUERY_OBEX;
  ocp->ias=ias;
  ocp->ias->handle=ocp;
  ocp->ias->status=iasStatus;
  ocp->con=0;
  ocp->chunkLength=0;
  ocp->outOfs=0;
  ocp->outLength=0;
  ocp->outBuf=0;

  if(!iasCltGetValueByClass(ocp->ias,"OBEX","IrDA:TinyTP:LsapSel")) {
    birda_log("IAS query not accepted is NYI\n");
  }

  return &ocp->obex;
}
