/*
 * FILE:
 * gncRpc_server.c
 *
 * FUNCTION:
 * Implements the Gnucash RPC server stubs, as generated by rpcgen.
 * Note: The function prototypes here must match those created by
 * rpcgen -- if any function prototypes in gncRpc.x are changed, this
 * file must change as well.
 *
 * HISTORY:
 * Created By:	Derek Atkins <warlord@MIT.EDU>
 * Copyright (c) 2001, Derek Atkins
 */

#include "gncRpc.h"
#include "RpcServerP.h"
#include "RpcUtils.h"
#include "TransactionP.h"
#include "AccountP.h"
#include "gnc-engine-util.h"

static short module = MOD_BACKEND;

static bool_t gncrpc_get_state (struct svc_req *req, TXPRT **xprt,
				GncRpcSvc **cl)
{
  TXPRT *x;
  bool_t retval;

  retval = svc_getargs (req->rq_xprt, (xdrproc_t) xprt_thrd_getargs_hook,
			(caddr_t) &x);
  if (!retval)
    return !retval;

  if (xprt)
    *xprt = x;
  if (cl) {
    RPCSock *s = (RPCSock *) TXPRT_GETSOCK (x);
    *cl = (GncRpcSvc *) RpcGetData (s);
  }

  return retval;
}

bool_t
gncrpc_version_1_svc(void *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;

	*result = GNCRPC_PROTOCOL_VERSION;
	return retval;
}

bool_t
gncrpc_book_begin_1_svc(gncrpc_book_begin_args *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;
	int res = ERR_BACKEND_SERVER_ERR;

	ENTER ("id=\"%s\"", argp->book_id ? argp->book_id : "");

	do {
	  gboolean ret;

	  retval = gncrpc_get_state (rqstp, NULL, &cl);
	  if (!retval)
	    break;

	  ret = gnc_book_begin (cl->book, argp->book_id, argp->ignore_lock,
				argp->create);
	  PINFO ("ret == %s", (ret == TRUE ? "true" : "false"));
	  res = gnc_book_pop_error (cl->book);
	} while (0);

	*result = res;
	LEAVE ("done %d", res);
	return retval;
}

bool_t
gncrpc_book_load_1_svc(char *argp, gncrpc_book_load_ret *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;
	gboolean ret;

	ENTER ("ok");
	memset (result, 0, sizeof (*result));

	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  result->error = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	ret = gnc_book_load (cl->book);
	if (!ret) {
	  result->error = gnc_book_pop_error (cl->book);
	} else {
	  result->commodities =
	    rpcend_build_gnccommoditylist (gnc_book_get_commodity_table 
					   (cl->book),
					   TRUE);
	  result->acctlist =
	    rpcend_build_gncacctlist (gnc_book_get_group (cl->book));
	}

	LEAVE ("res = %d", result->error);
	return retval;
}

bool_t
gncrpc_book_end_1_svc(char *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;

	ENTER ("ok");
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  *result = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}
	gnc_book_end (cl->book);
	*result = gnc_book_pop_error (cl->book);

	LEAVE ("res = %d", *result);
	return retval;
}

bool_t
gncrpc_account_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Account *acc;
	GUID *guid = (GUID *) &(argp->guid);

	ENTER ("ok");
	acc = xaccAccountLookup (guid);
	if (!acc) {
	  *result = -1;		/* XXX Deal with new accounts */
	  LEAVE ("no acc");
	  return retval;
	}

	xaccAccountBeginEdit (acc);
	*result = 0;		/* XXX book_error */

	LEAVE ("acct=%p (%s)", acc, acc ? acc->accountName : "");
	return retval;
}

bool_t
gncrpc_account_commit_edit_1_svc(gncrpc_commit_acct_args *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Account *acc;
	AccountGroup *ag;
	gnc_commodity_table *ct;
	GUID *guid = (GUID *) &(argp->acct.guid);
	GncRpcSvc *cl;
	int added;

	ENTER ("vers=%d", argp->acct.vers);
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  *result = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	ag = gnc_book_get_group (cl->book);
	ct = gnc_book_get_commodity_table (cl->book);

	/* Load the commodity, in case it changed */
#ifdef GNCACCT_COMMODITY
	rpcend_load_gnccommodity (ct, argp->commodity);
#else
	rpcend_load_gnccommodity (ct, argp->currency);
	rpcend_load_gnccommodity (ct, argp->security);
#endif

	/* Now add the account */
	added = rpcend_do_add_acct (ag, &(argp->acct), ct);

	acc = xaccAccountLookup (guid);
	if (!acc) {
	  *result = gnc_book_pop_error (cl->book);
	  if (*result == 0)
	    *result = ERR_RPC_FAILED;
	  LEAVE ("no acc");
	  return retval;
	}

	PINFO ("commiting acct..");
	xaccAccountCommitEdit (acc);

	LEAVE ("vers=%d, added=%d (%s)", acc->version, added,
	       acc ? acc->accountName : "");
	*result = added < 0 ? 0 : ERR_RPC_NOT_ADDED; /* XXX book error? */
	
	return retval;
}

bool_t
gncrpc_account_rollback_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Account *acc;
	GUID *guid = (GUID *) &(argp->guid);
	GncRpcSvc *cl;

	ENTER ("ok");
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  *result = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	acc = xaccAccountLookup (guid);
	if (!acc) {
	  *result = ERR_RPC_FAILED;
	  LEAVE ("no acc");
	  return retval;
	}

	/* XXX There is no account rollback.  Just commit it without
	 * any changes */
	xaccAccountCommitEdit (acc);
	*result = gnc_book_pop_error (cl->book);

	LEAVE ("acc=%p (%s)", acc, acc ? acc->accountName : "");
	return retval;
}

bool_t
gncrpc_txn_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Transaction *txn;
	GUID *guid = (GUID *) &(argp->guid);
	
	ENTER ("ok");
	txn = xaccTransLookup (guid);
	if (!txn) {
	  *result = -1;		/* XXX: deal with 'new' transaction */
	  LEAVE ("no txn");
	  return retval;
	}

	xaccTransBeginEdit (txn);
	*result = 0;		/* XXX: book error */

	LEAVE ("ok");
	return retval;
}

bool_t
gncrpc_txn_commit_edit_1_svc(gncrpc_commit_txn_args *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Transaction *txn;
	gnc_commodity_table *ct;
	GUID *guid = (GUID *) &(argp->new.guid);
	GncRpcSvc *cl;
	int added;

	ENTER ("vers=%d", argp->new.vers);
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  *result = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	ct = gnc_book_get_commodity_table (cl->book);
	added = rpcend_do_add_txn (&(argp->new), ct);

	txn = xaccTransLookup (guid);
	if (!txn) {
	  *result = ERR_RPC_FAILED;
	  LEAVE ("no txn!");
	  return retval;
	}
	PINFO ("Committing txn..");
	xaccTransCommitEdit (txn);
	LEAVE ("ok, added=%d, vers=%d", added, txn->version);
	*result = added < 0 ? 0 : ERR_RPC_NOT_ADDED; /* XXX book error? */

	return retval;
}

bool_t
gncrpc_txn_rollback_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	Transaction *txn;
	GUID *guid = (GUID *) &(argp->guid);

	ENTER ("ok");
	txn = xaccTransLookup (guid);
	if (!txn) {
	  *result = ERR_RPC_FAILED;
	  LEAVE ("no txn");
	  return retval;
	}

	xaccTransRollbackEdit (txn);
	*result = 0;		/* XXX book error? */

	LEAVE ("ok");
	return retval;
}

bool_t
gncrpc_run_query_1_svc(gncrpc_query_args *argp, gncrpc_query_ret *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	gncQuery thisq;
	Query *q = (Query *)&thisq;
	GList *txnlist;
	GncRpcSvc *cl;
	AccountGroup *ag = NULL;

	ENTER ("parent=%p, q=%p", argp->group_parent_guid, argp->query);

	memset (result, 0, sizeof (*result));

	/* Find state */
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  result->error = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("Bad state");
	  return retval;
	}

	/* Figure out the query account group */
	if (argp->group_parent_guid) {
	  Account *acc = xaccAccountLookup ((GUID *)argp->group_parent_guid);
	  if (acc)
	    ag = xaccAccountGetChildren (acc);
	}
	if (!ag)
	  ag = gnc_book_get_group (cl->book);

	/* Setup query */
	argp->query->acct_group = (int *)ag;
	rpcend_parse_gncquery (argp->query, q);
	argp->query->acct_group = NULL;

	/* Run Query */
	txnlist = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);

	/* Setup return value */
	result->txnlist = rpcend_build_gncverslist_txn (txnlist, TRUE);

	/* Free the txnlist */
	g_list_free (txnlist);

	/* Reset argument/query structure */
	g_list_free ((GList *)(((gncQuery *)q)->split_list));
	rpcend_free_query (q);

	LEAVE ("query done");
	return retval;
}

bool_t
gncrpc_sync1_1_svc(gncrpc_sync1_args *argp, gncrpc_sync1_ret *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;
	AccountGroup *ag = NULL;
	gnc_commodity_table *ct;
	gnc_vers_list *this, *next, *new, **endnew, *old, **endold;
	gnc_acctlist *acctlist = NULL;
	gnc_txnlist *txnlist = NULL;

	ENTER ("ok");

	memset (result, 0, sizeof (*result));
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  result->error = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	ct = gnc_book_get_commodity_table (cl->book);
	ag = gnc_book_get_group (cl->book);
	rpcend_load_commoditylist (ct, argp->commodities);

	/* Just send back the whole commodity list.  It wont be too
	 * big, right????  Otherwise, I can implement an N^2 algorithm
	 * to figure out which commodities we need to send back...
	 */
	result->commodities = rpcend_build_gnccommoditylist (ct, TRUE);

	/* Walk the acctlist and txnlist; for each one, if the client
	 * is newer, ask them to send it; if the server is newer, grab
	 * the new version
	 */
	new = NULL;
	endnew = &new;
	old = NULL;
	endold = &old;

	for (this = argp->acctlist; this; this = next) {
	  GUID *guid = (GUID *)(this->guid);
	  Account *acc = xaccAccountLookup (guid);

	  next = this->next;

	  if (!acc || acc->version < this->vers) {
	    /* Tell the client to send this account */
	    *endnew = this;
	    endnew = &(this->next);
	  } else {
	    /* Maybe send this account? */
	    if (acc->version < this->vers) {
	      gnc_acctlist *newaccl = malloc (sizeof (*newaccl));
	      gncAccount *newacc = malloc (sizeof (*newacc));
	      rpcend_build_gncacct (newacc, acc);
	      newaccl->acct = newacc;
	      newaccl->next = acctlist;
	      acctlist = newaccl;
	    }
	    *endold = this;
	    endold = &(this->next);
	  }
	} /* for */

	/* XXX Should we figure out if there are new accounts and send
	 * them?
	 */

	/* Be sure to NULL-terminate these lists! */
	*endold = NULL;
	*endnew = NULL;

	/* Reset the args and set the reply */
	result->send_acctlist = new;
	result->acctlist = acctlist;
	argp->acctlist = old;

	/* Do a similar thing for transactions */
	new = NULL;
	endnew = &new;
	old = NULL;
	endold = &old;

	for (this = argp->txnlist; this; this = next) {
	  GUID *guid = (GUID *)(this->guid);
	  Transaction *txn = xaccTransLookup (guid);

	  next = this->next;

	  if (!txn || txn->version < this->vers) {
	    /* Tell the client to send this account */
	    *endnew = this;
	    endnew = &(this->next);
	  } else {
	    /* Maybe send this txn? */
	    if (txn->version < this->vers) {
	      gnc_txnlist *newtxnl = malloc (sizeof (*newtxnl));
	      gncTransaction *newtxn = malloc (sizeof (*newtxn));
	      rpcend_build_gnctxn (newtxn, txn);
	      newtxnl->txn = newtxn;
	      newtxnl->next = txnlist;
	      txnlist = newtxnl;
	    }
	    *endold = this;
	    endold = &(this->next);
	  }
	} /* for */

	/* Be sure to NULL-terminate these lists! */
	*endold = NULL;
	*endnew = NULL;

	/* Reset the args and set the reply */
	result->send_txnlist = new;
	result->txnlist = txnlist;
	argp->txnlist = old;
	LEAVE ("ok");

	return retval;
}

bool_t
gncrpc_sync2_1_svc(gncrpc_sync2_args *argp, int *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;
	AccountGroup *ag = NULL;
	gnc_acctlist *acctlist = NULL;
	gnc_txnlist *txnlist = NULL;
	gnc_commodity_table *ct;

	ENTER ("ok");

	*result = 0;
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  *result = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("bad state");
	  return retval;
	}

	ag = gnc_book_get_group (cl->book);
	ct = gnc_book_get_commodity_table (cl->book);

	for (acctlist = argp->acctlist; acctlist; acctlist = acctlist->next) {
	  rpcend_do_add_acct (ag, acctlist->acct, ct);
	}

	for (txnlist = argp->txnlist; txnlist; txnlist = txnlist->next) {
	  rpcend_do_add_txn (txnlist->txn, ct);
	}

	gnc_book_save (cl->book);
	*result = gnc_book_pop_error (cl->book);
	/* XXX: clear results if error! */

	LEAVE ("ok");
	return retval;
}

bool_t
gncrpc_get_txns_1_svc(gncrpc_get_txns_args *argp, gncrpc_get_txns_ret *result, struct svc_req *rqstp)
{
	bool_t retval = TRUE;
	GncRpcSvc *cl;
	AccountGroup *ag;

	ENTER ("get txns");

	memset (result, 0, sizeof (*result));
	retval = gncrpc_get_state (rqstp, NULL, &cl);
	if (!retval) {
	  result->error = ERR_BACKEND_SERVER_ERR;
	  LEAVE ("Bad state");
	  return retval;
	}

	ag = gnc_book_get_group (cl->book);
	result->txnlist = rpcend_build_gnctxnlist_list (ag, argp->guids);

	LEAVE ("ok");
	return retval;
}

int
gncrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
{
	xdr_free (xdr_result, result);

	/*
	 * Insert additional freeing code here, if needed
	 */

	return 1;
}
