using System.Diagnostics;
using System.Text;
using HANDLE = System.IntPtr;
using i64 = System.Int64;

using sqlite3_int64 = System.Int64;

using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
	public partial class Sqlite3
	{
		/*
		** 2005 November 29
		**
		** The author disclaims copyright to this source code.  In place of
		** a legal notice, here is a blessing:
		**
		**    May you do good and not evil.
		**    May you find forgiveness for yourself and forgive others.
		**    May you share freely, never taking more than you give.
		**
		******************************************************************************
		**
		** This file contains OS interface code that is common to all
		** architectures.
		*************************************************************************
		**  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
		**  C#-SQLite is an independent reimplementation of the SQLite software library
		**
		**  SQLITE_SOURCE_ID: 2010-12-07 20:14:09 a586a4deeb25330037a49df295b36aaf624d0f45
		**
		*************************************************************************
		*/
		//#define _SQLITE_OS_C_ 1
		//#include "sqliteInt.h"
		//#undef _SQLITE_OS_C_

		/*
		** The default SQLite sqlite3_vfs implementations do not allocate
		** memory (actually, os_unix.c allocates a small amount of memory
		** from within OsOpen()), but some third-party implementations may.
		** So we test the effects of a malloc() failing and the sqlite3OsXXX()
		** function returning SQLITE_IOERR_NOMEM using the DO_OS_MALLOC_TEST macro.
		**
		** The following functions are instrumented for malloc() failure
		** testing:
		**
		**     sqlite3OsOpen()
		**     sqlite3OsRead()
		**     sqlite3OsWrite()
		**     sqlite3OsSync()
		**     sqlite3OsLock()
		**
		*/
#if (SQLITE_TEST)
    static int sqlite3_memdebug_vfs_oom_test = 1;

    //#define DO_OS_MALLOC_TEST(x)                                       \
    //if (sqlite3_memdebug_vfs_oom_test && (!x || !sqlite3IsMemJournal(x))) {  \
    //  void *pTstAlloc = sqlite3Malloc(10);                             \
    //  if (!pTstAlloc) return SQLITE_IOERR_NOMEM;                       \
    //  sqlite3_free(pTstAlloc);                                         \
    //}
    static void DO_OS_MALLOC_TEST( sqlite3_file x )
    {
    }
#else

		//#define DO_OS_MALLOC_TEST(x)
		private static void DO_OS_MALLOC_TEST(sqlite3_file x)
		{
		}

#endif

		/*
** The following routines are convenience wrappers around methods
** of the sqlite3_file object.  This is mostly just syntactic sugar. All
** of this would be completely automatic if SQLite were coded using
** C++ instead of plain old C.
*/

		private static int sqlite3OsClose(sqlite3_file pId)
		{
			int rc = SQLITE_OK;
			if (pId.pMethods != null)
			{
				rc = pId.pMethods.xClose(pId);
				pId.pMethods = null;
			}
			return rc;
		}

		private static int sqlite3OsRead(sqlite3_file id, byte[] pBuf, int amt, i64 offset)
		{
			DO_OS_MALLOC_TEST(id);
			if (pBuf == null)
				pBuf = sqlite3Malloc(amt);
			return id.pMethods.xRead(id, pBuf, amt, offset);
		}

		private static int sqlite3OsWrite(sqlite3_file id, byte[] pBuf, int amt, i64 offset)
		{
			DO_OS_MALLOC_TEST(id);
			return id.pMethods.xWrite(id, pBuf, amt, offset);
		}

		private static int sqlite3OsTruncate(sqlite3_file id, i64 size)
		{
			return id.pMethods.xTruncate(id, size);
		}

		private static int sqlite3OsSync(sqlite3_file id, int flags)
		{
			DO_OS_MALLOC_TEST(id);
			return id.pMethods.xSync(id, flags);
		}

		private static int sqlite3OsFileSize(sqlite3_file id, ref long pSize)
		{
			return id.pMethods.xFileSize(id, ref pSize);
		}

		private static int sqlite3OsLock(sqlite3_file id, int lockType)
		{
			DO_OS_MALLOC_TEST(id);
			return id.pMethods.xLock(id, lockType);
		}

		private static int sqlite3OsUnlock(sqlite3_file id, int lockType)
		{
			return id.pMethods.xUnlock(id, lockType);
		}

		private static int sqlite3OsCheckReservedLock(sqlite3_file id, ref int pResOut)
		{
			DO_OS_MALLOC_TEST(id);
			return id.pMethods.xCheckReservedLock(id, ref pResOut);
		}

		private static int sqlite3OsFileControl(sqlite3_file id, u32 op, ref sqlite3_int64 pArg)
		{
			return id.pMethods.xFileControl(id, (int)op, ref pArg);
		}

		private static int sqlite3OsSectorSize(sqlite3_file id)
		{
			dxSectorSize xSectorSize = id.pMethods.xSectorSize;
			return (xSectorSize != null ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
		}

		private static int sqlite3OsDeviceCharacteristics(sqlite3_file id)
		{
			return id.pMethods.xDeviceCharacteristics(id);
		}

		private static int sqlite3OsShmLock(sqlite3_file id, int offset, int n, int flags)
		{
			return id.pMethods.xShmLock(id, offset, n, flags);
		}

		private static void sqlite3OsShmBarrier(sqlite3_file id)
		{
			id.pMethods.xShmBarrier(id);
		}

		private static int sqlite3OsShmUnmap(sqlite3_file id, int deleteFlag)
		{
			return id.pMethods.xShmUnmap(id, deleteFlag);
		}

		private static int sqlite3OsShmMap(
	sqlite3_file id,              /* Database file handle */
	int iPage,
	int pgsz,
	int bExtend,                  /* True to extend file if necessary */
	out object pp                 /* OUT: Pointer to mapping */
	)
		{
			return id.pMethods.xShmMap(id, iPage, pgsz, bExtend, out pp);
		}

		/*
		** The next group of routines are convenience wrappers around the
		** VFS methods.
		*/

		private static int sqlite3OsOpen(
		sqlite3_vfs pVfs,
		string zPath,
		sqlite3_file pFile,
		int flags,
		ref int pFlagsOut
		)
		{
			int rc;
			DO_OS_MALLOC_TEST(null);
			/* 0x87f3f is a mask of SQLITE_OPEN_ flags that are valid to be passed
			** down into the VFS layer.  Some SQLITE_OPEN_ flags (for example,
			** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before
			** reaching the VFS. */
			rc = pVfs.xOpen(pVfs, zPath, pFile, flags & 0x87f3f, out pFlagsOut);
			Debug.Assert(rc == SQLITE_OK || pFile.pMethods == null);
			return rc;
		}

		private static int sqlite3OsDelete(sqlite3_vfs pVfs, string zPath, int dirSync)
		{
			return pVfs.xDelete(pVfs, zPath, dirSync);
		}

		private static int sqlite3OsAccess(sqlite3_vfs pVfs, string zPath, int flags, ref int pResOut)
		{
			DO_OS_MALLOC_TEST(null);
			return pVfs.xAccess(pVfs, zPath, flags, out pResOut);
		}

		private static int sqlite3OsFullPathname(
		sqlite3_vfs pVfs,
		string zPath,
		int nPathOut,
		StringBuilder zPathOut
		)
		{
			zPathOut.Length = 0;//zPathOut[0] = 0;
			return pVfs.xFullPathname(pVfs, zPath, nPathOut, zPathOut);
		}

#if !SQLITE_OMIT_LOAD_EXTENSION

		private static HANDLE sqlite3OsDlOpen(sqlite3_vfs pVfs, string zPath)
		{
			return pVfs.xDlOpen(pVfs, zPath);
		}

		private static void sqlite3OsDlError(sqlite3_vfs pVfs, int nByte, string zBufOut)
		{
			pVfs.xDlError(pVfs, nByte, zBufOut);
		}

		private static object sqlite3OsDlSym(sqlite3_vfs pVfs, HANDLE pHdle, ref string zSym)
		{
			return pVfs.xDlSym(pVfs, pHdle, zSym);
		}

		private static void sqlite3OsDlClose(sqlite3_vfs pVfs, HANDLE pHandle)
		{
			pVfs.xDlClose(pVfs, pHandle);
		}

#endif

		private static int sqlite3OsRandomness(sqlite3_vfs pVfs, int nByte, byte[] zBufOut)
		{
			return pVfs.xRandomness(pVfs, nByte, zBufOut);
		}

		private static int sqlite3OsSleep(sqlite3_vfs pVfs, int nMicro)
		{
			return pVfs.xSleep(pVfs, nMicro);
		}

		private static int sqlite3OsCurrentTimeInt64(sqlite3_vfs pVfs, ref sqlite3_int64 pTimeOut)
		{
			int rc;
			/* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64()
			** method to get the current date and time if that method is available
			** (if iVersion is 2 or greater and the function pointer is not NULL) and
			** will fall back to xCurrentTime() if xCurrentTimeInt64() is
			** unavailable.
			*/
			if (pVfs.iVersion >= 2 && pVfs.xCurrentTimeInt64 != null)
			{
				rc = pVfs.xCurrentTimeInt64(pVfs, ref pTimeOut);
			}
			else
			{
				double r = 0;
				rc = pVfs.xCurrentTime(pVfs, ref r);
				pTimeOut = (sqlite3_int64)(r * 86400000.0);
			}
			return rc;
		}

		private static int sqlite3OsOpenMalloc(
		ref sqlite3_vfs pVfs,
		string zFile,
		ref sqlite3_file ppFile,
		int flags,
		ref int pOutFlags
		)
		{
			int rc = SQLITE_NOMEM;
			sqlite3_file pFile;
			pFile = new sqlite3_file(); //sqlite3Malloc(ref pVfs.szOsFile);
			if (pFile != null)
			{
				rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, ref pOutFlags);
				if (rc != SQLITE_OK)
				{
					pFile = null; // was  sqlite3DbFree(db,ref  pFile);
				}
				else
				{
					ppFile = pFile;
				}
			}
			return rc;
		}

		private static int sqlite3OsCloseFree(sqlite3_file pFile)
		{
			int rc = SQLITE_OK;
			Debug.Assert(pFile != null);
			rc = sqlite3OsClose(pFile);
			//sqlite3_free( ref pFile );
			return rc;
		}

		/*
		** This function is a wrapper around the OS specific implementation of
		** sqlite3_os_init(). The purpose of the wrapper is to provide the
		** ability to simulate a malloc failure, so that the handling of an
		** error in sqlite3_os_init() by the upper layers can be tested.
		*/

		private static int sqlite3OsInit()
		{
			//void *p = sqlite3_malloc(10);
			//if( p==null ) return SQLITE_NOMEM;
			//sqlite3_free(ref p);
			return sqlite3_os_init();
		}

		/*
		** The list of all registered VFS implementations.
		*/
		private static sqlite3_vfs vfsList;
		//#define vfsList GLOBAL(sqlite3_vfs *, vfsList)

		/*
		** Locate a VFS by name.  If no name is given, simply return the
		** first VFS on the list.
		*/
		private static bool isInit = false;

		static public sqlite3_vfs sqlite3_vfs_find(string zVfs)
		{
			sqlite3_vfs pVfs = null;
#if SQLITE_THREADSAFE
			sqlite3_mutex mutex;
#endif
#if !SQLITE_OMIT_AUTOINIT
			int rc = sqlite3_initialize();
			if (rc != 0)
				return null;
#endif
#if SQLITE_THREADSAFE
			mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
			sqlite3_mutex_enter(mutex);
			for (pVfs = vfsList; pVfs != null; pVfs = pVfs.pNext)
			{
				if (zVfs == null || zVfs == "")
					break;
				if (zVfs == pVfs.zName)
					break; //strcmp(zVfs, pVfs.zName) == null) break;
			}
			sqlite3_mutex_leave(mutex);
			return pVfs;
		}

		/*
		** Unlink a VFS from the linked list
		*/

		private static void vfsUnlink(sqlite3_vfs pVfs)
		{
			Debug.Assert(sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)));
			if (pVfs == null)
			{
				/* No-op */
			}
			else if (vfsList == pVfs)
			{
				vfsList = pVfs.pNext;
			}
			else if (vfsList != null)
			{
				sqlite3_vfs p = vfsList;
				while (p.pNext != null && p.pNext != pVfs)
				{
					p = p.pNext;
				}
				if (p.pNext == pVfs)
				{
					p.pNext = pVfs.pNext;
				}
			}
		}

		/*
		** Register a VFS with the system.  It is harmless to register the same
		** VFS multiple times.  The new VFS becomes the default if makeDflt is
		** true.
		*/

		static public int sqlite3_vfs_register(sqlite3_vfs pVfs, int makeDflt)
		{
			sqlite3_mutex mutex;
#if !SQLITE_OMIT_AUTOINIT
			int rc = sqlite3_initialize();
			if (rc != 0)
				return rc;
#endif
			mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
			sqlite3_mutex_enter(mutex);
			vfsUnlink(pVfs);
			if (makeDflt != 0 || vfsList == null)
			{
				pVfs.pNext = vfsList;
				vfsList = pVfs;
			}
			else
			{
				pVfs.pNext = vfsList.pNext;
				vfsList.pNext = pVfs;
			}
			Debug.Assert(vfsList != null);
			sqlite3_mutex_leave(mutex);
			return SQLITE_OK;
		}

		/*
		** Unregister a VFS so that it is no longer accessible.
		*/

		private static int sqlite3_vfs_unregister(sqlite3_vfs pVfs)
		{
#if SQLITE_THREADSAFE
			sqlite3_mutex mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
			sqlite3_mutex_enter(mutex);
			vfsUnlink(pVfs);
			sqlite3_mutex_leave(mutex);
			return SQLITE_OK;
		}
	}
}