/* $Id$ */
/*
 * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifndef __PJSUA2_PERSISTENT_HPP__
#define __PJSUA2_PERSISTENT_HPP__

/**
 * @file pjsua2/persistent.hpp
 * @brief PJSUA2 Persistent Services
 */
#include <pjsua2/types.hpp>

#include <string>
#include <vector>

/** PJSUA2 API is inside pj namespace */
namespace pj
{

/**
 * @defgroup PJSUA2_PERSISTENT Persistent API
 * @ingroup PJSUA2_Ref
 * @{
 * The persistent API provides functionality to read/write data from/to
 * a document (string or file). The data can be simple data types such
 * as boolean, number, string, and string arrays, or a user defined object.
 * Currently the implementation supports reading and writing from/to JSON
 * document, but the framework allows application to extend the API to
 * support other document formats.
 */

using std::string;
using std::vector;

/* Forward declaration for ContainerNode */
class ContainerNode;

/**
 * This is the abstract base class of objects that can be serialized to/from
 * persistent document.
 */
class PersistentObject
{
public:
    /**
     * Virtual destructor
     */
    virtual ~PersistentObject()
    {}

    /**
     * Read this object from a container node.
     *
     * @param node		Container to read values from.
     */
    virtual void readObject(const ContainerNode &node) PJSUA2_THROW(Error) = 0;

    /**
     * Write this object to a container node.
     *
     * @param node		Container to write values to.
     */
    virtual void writeObject(ContainerNode &node) const PJSUA2_THROW(Error) = 0;
};


/**
 * This a the abstract base class for a persistent document. A document
 * is created either by loading from a string or a file, or by constructing
 * it manually when writing data to it. The document then can be saved
 * to either string or to a file. A document contains one root ContainerNode
 * where all data are stored under.
 *
 * Document is read and written serially, hence the order of reading must be
 * the same as the order of writing. The PersistentDocument class provides
 * API to read and write to the root node, but for more flexible operations
 * application can use the ContainerNode methods instead. Indeed the read
 * and write API in PersistentDocument is just a shorthand which calls the
 * relevant methods in the ContainerNode. As a tip, normally application only
 * uses the readObject() and writeObject() methods declared here to read/write
 * top level objects, and use the macros that are explained in ContainerNode
 * documentation to read/write more detailed data.
 */
class PersistentDocument
{
public:
    /**
     * Virtual destructor
     */
    virtual ~PersistentDocument()
    {}

    /**
     * Load this document from a file.
     *
     * @param filename	The file name.
     */
    virtual void   	loadFile(const string &filename)
				 PJSUA2_THROW(Error) = 0;

    /**
     * Load this document from string.
     *
     * @param input	The string.
     */
    virtual void   	loadString(const string &input)
				   PJSUA2_THROW(Error) = 0;

    /**
     * Write this document to a file.
     *
     * @param filename	The file name.
     */
    virtual void   	saveFile(const string &filename)
				 PJSUA2_THROW(Error) = 0;

    /**
     * Write this document to string.
     *
     * @return		The string document.
     */
    virtual string 	saveString() PJSUA2_THROW(Error) = 0;

    /**
     * Get the root container node for this document
     *
     * @return		The root node.
     */
    virtual ContainerNode & getRootContainer() const = 0;


    /*
     * Shorthand functions for reading and writing from/to the root container
     */


    /**
     * Determine if there is unread element. If yes, then app can use one of
     * the readXxx() functions to read it.
     *
     * @return		True if there is.
     */
    bool		hasUnread() const;

    /**
     * Get the name of the next unread element. It will throw Error if there
     * is no more element to read.
     *
     * @return		The name of the next element .
     */
    string		unreadName() const PJSUA2_THROW(Error);

    /**
     * Read an integer value from the document and return the value.
     * This will throw Error if the current element is not a number.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    int			readInt(const string &name="") const
				PJSUA2_THROW(Error);

    /**
     * Read a float value from the document and return the value.
     * This will throw Error if the current element is not a number.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    float		readNumber(const string &name="") const
				   PJSUA2_THROW(Error);

    /**
     * Read a boolean value from the container and return the value.
     * This will throw Error if the current element is not a boolean.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    bool		readBool(const string &name="") const
				 PJSUA2_THROW(Error);

    /**
     * Read a string value from the container and return the value.
     * This will throw Error if the current element is not a string.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    string		readString(const string &name="") const
				   PJSUA2_THROW(Error);

    /**
     * Read a string array from the container. This will throw Error
     * if the current element is not a string array. The read position
     * will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    StringVector	readStringVector(const string &name="") const
					 PJSUA2_THROW(Error);

    /**
     * Read the specified object from the container. This is equal to
     * calling PersistentObject.readObject(ContainerNode);
     *
     * @param obj	The object to read.
     */
    void		readObject(PersistentObject &obj) const
				   PJSUA2_THROW(Error);

    /**
     * Read a container from the container. This will throw Error if the
     * current element is not an object. The read position will be advanced
     * to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		Container object.
     */
    ContainerNode	readContainer(const string &name="") const
				      PJSUA2_THROW(Error);

    /**
     * Read array container from the container. This will throw Error if the
     * current element is not an array. The read position will be advanced
     * to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		Container object.
     */
    ContainerNode	readArray(const string &name="") const
				  PJSUA2_THROW(Error);

    /**
     * Write a number value to the container.
     *
     * @param name	The name for the value in the container.
     * @param num	The value to be written.
     */
    void		writeNumber(const string &name,
        		            float num) PJSUA2_THROW(Error);

    /**
     * Write a number value to the container.
     *
     * @param name	The name for the value in the container.
     * @param num	The value to be written.
     */
    void		writeInt(const string &name,
        		         int num) PJSUA2_THROW(Error);

    /**
     * Write a boolean value to the container.
     *
     * @param name	The name for the value in the container.
     * @param value	The value to be written.
     */
    void		writeBool(const string &name,
        		          bool value) PJSUA2_THROW(Error);

    /**
     * Write a string value to the container.
     *
     * @param name	The name for the value in the container.
     * @param value	The value to be written.
     */
    void		writeString(const string &name,
        		            const string &value) PJSUA2_THROW(Error);

    /**
     * Write string vector to the container.
     *
     * @param name	The name for the value in the container.
     * @param arr	The vector to be written.
     */
    void		writeStringVector(const string &name,
        		                  const StringVector &arr)
					  PJSUA2_THROW(Error);

    /**
     * Write an object to the container. This is equal to calling
     * PersistentObject.writeObject(ContainerNode);
     *
     * @param obj	The object to be written
     */
    void		writeObject(const PersistentObject &obj)
				    PJSUA2_THROW(Error);

    /**
     * Create and write an empty Object node that can be used as parent
     * for subsequent write operations.
     *
     * @param name	The name for the new container in the container.
     *
     * @return		A sub-container.
     */
    ContainerNode 	writeNewContainer(const string &name)
					  PJSUA2_THROW(Error);

    /**
     * Create and write an empty array node that can be used as parent
     * for subsequent write operations.
     *
     * @param name	The name for the array.
     *
     * @return		A sub-container.
     */
    ContainerNode 	writeNewArray(const string &name)
				      PJSUA2_THROW(Error);
};


/**
 * Forward declaration of container_node_op.
 */
struct container_node_op;


/**
 * Internal data for ContainerNode. See ContainerNode implementation notes
 * for more info.
 */
struct container_node_internal_data
{
    void	*doc;		/**< The document.	*/
    void	*data1;		/**< Internal data 1	*/
    void	*data2;		/**< Internal data 2	*/
};

/**
 * A container node is a placeholder for storing other data elements, which
 * could be boolean, number, string, array of strings, or another container.
 * Each data in the container is basically a name/value pair, with a type
 * internally associated with it so that written data can be read in the
 * correct type. Data is read and written serially, hence the order of
 * reading must be the same as the order of writing.
 *
 * Application can read data from it by using the various read methods, and
 * write data to it using the various write methods. Alternatively, it
 * may be more convenient to use the provided macros below to read and write
 * the data, because these macros set the name automatically:
 *	- NODE_READ_BOOL(node,item)
 *	- NODE_READ_UNSIGNED(node,item)
 *	- NODE_READ_INT(node,item)
 *	- NODE_READ_FLOAT(node,item)
 *	- NODE_READ_NUM_T(node,type,item)
 *	- NODE_READ_STRING(node,item)
 *	- NODE_READ_STRINGV(node,item)
 *	- NODE_READ_OBJ(node,item)
 *	- NODE_WRITE_BOOL(node,item)
 *	- NODE_WRITE_UNSIGNED(node,item)
 *	- NODE_WRITE_INT(node,item)
 *	- NODE_WRITE_FLOAT(node,item)
 *	- NODE_WRITE_NUM_T(node,type,item)
 *	- NODE_WRITE_STRING(node,item)
 *	- NODE_WRITE_STRINGV(node,item)
 *	- NODE_WRITE_OBJ(node,item)
 *
 * Implementation notes:
 *
 * The ContainerNode class is subclass-able, but not in the usual C++ way.
 * With the usual C++ inheritance, some methods will be made pure virtual
 * and must be implemented by the actual class. However, doing so will
 * require dynamic instantiation of the ContainerNode class, which means
 * we will need to pass around the class as pointer, for example as the
 * return value of readContainer() and writeNewContainer() methods. Then
 * we will need to establish who needs or how to delete these objects, or
 * use shared pointer mechanism, each of which is considered too inconvenient
 * or complicated for the purpose.
 *
 * So hence we use C style "inheritance", where the methods are declared in
 * container_node_op and the data in container_node_internal_data structures.
 * An implementation of ContainerNode class will need to set up these members
 * with values that makes sense to itself. The methods in container_node_op
 * contains the pointer to the actual implementation of the operation, which
 * would be specific according to the format of the document. The methods in
 * this ContainerNode class are just thin wrappers which call the
 * implementation in the container_node_op structure.
 *
 */
class ContainerNode
{
public:
    /**
     * Determine if there is unread element. If yes, then app can use one of
     * the readXxx() functions to read it.
     */
    bool		hasUnread() const;

    /**
     * Get the name of the next unread element.
     */
    string		unreadName() const PJSUA2_THROW(Error);

    /**
     * Read an integer value from the document and return the value.
     * This will throw Error if the current element is not a number.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    int			readInt(const string &name="") const
				PJSUA2_THROW(Error);

    /**
     * Read a number value from the document and return the value.
     * This will throw Error if the current element is not a number.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    float		readNumber(const string &name="") const
				   PJSUA2_THROW(Error);

    /**
     * Read a boolean value from the container and return the value.
     * This will throw Error if the current element is not a boolean.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    bool		readBool(const string &name="") const
				 PJSUA2_THROW(Error);

    /**
     * Read a string value from the container and return the value.
     * This will throw Error if the current element is not a string.
     * The read position will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    string		readString(const string &name="") const
				   PJSUA2_THROW(Error);

    /**
     * Read a string array from the container. This will throw Error
     * if the current element is not a string array. The read position
     * will be advanced to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		The value.
     */
    StringVector	readStringVector(const string &name="") const
					 PJSUA2_THROW(Error);

    /**
     * Read the specified object from the container. This is equal to
     * calling PersistentObject.readObject(ContainerNode);
     *
     * @param obj	The object to read.
     */
    void		readObject(PersistentObject &obj) const
				   PJSUA2_THROW(Error);

    /**
     * Read a container from the container. This will throw Error if the
     * current element is not a container. The read position will be advanced
     * to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		Container object.
     */
    ContainerNode	readContainer(const string &name="") const
				      PJSUA2_THROW(Error);

    /**
     * Read array container from the container. This will throw Error if the
     * current element is not an array. The read position will be advanced
     * to the next element.
     *
     * @param name	If specified, then the function will check if the
     * 			name of the next element matches the specified
     * 			name and throw Error if it doesn't match.
     *
     * @return		Container object.
     */
    ContainerNode	readArray(const string &name="") const
				  PJSUA2_THROW(Error);

    /**
     * Write a number value to the container.
     *
     * @param name	The name for the value in the container.
     * @param num	The value to be written.
     */
    void		writeNumber(const string &name,
        		            float num) PJSUA2_THROW(Error);

    /**
     * Write a number value to the container.
     *
     * @param name	The name for the value in the container.
     * @param num	The value to be written.
     */
    void		writeInt(const string &name,
        		         int num) PJSUA2_THROW(Error);

    /**
     * Write a boolean value to the container.
     *
     * @param name	The name for the value in the container.
     * @param value	The value to be written.
     */
    void		writeBool(const string &name,
        		          bool value) PJSUA2_THROW(Error);

    /**
     * Write a string value to the container.
     *
     * @param name	The name for the value in the container.
     * @param value	The value to be written.
     */
    void		writeString(const string &name,
        		            const string &value) PJSUA2_THROW(Error);

    /**
     * Write string vector to the container.
     *
     * @param name	The name for the value in the container.
     * @param arr	The vector to be written.
     */
    void		writeStringVector(const string &name,
        		                  const StringVector &arr)
					  PJSUA2_THROW(Error);

    /**
     * Write an object to the container. This is equal to calling
     * PersistentObject.writeObject(ContainerNode);
     *
     * @param obj	The object to be written
     */
    void		writeObject(const PersistentObject &obj)
				    PJSUA2_THROW(Error);

    /**
     * Create and write an empty Object node that can be used as parent
     * for subsequent write operations.
     *
     * @param name	The name for the new container in the container.
     *
     * @return		A sub-container.
     */
    ContainerNode 	writeNewContainer(const string &name)
					  PJSUA2_THROW(Error);

    /**
     * Create and write an empty array node that can be used as parent
     * for subsequent write operations.
     *
     * @param name	The name for the array.
     *
     * @return		A sub-container.
     */
    ContainerNode 	writeNewArray(const string &name)
				      PJSUA2_THROW(Error);

public:
    /* internal data */
    container_node_op *op;		/**< Method table.	*/
    container_node_internal_data data;	/**< Internal data	*/
};


/**
 * Pointer to actual ContainerNode implementation. See ContainerNode
 * implementation notes for more info.
 */
//! @cond Doxygen_Suppress
struct container_node_op
{
    bool		(*hasUnread)(const ContainerNode*);
    string		(*unreadName)(const ContainerNode*)
				      PJSUA2_THROW(Error);
    float		(*readNumber)(const ContainerNode*,
				      const string&)
				      PJSUA2_THROW(Error);
    bool		(*readBool)(const ContainerNode*,
				    const string&)
				    PJSUA2_THROW(Error);
    string		(*readString)(const ContainerNode*,
				      const string&)
				      PJSUA2_THROW(Error);
    StringVector	(*readStringVector)(const ContainerNode*,
					    const string&)
					    PJSUA2_THROW(Error);
    ContainerNode	(*readContainer)(const ContainerNode*,
					 const string &)
					 PJSUA2_THROW(Error);
    ContainerNode	(*readArray)(const ContainerNode*,
				     const string &)
				     PJSUA2_THROW(Error);
    void		(*writeNumber)(ContainerNode*,
				       const string &name,
        		               float num)
        		               PJSUA2_THROW(Error);
    void		(*writeBool)(ContainerNode*,
				     const string &name,
        			     bool value)
        			     PJSUA2_THROW(Error);
    void		(*writeString)(ContainerNode*,
				       const string &name,
        			       const string &value)
        			       PJSUA2_THROW(Error);
    void		(*writeStringVector)(ContainerNode*,
					     const string &name,
        				     const StringVector &value)
					     PJSUA2_THROW(Error);
    ContainerNode 	(*writeNewContainer)(ContainerNode*,
					     const string &name)
					     PJSUA2_THROW(Error);
    ContainerNode 	(*writeNewArray)(ContainerNode*,
				         const string &name)
					 PJSUA2_THROW(Error);
};

/*
 * Convenient macros.
 */
#define NODE_READ_BOOL(node,item)	item = node.readBool(#item)
#define NODE_READ_UNSIGNED(node,item)	item = (unsigned)node.readNumber(#item)
#define NODE_READ_INT(node,item)	item = (int) node.readNumber(#item)
#define NODE_READ_FLOAT(node,item)	item = node.readNumber(#item)
#define NODE_READ_NUM_T(node,T,item)	item = (T)(int)node.readNumber(#item)
#define NODE_READ_STRING(node,item)	item = node.readString(#item)
#define NODE_READ_STRINGV(node,item)	item = node.readStringVector(#item)
#define NODE_READ_OBJ(node,item)	node.readObject(item)

#define NODE_WRITE_BOOL(node,item)	node.writeBool(#item, item)
#define NODE_WRITE_UNSIGNED(node,item)	node.writeNumber(#item, (float)item)
#define NODE_WRITE_INT(node,item)	node.writeNumber(#item, (float)item)
#define NODE_WRITE_NUM_T(node,T,item)	node.writeNumber(#item, (float)item)
#define NODE_WRITE_FLOAT(node,item)	node.writeNumber(#item, item)
#define NODE_WRITE_STRING(node,item)	node.writeString(#item, item)
#define NODE_WRITE_STRINGV(node,item)	node.writeStringVector(#item, item)
#define NODE_WRITE_OBJ(node,item)	node.writeObject(item)

//! @endcond

/**
 * @}  PJSUA2
 */

} // namespace pj



#endif	/* __PJSUA2_PERSISTENT_HPP__ */
