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

#include <irda.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/param.h>

static void connection(void* handle);

typedef struct SockCon {
  int socket;
  int fd;
  int offset;
  int bufsize;
  u_char *buf;
#define SC_IDLE 1
#define SC_INPUT_READ 2
#define SC_CONNECTED 4
  int scstate;
#define LC_IDLE 1
#define LC_CONNECTED 2 
  int lcstate;
  void (*oldLapConnected)(LAP* lap);
  void (*oldLapDisconnected)(LAP* lap);
  void *oldHandle;
  OBEXClient* obex;
} SockCon;

static const char id_sc[]="SockCon";

static int verbosity = 1;

/**********************************************************************
 * OBEX control
 **********************************************************************/

static void obexStatus(OBEXClient* oc, int event)
{
  switch(event) {
  case OBEX_SUCCESS:
    if (verbosity > 0) 
      birda_log("put succeeded\n");
    break;
  case OBEX_FAILURE:
    birda_log("put failed\n");
    break;
  }
  obexCltClose(oc);
  lapDisconnect(optLap);
}

/**********************************************************************
 * LAP control
 **********************************************************************/

static void lapConnected(LAP* lap)
{
  SockCon* sc = lap->handle;

  if (verbosity > 0) 
    birda_log("LAP connected\n");

#if 0
  lapDisconnect(optLap);
#endif

  sc->obex = createOBEXClient(lap);
  if(!sc->obex) {
    birda_log("OBEX connect failed\n");
    lapDisconnect(lap);
  }
  if(verbosity > 0)  
    sc->obex->debug |= OBEX_DEBUG_INFO;
  sc->obex->status = obexStatus;
  obexCltPut(sc->obex,sc->buf,sc->offset);
}

static void showLap(LAP *lap)
{
  fprintf(stderr, "LAP: flags=%x debug=%x handle=%lx status=%lx\n",
	  lap->flags, lap->debug,
	  (u_long)lap->handle, (u_long)lap->status);
}

static void lapDisconnected(LAP* lap)
{
  SockCon* sc = lap->handle;

  if (verbosity > 0) 
    birda_log("LAP disconnected\n");
  optLapConnected = sc->oldLapConnected;
  optLapDisconnected = sc->oldLapDisconnected;
  optLap->handle = sc->oldHandle;
  sc->scstate = SC_IDLE;  
  freeMem(sc->buf);
  sc->buf = NULL;
  sc->offset = 0;
  showLap(optLap);
  evtAddSource(sc->socket, connection, sc); /* Listen for connections again */
}

static SockCon* createSocketConnection(void)
{
  SockCon* sc = allocMem(id_sc, sizeof(SockCon));

  sc->buf = NULL;
  sc->offset = 0;  
  sc->scstate = SC_IDLE;
  sc->lcstate = LC_IDLE;
  sc->obex = NULL;
  return sc;
}

static void input(void* handle)
{
  SockCon* sc = (SockCon *)handle;
  ssize_t len;

  if (sc->offset == sc->bufsize) {
    sc->bufsize *= 2;
    sc->buf = growMem(sc->buf, sc->bufsize);
  }

  len = read(sc->fd, &sc->buf[sc->offset], sc->bufsize-sc->offset);
  if(len>0) {
    sc->offset += len;
  } else if(len==0) {
    evtRemoveSource(sc->fd);
    close(sc->fd);

    /* Done reading from socket */
    fprintf(stderr, "Read OBEX object from socket, length=%d\n", sc->offset);

    if (sc->offset > 0) {
      sc->scstate = SC_INPUT_READ;
      sc->oldLapConnected = optLapConnected;
      sc->oldLapDisconnected = optLapDisconnected;
      sc->oldHandle = optLap->handle;
      optLapConnected = lapConnected;
      optLapDisconnected = lapDisconnected;
      optLap->handle = sc;
      fprintf(stderr, "We are initialized\n");
      showLap(optLap);
      doConnect();
    }
  } else if(len<0 && errno!=EAGAIN && errno!=EINTR) {
    evtRemoveSource(sc->fd);
    close(sc->fd);
    perror("Error reading from socket");
  }
}

static void connection(void* handle)
{
  SockCon* sc = (SockCon *)handle;
  struct sockaddr addr;
  socklen_t len;

  fprintf(stderr, "Someone connected!\n");
  showLap(optLap);
  evtRemoveSource(sc->socket); /* Can only handle one connection at a time */
  if (sc->scstate == SC_IDLE) {
    len = sizeof(addr);
    sc->fd = accept(sc->socket, &addr, &len);
    if (sc->fd > 0) {
      fprintf(stderr, "Conn: fd = %d\n", sc->fd);
      fprintf(stderr, "error    = %d\n", errno);

      if (sc->buf == NULL) {
#define INITIAL_BUFSIZE 4096
	sc->buf = allocMem(id_sc, INITIAL_BUFSIZE);
	sc->bufsize = INITIAL_BUFSIZE;
      }
      sc->offset = 0;

      evtAddSource(sc->fd, input, sc);
    }    
  }
}

#define MAXTRIES 3
#define SYSCALL_ERROR		-1
#define RESOLVER_ERROR		-2
#define OPTION_ERROR		-3

#define CLOSE( sd, t )\
    t = errno;\
    (void)close(sd);\
    errno = t

static int in_bind(short port, int type)
{
  int sd;
  struct sockaddr_in sin;
  int errno_save;

  if ((sd = socket( AF_INET, type, 0)) == -1)
    return SYSCALL_ERROR;

  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = htons(port);

  if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
    CLOSE(sd, errno_save);
    return SYSCALL_ERROR;
  }
  return sd;
}

#define TYPE_NAME_LEN					10
static char *socket_type_name(int type)
{
  static char type_name[TYPE_NAME_LEN];

  if (type == SOCK_STREAM)
    return "SOCK_STREAM";
  else if (type == SOCK_DGRAM)
    return "SOCK_DGRAM";
  else {
    (void) sprintf(type_name, "%d", type);
    return type_name;
  }
}

static void resolver_error(char *call, char *host)
{
  fprintf(stderr, "%s: resolver error. ", call);

#ifdef HAS_H_ERRNO
  switch (errno) {
  case HOST_NOT_FOUND:
    fprintf(stderr, "Host not found. ");
    break;
  case TRY_AGAIN:
    fprintf(stderr, "Got a TRY_AGAIN after %d times. ", MAX_TRIES);
    break;
  case NO_RECOVERY:
    fprintf(stderr, "Non-recoverable error. ");
    break;
  case NO_DATA:
    fprintf(stderr,
	    "Valid name, but no data record of requested type. ");
    break;
#if NO_ADDRESS != NO_DATA
  case NO_ADDRESS:
    fprintf(stderr, "No address. ");
    break;
#endif
  default:
    fprintf(stderr, "UNKNOWN h_errno VALUE (h_errno=%d). ", errno);
    break;
  }
#endif 	/* HAS_H_ERRNO */
  fprintf(stderr, "Host = %s\n", ( host ) ? host : "LOCAL");
  /* exit 1; */
}

static int in_bind_ne(short port, int type)
{
  int tries = 0, sd;

  while ((tries++ < MAXTRIES) && ((sd = in_bind(port, type)) < 0))
    sleep(1);

  if (sd < 0)
    switch (sd) {
    case SYSCALL_ERROR:
      fprintf(stderr, "in_bind_ne: bind", "port = %d, type = %s\n", 
	      port, socket_type_name(type));
      /* NOTREACHED */
    case RESOLVER_ERROR:
      resolver_error("in_bind_ne", NULL);
      /* NOTREACHED */
    default:
      fprintf(stderr,
	      "in_bind_ne: in_bind RETURNED BAD VALUE: %d\n", sd);
      exit(1);
    }
  return sd;
}

void createOBEXSender(int port)
{
  //int fdesc;
  int error;
  SockCon* sc = createSocketConnection();

  fprintf(stderr, "Trying to listen on port %d\n", port);
  if (listen(sc->socket = in_bind_ne(port, SOCK_STREAM), 5) >= 0) {
    //struct sockaddr addr;
    //socklen_t len;
    fprintf(stderr, "Socket fd=%d\n", sc->socket);
    error = fcntl(sc->socket, F_SETFL, O_NONBLOCK | O_ASYNC);
    if (error == -1) {
      perror("Couldn't fcntl socket\n");
    }
#if 0
    len=sizeof(addr);
    fdesc = accept(sc->socket, &addr, &len);

    if (fdesc != -1 || errno != EWOULDBLOCK) {
      fprintf(stderr, "odd accept: %d, %d\n", fdesc, errno);
    }    
#endif
    showLap(optLap);
    evtAddSource(sc->socket, connection, sc);
  } else {
    fprintf(stderr, "Couldn't listen on port %d: errno=%d\n", port, errno);
  }
}
