#include "unistd_.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ijs.h"
#include "ijs_client.h"

struct _IjsClientCtx {
  int fd_from;
  int child_pid;
  IjsSendChan send_chan;
  IjsRecvChan recv_chan;
  int version;
};

IjsClientCtx *
ijs_invoke_server (const char *server_cmd)
{
  IjsClientCtx *ctx;
  int fds_to[2], fds_from[2];
  int child_pid;
  char helo_buf[8] = IJS_HELO_STR;
  char resp_buf[8];
  const char exp_resp_buf[8] = IJS_RESP_STR;
  ijs_bool ok = TRUE;
  int nbytes;
  int version;

  if (ijs_exec_server(server_cmd, &fds_to[1], &fds_from[0], &child_pid) < 0)
    return NULL;

  ctx = (IjsClientCtx *)malloc (sizeof(IjsClientCtx));
  ctx->fd_from = fds_from[0];
  ctx->child_pid = child_pid;
  ijs_send_init (&ctx->send_chan, fds_to[1]);
  ijs_recv_init (&ctx->recv_chan, fds_from[0]);

  nbytes = write (ctx->send_chan.fd, helo_buf, sizeof(helo_buf));
  if (nbytes != sizeof(helo_buf))
    ok = FALSE;

  if (ok)
    {
      nbytes = read (ctx->recv_chan.fd, resp_buf, sizeof(resp_buf));
      if (nbytes != sizeof(resp_buf) ||
	  memcmp (resp_buf, exp_resp_buf, sizeof(resp_buf)))
	ok = FALSE;
    }

  /* exchange version information with server */
  if (ok)
    ok = ijs_client_begin_cmd (ctx, IJS_CMD_PING) >= 0;
  if (ok)
    ok = ijs_send_int (&ctx->send_chan, IJS_VERSION) >= 0;
  if (ok)
    ok = ijs_client_send_cmd_wait (ctx) >= 0;
  if (ok)
    ok = ijs_recv_int (&ctx->recv_chan, &version) >= 0;
  if (ok)
    {
      if (version > IJS_VERSION)
	version = IJS_VERSION;
      ctx->version = version;
    }

  if (!ok)
    {
      close (ctx->send_chan.fd);
      close (ctx->recv_chan.fd);
      free (ctx);
      ctx = NULL;
    }

  return ctx;
}

int
ijs_client_begin_cmd (IjsClientCtx *ctx, IjsCommand cmd)
{
  return ijs_send_begin (&ctx->send_chan, cmd);
}

int
ijs_client_send_int (IjsClientCtx *ctx, int val)
{
  return ijs_send_int (&ctx->send_chan, val);
}

int
ijs_client_send_cmd (IjsClientCtx *ctx)
{
  return ijs_send_buf (&ctx->send_chan);
}

/**
 * ijs_client_send_cmd_wait: Send command and wait for ack.
 * @ctx: IJS client context.
 *
 * Sends the command in the client context's buffer, and waits for ack.
 *
 * Return value: 0 on successful ack, otherwise negative.
 **/
int
ijs_client_send_cmd_wait (IjsClientCtx *ctx)
{
  int status;

  status = ijs_client_send_cmd (ctx);
  if (status >= 0)
    {
      status = ijs_recv_ack (&ctx->recv_chan);
    }
  return status;
}

/* This is the blocking version; it's not likely to be efficient */
int
ijs_client_send_data_wait (IjsClientCtx *ctx, IjsJobId job_id,
			   const char *buf, int size)
{
  int status;

  ijs_client_begin_cmd (ctx, IJS_CMD_SEND_DATA_BLOCK);
  ijs_send_int (&ctx->send_chan, job_id);
  ijs_send_int (&ctx->send_chan, size);
  status = ijs_client_send_cmd (ctx);
  if (status)
    return status;
  status = write (ctx->send_chan.fd, buf, size);
  if (status != size)
    return IJS_EIO;
  status = ijs_recv_ack (&ctx->recv_chan);
  return status;
}

int
ijs_client_open (IjsClientCtx *ctx)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_OPEN);
  return ijs_client_send_cmd_wait (ctx);
}

int
ijs_client_close (IjsClientCtx *ctx)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_CLOSE);
  return ijs_client_send_cmd_wait (ctx);
}

int
ijs_client_begin_job (IjsClientCtx *ctx, IjsJobId job_id)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_BEGIN_JOB);
  ijs_send_int (&ctx->send_chan, job_id);
  return ijs_client_send_cmd_wait (ctx);
}

int
ijs_client_end_job (IjsClientCtx *ctx, IjsJobId job_id)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_END_JOB);
  ijs_send_int (&ctx->send_chan, job_id);
  return ijs_client_send_cmd_wait (ctx);
}

/**
 * Return value: data block size if nonnegative, or error code if
 * negative.
 **/
int
ijs_client_list_params (IjsClientCtx *ctx, IjsJobId job_id,
		      char *value, int value_size)
{
  int status;

  ijs_client_begin_cmd (ctx, IJS_CMD_LIST_PARAMS);
  ijs_send_int (&ctx->send_chan, job_id);
  status = ijs_client_send_cmd (ctx);
  if (status)
    return status;
  status = ijs_recv_ack (&ctx->recv_chan);
  if (status)
    return status;
  status = ijs_recv_block (&ctx->recv_chan, value, value_size);
  return status;
}

/**
 * Return value: data block size if nonnegative, or error code if
 * negative.
 **/
int
ijs_client_enum_param (IjsClientCtx *ctx, IjsJobId job_id,
		      const char *key,
		      char *value, int value_size)
{
  int key_size = strlen (key);
  int status;

  ijs_client_begin_cmd (ctx, IJS_CMD_ENUM_PARAM);
  ijs_send_int (&ctx->send_chan, job_id);
  status = ijs_send_block (&ctx->send_chan, key, key_size + 1);
  if (status < 0)
    return IJS_EIO;
  status = ijs_client_send_cmd (ctx);
  if (status)
    return status;
  status = ijs_recv_ack (&ctx->recv_chan);
  if (status)
    return status;
  status = ijs_recv_block (&ctx->recv_chan, value, value_size);
  return status;
}

int
ijs_client_set_param (IjsClientCtx *ctx, IjsJobId job_id,
		      const char *key,
		      const char *value, int value_size)
{
  int key_size = strlen (key);
  int status;

  ijs_client_begin_cmd (ctx, IJS_CMD_SET_PARAM);
  ijs_send_int (&ctx->send_chan, job_id);
  ijs_send_int (&ctx->send_chan, key_size + 1 + value_size);
  status = ijs_send_block (&ctx->send_chan, key, key_size+1);
  if (status)
    return status;
  status = ijs_send_block (&ctx->send_chan, value, value_size);
  if (status)
    return status;
  status = ijs_client_send_cmd (ctx);
  if (status)
    return status;
  status = ijs_recv_ack (&ctx->recv_chan);
  return status;
}

/**
 * Return value: data block size if nonnegative, or error code if
 * negative.
 **/
int
ijs_client_get_param (IjsClientCtx *ctx, IjsJobId job_id,
		      const char *key,
		      char *value, int value_size)
{
  int key_size = strlen (key);
  int status;

  ijs_client_begin_cmd (ctx, IJS_CMD_GET_PARAM);
  ijs_send_int (&ctx->send_chan, job_id);
  status = ijs_send_block (&ctx->send_chan, key, key_size + 1);
  if (status < 0)
    return IJS_EIO;
  status = ijs_client_send_cmd (ctx);
  if (status)
    return status;
  status = ijs_recv_ack (&ctx->recv_chan);
  if (status)
    return status;
  status = ijs_recv_block (&ctx->recv_chan, value, value_size);
  return status;
}

int
ijs_client_begin_page (IjsClientCtx *ctx, IjsJobId job_id)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_BEGIN_PAGE);
  ijs_send_int (&ctx->send_chan, job_id);
  return ijs_client_send_cmd_wait (ctx);
}

int
ijs_client_end_page (IjsClientCtx *ctx, IjsJobId job_id)
{
  ijs_client_begin_cmd (ctx, IJS_CMD_END_PAGE);
  ijs_send_int (&ctx->send_chan, job_id);
  return ijs_client_send_cmd_wait (ctx);
}

int
ijs_client_get_version (IjsClientCtx *ctx)
{
  return ctx->version;
}
