/********************************************************************\
 * qof-util.h -- QOF utility functions                              *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
\********************************************************************/

/** @addtogroup Utilities
    @{ */
/** @file qof-util.h 
    @brief QOF utility functions
    @author Copyright (C) 1997 Robin D. Clark <rclark@cs.hmc.edu>
    @author Copyright (C) 2000 Bill Gribble <grib@billgribble.com>
    @author Copyright (C) 1997-2002,2004 Linas Vepstas <linas@linas.org>
    @author Copyright 2006  Neil Williams  <linux@codehelp.co.uk>
*/

#ifndef QOF_UTIL_H
#define QOF_UTIL_H

#include <stddef.h>
#include "qof.h"
#include "qoflog.h"
#include "qofutil.h"
#include "qofbackend.h"
#include "qofclass.h"
#include "qofbook.h"
#include "qofinstance.h"

/** Do not use these for printf, only scanf */
#if HAVE_SCANF_LLD
# define QOF_SCANF_LLD "%lld"
#else
# define QOF_SCANF_LLD "%qd"
#endif

#define QOF_MOD_UTIL "qof-utilities"

/** \name typedef enum as string macros
@{
*/
#define ENUM_BODY(name, value)           \
    name value,

#define AS_STRING_CASE(name, value)      \
    case name: { return #name; }

#define FROM_STRING_CASE(name, value)    \
    if (strcmp(str, #name) == 0) {       \
        return name;  }

#define DEFINE_ENUM(name, list)          \
    typedef enum {                       \
        list(ENUM_BODY)                  \
    }name;

#define AS_STRING_DEC(name, list)        \
    const gchar* name##asString(name n);

#define AS_STRING_FUNC(name, list)        \
    const gchar* name##asString(name n) { \
        switch (n) {                      \
            list(AS_STRING_CASE)          \
            default: return "";  } }

#define FROM_STRING_DEC(name, list)      \
    name name##fromString                \
    (const gchar* str);

#define FROM_STRING_FUNC(name, list)     \
    name name##fromString                \
    (const gchar* str) {                 \
    if(str == NULL) { return 0; }        \
        list(FROM_STRING_CASE)           \
        return 0;  }

/** @} */

/** \name enum as string with no typedef
@{

  Similar but used when the enum is NOT a typedef
  Make sure you use the DEFINE_ENUM_NON_TYPEDEF macro.

 You can precede the FROM_STRING_FUNC_NON_TYPEDEF 
 and AS_STRING_FUNC_NON_TYPEDEF macros with the 
 keyword static if appropriate.
  
 ENUM_BODY is used in both types.
 */

#define DEFINE_ENUM_NON_TYPEDEF(name, list)   \
    enum name {                               \
        list(ENUM_BODY)                       \
    };

#define FROM_STRING_DEC_NON_TYPEDEF(name, list)   \
   void name##fromString                          \
   (const gchar* str, enum name *type);

#define FROM_STRING_CASE_NON_TYPEDEF(name, value) \
   if (strcmp(str, #name) == 0) { *type = name; }

#define FROM_STRING_FUNC_NON_TYPEDEF(name, list)  \
   void name##fromString                          \
   (const gchar* str, enum name *type) {          \
   if(str == NULL) { return; }                    \
    list(FROM_STRING_CASE_NON_TYPEDEF) }

#define AS_STRING_DEC_NON_TYPEDEF(name, list)     \
   const gchar* name##asString(enum name n);

#define AS_STRING_FUNC_NON_TYPEDEF(name, list)    \
   const gchar* name##asString(enum name n) {     \
       switch (n) {                               \
           list(AS_STRING_CASE_NON_TYPEDEF)       \
           default: return ""; } }

#define AS_STRING_CASE_NON_TYPEDEF(name, value)   \
   case name: { return #name; }

/** @} */

/** @name Convenience wrappers
   @{
*/
   
/** \brief Initialise the Query Object Framework 

Use in place of separate init functions (like guid_init()
and qof_query_init() etc.) to protect against future changes.
*/
void qof_init (void);

/** \brief Safely close down the Query Object Framework 

Use in place of separate close / shutdown functions 
(like guid_shutdown(), qof_query_shutdown() etc.) to protect
against future changes.
*/
void qof_close (void);

/** @} */

/* **** Prototypes *********************************************/

/** The safe_strcmp compares strings da and db the same way that strcmp()
 does, except that either may be null.  This routine assumes that
 a non-null string is always greater than a null string.
 
 @param da string 1.
 @param db string 2.
 
 @return If da == NULL && db != NULL, returns -1.
         If da != NULL && db == NULL, returns +1.
         If da != NULL && db != NULL, returns the result of 
                   strcmp(da, db).
         If da == NULL && db == NULL, returns 0. 
*/
gint safe_strcmp (const gchar * da, const gchar * db);

/** case sensitive comparison of strings da and db - either
may be NULL. A non-NULL string is greater than a NULL string.
 
 @param da string 1.
 @param db string 2.
 
 @return If da == NULL && db != NULL, returns -1.
         If da != NULL && db == NULL, returns +1.
         If da != NULL && db != NULL, returns the result of 
                   strcmp(da, db).
         If da == NULL && db == NULL, returns 0. 
*/
gint safe_strcasecmp (const gchar * da, const gchar * db);

/** The null_strcmp compares strings a and b the same way that strcmp()
 * does, except that either may be null.  This routine assumes that
 * a null string is equal to the empty string.
 */
gint null_strcmp (const gchar * da, const gchar * db);

/** Search for str2 in first nchar chars of str1, ignore case. Return
 * pointer to first match, or null. These are just like that strnstr
 * and the strstr functions, except that they ignore the case. */
extern gchar *strncasestr(const guchar *str1, const guchar *str2, 
	size_t len);
extern gchar *strcasestr(const gchar *str1, const gchar *str2);

/** The ultostr() subroutine is the inverse of strtoul(). It accepts a
 * number and prints it in the indicated base.  The returned string
 * should be g_freed when done.  */
gchar * ultostr (gulong val, gint base);

/** Returns true if string s is a number, possibly surrounded by
 * whitespace. */
gboolean gnc_strisnum(const guchar *s);

#ifndef HAVE_STPCPY
#define stpcpy g_stpcpy
#endif

/** Return NULL if the field is whitespace (blank, tab, formfeed etc.)  
 *  Else return pointer to first non-whitespace character. 
 */
const gchar * qof_util_whitespace_filter (const gchar * val);

/** Return integer 1 if the string starts with 't' or 'T' or 
 *  contains the word 'true' or 'TRUE'; if string is a number, 
 *  return that number. (Leading whitespace is ignored). */
gint qof_util_bool_to_int (const gchar * val);

/** \brief Converts a parameter to a printable string.

The returned string must be freed by the caller.
*/
gchar* qof_util_param_as_string(QofEntity *ent, QofParam *param);

/** The QOF String Cache:
 *
 * Many strings used throughout QOF and QOF applications are likely to
 * be duplicated.
 *
 * QOF provides a reference counted cache system for the strings, which
 * shares strings whenever possible.
 *
 * Use qof_util_string_cache_insert to insert a string into the cache (it
 * will return a pointer to the cached string).  Basically you should
 * use this instead of g_strdup.
 *
 * Use qof_util_string_cache_remove (giving it a pointer to a cached
 * string) if the string is unused.  If this is the last reference to
 * the string it will be removed from the cache, otherwise it will
 * just decrement the reference count.  Basically you should use this
 * instead of g_free.
 *
 * Just in case it's not clear: The remove function must NOT be called
 * for the string you passed INTO the insert function.  It must be
 * called for the _cached_ string that is _returned_ by the insert
 * function.
 *
 * Note that all the work is done when inserting or removing.  Once
 * cached the strings are just plain C strings.
 *
 * The string cache is demand-created on first use.
 *
 **/
/** Destroy the qof_util_string_cache */
void qof_util_string_cache_destroy (void);

/** You can use this function as a destroy notifier for a GHashTable
   that uses common strings as keys (or values, for that matter.)
*/
void qof_util_string_cache_remove(gconstpointer key);

/** You can use this function with g_hash_table_insert(), for the key
   (or value), as long as you use the destroy notifier above.
*/
gpointer qof_util_string_cache_insert(gconstpointer key);

#define CACHE_INSERT(str) qof_util_string_cache_insert((gconstpointer)(str))
#define CACHE_REMOVE(str) qof_util_string_cache_remove((str))

/* Replace cached string currently in 'dst' with string in 'src'.
 * Typical usage:
 *     void foo_set_name(Foo *f, const char *str) {
 *        CACHE_REPLACE(f->name, str);
 *     }
 * It avoids unnecessary ejection by doing INSERT before REMOVE.
*/
#define CACHE_REPLACE(dst, src) do {          \
        gpointer tmp = CACHE_INSERT((src));   \
        CACHE_REMOVE((dst));                  \
        (dst) = tmp;                          \
    } while (0)

#define QOF_CACHE_NEW(void) qof_util_string_cache_insert("")

/** begin_edit helper
 *
 * @param  inst: an instance of QofInstance
 *
 * The caller should use this macro first and then perform any other operations.
 
 Uses newly created functions to allow the macro to be used
 when QOF is linked as a library. qofbackend-p.h is a private header.
 */

#define QOF_BEGIN_EDIT(inst)                                        \
  if (!(inst)) return;                                              \
                                                                    \
  (inst)->editlevel++;                                              \
  if (1 < (inst)->editlevel) return;                                \
                                                                    \
  if (0 >= (inst)->editlevel)                                       \
  {                                                                 \
    PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
    (inst)->editlevel = 1;                                          \
  }                                                                 \
  ENTER ("(inst=%p)", (inst));                                      \
                                                                    \
  /* See if there's a backend.  If there is, invoke it. */          \
  {                                                                 \
    QofBackend * be;                                                \
    be = qof_book_get_backend ((inst)->book);                       \
      if (be && qof_backend_begin_exists(be)) {                     \
         qof_backend_run_begin(be, (inst));                         \
    } else {                                                        \
      /* We tried and failed to start transaction! */               \
      (inst)->dirty = TRUE;                                         \
    }                                                               \
  }                                                                 \
  LEAVE (" ");

/** \brief function version of QOF_BEGIN_EDIT

The macro cannot be used in a function that returns a value,
this function can be used instead.
*/
gboolean qof_begin_edit(QofInstance *inst);

/**
 * commit_edit helpers
 *
 * The caller should call PART1 as the first thing, then 
 * perform any local operations prior to calling the backend.
 * Then call PART2.  
 */

/**
 * part1 -- deal with the editlevel
 * 
 * @param inst: an instance of QofInstance
 */

#define QOF_COMMIT_EDIT_PART1(inst) {                            \
  if (!(inst)) return;                                           \
                                                                 \
  (inst)->editlevel--;                                           \
  if (0 < (inst)->editlevel) return;                             \
                                                                 \
  /* The pricedb suffers from delayed update...     */          \
  /* This may be setting a bad precedent for other types, I fear. */ \
  /* Other types probably really should handle begin like this. */ \
  if ((0 == (inst)->editlevel) && (inst)->dirty)                   \
  {                                                              \
    QofBackend * be;                                             \
    be = qof_book_get_backend ((inst)->book);                    \
    if (be && qof_backend_commit_exists(be)) {                   \
        qof_backend_run_commit(be, (inst));                      \
    }                                                            \
    (inst)->editlevel = 0;                                       \
  }                                                              \
  if (0 > (inst)->editlevel)                                     \
  {                                                              \
    PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
    (inst)->editlevel = 0;                                       \
  }                                                              \
  ENTER ("(inst=%p) dirty=%d do-free=%d",                        \
            (inst), (inst)->dirty, (inst)->do_free);             \
}

/** \brief function version of QOF_COMMIT_EDIT_PART1

The macro cannot be used in a function that returns a value,
this function can be used instead. Only Part1 is implemented.
*/
gboolean qof_commit_edit(QofInstance *inst);

/**
 * part2 -- deal with the backend
 * 
 * @param inst: an instance of QofInstance
 * @param on_error: a function called if there is a backend error.
 *                void (*on_error)(inst, QofBackendError)
 * @param on_done: a function called after the commit is completed 
 *                successfully for an object which remained valid.
 *                void (*on_done)(inst)
 * @param on_free: a function called if the commit succeeded and the instance
 *                 is to be freed. 
 *                void (*on_free)(inst)
 * 
 * Note that only *one* callback will be called (or zero, if that
 * callback is NULL).  In particular, 'on_done' will not be called for
 * an object which is to be freed.
 *
 * Returns TRUE, if the commit succeeded, FALSE otherwise.
 */
gboolean
qof_commit_edit_part2(QofInstance *inst, 
                      void (*on_error)(QofInstance *, QofBackendError), 
                      void (*on_done)(QofInstance *), 
                      void (*on_free)(QofInstance *));

/** \brief Macro version of ::qof_commit_edit_part2

\note This macro changes programme flow if the instance is freed.
*/
#define QOF_COMMIT_EDIT_PART2(inst,on_error,on_done,on_free) {   \
  QofBackend * be;                                               \
                                                                 \
  /* See if there's a backend.  If there is, invoke it. */       \
  be = qof_book_get_backend ((inst)->book);                      \
  if (be && qof_backend_commit_exists(be))                       \
  {                                                              \
    QofBackendError errcode;                                     \
                                                                 \
    /* clear errors */                                           \
    do {                                                         \
      errcode = qof_backend_get_error (be);                      \
    } while (ERR_BACKEND_NO_ERR != errcode);                     \
                                                                 \
    qof_backend_run_commit(be, (inst));                          \
    errcode = qof_backend_get_error (be);                        \
    if (ERR_BACKEND_NO_ERR != errcode)                           \
    {                                                            \
      /* XXX Should perform a rollback here */                   \
      (inst)->do_free = FALSE;                                   \
                                                                 \
      /* Push error back onto the stack */                       \
      qof_backend_set_error (be, errcode);                       \
      (on_error)((inst), errcode);                               \
    }                                                            \
    /* XXX the backend commit code should clear dirty!! */       \
    (inst)->dirty = FALSE;                                       \
  }                                                              \
  (on_done)(inst);                                               \
                                                                 \
  LEAVE ("inst=%p, dirty=%d do-free=%d",                         \
            (inst), (inst)->dirty, (inst)->do_free);             \
  if ((inst)->do_free) {                                         \
     (on_free)(inst);                                            \
     return;                                                     \
  }                                                              \
}
    
#endif /* QOF_UTIL_H */
/** @} */
