/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * This is an HTTP-based server VFS layer.
 * It amounts to a kind of RPC interface to invoke some well-defined functions.
 * These functions may be applied to objects in a database, for example,
 * but the implementation details are inconsequential (at least in the
 * current version) as long as the required semantics are provided.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_vfsc.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

#include "local.h"
#include "http.h"

static const char *log_module_name = "vfs_vfsc";

static int vfsc_open(Vfs_handle *, char *name_context);
static int vfsc_close(Vfs_handle *handle);
static int vfsc_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int vfsc_get(Vfs_handle *handle, char *key, void **buffer,
					size_t *length);
static int vfsc_getsize(Vfs_handle *handle, char *key, size_t *length);
static int vfsc_put(Vfs_handle *handle, char *key, void *buffer,
					size_t length);
static int vfsc_delete(Vfs_handle *handle, char *key);
static int vfsc_exists(Vfs_handle *handle, char *key);
static int vfsc_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int vfsc_list(Vfs_handle *handle, int (*is_valid)(char *),
					 int (*compar)(const void *, const void *),
					 int (*add)(char *, char *, void ***), void ***keys);

static Vfs_switch vfsc_conf = {
  "vfs",
  vfsc_open,
  vfsc_close,
  vfsc_control,
  vfsc_get,
  vfsc_getsize,
  vfsc_put,
  vfsc_delete,
  vfsc_exists,
  vfsc_rename,
  vfsc_list
};

/*
 *
 */
typedef struct {
  char *naming_context;
  Http_connection_mode connection_mode;
  char **cookies;
} Handle;

/*
 * Invoke an operation using http/https.
 * EKWV are standard arguments provided at "open" time and needed for
 * all operations.
 * EKWV, if not NULL, are "extended" arguments; that is, arguments that are
 * specific to a particular operation.
 */
static int
invoke_cgi(Vfs_handle *handle, Http_method method, char *request, Kwv *ekwv,
		   char *body, char **store_reply)
{
  int i, reply_len, st, status_code;
  char *url, *reply;
  Ds *ds;
  Dsvec *v, *response_headers;
  Handle *h;
  Http_params *params;

  h = (Handle *) handle->h;

  v = dsvec_init(NULL, sizeof(Http_params));

  params = http_param(v, "DACS_JURISDICTION",
					  conf_val(CONF_JURISDICTION_NAME), NULL, 0);

  params = http_param(v, "DACS_VERSION", DACS_VERSION_NUMBER, NULL, 0);

  params = http_param(v, "ITEM_TYPE", NULL, NULL, 0);
  if (handle->sd->item_type != NULL)
	params->value = handle->sd->item_type;
  else
	params->value = "";

  params = http_param(v, "REQUEST", request, NULL, 0);

  if (handle->null_flag)
	params = http_param(v, "NULL_FLAG", "1", NULL, 0);
  if (handle->create_flag)
	params = http_param(v, "CREATE_FLAG", "1", NULL, 0);
  if (handle->delete_flag)
	params = http_param(v, "DELETE_FLAG", "1", NULL, 0);
  if (handle->append_flag)
	params = http_param(v, "APPEND_FLAG", "1", NULL, 0);
  if (handle->lock_flag != VFS_NOLOCK)
	params = http_param(v, "LOCK_FLAG", ds_xprintf("%d", handle->lock_flag),
						NULL, 0);

  for (i = 0; i < kwv_count(handle->kwv, NULL); i++)
	params = http_param(v, handle->kwv->pairs[i]->name,
						handle->kwv->pairs[i]->val, NULL, 0);

  if (ekwv != NULL) {
    for (i = 0; i < kwv_count(ekwv, NULL); i++)
	  params = http_param(v, ekwv->pairs[i]->name,
						  ekwv->pairs[i]->val, NULL, 0);
  }

  url = h->naming_context;

  /*
   * If the operation fails, -1 will be returned and REPLY will be set to point
   * to an error message.
   */
  response_headers = dsvec_init(NULL, sizeof(char *));
  reply_len = -1;
  ds = ds_set(NULL, body);
  st = http_invoke(url, method, h->connection_mode,
				   dsvec_len(v), (Http_params *) dsvec_base(v),
				   ds, h->cookies,
				   &reply, &reply_len, &status_code, response_headers);

  if (st == -1) {
	if (*reply != '\0')
	  handle->error_msg = reply;
	else
	  handle->error_msg = "HTTP operation failed";
	return(-1);
  }

  if (status_code < 200 || status_code > 299) {
	if (*reply != '\0')
	  handle->error_msg = reply;
	else
	  handle->error_msg
		= ds_xprintf("HTTP operation returned status code %d", status_code);
	return(-1);
  }

  *store_reply = reply;
  return(0);
}

static int
vfsc_open(Vfs_handle *handle, char *naming_context)
{
  Handle *h;

  if (handle->delete_flag) {
	/* XXX not implemented */
	handle->error_num = ENOSYS;
	handle->error_msg = strerror(ENOSYS);
	return(-1);
  }

  h = ALLOC(Handle);
  h->naming_context = strdup(naming_context);
  h->connection_mode = HTTP_SSL_URL_SCHEME;
  h->cookies = NULL;

  handle->h = (void *) h;

  return(0);
}

static int
vfsc_close(Vfs_handle *handle)
{

  return(0);
}

static int
vfsc_control(Vfs_handle *handle, Vfs_control_op op, va_list ap)
{
  Handle *h;

  h = (Handle *) handle->h;

  switch (op) {
  case VFS_SET_COOKIES:
	h->cookies = va_arg(ap, char **);
	break;

  default:
    log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));
    return(-1);
    /*NOTREACHED*/
  }

  return(0);
}

static int
vfsc_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int st;
  char *reply, *request;
  Handle *h;

  h = (Handle *) handle->h;

  if (key != NULL)
	request = ds_xprintf("get %s", key);
  else
	request = "get";

  st = invoke_cgi(handle, HTTP_GET_METHOD, request, NULL, NULL, &reply);
  if (st == -1)
	return(-1);

  if (length != NULL)
	*length = strlen(reply);
  *buffer = (void *) reply;

  return(0);
}

static int
vfsc_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st;
  char *reply, *request;
  unsigned long len;
  Handle *h;

  h = (Handle *) handle->h;

  if (key != NULL)
	request = ds_xprintf("getsize %s", key);
  else
	request = "getsize";

  st = invoke_cgi(handle, HTTP_GET_METHOD, request, NULL, NULL, &reply);
  if (st == -1)
	return(-1);

  if (strnum(reply, STRNUM_UL, &len) == -1) {
	handle->error_msg = "Size value was out-of-range";
	return(-1);
  }
  *length = (size_t) len;

  return(0);
}

static int
vfsc_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int st;
  char *reply, *request;
  Handle *h;
  Kwv *kwv;

  h = (Handle *) handle->h;

  if (key != NULL)
	request = ds_xprintf("put %s", key);
  else
	request = "put";

  kwv = kwv_init(10);
  kwv_add(kwv, "VALUE", (char *) buffer);
  st = invoke_cgi(handle, HTTP_POST_METHOD, request, kwv, NULL, &reply);

  if (st == -1)
	return(-1);

  if (strcaseeq(reply, "yes"))
	return(1);

  return(0);
}

static int
vfsc_delete(Vfs_handle *handle, char *key)
{
  int st;
  char *reply, *request;
  Handle *h;

  h = (Handle *) handle->h;

  if (key != NULL)
	request = ds_xprintf("delete %s", key);
  else
	request = "delete";

  st = invoke_cgi(handle, HTTP_POST_METHOD, request, NULL, NULL, &reply);
  if (st == -1)
	return(-1);

  if (strcaseeq(reply, "yes"))
	return(1);

  return(0);
}

static int
vfsc_exists(Vfs_handle *handle, char *key)
{
  int st;
  char *reply, *request;
  Handle *h;

  h = (Handle *) handle->h;

  if (key != NULL)
	request = ds_xprintf("exists %s", key);
  else
	request = "exists";

  st = invoke_cgi(handle, HTTP_GET_METHOD, request, NULL, NULL, &reply);
  if (st == -1)
	return(-1);

  if (strcaseeq(reply, "yes"))
	return(1);

  if (strcaseeq(reply, "no"))
	return(0);

  handle->error_msg = "Unrecognized reply";

  return(-1);
}

static int
vfsc_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  int st;
  char *reply, *request;
  Handle *h;
  Kwv *ekwv;

  h = (Handle *) handle->h;

  ekwv = kwv_add_nocopy(NULL, "VALUE", newkey);

  if (oldkey != NULL && newkey != NULL)
	request = ds_xprintf("rename %s %s", oldkey, newkey);
  else
	request = ds_xprintf("rename %s", newkey);
  st = invoke_cgi(handle, HTTP_POST_METHOD, request, ekwv, NULL, &reply);
  if (st == -1)
    return(-1);

  if (strcaseeq(reply, "yes"))
	return(1);

  return(0);
}

static int
vfsc_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{
  int n, st;
  char *p, *q, *reply, *request;
  Handle *h;

  h = (Handle *) handle->h;

  request = "list";
  st = invoke_cgi(handle, HTTP_GET_METHOD, request, NULL, NULL, &reply);
  if (st == -1)
    return(-1);

  n = 0;
  p = reply;
  while (*p != '\0') {
	if ((q = strchr(p, '\n')) != NULL)
	  *q++ = '\0';
	if (is_valid == NULL || is_valid(p)) {
	  if (add(handle->sd->naming_context, p, names) == 1)
		n++;
	}
	if (q == NULL)
	  break;
	p = q;
  }

  if (compar != NULL) {
	if (handle->list_sort == NULL)
	  qsort(names, n, sizeof(void **), compar);
	else
	  handle->list_sort(names, n, sizeof(void **), compar);
  }

  return(n);
}

Vfs_switch *
vfs_vfsc_init(char *store_name)
{

  return(&vfsc_conf);
}
