#ifndef KPILOT_PILOT_H
#define KPILOT_PILOT_H
/* KPilot
**
** Copyright (C) 1998-2001 by Dan Pilone <dan@kpilot.org>
** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
** Copyright (C) 2003-2006 Adriaan de Groot <groot@kde.org>
**
*/

/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with this program in a file called COPYING; if not, write to
** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
** MA 02110-1301, USA.
*/

/*
** Bug reports and questions can be sent to kde-pim@kde.org
*/

// KPilot headers
#include "kpilot_export.h"
#include "pilotLinkVersion.h"

// pilot-link headers
#include <pi-appinfo.h>
#include <pi-buffer.h>
#include <pi-dlp.h>

// Qt headers
#include <QtCore/QStringList>

/** @file
* These are some base structures that reside on the
* handheld device -- strings and binary data.
*/

class PilotDatabase;     // A database
class PilotRecord;       // ... has records
class PilotCategoryInfo; // ... and category information

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

/**
* The Pilot namespace holds constants that are global for
* the handheld data structures. Also contains some global
* functions that deal with pilot-link structures as well
* as mapping user-visible strings from UTF8 (KDE side) to
* the encoding used on the handheld.
*/
namespace Pilot
{
	/** Maximum size of an AppInfo block, taken roughly from the pilot-link source. */
	static const int MAX_APPINFO_SIZE=8192;

	/** Maximum number of categories the handheld has */
	static const unsigned int CATEGORY_COUNT=16;

	/** Maximum size of a category label */
	static const unsigned int CATEGORY_SIZE=16;

	/** Category number for unfiled records */
	static const int Unfiled = 0;

	/** Maximum size (in bytes) of a record's data */
	static const int MAX_RECORD_SIZE = 65535;

	typedef QList<recordid_t> RecordIDList;

	/** Static translation function that maps handheld native (8 bit,
	* usually latin1 but sometimes someting else) encoded data to
	* a Unicode string. Converts the @p len characters in @p c
	* to a Unicode string.
	*/
	KPILOT_EXPORT QString fromPilot( const char *c, int len );

	/** Static translation function mapping a NUL-terminated
	* string from the handheld's encoding to UTF-8.
	* @param c the NUL-terminated string to decode
	* @return QString (UTF-8) value of @p c
	* @note NUL-terminated strings are rare on the handheld.
	*/
	KPILOT_EXPORT QString fromPilot( const char *c );

	/** Static translation function that maps a QString onto the
	* native 8 bit encoding of the handheld. Writes the result into
	* the buffer @p buf which has size @p len. Returns the length
	* of the result. Zero-fills the buffer as needed.
	*/
	KPILOT_EXPORT int toPilot( const QString &s, char *buf, int len);
	KPILOT_EXPORT int toPilot( const QString &s, unsigned char *buf, int len);

	/** Static translation function that maps a QString onto the
	* native 8 bit encoding of the handheld.
	*
	* @param s String to encode
	* @return Encoded string in a QCString
	*/
	KPILOT_EXPORT QByteArray toPilot( const QString &s );

	/** Create a codec for translating handheld native 8 bit to Unicode,
	* using the given codec @p name -- this will often be latin1, but
	* might be something else for, say, Russian-language Pilots.
	* If @p name is empty, use latin1.
	*
	* @return @c true on success, @c false otherwise
	*/
	KPILOT_EXPORT bool setupPilotCodec(const QString &name);

	/** Returns the name of the codec being used. */
	KPILOT_EXPORT QString codecName();

	/** For debugging, display category names for the given AppInfo
	* structure. Called by dump(). You must pass a valid reference.
	*/
	KPILOT_EXPORT void dumpCategories(const struct CategoryAppInfo *info);

	/** Check that a given category number is valid. This
	* restricts the range of integers to [0..CATEGORY_COUNT-1]
	* (i.e. [0..15]) which is what the handheld supports.
	*/
	inline bool validCategory(int c)
	{
		if (c<0)
		{
			return false;
		}
		return ((unsigned int)c<CATEGORY_COUNT);
	}

	/** Returns the QString for the requested category @p i
	* in the category structure @p info. Returns @c QString()
	* on error (bad pointer or bad category number). May also
	* return @c QString() if the category name is empty.
	*/
	inline QString categoryName(const struct CategoryAppInfo *info, unsigned int i)
	{
		if ( ( i < CATEGORY_COUNT ) && ( info->name[i][0] ) )
		{
			/*
			 * Seems to be important that we try to pass the real length here
			 * to the codec.
			 */
			return fromPilot( info->name[i], MIN(strlen(info->name[i]),CATEGORY_SIZE) );
		}
		else
		{
			return QString();
		}
	}

	/** Returns a list of all the category names available on the
	*  handheld. This list is neither ordered nor does it contain
	*  all sixteen categories -- empty category names on the
	*  handheld are skipped.
	*/
	inline QStringList categoryNames(const struct CategoryAppInfo *info)
	{
		QStringList l;
		if (!info)
		{
			return l;
		}
		for (unsigned int i=0; i<CATEGORY_COUNT; ++i)
		{
			QString s = categoryName(info,i);
			if (!s.isEmpty())
			{
				l.append(s);
			}
		}
		return l;
	}

	/** Search for the given category @p name in the list
	* of categories; returns the category number. If @p unknownIsUnfiled
	* is true, then map unknown categories to Unfiled instead of returning
	* an error number.
	*
	* @return >=0   is a specific category based on the text-to-
	*               category number mapping defined by the Pilot,
	*               where 0 is always the 'unfiled' category.
	*  @return -1   means unknown category selected when
	*               @p unknownIsUnfiled is false.
	*  @return  0   == Unfiled means unknown category selected when
	*               @p unknownIsUnfiled is true.
	*
	*/
	KPILOT_EXPORT int findCategory(const struct CategoryAppInfo *info, const QString &name, bool unknownIsUnfiled);

	/** Search for the given category @p name in the list
	* of categories; returns the category number. If @p unknownIsUnfiled
	* is @c true, then map unknown categories to Unfiled.
	* If @p unknownIsUnfiled is @c false, insert a @em new
	* category into the structure and return the category
	* number of the new category. Return -1 if (and only if)
	* @p unknownIsUnfiled is false and the category structure
	* is already full.
	*
	* @return >=0   is a specific category based on the text-to-
	*               category number mapping defined by the Pilot,
	*               where 0 is always the 'unfiled' category.
	* @return 0     Unknown category and @p unknownIsUnfiled is @c true
	* @return -1    means unknown category selected when
	*               @p unknownIsUnfiled is false and categories
	*               are all full.
	*
	*/
	KPILOT_EXPORT int insertCategory(struct CategoryAppInfo *info, const QString &label, bool unknownIsUnfiled);

	/** The handheld also holds data about each database
	* in a DBInfo structure; check if the database described
	* by this structure is a resource database.
	*/
	static inline bool isResource(struct DBInfo *info)
	{
		return (info->flags & dlpDBFlagResource);
	}


/** @section Binary blob handling
*
* For reading and writing binary blobs -- which has to happen to
* pack data into the format that the handheld needs -- it is important
* to remember that the handheld has only four data types (as far
* as I can tell: byte, short (a 2 byte integer), long (a 4 byte integer)
* and string (NUL terminated). The sizes of the types on the handheld
* do not necessarily correspond to the sizes of the same-named types
* on the desktop. This means that 'reading a long' from a binary
* blob must always be 4 bytes -- not sizeof(long).
*
* The following templates help out in manipulating the blobs.
* Instantiate them with the type @em name you need (char, short, long or
* char *) and you get a ::size enum specifying the number of bytes
* (where applicable) and ::append and ::read methods for appending
* a value of the given type to a pi_buffer_t or reading one from
* the buffer, respectively.
*
* The usage of ::read and ::append is straightforward:
*
* append(pi_buffer_t *b, TYPE_VALUE v) Appends the type value @p v to the
* buffer @p b , extending the buffer as needed.
*
* TYPE_VALUE read(pi_buffer_t *b, unsigned int &offset) Read a value from
* the buffer @p b at position @p offset and return it. The offset value
* is increased by the number of bytes read from the buffer.
*
* To write a binary blob, a sequence of ::append calls constructs the
* blob. To read the same blob, a sequence of ::read calls with the
* @em same type parameters is sufficient.
*
* The calls may vary a little: the exact interface differs depending
* on the needs of the type of data to be written to the blob.
*/
template<typename t> struct dlp { } ;

template<> struct dlp<char>
{
	enum { size = 1 };

	static void append(pi_buffer_t *b, char v)
	{
		pi_buffer_append(b,&v,size);
	}

	/**
	* Returns next byte from buffer or 0 on error (0 is also a
	* valid return value, though).
	*/
	static char read(const pi_buffer_t *b, unsigned int &offset)
	{
		if (offset+size > b->used)
		{
			return 0;
		}
		char c = b->data[offset];
		offset+=size;
		return c;
	}
} ;

template<> struct dlp<short>
{
	enum { size = 2 };

	static void append(pi_buffer_t *b, short v)
	{
		char buf[size];
		set_short(buf,v);
		pi_buffer_append(b,buf,size);
	}

	/**
	* Returns the next short (2 byte) value from the buffer, or
	* -1 on error (which is also a valid return value).
	*/
	static int read(const pi_buffer_t *b, unsigned int &offset)
	{
		if (offset+size > b->used)
		{
			return -1;
		}
		else
		{
			int r = get_short(b->data + offset);
			offset+=size;
			return r;
		}
	}

	/**
	* Overload to read from a data buffer instead of a real pi_buffer;
	* does no bounds checking.
	*/
	static int read(const unsigned char *b, unsigned int &offset)
	{
		int r = get_short(b+offset);
		offset+=size;
		return r;
	}
} ;

template<> struct dlp<long>
{
	enum { size = 4 };

	static void append(pi_buffer_t *b, int v)
	{
		char buf[size];
		set_long(buf,v);
		pi_buffer_append(b,buf,size);
	}

	/**
	* Returns the next long (4 byte) value from the buffer or
	* -1 on error (which is also a valid value).
	*/
	static int read(const pi_buffer_t *b, unsigned int &offset)
	{
		if (offset+size > b->used)
		{
			return -1;
		}
		else
		{
			int r = get_long(b->data + offset);
			offset+=size;
			return r;
		}
	}

	/**
	* Overload to read a long value from a data buffer; does
	* no bounds checking.
	*/
	static int read(const unsigned char *b, unsigned int &offset)
	{
		int r = get_long(b+offset);
		offset+=size;
		return r;
	}
} ;

template<> struct dlp<char *>
{
	// No size enum, doesn't make sense
	// No append, use pi_buffer_append
	/**
	* Read a fixed-length string from the buffer @p b into data buffer
	* @p v which has size (including terminating NUL) of @p s.
	* Returns the number of bytes read (which will normally  be @p s
	* but will be less than @p s on error).
	*/
	static int read(const pi_buffer_t *b,
		unsigned int &offset,
		unsigned char *v,
		size_t s)
	{
		if ( s+offset > b->used )
		{
			s = b->allocated - offset;
		}
		memcpy(v, b->data + offset, s);
		offset+=s;
		return s;
	}

	/** Overload for signed char. */
	inline static int read(const pi_buffer_t *b, unsigned int &offset, char *v, size_t s)
	{
		return read(b,offset,(unsigned char *)v,s);
	}
} ;

}

#endif

