/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_store.hxx"

#define _T_STORE_CXX "$Revision: 1.7.8.2 $"
#include <sal/types.h>
#include <osl/diagnose.h>
#include <osl/thread.h>
#include <osl/time.h>
#include <rtl/ustring.hxx>
#include <store/store.hxx>

#include <stdio.h>

#if (defined(WNT) && defined(PROFILE))
extern "C"
{
	void StartCAP (void);
	void StopCAP  (void);
	void DumpCAP  (void);
}
#endif /* PROFILE */

using rtl::OUString;

/*========================================================================
 *
 * Internals.
 *
 *======================================================================*/
#define _DEMOSTOR_BUFSIZ          512  /* 4096, 1024, 512 */
#define _DEMOSTOR_LOOPS           1000 /* 1000, 2000 */

#define _DEMOSTOR_REMOVE          0
#define _DEMOSTOR_REBUILD         0

enum Options
{
	OPTION_HELP    = 0x0001,
	OPTION_FILE    = 0x0002,
	OPTION_PAUSE   = 0x0004,
	OPTION_REBUILD = 0x0008,

	OPTION_DUMP    = 0x0010,
	OPTION_ITER    = 0x0020,
	OPTION_LINK    = 0x0040,

	OPTION_READ    = 0x0100,
	OPTION_WRITE   = 0x0200,
	OPTION_CREAT   = 0x0400,
	OPTION_TRUNC   = 0x0800
};

inline sal_Char ascii_toLowerCase (sal_Char ch)
{
	if ((ch >= 0x41) && (ch <= 0x5A))
		return (ch + 0x20);
	else
		return (ch);
}

/*========================================================================
 *
 * Timing.
 *
 *======================================================================*/
struct OTime : public TimeValue
{
	OTime (void)
	{
		Seconds = 0;
		Nanosec = 0;
	}

	static OTime getSystemTime (void)
	{
		OTime tv;
		osl_getSystemTime (&tv);
		return tv;
	}

	OTime& operator-= (const OTime& rPast)
	{
		Seconds -= rPast.Seconds;
		if (Nanosec < rPast.Nanosec)
		{
			Seconds -= 1;
			Nanosec += 1000000000;
		}
		Nanosec -= rPast.Nanosec;
		return *this;
	}

	friend OTime operator- (const OTime& rTimeA, const OTime& rTimeB)
	{
		OTime aTimeC (rTimeA);
		aTimeC -= rTimeB;
		return aTimeC;
	}
};

/*========================================================================
 *
 * DirectoryTraveller.
 *
 *======================================================================*/
typedef store::OStoreDirectory Directory;

class DirectoryTraveller : public Directory::traveller
{
	typedef store::OStoreFile   file;
	typedef Directory::iterator iter;

	store::OStoreFile m_aFile;
	OUString          m_aPath;

	sal_uInt32  m_nOptions;
	unsigned int  m_nLevel;
	unsigned int  m_nCount;

public:
	DirectoryTraveller (
		const file&     rFile,
		const OUString &rPath,
		const OUString &rName,
		sal_uInt32      nOptions,
		unsigned int nLevel = 0);

	virtual ~DirectoryTraveller (void);

	virtual sal_Bool visit (const iter& it);
};

/*
 * DirectoryTraveller.
 */
DirectoryTraveller::DirectoryTraveller (
	const file&     rFile,
	const OUString &rPath,
	const OUString &rName,
	sal_uInt32      nOptions,
	unsigned int nLevel)
	: m_aFile    (rFile),
	  m_aPath    (rPath),
	  m_nOptions (nOptions),
	  m_nLevel   (nLevel),
	  m_nCount   (0)
{
	m_aPath += rName;
	m_aPath += OUString::createFromAscii("/");
}

/*
 * ~DirectoryTraveller.
 */
DirectoryTraveller::~DirectoryTraveller (void)
{
}

/*
 * visit.
 */
sal_Bool DirectoryTraveller::visit (const iter& it)
{
	m_nCount++;
	if (m_nOptions & OPTION_DUMP)
	{
		rtl::OString aName (it.m_pszName, it.m_nLength, RTL_TEXTENCODING_UTF8);
		printf ("Visit(%d,%d): %s [0x%08x] %d [Bytes]\n",
				m_nLevel, m_nCount,
				aName.pData->buffer, (unsigned int)(it.m_nAttrib), (unsigned int)(it.m_nSize));
	}
	if (it.m_nAttrib & STORE_ATTRIB_ISDIR)
	{
		OUString  aName (it.m_pszName, it.m_nLength);
		if (aName.compareToAscii ("XTextViewCursorSupplier") == 0)
		{
			m_nCount += 1 - 1;
		}
		Directory aSubDir;

		storeError eErrCode = aSubDir.create (
			m_aFile, m_aPath, aName, store_AccessReadOnly);
		if (eErrCode == store_E_None)
		{
			sal_uInt32 nRefCount = 0;
			m_aFile.getRefererCount (nRefCount);

			DirectoryTraveller aSubTraveller (
				m_aFile, m_aPath, aName, m_nOptions, m_nLevel + 1);
			aSubDir.travel (aSubTraveller);
		}
	}
	return sal_True;
}

/*========================================================================
 *
 * main.
 *
 *======================================================================*/
int SAL_CALL main (int argc, char **argv)
{
#if (defined(WNT) && defined(PROFILE))
	StartCAP();
#else
	OTime aMainStartTime (OTime::getSystemTime());
#endif /* PROFILE */

	store::OStoreFile aFile;
	storeError eErrCode = store_E_None;

	sal_uInt32 nOptions = 0;
	for (int i = 1; i < argc; i++)
	{
		const char *opt = argv[i];
		if (opt[0] == '-')
		{
			switch (ascii_toLowerCase(sal_Char(opt[1])))
			{
				case 'f':
					nOptions |= OPTION_FILE;
					break;

				case 'd':
					nOptions |= OPTION_DUMP;
					break;
				case 'i':
					nOptions |= OPTION_ITER;
					break;
				case 'l':
					nOptions |= OPTION_LINK;
					break;

				case 'r':
					nOptions |= OPTION_READ;
					break;
				case 'w':
					nOptions |= OPTION_WRITE;
					break;
				case 'c':
					nOptions |= OPTION_CREAT;
					break;
				case 't':
					nOptions |= OPTION_TRUNC;
					break;

				case 'p':
					nOptions |= OPTION_PAUSE;
					break;

				case 'h':
				default:
					nOptions |= OPTION_HELP;
					break;
			}
		}
		else
		{
			if (nOptions & OPTION_FILE)
			{
				OUString aName (
					argv[i], rtl_str_getLength(argv[i]),
					osl_getThreadTextEncoding());
				if ((nOptions & OPTION_CREAT) && (nOptions & OPTION_TRUNC))
					eErrCode = aFile.create (aName, store_AccessCreate);
				else if (nOptions & OPTION_CREAT)
					eErrCode = aFile.create (aName, store_AccessReadCreate);
				else if (nOptions & OPTION_WRITE)
					eErrCode = aFile.create (aName, store_AccessReadWrite);
				else
					eErrCode = aFile.create (aName, store_AccessReadOnly);
				if (eErrCode != store_E_None)
				{
					printf ("Error: can't open file: %s\n", argv[i]);
					exit (0);
				}
			}
		}
	}

	if ((nOptions == 0) || (nOptions & OPTION_HELP))
	{
		printf ("Usage:\tt_store "
				"[[-c] [-t] [-r] [-w]] [[-i] [-d] [-h]] "
				"[-f filename]\n");

		printf ("\nOptions:\n");
		printf ("-c\tcreate\n");
		printf ("-t\ttruncate\n");
		printf ("-r\tread\n");
		printf ("-w\twrite\n");
		printf ("-i\titerate\n");
		printf ("-d\tdump\n");
		printf ("-h\thelp\n");
		printf ("-f\tfilename\n");

		printf ("\nExamples:");
		printf ("\nt_store -c -w -f t_store.rdb\n");
		printf ("\tCreate file 't_store.rdb',\n"
				"\twrite fixed number (1000) of streams.\n");
		printf ("\nt_store -c -i -d -f t_store.rdb\n");
		printf ("\tOpen file 't_store.rdb', "
				"create '/' directory,\n"
				"\titerate directory tree, "
				"dump directory info.\n");

		exit (0);
	}

	if (!aFile.isValid())
	{
		eErrCode = aFile.createInMemory();
		if (eErrCode != store_E_None)
		{
			printf ("Error: can't create memory file\n");
			exit (0);
		}
	}

	// Stream Read/Write.
	OUString aPath (RTL_CONSTASCII_USTRINGPARAM("/"));
	if ((nOptions & OPTION_READ) || (nOptions & OPTION_WRITE))
	{
		// Mode.
		storeAccessMode eMode = store_AccessReadOnly;
		if (nOptions & OPTION_WRITE)
			eMode = store_AccessReadWrite;
		if (nOptions & OPTION_CREAT)
			eMode = store_AccessCreate;

		// Buffer.
		char pBuffer[_DEMOSTOR_BUFSIZ] = "Hello World";
		pBuffer[_DEMOSTOR_BUFSIZ - 2] = 'B';
		pBuffer[_DEMOSTOR_BUFSIZ - 1] = '\0';

		// Load/Save.
#ifndef PROFILE
		OTime aStartTime (OTime::getSystemTime());
#endif /* PROFILE */

		for (int i = 0; i < _DEMOSTOR_LOOPS; i++)
		{
			OUString aName (RTL_CONSTASCII_USTRINGPARAM("demostor-"));
			aName += OUString::valueOf ((sal_Int32)(i + 1), 10);
			aName += OUString::createFromAscii (".dat");

#if (_DEMOSTOR_REMOVE == 1)
			eErrCode = aFile.remove (aPath, aName);
			if ((eErrCode != store_E_None     ) &&
				(eErrCode != store_E_NotExists)    )
				break;
#endif /* _REMOVE */

			store::OStoreStream aStream;
			eErrCode = aStream.create (aFile, aPath, aName, eMode);
			if (eErrCode != store_E_None)
            {
                OSL_TRACE("OStoreStream(%d)::create(): error: %d", i, eErrCode);
				break;
            }

			if (nOptions & OPTION_TRUNC)
			{
				eErrCode = aStream.setSize(0);
				if (eErrCode != store_E_None)
                {
                    OSL_TRACE("OStoreStream(%d)::setSize(0): error: %d", i, eErrCode);
					break;
                }
			}

			sal_uInt32 nDone = 0;
			if (nOptions & OPTION_WRITE)
			{
				eErrCode = aStream.writeAt (
					0, pBuffer, sizeof(pBuffer), nDone);
				if (eErrCode != store_E_None)
                {
                    OSL_TRACE("OStoreStream(%d)::writeAt(): error: %d", i, eErrCode);
					break;
                }
			}

			if (nOptions & OPTION_READ)
			{
				sal_uInt32 nOffset = 0;
				for (;;)
				{
					eErrCode = aStream.readAt (
						nOffset, pBuffer, sizeof(pBuffer), nDone);
					if (eErrCode != store_E_None)
                    {
                        OSL_TRACE("OStoreStream(%d)::readAt(): error: %d", i, eErrCode);
						break;
                    }
					if (nDone == 0)
						break;
					nOffset += nDone;
				}
			}

			aStream.close();

#ifndef PROFILE
			if (((i + 1) % (_DEMOSTOR_LOOPS/10)) == 0)
			{
				OTime aDelta (OTime::getSystemTime() - aStartTime);

				sal_uInt32 nDelta = aDelta.Seconds * 1000000;
				nDelta += (aDelta.Nanosec / 1000);

				printf ("%d: %12.4g[usec]\n", (i+1),
						(double)(nDelta)/(double)(i+1));
			}
#endif /* PROFILE */
		}

#ifndef PROFILE
		OTime aDelta (OTime::getSystemTime() - aStartTime);

		sal_uInt32 nDelta = aDelta.Seconds * 1000000;
		nDelta += (aDelta.Nanosec / 1000);

		printf ("Total(rd,wr): %d[usec]\n", (unsigned int)(nDelta));
#endif /* PROFILE */
	}

	// Link/Rename.
	if (nOptions & OPTION_LINK)
	{
		// Create symlink to (root) directory.
		eErrCode = aFile.symlink (
			aPath,      OUString::createFromAscii("000000/"),
			OUString(), aPath);
		OSL_POSTCOND(
			((eErrCode == store_E_None         ) ||
			 (eErrCode == store_E_AlreadyExists)    ),
			"t_store::main(): store_symlink() failed");

		// Create symlink to file.
		OUString aLinkName (RTL_CONSTASCII_USTRINGPARAM("demostor-1.lnk"));

		eErrCode = aFile.symlink (
			aPath, aLinkName,
			aPath, OUString::createFromAscii("demostor-1.dat"));
		OSL_POSTCOND(
			((eErrCode == store_E_None         ) ||
			 (eErrCode == store_E_AlreadyExists)    ),
			"t_store::main(): store_symlink() failed");
		if ((eErrCode == store_E_None         ) ||
			(eErrCode == store_E_AlreadyExists)    )
		{
			OUString aShortcut (
				RTL_CONSTASCII_USTRINGPARAM("Shortcut to demostor-1.dat"));
			eErrCode = aFile.rename (
				aPath, aLinkName,
				aPath, aShortcut);
			OSL_POSTCOND(
				((eErrCode == store_E_None         ) ||
				 (eErrCode == store_E_AlreadyExists)    ),
				"t_store::main(): store_rename() failed");
		}

		// Create directory.
		OUString aDirName (RTL_CONSTASCII_USTRINGPARAM("demostor-1.dir"));
		store::OStoreDirectory aDir;

		eErrCode = aDir.create (
			aFile, aPath, aDirName, store_AccessReadCreate);
		OSL_POSTCOND(
			(eErrCode == store_E_None),
			"t_store::main(): store_createDirectory() failed");
		if (eErrCode == store_E_None)
		{
#if 0  /* NYI */
			// Rename directory.
			eErrCode = aFile.rename (
				aPath, "demostor-1.dir/",
				aPath, "Renamed demostor-1.dir");
			OSL_POSTCOND(
				((eErrCode == store_E_None         ) ||
				 (eErrCode == store_E_AlreadyExists)    ),
				"t_store::main(): store_rename() failed");

			eErrCode = aFile.rename (
				aPath, "Renamed demostor-1.dir/",
				aPath, "demostor-1.dir");
			OSL_POSTCOND(
				(eErrCode == store_E_None),
				"t_store::main(): store_rename() failed");
#endif  /* NYI */
		}
	}

	// Directory iteration.
	if (nOptions & OPTION_ITER)
	{
#ifndef PROFILE
		OTime aStartTime (OTime::getSystemTime());
#endif /* PROFILE */
		OUString aEmpty;

		// Root directory.
		store::OStoreDirectory aRootDir;
		if (nOptions & OPTION_LINK)
		{
			// Open symlink entry.
			eErrCode = aRootDir.create (
				aFile, aPath, OUString::createFromAscii("000000"),
				store_AccessReadOnly);
		}
		else
		{
			// Open direct entry.
			if (nOptions & OPTION_CREAT)
				eErrCode = aRootDir.create (
					aFile, aEmpty, aEmpty, store_AccessReadCreate);
			else if (nOptions & OPTION_WRITE)
				eErrCode = aRootDir.create (
					aFile, aEmpty, aEmpty, store_AccessReadWrite);
			else
				eErrCode = aRootDir.create (
					aFile, aEmpty, aEmpty, store_AccessReadOnly);
		}

		if (eErrCode == store_E_None)
		{
			// Traverse directory tree.
			DirectoryTraveller aTraveller (
				aFile, aEmpty, aEmpty, nOptions, 0);
			aRootDir.travel (aTraveller);
		}
		else
		{
			// Failure.
			printf ("Error: can't open directory: \"/\"\n");
		}

#ifndef PROFILE
		OTime aDelta (OTime::getSystemTime() - aStartTime);

		sal_uInt32 nDelta = aDelta.Seconds * 1000000;
		nDelta += (aDelta.Nanosec / 1000);

		printf ("Total(iter): %d[usec]\n", (unsigned int)(nDelta));
#endif /* PROFILE */
	}

	if (nOptions & OPTION_PAUSE)
	{
		TimeValue tv;
		tv.Seconds = 300;
		tv.Nanosec = 0;
		osl_waitThread (&tv);
	}

	// Size.
	sal_uInt32 nSize = 0;
	aFile.getSize (nSize);

	// Done.
	aFile.close();

#if (defined(WNT) && defined(PROFILE))
	StopCAP();
	DumpCAP();
#endif /* PROFILE */
#ifndef PROFILE
	OTime aDelta (OTime::getSystemTime() - aMainStartTime);

	sal_uInt32 nDelta = aDelta.Seconds * 1000000;
	nDelta += (aDelta.Nanosec / 1000);

	printf ("Total: %d[usec]\n", (unsigned int)(nDelta));
#endif /* PROFILE */

	return 0;
}
