/*
 * Copyright 2001-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

#ifndef __NEO_HDF_H_
#define __NEO_HDF_H_ 1

__BEGIN_DECLS

#include <stdio.h>
#include "util/neo_err.h"
#include "util/neo_hash.h"

#define FORCE_HASH_AT 10

typedef struct _hdf HDF;

/* HDFFILELOAD is a callback function to intercept file load requests and
 * provide templates via another mechanism.  This way you can load templates
 * that you compiled-into your binary, from in-memory caches, or from a
 * zip file, etc.  The HDF is provided so you can choose to use the 
 * hdf_search_path function to find the file.  contents should return
 * a full malloc copy of the contents of the file, which the parser will
 * own and free.  Use hdf_register_fileload to set this function for
 * your top level HDF node.
 * NOTE: Technically, we shouldn't need a separate copy for each parse, but 
 * using the separate copy makes this equivalent to the CSFILELOAD function.  We
 * can change this if we really want to save that copy at the expense of 
 * slightly more complicated code. */
typedef NEOERR* (*HDFFILELOAD)(void *ctx, HDF *hdf, const char *filename,
                              char **contents);

typedef struct _attr
{
  char *key;
  char *value;
  struct _attr *next;
} HDF_ATTR;

struct _hdf
{
  int link;
  int alloc_value;
  char *name;
  int name_len;
  char *value;
  struct _attr *attr;
  struct _hdf *top;
  struct _hdf *next;
  struct _hdf *child;

  /* the following fields are used to implement a cache */
  struct _hdf *last_hp;
  struct _hdf *last_hs;

  /* the following HASH is used when we reach more than FORCE_HASH_AT
   * elements */
  NE_HASH *hash;
  /* When using the HASH, we need to know where to append new children */
  struct _hdf *last_child;

  /* Should only be set on the head node, used to override the default file
   * load method */
  void *fileload_ctx;
  HDFFILELOAD fileload;
};

/*
 * Function: hdf_init - Initialize an HDF data set
 * Description: hdf_init initializes an HDF data set and returns the
 *              pointer to the top node in the data set. 
 * Input: hdf - pointer to an HDF pointer
 * Output: hdf - allocated hdf node
 * Returns: NERR_NOMEM - unable to allocate memory for dataset
 */
NEOERR* hdf_init (HDF **hdf);

/*
 * Function: hdf_destroy - deallocate an HDF data set
 * Description: hdf_destroy is used to deallocate all memory associated
 *              with an hdf data set.  Although you can pass an HDF node
 *              as an argument to this function, you are likely to cause
 *              a segfault if you continue to access the data set.  In
 *              the future, we may restrict hdf_destroy so it only works
 *              on the top level node.
 * Input: hdf - pointer to an HDF data set allocated with hdf_init
 * Output: None
 * Returns: None
 */
void hdf_destroy (HDF **hdf);

/*
 * Function: hdf_get_int_value - Return the integer value of a point in
 *           the data set
 * Description: hdf_get_int_value walks the HDF data set pointed to by
 *              hdf to name, and returns the value of that node
 *              converted to an integer.  If that node does not exist,
 *              or it does not contain a number, the defval is returned.  
 * Input: hdf -> a node in an HDF data set
 *        name -> the name of a node to walk to in the data set
 *        defval -> value to return in case of error or if the node
 *                  doesn't exist
 * Output: None
 * Returns: The integer value of the node, or the defval
 */
int hdf_get_int_value (HDF *hdf, const char *name, int defval);

/*
 * Function: hdf_get_value - Return the value of a node in the data set
 * Description: hdf_get_value walks the data set pointed to by hdf via
 *              name and returns the string value located there, or
 *              defval if the node doesn't exist
 * Input: hdf -> the dataset node to start from
 *        name -> the name to walk the data set to
 *        defval -> the default value to return if the node doesn't
 *                  exist
 * Output: None
 * Returns: A pointer to the string stored in the data set, or defval.
 *          The data set maintains ownership of the string, if you want
 *          a copy you either have to call strdup yourself, or use
 *          hdf_get_copy
 */
char *hdf_get_value (HDF *hdf, const char *name, const char *defval);

/*
 * Function: hdf_get_valuevf - Return the value of a node in the data set
 * Description: hdf_get_valuevf walks the data set pointed to by hdf via
 *              namefmt printf expanded with varargs ap, and returns the
 *              string value located there, or NULL if it doesn't exist.
 *              This differs from hdf_get_value in that there is no
 *              default value possible.
 * Input: hdf -> the dataset node to start from
 *        namefmt -> the format string
 *        ap -> va_list of varargs
 * Output: None
 * Returns: A pointer to the string stored in the data set, or NULL.
 *          The data set maintains ownership of the string, if you want
 *          a copy you either have to call strdup yourself.
 */
char* hdf_get_valuevf (HDF *hdf, const char *namefmt, va_list ap);

/*
 * Function: hdf_get_valuef - Return the value of a node in the data set
 * Description: hdf_get_valuef walks the data set pointed to by hdf via
 *              namefmt printf expanded with varargs, and returns the
 *              string value located there, or NULL if it doesn't exist.
 *              This differs from hdf_get_value in that there is no
 *              default value possible.
 * Input: hdf -> the dataset node to start from
 *        namefmt -> the printf-style format string
 *        ... -> arguments to fill out namefmt 
 * Output: None
 * Returns: A pointer to the string stored in the data set, or NULL.
 *          The data set maintains ownership of the string, if you want
 *          a copy you either have to call strdup yourself.
 */
char* hdf_get_valuef (HDF *hdf, const char *namefmt, ...);

/*
 * Function: hdf_get_copy - Returns a copy of a string in the HDF data set
 * Description: hdf_get_copy is similar to hdf_get_value, except that it
 *              returns an malloc'd copy of the string.
 * Input: hdf -> the dataset node to start from
 *        name -> the name to walk the data set to
 *        defval -> the default value to return if the node doesn't
 *                  exist
 * Output: value -> the allocated string (if defval = NULL, then value
 *                  will be NULL if defval is used)
 * Returns: NERR_NOMEM if unable to allocate the new copy 
 */
NEOERR* hdf_get_copy (HDF *hdf, const char *name, char **value, 
                      const char *defval);

/*
 * Function: hdf_get_obj - return the HDF data set node at a named location
 * Description: hdf_get_obj walks the dataset given by hdf to the node
 *              named name, and then returns the pointer to that node
 * Input: hdf -> the dataset node to start from
 *        name -> the name to walk to
 * Output: None
 * Returns: the pointer to the named node, or NULL if it doesn't exist
 */
HDF* hdf_get_obj (HDF *hdf, const char *name);

/*
 * Function: hdf_get_node - Similar to hdf_get_obj except all the nodes
 *           are created if the don't exist.
 * Description: hdf_get_node is similar to hdf_get_obj, except instead
 *              of stopping if it can't find a node in the tree, it will
 *              create all of the nodes necessary to hand you back the
 *              node you ask for.  Nodes are created with no value.
 * Input: hdf -> the dataset node to start from
 *        name -> the name to walk to
 * Output: ret -> the dataset node you asked for
 * Returns: NERR_NOMEM - unable to allocate new nodes
 */
NEOERR * hdf_get_node (HDF *hdf, const char *name, HDF **ret);

/*
 * Function: hdf_get_child - return the first child of the named node
 * Description: hdf_get_child will walk the dataset starting at hdf to
 *              name, and return the first child of that node
 * Input: hdf -> the dataset node to start from
 *        name -> the name to walk to
 * Output: None
 * Returns: The first child of the named dataset node or NULL if the
 *          node is not found (or it has no children)
 */
HDF* hdf_get_child (HDF *hdf, const char *name);

/*
 * Function: hdf_get_attr -
 * Description:
 * Input:
 * Output:
 * Returns:
 */
HDF_ATTR* hdf_get_attr (HDF *hdf, const char *name);

/*
 * Function: hdf_set_attr -
 * Description:
 * Input:
 * Output:
 * Returns:
 */
NEOERR* hdf_set_attr (HDF *hdf, const char *name, const char *key, 
                      const char *value);

/*
 * Function: hdf_obj_child - Return the first child of a dataset node
 * Description: hdf_obj_child and the other hdf_obj_ functions are
 *              accessors to the HDF dataset.  Although we do not
 *              currently "hide" the HDF struct implementation, we
 *              recommend you use the accessor functions instead of
 *              accessing the values directly.
 * Input: hdf -> the hdf dataset node
 * Output: None
 * Returns: The pointer to the first child, or NULL if there is none
 */
HDF* hdf_obj_child (HDF *hdf);

/*
 * Function: hdf_obj_next - Return the next node of a dataset level
 * Description: hdf_obj_next is an accessor function for the HDF struct
 * Input: hdf -> the hdf dataset node
 * Output: None
 * Returns: The pointer to the next node, or NULL if there is none
 */
HDF* hdf_obj_next (HDF *hdf);

/*
 * Function: hdf_obj_top - Return the pointer to the top dataset node
 * Description: hdf_obj_top is an accessor function which returns a
 *              pointer to the top of the dataset, the node which was
 *              returned by hdf_init.  This is most useful for
 *              implementations of language wrappers where individual
 *              nodes are tied garbage colletion wise to the top node of
 *              the data set
 * Input: hdf -> the hdf dataset node
 * Output: None
 * Returns: The pointer to the top node
 */
HDF* hdf_obj_top (HDF *hdf);

/*
 * Function: hdf_obj_attr - Return the HDF Attributes for a node
 * Description:
 * Input:
 * Output:
 * Returns:
 */
HDF_ATTR* hdf_obj_attr (HDF *hdf);

/*
 * Function: hdf_obj_name - Return the name of a node
 * Description: hdf_obj_name is an accessor function for a datset node
 *              which returns the name of the node.  This is just the
 *              local name, and not the full path.
 * Input: hdf -> the hdf dataset node
 * Output: None
 * Returns: The name of the node.  If this is the top node, the name is
 * NULL.
 */
char* hdf_obj_name (HDF *hdf);

/*
 * Function: hdf_obj_value - Return the value of a node
 * Description: hdf_obj_value is an accessor function for a dataset node
 *              which returns the value of the node, or NULL if the node
 *              has no value.  This is not a copy of the value, so the
 *              node retains ownership of the value
 * Input: hdf -> the hdf dataset node
 * Output: None
 * Returns: The value of the node, or NULL if it has no value
 */
char* hdf_obj_value (HDF *hdf);

/*
 * Function: hdf_set_value - Set the value of a named node
 * Description: hdf_set_value will set the value of a named node.  All
 *              of the interstitial nodes which don't exist will be
 *              created with a value of NULL.  Existing nodes are not
 *              modified.  New nodes are created at the end of the list.
 *              If a list of nodes exceeds FORCE_HASH_AT, then a HASH
 *              will be created at that level and all of the nodes will
 *              be added to the hash for faster lookup times.
 *              The copy of the value will be made which the dataset
 *              will own.
 * Input: hdf -> the pointer to the hdf dataset
 *        name -> the named node to walk to
 *        value -> the value to set the node to
 * Output: None
 * Returns: NERR_NOMEM
 */
NEOERR* hdf_set_value (HDF *hdf, const char *name, const char *value);

/*
 * Function: hdf_set_valuef - Set the value of a named node
 * Description: hdf_set_valuef is a convenience function that wraps
 *              hdf_set_value.  Due to limitations of C, the fmt is in
 *              the format "name=value", where we will first format the
 *              entire string, and then break it at the first (from the
 *              left) equal sign (=) and use the left portion as the
 *              name and the right portion as the value.  This function
 *              is somewhat inefficient in that it first allocates the
 *              full name=value, and then the call to hdf_set_value
 *              duplicates the value portion, and then we free the
 *              name=value.
 *              Currently, we don't strip whitespace from the key or
 *              value.  In the future, this function might work more
 *              like reading a single line of an HDF string or file,
 *              allowing for attributes and symlinks to be specified...
 *              maybe.
 * Input: hdf -> the pointer to the hdf dataset
 *        fmt -> the name=value printf(3) format string
 * Output: None
 * Returns: NERR_NOMEM
 */
NEOERR* hdf_set_valuef (HDF *hdf, const char *fmt, ...);
NEOERR* hdf_set_valuevf (HDF *hdf, const char *fmt, va_list ap); 

/*
 * Function: hdf_set_int_value - Set the value of a named node to a number
 * Description: hdf_set_int_value is a helper function that maps an
 *              integer to a string, and then calls hdf_set_value with
 *              that string
 * Input: hdf -> the pointer to the hdf dataset
 *        name -> the named node to walk to
 *        value -> the value to set the node to
 * Output: None
 * Returns: NERR_NOMEM
 */
NEOERR* hdf_set_int_value (HDF *hdf, const char *name, int value);

/*
 * Function: hdf_set_copy -> Copy a value from one location in the
 *           dataset to another
 * Description: hdf_set_copy first walks the hdf dataset to the named src
 *              node, and then copies that value to the named dest node.
 *              If the src node is not found, an error is raised.
 * Input: hdf -> the pointer to the dataset node
 *        dest -> the name of the destination node
 *        src -> the name of the source node
 * Output: None
 * Returns: NERR_NOMEM, NERR_NOT_FOUND
 */
NEOERR* hdf_set_copy (HDF *hdf, const char *dest, const char *src);

/*
 * Function: hdf_set_buf - Set the value of a node without duplicating
 *           the value
 * Description: hdf_set_buf is similar to hdf_set_value, except the
 *              dataset takes ownership of the value instead of making a
 *              copy of it.  The dataset assumes that value was
 *              malloc'd, since it will attempt to free it when
 *              hdf_destroy is called
 * Input: hdf -> the hdf dataset node
 *        name -> the name to walk to
 *        value -> the malloc'd value
 * Output: None
 * Returns: NERR_NOMEM - unable to allocate a node
 */

NEOERR* hdf_set_buf (HDF *hdf, const char *name, char *value);

/*
 * Function: hdf_set_symlink - Set part of the tree to link to another
 * Description: hdf_set_symlink creates a link between two sections of
 *              an HDF dataset.  The link is "by name" hence the term
 *              "symlink".  This means that the destination node does
 *              not need to exist.  Any attempt to access the source
 *              node will cause the function to walk to the dest node,
 *              and then continue walking from there.  Using symlinks
 *              can "hide" values in the dataset since you won't be able
 *              to access any children of the linked node directly,
 *              though dumps and other things which access the data
 *              structure directly will bypass the symlink.  Use this
 *              feature sparingly as its likely to surprise you.
 * Input: hdf -> the dataset node
 *        src -> the source node name 
 *        dest -> the destination node name (from the top of the
 *        dataset, not relative names)
 * Output: None
 * Returns: NERR_NOMEM
 */
NEOERR *hdf_set_symlink (HDF *hdf, const char *src, const char *dest);

/*
 * Function: hdf_sort_obj - sort the children of an HDF node 
 * Description: hdf_sort_obj will sort the children of an HDF node,
 *              based on the given comparison function.
 *              This function works by creating an array of the pointers
 *              for each child object of h, using qsort to sort that
 *              array, and then re-ordering the linked list of children
 *              to the new order.  The qsort compare function uses a
 *              pointer to the value in the array, which in our case is
 *              a pointer to an HDF struct, so your comparison function
 *              should work on HDF ** pointers.
 * Input: h - HDF node
 *        compareFunc - function which returns 1,0,-1 depending on some 
 *                      criteria.  The arguments to this sort function
 *                      are pointers to pointers to HDF elements.  For
 *                      example:
 *                      int sortByName(const void *a, const void *b) {
 *                        HDF **ha = (HDF **)a;
 *                        HDF **hb = (HDF **)b;
 *
 *                      return strcasecmp(hdf_obj_name(*ha), hdf_obj_name(*hb));
 *                      }
 *
 * Output: None (h children will be sorted)
 * Return: NERR_NOMEM 
 */
NEOERR *hdf_sort_obj(HDF *h, int (*compareFunc)(const void *, const void *));

/*
 * Function: hdf_read_file - read an HDF data file
 * Description:
 * Input:
 * Output:
 * Returns: NERR_IO, NERR_NOMEM, NERR_PARSE
 */
NEOERR* hdf_read_file (HDF *hdf, const char *path);

/*
 * Function: hdf_write_file - write an HDF data file
 * Description:
 * Input:
 * Output:
 * Returns: NERR_IO
 */
NEOERR* hdf_write_file (HDF *hdf, const char *path);

/*
 * Function: hdf_write_file_atomic - write an HDF data file atomically
 * Description: hdf_write_file_atomic is similar to hdf_write_file,
 *              except the new file is created with a unique name and
 *              then rename(2) is used to atomically replace the old
 *              file with the new file
 * Input:
 * Output:
 * Returns: NERR_IO
 */
NEOERR* hdf_write_file_atomic (HDF *hdf, const char *path);

/*
 * Function: hdf_read_string - read an HDF string
 * Description:
 * Input:
 * Output:
 * Returns: NERR_NOMEM, NERR_PARSE
 */
NEOERR* hdf_read_string (HDF *hdf, const char *s);

/*
 * Function: hdf_read_string_ignore - Read an HDF string and ignore errors
 * Description:
 * Input:
 * Output:
 * Returns: NERR_NOMEM
 */
NEOERR* hdf_read_string_ignore (HDF *hdf, const char *s, int ignore);

/*
 * Function: hdf_write_string - serialize an HDF dataset to a string
 * Description:
 * Input:
 * Output:
 * Returns: NERR_NOMEM
 */
NEOERR* hdf_write_string (HDF *hdf, char **s);

/*
 * Function: hdf_dump - dump an HDF dataset to stdout
 * Description:
 * Input:
 * Output:
 * Returns:
 */
NEOERR* hdf_dump (HDF *hdf, const char *prefix);

/*
 * Function: hdf_dump_format - dump an HDF dataset to FILE *fp
 * Description:
 * Input:
 * Output:
 * Returns:
 */
NEOERR* hdf_dump_format (HDF *hdf, int lvl, FILE *fp);

/*
 * Function: hdf_dump_str - dump an HDF dataset to STRING
 * Description:
 * Input:
 * Output:
 * Returns:
 */
NEOERR* hdf_dump_str(HDF *hdf, const char *prefix, int compact, STRING *str);

/*
 * Function: hdf_remove_tree - delete a subtree of an HDF dataset
 * Description:
 * Input:
 * Output:
 * Returns:
 */
NEOERR* hdf_remove_tree (HDF *hdf, const char *name);

/*
 * Function: hdf_copy - copy part of an HDF dataset to another
 * Description: hdf_copy is a deep copy of an HDF tree pointed to by
 *              src to the named node of dest.  dest and src need not be
 *              part of the same data set
 * Input: dest_hdf -> the destination dataset
 *        name -> the name of the destination node
 *        src -> the hdf dataset to copy to the destination
 * Output: None
 * Returns: NERR_NOMEM, NERR_NOT_FOUND
 */
NEOERR* hdf_copy (HDF *dest_hdf, const char *name, HDF *src);

/*
 * Function: hdf_search_path - Find a file given a search path in HDF
 * Description: hdf_search_path is a convenience/utility function that
 *              searches for relative filenames in a search path.  The
 *              search path is the list given by the children of
 *              hdf.loadpaths.
 * Input: hdf -> the hdf dataset to use
 *        path -> the relative path
 *        full -> a pointer to a _POSIX_PATH_MAX buffer
 * Output: full -> the full path of the file
 * Returns: NERR_NOT_FOUND if the file wasn't found in the search path
 */
NEOERR* hdf_search_path (HDF *hdf, const char *path, char *full);

/*
 * Function: hdf_register_fileload - register a fileload function
 * Description: hdf_register_fileload registers a fileload function that 
 *              overrides the built-in function.  The built-in function
 *              uses hdf_search_path and ne_file_load (based on stat/open/read)
 *              to find and load the file on every hdf_read_file (including 
 *              #include).  You can override this function if you wish to provide
 *              other file search functions, or load the hdf file
 *              from an in-memory cache, etc.
 * Input: hdf - pointer to a head HDF node
 *        ctx - pointer that is passed to the HDFFILELOAD function when called
 *        fileload - a HDFFILELOAD function
 * Output: None
 * Return: None
 *
 */

void hdf_register_fileload(HDF *hdf, void *ctx, HDFFILELOAD fileload);

__END_DECLS

#endif /* __NEO_HDF_H_ */
