/*****************************************************************************
 *
 * FILE:	postal_xmlrpc_server.c
 * DESCRIPTION:	Postal: XML-RPC server
 * DATE:	Tue, Aug 28 2007
 * UPDATED:	Tue, Sep  4 2007
 * AUTHOR:	Kouichi ABE (WALL) / °¤Éô¹¯°ì
 * E-MAIL:	kouichi@MysticWALL.COM
 * URL:		http://www.MysticWALL.COM/
 * COPYRIGHT:	(c) 2007 °¤Éô¹¯°ì¡¿Kouichi ABE (WALL), All rights reserved.
 * LICENSE:
 *
 *  Copyright (c) 2007 Kouichi ABE (WALL) <kouichi@MysticWALL.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.
 *
 * $Id: postal_xmlrpc_server.c,v 1.4 2007/09/04 09:21:04 kouichi Exp $
 *
 *****************************************************************************/

#if	HAVE_CONFIG_H
#include "config.h"
#endif	/* HAVE_CONFIG_H */

#include <stdio.h>
#if	HAVE_STDLIB_H
#include <stdlib.h>
#endif	/* HAVE_STDLIB_H */
#if	HAVE_UNISTD_H
#include <unistd.h>
#endif	/* HAVE_UNISTD_H */
#if	HAVE_STRING_H
#include <string.h>
#endif	/* HAVE_STRING_H */
#include <errno.h>
#include <sqlite3.h>
#include <sxmlrpc.h>
#include "postal_xmlrpc.h"

/******************************************************************************
 *
 *	Program and Copyright
 *
 *****************************************************************************/
const char	Program[]   = "postal_xmlrpc_server";
const char	Copyright[] =
	"Copyright (c) 2007 Kouichi ABE (WALL), All rights reserved.";

/******************************************************************************
 *
 *	Macros and structures definition
 *
 *****************************************************************************/
/*
 * Numeric release version identifier:
 * MNNFFPPS: major minor fix patch status
 * The status nibble has one of the values 0 for development,
 * 1 to e for betas 1 to 14, and f for release.
 * The patch level is exactly that.
 */
#define	POSTAL_VERSION_NUMBER	0x10000020L
#define	POSTAL_VERSION		"Postal-XMLRPC-Server/1.0"
#define	POSTAL_VERSION_TEXT	POSTAL_VERSION " (2007/09/04)"
#define	POSTAL_VERSION_TEXT_LONG \
	"Postal-XMLRPC-Server 1.0, Tue, Sep  4 2007"

/*****************************************************************************/

#define	CODE_NO_METHOD_NAME		400
#define	CODE_INVALID_PARAM_TYPE		401
#define	CODE_REQUEST_STRING_PARAM	402
#define	CODE_UNSUPPORTED_METHOD		403
#define	CODE_DATABASE_ERROR		404
#define	CODE_NO_ADDRESS			405

#define	MESG_NO_METHOD_NAME		"No method name"
#define	MESG_INVALID_PARAM_TYPE		"Invalid type of parameter"
#define	MESG_REQUEST_STRING_PARAM	"Request a string parameter as a 7-digit number"
#define	MESG_UNSUPPORTED_METHOD		"Unsupported method"
#define	MESG_DATABASE_ERROR		"Failed to access database"
#define	MESG_NO_ADDRESS			"No address for postcode"

#define	POSTCODE_DB	"/usr/local/share/postal/postcode.db"
#define	POSTCODE_LEN	(7)
#define	SQL_JAPAN \
	"SELECT prefecture,city,address FROM japan WHERE postcode=$POSTCODE;"
#define	SQL_OFFICE \
	"SELECT prefecture,city,address,name FROM office WHERE postcode=$POSTCODE;"

#define	BACKLOG	(48)

/******************************************************************************
 *
 *	Lobal functions declaration
 *
 *****************************************************************************/
static void	version(void);
static void	usage(void);

static int	postal(const char * client, const char * method,
		       sxmlrpc_params_t * params, sxmlrpc_param_t * param);
static int	lookup(sqlite3 * conn, const char * sql, const char * postcode,
		       char ** address, char ** name);

/*****************************************************************************
 *
 *	Local variables declaration
 *
 *****************************************************************************/
static const char *	dbfile = POSTCODE_DB;

/******************************************************************************
 *
 *	Lobal variable definition
 *
 *****************************************************************************/
static void
version(void)
{
  fprintf(stderr, "%s\n%s\n\n", POSTAL_VERSION_TEXT, Copyright);
  exit(64);
}

static void
usage(void)
{
  fprintf(stderr, "usage: %s [-a <ipaddr>][-p <port>][-b <backlog>][-d <database>][-D]\n", Program);
  fprintf(stderr, "\n    [ example ]\n");
  fprintf(stderr, "        %s -a 192.168.1.23 -p 8080\n", Program);
  fprintf(stderr, "        %s -a 192.168.1.23 -d postcode.db\n", Program);
  fprintf(stderr, "        %s -a 192.168.1.23 -b 10 -D\n\n", Program);
  exit(64);
}

static int
postal(client, method, params, param)
	const char *		client;
	const char *		method;
	sxmlrpc_params_t *	params;
	sxmlrpc_param_t *	param;	/* returns */
{
  static const char	digits[] = "0123456789";
  const char *		postcode;
  size_t		len;
  size_t		pos;
  sqlite3 *		conn;
  char *		address;
  char *		office	= NULL;	/* office name */
  int			status	= -1;

  /* 1st error check */
  if (method == NULL) {
    return sxmlrpc_set_fault(param, CODE_NO_METHOD_NAME,
			     MESG_NO_METHOD_NAME);
  }
  if (strcmp(method, METHOD_NAME) != 0) {
    return sxmlrpc_set_fault(param, CODE_UNSUPPORTED_METHOD,
			     MESG_UNSUPPORTED_METHOD);
  }
  if (params == NULL || params->size != 1) {
    return sxmlrpc_set_fault(param, CODE_REQUEST_STRING_PARAM,
			     MESG_REQUEST_STRING_PARAM);
  }
  if (sxmlrpc_get_value_type(&params->param->value) != SXMLRPC_VALUE_STRING) {
    return sxmlrpc_set_fault(param, CODE_INVALID_PARAM_TYPE,
			     MESG_INVALID_PARAM_TYPE);
  }

  /* set postcode */
  postcode = sxmlrpc_get_value_string(&params->param->value);

  /* 2nd error check */
  len = strlen(postcode);
  if (len != POSTCODE_LEN) {
    goto error;
  }
  pos = strspn(postcode, digits);
  if (pos != POSTCODE_LEN) {
    goto error;
  }

  /* main process */
  status = sqlite3_open(dbfile, &conn);
  if (status != SQLITE_OK) {
    return sxmlrpc_set_fault(param, CODE_DATABASE_ERROR, MESG_DATABASE_ERROR);
  }

  status = lookup(conn, SQL_JAPAN, postcode, &address, NULL);
  if (status != 0) {
    status = lookup(conn, SQL_OFFICE, postcode, &address, &office);
  }

  sqlite3_close(conn);

  if (status == 0) {
    sxmlrpc_member_t *	mval;
    int			n = 2;

    if (office != NULL) {
      n++;
    }
    mval = (sxmlrpc_member_t *)calloc(n, sizeof(sxmlrpc_member_t));
    if (mval != NULL) {
      sxmlrpc_struct_t	tval;
      sxmlrpc_value_t	val;
      int		i = 0;

      mval[i].name = strdup("postcode");
      sxmlrpc_set_value_string(&mval[i].value, postcode);	i++;
      mval[i].name = strdup("address");
      sxmlrpc_set_value_string(&mval[i].value, address);	i++;
      if (office != NULL) {
	mval[i].name = strdup("office");
	sxmlrpc_set_value_string(&mval[i].value, office);	i++;
	free(office);
      }
      sxmlrpc_set_struct(&tval, n, mval);
      sxmlrpc_set_value_struct(&val, tval);
      sxmlrpc_set_param(param, val);
    }
    free(address);

    return 0;
  }

  return sxmlrpc_set_fault(param, CODE_NO_ADDRESS, MESG_NO_ADDRESS);

error:
  return sxmlrpc_set_fault(param, CODE_REQUEST_STRING_PARAM,
			   MESG_REQUEST_STRING_PARAM);
}

static int
lookup(conn, sql, postcode, address, name)
	sqlite3 *	conn;
	const char *	sql;
	const char *	postcode;
	char **		address;
	char **		name;
{
  sqlite3_stmt *	res;
  int			status;

  status = sqlite3_prepare(conn, sql, (int)strlen(sql), &res, NULL);
  if (status != SQLITE_OK) {
    return -1;
  }
  status = sqlite3_bind_text(res, 1, postcode, POSTCODE_LEN, SQLITE_TRANSIENT);
  if (status != SQLITE_OK) {
    status = -1;
    goto done;
  }
  switch (sqlite3_step(res)) {
    case SQLITE_ROW: {
	int	n;

	n = sqlite3_column_count(res);
	if (n >= 3) {
	  asprintf(address, "%s%s%s",
		   sqlite3_column_text(res, 0),
		   sqlite3_column_text(res, 1),
		   sqlite3_column_text(res, 2));
	  if (*address != NULL) {
	    status = 0;
	  }
	  if (n == 4 && name != NULL) {
	    *name = strdup(sqlite3_column_text(res, 3));
	  }
	}
      }
      break;
    case SQLITE_DONE:
      status = 1;
      break;
    default:
      status = -1;
      break;
  }

done:
  sqlite3_finalize(res);

  return status;
}

int
main(argc, argv)
	int	argc;
	char *	argv[];
{
  register int		ch;
  const char *		hostname  = HOSTNAME;
  const char *		servname  = SERVNAME;
  int			backlog	  = BACKLOG;
  sxmlrpc_boolean_t	daemonize = false;
  sxmlrpc_t *		sxRPC;

  /* parse options */
  while ((ch = getopt(argc, argv, "a:p:b:d:Dhv?")) != -1) {
    switch (ch) {
      case 'a': hostname  = optarg; break;
      case 'p': servname  = optarg; break;
      case 'b': backlog	  = atoi(optarg); break;
      case 'd': dbfile	  = optarg; break;
      case 'D': daemonize = true; break;
      case 'v': version();
      case 'h':
      default:
	usage();
    }
  }
  argc -= optind;
  argv += optind;

  sxRPC = sxmlrpc_new(hostname, servname, REQUEST_URI);
  if (sxRPC != NULL) {
    if (daemonize) {
      daemon(1, 0);
    }
    sxmlrpc_set_encoding(sxRPC, "euc-jp");
    sxmlrpc_server(sxRPC, backlog, postal);
    sxmlrpc_free(sxRPC);
  }

  return 0;
}
