using System;
using System.Diagnostics;

using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
	using MemJournal = Sqlite3.sqlite3_file;
	using sqlite3_int64 = System.Int64;

	public partial class Sqlite3
	{
		/*
		** 2007 August 22
		**
		** 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 code use to implement an in-memory rollback journal.
		** The in-memory rollback journal is used to journal transactions for
		** ":memory:" databases and when the journal_mode=MEMORY pragma is used.
		*************************************************************************
		**  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
		**
		*************************************************************************
		*/

		//#include "sqliteInt.h"

		/* Forward references to internal structures */
		//typedef struct MemJournal MemJournal;
		//typedef struct FilePoint FilePoint;
		//typedef struct FileChunk FileChunk;

		/* Space to hold the rollback journal is allocated in increments of
		** this many bytes.
		**
		** The size chosen is a little less than a power of two.  That way,
		** the FileChunk object will have a size that almost exactly fills
		** a power-of-two allocation.  This mimimizes wasted space in power-of-two
		** memory allocators.
		*/

		//#define JOURNAL_CHUNKSIZE ((int)(1024-sizeof(FileChunk*)))
		private const int JOURNAL_CHUNKSIZE = 4096;

		/* Macro to find the minimum of two numeric values.
		*/

		//#if !MIN
		//# define MIN(x,y) ((x)<(y)?(x):(y))
		//#endif
		private static int MIN(int x, int y)
		{
			return (x < y) ? x : y;
		}

		private static int MIN(int x, u32 y)
		{
			return (x < y) ? x : (int)y;
		}

		/*
		** The rollback journal is composed of a linked list of these structures.
		*/

		public class FileChunk
		{
			public FileChunk pNext;                             /* Next chunk in the journal */
			public byte[] zChunk = new byte[JOURNAL_CHUNKSIZE]; /* Content of this chunk */
		};

		/*
		** An instance of this object serves as a cursor into the rollback journal.
		** The cursor can be either for reading or writing.
		*/

		public class FilePoint
		{
			public long iOffset;           /* Offset from the beginning of the file */
			public FileChunk pChunk;      /* Specific chunk into which cursor points */
		};

		/*
		** This subclass is a subclass of sqlite3_file.  Each open memory-journal
		** is an instance of this class.
		*/

		public partial class sqlite3_file
		{
			//public sqlite3_io_methods pMethods; /* Parent class. MUST BE FIRST */
			public FileChunk pFirst;              /* Head of in-memory chunk-list */

			public FilePoint endpoint;            /* Pointer to the end of the file */
			public FilePoint readpoint;           /* Pointer to the end of the last xRead() */
		};

		/*
		** Read data from the in-memory journal file.  This is the implementation
		** of the sqlite3_vfs.xRead method.
		*/

		private static int memjrnlRead(
		sqlite3_file pJfd,     /* The journal file from which to read */
		byte[] zBuf,           /* Put the results here */
		int iAmt,              /* Number of bytes to read */
		sqlite3_int64 iOfst    /* Begin reading at this offset */
		)
		{
			MemJournal p = (MemJournal)pJfd;
			byte[] zOut = zBuf;
			int nRead = iAmt;
			int iChunkOffset;
			FileChunk pChunk;

			/* SQLite never tries to read past the end of a rollback journal file */
			Debug.Assert(iOfst + iAmt <= p.endpoint.iOffset);

			if (p.readpoint.iOffset != iOfst || iOfst == 0)
			{
				int iOff = 0;
				for (pChunk = p.pFirst;
				ALWAYS(pChunk != null) && (iOff + JOURNAL_CHUNKSIZE) <= iOfst;
				pChunk = pChunk.pNext
				)
				{
					iOff += JOURNAL_CHUNKSIZE;
				}
			}
			else
			{
				pChunk = p.readpoint.pChunk;
			}

			iChunkOffset = (int)(iOfst % JOURNAL_CHUNKSIZE);
			int izOut = 0;
			do
			{
				int iSpace = JOURNAL_CHUNKSIZE - iChunkOffset;
				int nCopy = MIN(nRead, (JOURNAL_CHUNKSIZE - iChunkOffset));
				Buffer.BlockCopy(pChunk.zChunk, iChunkOffset, zOut, izOut, nCopy); //memcpy( zOut, pChunk.zChunk[iChunkOffset], nCopy );
				izOut += nCopy;// zOut += nCopy;
				nRead -= iSpace;
				iChunkOffset = 0;
			} while (nRead >= 0 && (pChunk = pChunk.pNext) != null && nRead > 0);
			p.readpoint.iOffset = (int)(iOfst + iAmt);
			p.readpoint.pChunk = pChunk;

			return SQLITE_OK;
		}

		/*
		** Write data to the file.
		*/

		private static int memjrnlWrite(
		sqlite3_file pJfd,    /* The journal file into which to write */
		byte[] zBuf,          /* Take data to be written from here */
		int iAmt,             /* Number of bytes to write */
		sqlite3_int64 iOfst   /* Begin writing at this offset into the file */
		)
		{
			MemJournal p = (MemJournal)pJfd;
			int nWrite = iAmt;
			byte[] zWrite = zBuf;
			int izWrite = 0;

			/* An in-memory journal file should only ever be appended to. Random
			** access writes are not required by sqlite.
			*/
			Debug.Assert(iOfst == p.endpoint.iOffset);
			UNUSED_PARAMETER(iOfst);

			while (nWrite > 0)
			{
				FileChunk pChunk = p.endpoint.pChunk;
				int iChunkOffset = (int)(p.endpoint.iOffset % JOURNAL_CHUNKSIZE);
				int iSpace = MIN(nWrite, JOURNAL_CHUNKSIZE - iChunkOffset);

				if (iChunkOffset == 0)
				{
					/* New chunk is required to extend the file. */
					FileChunk pNew = new FileChunk();// sqlite3_malloc( sizeof( FileChunk ) );
					if (null == pNew)
					{
						return SQLITE_IOERR_NOMEM;
					}
					pNew.pNext = null;
					if (pChunk != null)
					{
						Debug.Assert(p.pFirst != null);
						pChunk.pNext = pNew;
					}
					else
					{
						Debug.Assert(null == p.pFirst);
						p.pFirst = pNew;
					}
					p.endpoint.pChunk = pNew;
				}

				Buffer.BlockCopy(zWrite, izWrite, p.endpoint.pChunk.zChunk, iChunkOffset, iSpace); //memcpy( &p.endpoint.pChunk.zChunk[iChunkOffset], zWrite, iSpace );
				izWrite += iSpace;//zWrite += iSpace;
				nWrite -= iSpace;
				p.endpoint.iOffset += iSpace;
			}

			return SQLITE_OK;
		}

		/*
		** Truncate the file.
		*/

		private static int memjrnlTruncate(sqlite3_file pJfd, sqlite3_int64 size)
		{
			MemJournal p = (MemJournal)pJfd;
			////FileChunk pChunk;
			Debug.Assert(size == 0);
			UNUSED_PARAMETER(size);
			////pChunk = p.pFirst;
			////while ( pChunk != null )
			////{
			////FileChunk pTmp = pChunk;
			////pChunk = pChunk.pNext;
			////sqlite3_free( ref pTmp );
			////}
			sqlite3MemJournalOpen(pJfd);
			return SQLITE_OK;
		}

		/*
		** Close the file.
		*/

		private static int memjrnlClose(MemJournal pJfd)
		{
			memjrnlTruncate(pJfd, 0);
			return SQLITE_OK;
		}

		/*
		** Sync the file.
		**
		** Syncing an in-memory journal is a no-op.  And, in fact, this routine
		** is never called in a working implementation.  This implementation
		** exists purely as a contingency, in case some malfunction in some other
		** part of SQLite causes Sync to be called by mistake.
		*/

		private static int memjrnlSync(sqlite3_file NotUsed, int NotUsed2)
		{
			UNUSED_PARAMETER2(NotUsed, NotUsed2);
			return SQLITE_OK;
		}

		/*
		** Query the size of the file in bytes.
		*/

		private static int memjrnlFileSize(sqlite3_file pJfd, ref long pSize)
		{
			MemJournal p = (MemJournal)pJfd;
			pSize = p.endpoint.iOffset;
			return SQLITE_OK;
		}

		/*
		** Table of methods for MemJournal sqlite3_file object.
		*/

		private static sqlite3_io_methods MemJournalMethods = new sqlite3_io_methods(
		1,                /* iVersion */
		(dxClose)memjrnlClose,       /* xClose */
		(dxRead)memjrnlRead,         /* xRead */
		(dxWrite)memjrnlWrite,       /* xWrite */
		(dxTruncate)memjrnlTruncate, /* xTruncate */
		(dxSync)memjrnlSync,         /* xSync */
		(dxFileSize)memjrnlFileSize, /* xFileSize */
		null,                        /* xLock */
		null,                        /* xUnlock */
		null,                        /* xCheckReservedLock */
		null,                        /* xFileControl */
		null,                        /* xSectorSize */
		null,                        /* xDeviceCharacteristics */
		null,                        /* xShmMap */
		null,                        /* xShmLock */
		null,                        /* xShmBarrier */
		null                         /* xShmUnlock */
		  );

		/*
		** Open a journal file.
		*/

		private static void sqlite3MemJournalOpen(sqlite3_file pJfd)
		{
			MemJournal p = (MemJournal)pJfd;
			//memset( p, 0, sqlite3MemJournalSize() );
			p.pFirst = null;
			p.endpoint = new FilePoint();
			p.readpoint = new FilePoint();
			p.pMethods = MemJournalMethods;//(sqlite3_io_methods*)&MemJournalMethods;
		}

		/*
		** Return true if the file-handle passed as an argument is
		** an in-memory journal
		*/

		private static bool sqlite3IsMemJournal(sqlite3_file pJfd)
		{
			return pJfd.pMethods == MemJournalMethods;
		}

		/*
		** Return the number of bytes required to store a MemJournal file descriptor.
		*/

		private static int sqlite3MemJournalSize()
		{
			return 3096; // sizeof( MemJournal );
		}
	}
}