/* 
 * vfs-test.c:  VFS Regression Test
 *
 * (c) 2002, JP Rosevear.
 * (c) 2004, Florent Pillet
 *
 * 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.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * This regression test calls every DLP function except the following:
 *    dlp_CallApplication
 *    dlp_ResetSystem
 *    dlp_ProcessRPC
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "pi-debug.h"
#include "pi-socket.h"
#include "pi-dlp.h"
#include "pi-error.h"
#include "pi-source.h"
#include "pi-header.h"


/* For various protocol versions, set to 0 to not test those versions */
#define DLP_1_1 1
#define DLP_1_2 1
#define DLP_1_3 1

/* Logging defines */
#define CHECK_RESULT(func) \
	if (result < 0) { \
		LOG((PI_DBG_USER, PI_DBG_LVL_ERR, "VFSTEST " #func " failed (%d, last palmos error: 0x%04x)\n", result, pi_palmos_error(sd))); \
		if (result == PI_ERR_SOCK_DISCONNECTED) \
			goto error; \
	}

#define TEST_VFS_DIR			"/vfs-test"
#define TEST_VFS_FILE			"/vfs-test/test.dat"
#define TEST_VFS_FILE2			"/vfs-test/test2.dat"
#define TEST_VFS_FILE2_SHORT	"test2.dat"
#define BIG_FILE_SIZE			(2 * 1024 * 1024)			/* test vfs read/write with 1Mb data */

int main (int argc, char **argv)
{
	int 	i,
		j,
		sd,
		result,
		refs[200],
		ref_length,
		len,
		numStrings;
	char *strings;
	FileRef fileRef;
	unsigned long fileAttrs;
	unsigned char *buf1;

	time_t t1, t2;
	char name[255],
		oldName[255];

	unsigned long flags;

	sd = pilot_connect (argv[1]);
	if (sd < 0) return 1;

	t1 = time (NULL);
	LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "VFSTEST Starting at %s", ctime (&t1)));

	/*********************************************************************
	 *
	 * Test: Expansion
	 *
	 * Direct Testing Functions:
	 *   dlp_ExpSlotEnumerate
	 *   dlp_ExpCardPresent
	 *   dlp_ExpCardInfo
	 *
	 * Indirect Testing Functions:
	 *   None
	 *
	 *********************************************************************/
#if DLP_1_3
	ref_length = sizeof (refs) / sizeof (refs[0]);
	result = dlp_ExpSlotEnumerate (sd, &ref_length, refs);
	CHECK_RESULT(dlp_ExpSlotEnumerate);

	for (i = 0; i < ref_length; i++) {
		result = dlp_ExpCardPresent (sd, refs[i]);
		if (result == PI_ERR_DLP_PALMOS) { 
			LOG((PI_DBG_USER, PI_DBG_LVL_ERR, "* dlp_ExpCardPresent: card not present in slot %d, PalmOS err 0x%04x\n", i, pi_palmos_error(sd)));
		} else {
			CHECK_RESULT(dlp_ExpCardPresent);
			if (result >= 0) {
				strings = NULL;
				result = dlp_ExpCardInfo (sd, refs[i], &flags, &numStrings, &strings);
				CHECK_RESULT(dlp_ExpCardInfo);

				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Expansion card {%d}: "
					"capabilities = 0x%08lx, numStrings = %d\n", refs[i], flags, numStrings));

				if (strings) {
					char *p = strings;
					for (j = 0; j < numStrings; j++) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "\tstring[%d] = '%s'\n", j, p));
						p += strlen (p) + 1;
					}
					free (strings);
				}
			}
		}
	}
#endif

	/*********************************************************************
	 *
	 * Test: VFS Volumes, files and directories
	 *
	 * Direct Testing Functions:
	 *   dlp_VFSDirCreate
	 *   dlp_VFSDirEntryEnumerate
	 *   dlp_VFSFileClose
	 *   dlp_VFSFileCreate
	 *   dlp_VFSFileDelete
	 *   dlp_VFSFileEOF
	 *   dlp_VFSFileGetAttributes
	 *   dlp_VFSFileGetDate
	 *   dlp_VFSFileOpen
	 *   dlp_VFSFileRead
	 *   dlp_VFSFileRename
	 *   dlp_VFSFileResize
	 *   dlp_VFSFileSeek
	 *   dlp_VFSFileSetAttributes
	 *   dlp_VFSFileSetDate
	 *   dlp_VFSFileSize
	 *   dlp_VFSFileTell
	 *   dlp_VFSFileWrite
	 *   dlp_VFSGetDefaultDir
	 *   dlp_VFSVolumeEnumerate
	 *   dlp_VFSVolumeGetLabel
	 *   dlp_VFSVolumeInfo
	 *   dlp_VFSVolumeSetLabel
	 *   dlp_VFSVolumeSize
	 *
	 * Indirect Testing Functions:
	 *   None
	 *
	 *********************************************************************/
#if DLP_1_3
	ref_length = sizeof (refs) / sizeof (refs[0]);
	result = dlp_VFSVolumeEnumerate (sd, &ref_length, refs);
	CHECK_RESULT(dlp_VFSVolumeEnumerate);

	for (i = 0; i < ref_length; i++) {
		struct VFSInfo vfs;
		long used, total;

		LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "\nTESTING VOLUME %d/%d {ref=%d}\n\n", i+1, ref_length, refs[i]));

		result = dlp_VFSVolumeInfo (sd, refs[i], &vfs);
		CHECK_RESULT(dlp_VFSVolumeInfo);
		if (result >= 0) {
			name[0] = 0;
			if (vfs.attributes & 0x00000004)
				strcpy (name, "hidden ");
			if (vfs.attributes & 0x00000002)
				strcat (name, "read-only ");
			if (vfs.attributes & 0x00000001)
				strcat (name, "slot-based");
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Volume attributes:\n\tattributes: %s\n", name));
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "\tfsType: '%4.4s'\n\tfsCreator: '%4.4s'\n\tmountClass: '%4.4s'\n", &vfs.fsType, &vfs.fsCreator, &vfs.mountClass));
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "\tslotLibRefNum: %d\n\tslotRefNum: %d\n\tmediaType: '%4.4s'\n\n", vfs.slotLibRefNum, vfs.slotRefNum, &vfs.mediaType));
		}

		result = dlp_VFSVolumeSize (sd, refs[i], &used, &total);
		CHECK_RESULT(dlp_VFSVolumeSize);
		if (result >= 0) {
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* volume used: %ld / %ld bytes\n", used, total));
		}

		/* volume label test */
		len = sizeof (name);
		result = dlp_VFSVolumeGetLabel (sd, refs[i], &len, name);
		CHECK_RESULT(dlp_VFSVolumeGetLabel);
		if (result >= 0) {
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* volume label: '%s'\n", name));
		}
		strcpy (oldName, name);

		result = dlp_VFSVolumeSetLabel (sd, refs[i], "Test");
		CHECK_RESULT(dlp_VFSVolumeSetLabel);
		if (result >= 0) {
			len = sizeof (name);
			result = dlp_VFSVolumeGetLabel (sd, refs[i], &len, name);
			CHECK_RESULT(dlp_VFSVolumeGetLabel);
			if (result >= 0) {
				if (strcmp("Test", name)) {
					LOG((PI_DBG_USER, PI_DBG_LVL_ERR, "* ERROR: Label change mismatch\n"));
				} else {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Label change successful\n"));
				}
			}

			result = dlp_VFSVolumeSetLabel (sd, refs[i], oldName);
			CHECK_RESULT(dlp_VFSVolumeSetLabel);
		}

		/* directory listing test */
		len = sizeof (name);
		result = dlp_VFSGetDefaultDir (sd, refs[i], ".prc", name, &len);
		CHECK_RESULT(dlp_VFSGetDefaultDir);
		if (result >= 0) {
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Listing directory contents at '%s'\n", name));

			result = dlp_VFSFileOpen (sd, refs[i], name, 0x0007 /* vfsModeReadWrite */, &fileRef);
			CHECK_RESULT(dlp_VFSFileOpen);
			if (result >= 0) {
				unsigned long dirIterator = vfsIteratorStart;
				do {
					struct VFSDirInfo dirItems[16];
					int dirCount;
					
					memset (dirItems, 0, sizeof(dirItems));
					dirCount = 16;
					result = dlp_VFSDirEntryEnumerate (sd, fileRef, &dirIterator, &dirCount, dirItems);
					CHECK_RESULT(dlp_VFSDirEntryEnumerate);

					if (result >= 0) {
						for (j = 0; j < dirCount; j++) {
							LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "\t'%s' attrs = 0x%08lx\n", dirItems[j].name, dirItems[j].attr));
						}
					}
				} while (dirIterator != vfsIteratorStop && result >= 0);

				result = dlp_VFSFileClose (sd, fileRef);
				CHECK_RESULT(dlp_VFSFileClose);
				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Closed directory %s\n", name));
			}
		}
		
		/* directory creation test */
		result = dlp_VFSDirCreate (sd, refs[i], "/vfs-test");
		CHECK_RESULT(dlp_VFSDirCreate);
		if (result >= 0) {
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Created directory %s\n", TEST_VFS_DIR));

			/* file access tests */
			result = dlp_VFSFileCreate (sd, refs[i], TEST_VFS_FILE);
			CHECK_RESULT(dlp_VFSFileCreate);
			LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Created file %s\n", TEST_VFS_FILE));

			result = dlp_VFSFileOpen (sd, refs[i], TEST_VFS_FILE, 0x0007 /* read-write */, &fileRef);
			CHECK_RESULT(dlp_VFSFileOpen);
			if (result >= 0) {
				pi_buffer_t *fileBuf =
					pi_buffer_new (BIG_FILE_SIZE);

				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Opened file %s\n", TEST_VFS_FILE));

				strcpy (name, "a test string written to a file\n");
				result = dlp_VFSFileWrite (sd, fileRef, (unsigned char *)name, strlen (name));
				CHECK_RESULT(dlp_VFSFileWrite);
				if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Wrote small string to file %s\n", TEST_VFS_FILE));

					result = dlp_VFSFileTell (sd, fileRef, &j);
					CHECK_RESULT(dlp_VFSFileTell);
					if (result >= 0) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Current seek position: %d\n", j));
					}

					result = dlp_VFSFileSeek (sd, fileRef, vfsOriginBeginning, 0);
					CHECK_RESULT(dlp_VFSFileSeek);
					if (result >= 0) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Seeked to beginning of file\n", j));
					}

					result = dlp_VFSFileRead (sd, fileRef, fileBuf, strlen(name));
					CHECK_RESULT(dlp_VFSFileRead);
					if (fileBuf->used != strlen (name)) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* ERROR: File read: read %d instead of the expected %d\n", len, strlen (name)));
					} else if (memcmp (name, fileBuf->data, fileBuf->used)) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* ERROR: File read: read data mismatch\n", fileBuf->used, strlen (name)));
						pi_dumpdata (name, fileBuf->used);
						pi_dumpdata ((const char *)fileBuf->data, fileBuf->used);
					} else {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File read: successfully read the data back\n"));
					}
				}

				/* huge file write/read test - allocate a big buffer,
				   don't clean it (keep the garbage), write it to the
				   file and read it back them compare */
				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Please wait, writing %ld bytes of data to the file...\n", (long)BIG_FILE_SIZE));
				buf1 = (unsigned char *)malloc (BIG_FILE_SIZE);
				if (buf1) {
					result = dlp_VFSFileWrite (sd, fileRef, buf1, BIG_FILE_SIZE);
					CHECK_RESULT(dlp_VFSFileWrite);
					if (result >= 0) {
						LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Successful, now reading it back...\n"));

						result = dlp_VFSFileSeek (sd, fileRef, vfsOriginCurrent, -BIG_FILE_SIZE);
						CHECK_RESULT(dlp_VFSFileSeek);

						result = dlp_VFSFileRead (sd, fileRef, fileBuf, BIG_FILE_SIZE);
						CHECK_RESULT(dlp_VFSFileRead);
						if (result >= 0) {
							if (fileBuf->used != BIG_FILE_SIZE || memcmp (buf1, fileBuf->data, BIG_FILE_SIZE))
								LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* ERROR, data differs!\n"));
							else
								LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Successful, all bytes are the same...\n"));
						}
					}
				}
				if (buf1)
					free (buf1);
				pi_buffer_free (fileBuf);

				result = dlp_VFSFileEOF (sd, fileRef);
				if (pi_palmos_error(sd) == 0x2A07) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File EOF: YES\n"));
				} else if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File EOF: NO\n"));
				} else {
					CHECK_RESULT(dlp_VFSFileEOF);
				}
				
				result = dlp_VFSFileSize (sd, fileRef, &len);
				CHECK_RESULT(dlp_VFSFileSize);
				if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File size: %d bytes\n", len));
				}

				result = dlp_VFSFileGetAttributes (sd, fileRef, &fileAttrs);
				CHECK_RESULT(dlp_VFSFileGetAttributes);
				if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File attributes: 0x%08lx\n", fileAttrs));
				}
				
				fileAttrs |= 0x00000001L;
				result = dlp_VFSFileSetAttributes (sd, fileRef, fileAttrs);
				CHECK_RESULT(dlp_VFSFileSetAttributes);

				result = dlp_VFSFileGetAttributes (sd, fileRef, &fileAttrs);
				CHECK_RESULT(dlp_VFSFileGetAttributes);
				if (fileAttrs & 0x00000001L) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Successfully set read-only bit on file attributes\n"));
				}

				fileAttrs &= ~0x00000001L;
				result = dlp_VFSFileSetAttributes (sd, fileRef, fileAttrs);
				CHECK_RESULT(dlp_VFSFileSetAttributes);

				result = dlp_VFSFileResize (sd, fileRef, 1024);
				CHECK_RESULT(dlp_VFSFileResize);
				
				result = dlp_VFSFileSize (sd, fileRef, &len);
				CHECK_RESULT(dlp_VFSFileGetSize);
				if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* File size after resize: %d\n", len));
				}

				result = dlp_VFSFileGetDate (sd, fileRef, vfsFileDateCreated, &t1);
				CHECK_RESULT(dlp_VFSFileGetDate);
				if (result >= 0) {
					ctime_r (&t1, name);
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Date created: %s", name));
				}

				result = dlp_VFSFileGetDate (sd, fileRef, vfsFileDateModified, &t2);
				CHECK_RESULT(dlp_VFSFileGetDate);
				if (result >= 0) {
					ctime_r (&t2, name);
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Date modified: %s", name));
				}

				result = dlp_VFSFileGetDate (sd, fileRef, vfsFileDateAccessed, &t2);
				CHECK_RESULT(dlp_VFSFileGetDate);
				if (result >= 0) {
					ctime_r (&t2, name);
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Date accessed: %s", name));
				}

				t1 -= 3600 * 24;
				result = dlp_VFSFileSetDate (sd, fileRef, vfsFileDateCreated, t1);
				CHECK_RESULT(dlp_VFSFileSetDate);
				if (result >= 0) {
					result = dlp_VFSFileGetDate (sd, fileRef, vfsFileDateCreated, &t2);
					CHECK_RESULT(dlp_VFSFileGetDate);
					if (result >= 0) {
						if (t1 == t2)
							LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* dlp_VFSFileSetDate successful\n"));
						else
							LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* BUG: dlp_VFSFileSetDate FAILED\n"));
					}
				}

				result = dlp_VFSFileClose (sd, fileRef);
				CHECK_RESULT(dlp_VFSFileClose);
				if (result >= 0) {
					LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Closed file %s\n", TEST_VFS_FILE));
				}
			}

			result = dlp_VFSFileRename (sd, refs[i], TEST_VFS_FILE, TEST_VFS_FILE2_SHORT);
			CHECK_RESULT(dlp_VFSFileRename);
			if (result >= 0) {
				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Renamed file %s to %s\n", TEST_VFS_FILE, TEST_VFS_FILE2_SHORT));
			}

			result = dlp_VFSFileDelete (sd, refs[i], TEST_VFS_FILE2);
			CHECK_RESULT(dlp_VFSFileDelete);
			if (result >= 0) {
				LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Deleted file %s\n", TEST_VFS_FILE2));
			}
		}

		result = dlp_VFSFileDelete (sd, refs[i], "/vfs-test");
		CHECK_RESULT(dlp_VFSFileDelete);
		LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "* Deleted directory /vfs-test\n"));
	}
#endif

	/*********************************************************************
	 *
	 * Test: VFS Import/Export Databases
	 *
	 * Direct Testing Functions:
	 *   dlp_VFSImportDatabaseFromFile
	 *   dlp_VFSExportDatabaseFromFile
	 *
	 * Indirect Testing Functions:
	 *   None
	 *
	 *********************************************************************/

	t1 = time (NULL);
	LOG((PI_DBG_USER, PI_DBG_LVL_INFO, "VFSTEST Ending at %s", ctime (&t1)));

error:
	pi_close (sd);

	return 0;
}

