/*
 * Interface for accessing iRiver's IFP devices
 * $Id: ifp.h,v 1.27.2.3 2005/08/22 02:35:35 oakhamg Exp $
 *
 * Copyright (C) Geoff Oakham, 2004; <oakhamg@users.sourceforge.net>
 *
 * This driver 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; only version 2 of the License.
 *
 * 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 in the
 * files "COPYING" or "COPYING.iriverfs"; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifndef IFP_H
#define IFP_H 1

#ifdef __KERNEL__
#include <linux/types.h> //uint8_t, loff_t, ... etc
#else
#include <inttypes.h>    //uint8_t (16,32...)
#include <sys/types.h>   //off_t
#include <stdio.h>       //FILE*
#endif

#ifdef __KERNEL__
typedef loff_t		ifp_off_t;
#else
typedef off_t		ifp_off_t;
#endif

/** \example simple.c
 *
 * Basic introduction into how to use libifp.
 */

/** @file ifp.h
  \brief An interface for iRiver's flash-based portable music players.
  \author Copyright (C) Geoff Oakham, 2004; <oakhamg@users.sourceforge.net>
  
  Calling Conventions (unless otherwise stated):
  	- integer return value 0 indicates success, error otherwise
	- argument 'dev' is a device handle
	- argument 'f' is the full path to a remote file or directory. (eg. '\\foo\\bar.mp3')
	- argument 'b' is a buffer for returning data
	- remote pathnames use '\\' instead of '/' for separating directories
	 (eg '\\classical\\mozart\\magicflute.ogg')
	- functions that return numerical values (eg. ::ifp_freespace) return 
	  negative values on error

 */

//You should be able to set these from a build configuration tool: either
//'configure' or 'make menuconfig'
//#define IFP_DEBUG_SYSCALL 1
//#define IFP_DEBUG_USB_SNOOPING	1
//#define IFP_DEBUG_USB_RAWDATA	1

#define IFP_BULK_MAXPATHLEN	0x400 //ie, utf16
#define IFP_MAXPATHLEN		0x200 //old value.  (no idea where it came from)
#define IFP_MAX_PATH		0x080 //maximum number of latin characters (discovered experimentally)
#define IFP_MAX_FILENAME	0x080 //maximum number of latin characters
#define IFP_BUFFER_SIZE		IFP_BULK_MAXPATHLEN

#define IFP_BULK_BUFF_BITS 14
#define IFP_BULK_BUFF_SIZE 0x4000

#define IFP_1XX  0x1001  /**< Model number for iFP-100 series */
#define IFP_3XX  0x1003  /**< Model number for iFP-300 series */
#define IFP_5XX  0x1005  /**< Model number for iFP-500 series */
#define IFP_7XX  0x1007  /**< Model number for iFP-700 series */
#define IFP_8XX  0x1008  /**< Model number for iFP-800 series */
#define IFP_9XX  0x1008  /**< Model number for iFP-900 series */
#define IFP_N10  0x1011  /**< Model number for N10 */

/** \brief An ifp_device handle.
 *
 * End-users are responsible for allocating and deallocating this structure.
 * However, its contents should be considered private and not directly
 * accessible.
 *
 * (This requirement---that users allocate memory themselves---helps make the
 * library more flexible.  For example, it could be used inside a kernel, where
 * memory allocation failure is a headache.  On the positive side, userland
 * coders can declare their structures on the stack.  Bad habit?  Maybe.  Does
 * it matter?  Perhaps not.)
 */
struct ifp_device {
	/** \internal usb outgoing endpoint */
	int bulk_to;

	/** \internal usb incomming endpoint */
	int bulk_from;

	/** \internal USB ProductID, which indicates the family number.
	 * Observed values include ::IFP_1XX, ::IFP_8XX, ::IFP_N10.. etc.
	 */
	int model;
	
	//These buffers are use for servicing individual function calls.
	//(Which eliminates the need for memory allocations.) Contents are
	//not preserved between calls.
	
	/** \internal b1 is used by:
		push_unicode
		pop_unicode
		ifp_dir_next_debug
		ifp_set_fat_page
	*/
	uint8_t b1[IFP_BUFFER_SIZE];

	/** \internal b1 is used by:
		swap_fat_entries	(ifp_rename* helper)
		_ifp_list_dirs_debug	(ifp_rename* helper)
		_ifp_list_dirs		(ifp_list_dirs helper)
		ifp_device_info
	*/
	uint8_t b2[IFP_BUFFER_SIZE];

	/** \internal b3 is used by:
		swap_fat_entries	(ifp_rename* helper)
		swap_filenames		(ifp_rename* helper)
		get_file_size		(_ifp_list_dirs helper)
	*/
	uint8_t b3[IFP_BUFFER_SIZE];

	//State preserved between function calls.  Used to track things like
	//open files or the last block of data received.
	
	/** \internal The last buffer size announced to the device.*/
	int last_buffer_size;

	/** \internal
	 * Main data buffer.  Contains the last block read, or part of the
	 * block being written.  The current position (for reading or writing)
	 * is current_offset % IFP_BULK_BUFF_SIZE.
	 *
	 * If current_offset%IFP_BULK_BUFF_SIZE=0, the data is "old".. ie has
	 * not yet been read or has been written (respectively).
	 */
	uint8_t iobuff[IFP_BULK_BUFF_SIZE];

	#define IFP_MODE_NONE    (0) /** \internal */
	#define IFP_MODE_READING (1) /** \internal */
	#define IFP_MODE_WRITING (2) /** \internal */
	/** \internal Current device mode.  Either 'reading', 'writing' or 'none' (other). */
	int mode;

	/** \internal Current read/write offset. */
	ifp_off_t current_offset;

	/** \internal Total filesize (expected or actual). */
	ifp_off_t filesize;

	/** \internal Cache of the current file's directory name. */
	uint8_t dirname[IFP_BUFFER_SIZE];

	/** \internal Cache of the current file's file name. */
	uint8_t filename[IFP_BUFFER_SIZE];

	/** \internal debugging/sanity counters */
	int readcount;
	int alt_readcount;
	int download_pipe_errors;

	/** \internal USB device handle (system specific) */
	void * device;
};

//Note: you can also use these to request only directories, only files
//or both files and directories by ORing them together:
//	IFP_DIR
//	IFP_FILE
//	IFP_FILE | IFP_DIR
//
//Of course this is only useful inside libifp.

/** File 'type'. */
#define IFP_FILE                1
/** Dir 'type'. */
#define IFP_DIR                 2

//0-2 reserved for boolean functions and other simplish stuff.
//#define IFP_ERR_FILE_NOT_FOUND	(-ENOENT) /**< File not found */
//#define IFP_ERR_FILE_EXISTS	(-EEXIST)      /**< Filename unavailable, file or dir exists */
//#define IFP_ERR_DIR_NOT_FOUND	(-ENOENT)      /**< Directory not found */
//#define IFP_ERR_DIR_EXISTS	(-EEXIST)      /**< Directory exists */
//#define IFP_ERR_DIR_NOT_EMPTY	(-ENOTEMPTY)      /**< Directory is not empty */
#define IFP_ERR_DEV_FUBAR	8      /**< device not responding; try jiggling handle */
#define IFP_ERR_BAD_FREQUENCY	9      /**< tuner frequency out of range */
#define IFP_ERR_BAD_FILENAME	10     /**< filename is invalid.  Likely causes are unsupported characters,
					* or a filename that's too long (more than ::IFP_MAXFILENAMELEN chars).
					*
					* note: linux's fatfs returns EINVAL instead.  Should
					* we switch?*/
#define IFP_ERR_USER_CANCEL 	11     /**< A user callback requested the transfer be cancelled. */

#define IFP_TUNER_PRESET_DATA	240    /**< Tuner preset buffer size. */
#define IFP_FREQ_MIN		8750   /**< lowist valid frequency (87.5kHz) */
#define IFP_FREQ_MAX		10800  /**< highist valid frequency (108.0kHz) */
#define IFP_PRESET_TOTAL	20     /**< number of preset stations. */
#define IFP_TUNER_LABEL		6      /**< max size of label string. */

#ifndef KERNEL
/** \brief Statistics about a file transfer in progress.
 *
 * The fields file_bytes, batch_bytes and is_batch are guaranteed to be valid.
 * Fields not in use are set to 0 or NULL, so test before dereferrencing
 * pointers or dividing numbers.
 */
struct ifp_transfer_status {
	int file_bytes; /**< the number of bytes transferred for the current file */
	int file_total; /**< the expected number of bytes in the current file */
	const char * file_name; /**< the name of the current file */

	long batch_bytes; /**< The number of bytes transferred in the transfer so far */
	long batch_total; /**< the total number of bytes expected during this transfer */

	int files_count; /**< The number of files successfully transferred so far. */
	int files_total; /**< The number of files expected to be transferred in this batch. */

	int is_batch; /**< 0 during single-file transfers, 1 during multi-file transfers */

	void * reserved1; /**< reserved for internal use.  */
	void * reserved2; /**< reserved for internal use.  */
	void * reserved3; /**< reserved for internal use.  */
};

/** \brief File types for treewalking */
enum {
	IFP_WALK_FILE = IFP_FILE,   /**< file */
	IFP_WALK_DIR_PRE = IFP_DIR, /**< directory, before visiting its children */
	IFP_WALK_DIR_POST,          /**< directory, after visiting its children */
	IFP_WALK_NONE               /**< none of the above */
};
/** \brief A remote file or directory. */
struct ifp_treewalk_entry {
	int type;          /**< ::IFP_WALK_FILE, ::IFP_WALK_DIR_PRE, or ::IFP_WALK_DIR_POST */
	const char * path; /**< full pathname */
	int pathlen;       /**< strlen(path) */
	const char * name; /**< filename without directory stuff */
	int namelen;       /**< strlen(name) */
	int filesize;      /**< filesize, if type==::IFP_WALK_FILE */
};

/** \brief Callback for implementing a progress metre.
 *
 * If provided, this function is typically called several times during a file
 * transfer to give GUI applications a chance provide user feedback.
 *
 * This function should return 0 for 'success' or 1 to request the transfer
 * be cancelled when possible.  (The request might be ignored.)  Other values
 * are considered 'error values'.
 *
 * The first parameter is whatever if you passed to the main function as
 * 'context'.  You may use this for anything you like or leave it NULL.
 *
 * The second parameter is a pointer to information about the transfer in
 * progress.  The only value guaranteed to be valid for _all_ transfers is
 * file_bytes.  Which values *are* valid should be obvious from context.
 * To be on the safe side, please guard against NULL pointers and divide-by
 * -zero errors.  (The structure itself won't be NULL.  I promise.)
 */
typedef int(*ifp_progress)(void *, struct ifp_transfer_status *);
#endif

#ifdef __cplusplus
extern "C" {
#endif


/** @name Setup and initialization */
//@{

int ifp_init(struct ifp_device * dev, void * dev_handle);
int ifp_finalize(struct ifp_device * dev);

/** \brief Scans the system and returns the first compatible iFP device.
 *
 * If no device is found, NULL is returned.  The handle must be released with
 *  ::ifp_release_device.
 */
void * ifp_find_device(void);
/** \brief Releases device handle allocated by ::ifp_find_device. */
int ifp_release_device(void *);

/** Quick communications test with the device.  Normally, users shouldn't have
 * to use this because it's run automatically by ifp_init.  */
int ifp_selftest(struct ifp_device * dev);
int ifp_format(struct ifp_device * dev);
#ifndef KERNEL
int ifp_update_firmware(struct ifp_device * dev, const char * localfile, ifp_progress fn, void * context);
#endif
//@}

/** @name Device status */
//@{
//status and misc
int ifp_device_info(struct ifp_device * dev, char * b, int n);
int ifp_battery(struct ifp_device * dev);
int ifp_capacity(struct ifp_device * dev);
int ifp_freespace(struct ifp_device * dev);
int ifp_model(struct ifp_device * dev, char * b, int n);
int ifp_delta(struct ifp_device * dev, int * values);  //int values[4]
/* Version is returned in raw BCD. */
int ifp_firmware_version(struct ifp_device * dev);

#ifndef KERNEL
const char * ifp_error_message(int n);
#endif
//@}

/** @name Metadata */
//@{
int ifp_rename(struct ifp_device * dev, const char * old_path, const char * new_path);
int ifp_delete(struct ifp_device * dev, const char * f);
int ifp_mkdir(struct ifp_device * dev, const char * f);
int ifp_rmdir(struct ifp_device * dev, const char * f);
int ifp_list_dirs(struct ifp_device * dev, const char * dirname,
	int(*callbk)(void *, int, const char *, int),
	void * context);

#ifndef KERNEL
/* Tree-walking functions--the API is similar to 'tws'. */
struct ifp_treewalk_entry;
int ifp_treewalk_open(struct ifp_device * dev, const char * directory, void ** handle);
int ifp_treewalk_close(void * tws_p);
struct ifp_treewalk_entry * ifp_treewalk_next(void * tws_p);
#endif

//@}


/** @name Reading files */
//@{
// reading files
int ifp_read_open(struct ifp_device * dev, const char * f);
int ifp_read_close(struct ifp_device * dev);
int ifp_read_seek(struct ifp_device * dev, int abs_position);
/* Returns number of bytes read. */
int ifp_read_data(struct ifp_device * dev, void * b, int bytes);
int ifp_read_eof(struct ifp_device * dev);
int ifp_read_size(struct ifp_device * dev);
//@}

/** @name Creating files */
//@{
// writing files
int ifp_write_open(struct ifp_device * dev, const char * f, int fsize);
int ifp_write_close(struct ifp_device * dev);
int ifp_write_data(struct ifp_device * dev, void * b, int bytes);
//@}

#ifndef __KERNEL__ /** @name Bulk file transfers (userland only) */
//@{
int ifp_read_file_progress(struct ifp_device * dev, FILE * dst, const char * f,
	int(*progress)(void *, int), void * context);
int ifp_write_file_progress(struct ifp_device * dev, FILE * src,
	int filesize, const char * f,
	int(*progress)(void *, int), void * context);
/** \brief Reads the file 'f' into dst. */
static inline int ifp_read_file (struct ifp_device * dev, FILE * dst, const char * f)
{ return ifp_read_file_progress(dev, dst, f, NULL, NULL);}

/** \brief Creates a new file 'f' from src. */
static inline int ifp_write_file (struct ifp_device * dev, FILE * src,
	int filesize, const char * f)
{ return ifp_write_file_progress(dev, src, filesize, f, NULL, NULL);}

struct ifp_transfer_status;

int ifp_download_file(struct ifp_device * dev,
	const char * remotefile, const char * localfile,
	ifp_progress fn, void * fn_context);
int ifp_upload_file(struct ifp_device * dev,
	const char * localfile, const char * remotefile,
	ifp_progress fn, void * fn_context);

int ifp_delete_dir_recursive(struct ifp_device * dev, const char * f);

int ifp_download_dir(struct ifp_device * dev,
	const char * remotedir, const char * localdir,
	ifp_progress fn, void * fn_context);
int ifp_upload_dir(struct ifp_device * dev,
	const char * localdir, const char * remotedir,
	ifp_progress fn, void * fn_context);

#ifdef FUTURE
//int ifp_check_filename(const char * s);
//int ifp_clean_filename(const char * s);
#endif //FUTURE

//@}
#endif

/** @name Boolean tests */
//@{
int ifp_is_file(struct ifp_device * dev, const char * f);
int ifp_is_dir (struct ifp_device * dev, const char * f);
int ifp_exists (struct ifp_device * dev, const char * f);
//@}


/** @name FM Tuner preset station manipulation */
//@{
int ifp_get_tuner_presets(struct ifp_device * dev, void * data, int n);
int ifp_set_tuner_presets(struct ifp_device * dev, void * data, int n);

int ifp_get_station(int n, void * b, char * callsign, int * freq);
int ifp_set_station(int n, void * data, const char * callsign, int freq);

//@}

#ifdef IFP_FUTURE
//int ifp_file_size(struct ifp_device * dev, char * f);
//int ifp_read_pos(struct ifp_device * dev); /* current read position */

//missing functionality:
//problem: this is a multi-block call
//int ifp_update_firmware(struct ifp_device * dev, void * data, int n);
#endif //IFP_FUTURE
#ifdef __cplusplus
}
#endif

//portable error-handling macros.
#ifdef __KERNEL__
/** \internal */
#define ifp_os_err(fmt, arg...) printk(KERN_ERR fmt, ##arg)
#else
/** \internal */
#define ifp_os_err(fmt, arg...) fprintf(stderr, "err:  " fmt, ##arg)
#endif

/*** \brief Super-portable error reporting. */
#define ifp_err(fmt, arg...) ifp_os_err("[%s] " fmt "\n", __FUNCTION__ , ##arg)
/*** \brief Super-portable error value reporting. */
#define ifp_err_i(i, fmt, arg...) ifp_err("err=%d. " fmt, i, ##arg)
/*** \brief Error handling macro.  If 'i' is non-zero, display an error message
 * and jump to 'label'.
 */
#define ifp_err_jump(i, label, fmt, arg...) \
	if (i){ ifp_err_i(i, fmt, ##arg); goto label; }
/*** \brief Error handling macro.  If 'i' is non-zero, display an error message
 * and jump to 'label'.  If 'e' is true, don't report the message.
 */
#define ifp_err_expect(i, e, label, fmt, arg...) \
	if (i){ if (!(e)){ifp_err_i(i, fmt, ##arg);} goto label; }
//#define ifp_err_jump(i, label, fmt, arg...) 
//	if (i){ if(i<0){ifp_err_i(i, fmt, ##arg);} goto label; }

#endif // IFP_H

