/**
 * SqlJetBtShared.java
 * Copyright (C) 2009-2010 TMate Software Ltd
 *
 * 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; 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.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@sqljet.com
 */
package org.tmatesoft.sqljet.core.internal.btree;

import static org.tmatesoft.sqljet.core.internal.btree.SqlJetBtree.TRACE;

import java.util.LinkedList;
import java.util.List;

import org.tmatesoft.sqljet.core.ISqlJetMutex;
import org.tmatesoft.sqljet.core.SqlJetErrorCode;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.internal.ISqlJetDbHandle;
import org.tmatesoft.sqljet.core.internal.ISqlJetFile;
import org.tmatesoft.sqljet.core.internal.ISqlJetMemoryPointer;
import org.tmatesoft.sqljet.core.internal.ISqlJetPage;
import org.tmatesoft.sqljet.core.internal.ISqlJetPager;
import org.tmatesoft.sqljet.core.internal.SqlJetUtility;
import org.tmatesoft.sqljet.core.internal.btree.SqlJetBtree.TransMode;
import org.tmatesoft.sqljet.core.internal.mutex.SqlJetEmptyMutex;

/**
 * An instance of this object represents a single database file.
 *
 * A single database file can be in use as the same time by two or more database
 * connections. When two or more connections are sharing the same database file,
 * each connection has it own private Btree object for the file and each of
 * those Btrees points to this one BtShared object. BtShared.nRef is the number
 * of connections currently sharing this database file.
 *
 * Fields in this structure are accessed under the BtShared.mutex mutex, except
 * for nRef and pNext which are accessed under the global
 * SQLITE_MUTEX_STATIC_MASTER mutex. The pPager field may not be modified once
 * it is initially set as long as nRef>0. The pSchema field may be set once
 * under BtShared.mutex and thereafter is unchanged as long as nRef>0.
 *
 * @author TMate Software Ltd.
 * @author Sergey Scherbina (sergey.scherbina@gmail.com)
 *
 */
public class SqlJetBtreeShared {

    public final static byte PTRMAP_ROOTPAGE = 1;
    public final static byte PTRMAP_FREEPAGE = 2;
    public final static byte PTRMAP_OVERFLOW1 = 3;
    public final static byte PTRMAP_OVERFLOW2 = 4;
    public final static byte PTRMAP_BTREE = 5;

    /** The page cache */
    ISqlJetPager pPager;

    /** Database connection currently using this Btree */
    ISqlJetDbHandle db;

    /** A list of all open cursors */
    SqlJetBtreeCursor pCursor;

    /** First page of the database */
    SqlJetMemPage pPage1;

    /** True if we are in a statement subtransaction */
    boolean inStmt;

    /** True if the underlying file is readonly */
    boolean readOnly;

    /** True if the page size can no longer be changed */
    boolean pageSizeFixed;

    /** True if auto-vacuum is enabled */
    boolean autoVacuum;

    /** True if incr-vacuum is enabled */
    boolean incrVacuum;

    /** Total number of bytes on a page */
    int pageSize;

    /** Number of usable bytes on each page */
    int usableSize;

    /** Maximum local payload in non-LEAFDATA tables */
    int maxLocal;

    /** Minimum local payload in non-LEAFDATA tables */
    int minLocal;

    /** Maximum local payload in a LEAFDATA table */
    int maxLeaf;

    /** Minimum local payload in a LEAFDATA table */
    int minLeaf;

    /** Transaction state */
    TransMode inTransaction = TransMode.NONE;

    /** Number of open transactions (read + write) */
    int nTransaction;

    /** Pointer to space allocated by sqlite3BtreeSchema() */
    Object pSchema;

    /** Non-recursive mutex required to access this struct */
    ISqlJetMutex mutex = new SqlJetEmptyMutex();

    /** Number of references to this structure */
    int nRef;

    /** Next on a list of sharable BtShared structs */
    SqlJetBtreeShared pNext;

    /** List of locks held on this shared-btree struct */
    List<SqlJetBtreeLock> pLock = new LinkedList<SqlJetBtreeLock>();

    /** Btree with an EXCLUSIVE lock on the whole db */
    SqlJetBtree pExclusive;

    /** BtShared.pageSize bytes of space for tmp use */
    ISqlJetMemoryPointer pTmpSpace;

    /**
     * The database page the PENDING_BYTE occupies. This page is never used.
     * TODO: This macro is very similary to PAGER_MJ_PGNO() in pager.c. They
     * should possibly be consolidated (presumably in pager.h).
     *
     * If disk I/O is omitted (meaning that the database is stored purely in
     * memory) then there is no pending byte.
     */
    public int PENDING_BYTE_PAGE() {
        return (int) (ISqlJetFile.PENDING_BYTE / pageSize) + 1;
    }

    /**
     * The following value is the maximum cell size assuming a maximum page size
     * give above.
     */
    public int MX_CELL_SIZE() {
        return (pageSize - 8);
    }

    /**
     * The maximum number of cells on a single page of the database. This
     * assumes a minimum cell size of 6 bytes (4 bytes for the cell itself plus
     * 2 bytes for the index to the cell in the page header). Such small cells
     * will be rare, but they are possible.
     */
    public int MX_CELL() {
        return ((pageSize - 8) / 6);
    }

    /**
     * These macros define the location of the pointer-map entry for a database
     * page. The first argument to each is the number of usable bytes on each
     * page of the database (often 1024). The second is the page number to look
     * up in the pointer map.
     *
     * PTRMAP_PAGENO returns the database page number of the pointer-map page
     * that stores the required pointer. PTRMAP_PTROFFSET returns the offset of
     * the requested map entry.
     *
     * If the pgno argument passed to PTRMAP_PAGENO is a pointer-map page, then
     * pgno is returned. So (pgno==PTRMAP_PAGENO(pgsz, pgno)) can be used to
     * test if pgno is a pointer-map page. PTRMAP_ISPAGE implements this test.
     *
     */
    public int PTRMAP_PAGENO(int pgno) {
        return ptrmapPageno(pgno);
    }

    boolean PTRMAP_ISPAGE(int pgno) {
        return PTRMAP_PAGENO(pgno) == pgno;
    }

    private int PTRMAP_PTROFFSET(int pgptrmap, int pgno) {
        return (5 * (pgno - pgptrmap - 1));
    }

    /**
     * Invalidate the overflow page-list cache for all cursors opened on the
     * shared btree structure pBt.
     */
    public void invalidateAllOverflowCache() {
        assert (mutex.held());
        for (SqlJetBtreeCursor p = pCursor; p != null; p = p.pNext) {
            p.aOverflow = null;
        }
    }

    /**
     * Return the size of the database file in pages. If there is any kind of
     * error, return ((unsigned int)-1).
     *
     * @throws SqlJetException
     */
    int getPageCount() throws SqlJetException {
        assert (pPage1 != null);
        return pPager.getPageCount();
    }

    /**
     * Given a page number of a regular database page, return the page number
     * for the pointer-map page that contains the entry for the input page
     * number.
     */
    private int ptrmapPageno(int pgno) {
        int nPagesPerMapPage;
        int iPtrMap, ret;
        assert (mutex.held());
        nPagesPerMapPage = (usableSize / 5) + 1;
        iPtrMap = (pgno - 2) / nPagesPerMapPage;
        ret = (iPtrMap * nPagesPerMapPage) + 2;
        if (ret == PENDING_BYTE_PAGE()) {
            ret++;
        }
        return ret;
    }

    /**
     * Write an entry into the pointer map.
     *
     * This routine updates the pointer map entry for page number 'key' so that
     * it maps to type 'eType' and parent page number 'pgno'. An error code is
     * returned if something goes wrong, otherwise SQLITE_OK.
     */
    public void ptrmapPut(int key, short eType, int parent) throws SqlJetException {
        ISqlJetPage pDbPage; /* The pointer map page */
        ISqlJetMemoryPointer pPtrmap; /* The pointer map data */
        int iPtrmap; /* The pointer map page number */
        int offset; /* Offset in pointer map page */

        assert (mutex.held());
        /*
         * The master-journal page number must never be used as a pointer map
         * page
         */
        assert (!PTRMAP_ISPAGE(PENDING_BYTE_PAGE()));

        assert (autoVacuum);
        if (key == 0) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        iPtrmap = PTRMAP_PAGENO(key);
        pDbPage = pPager.getPage(iPtrmap);
        offset = PTRMAP_PTROFFSET(iPtrmap, key);
        pPtrmap = pDbPage.getData();

        if (eType != SqlJetUtility.getUnsignedByte(pPtrmap, offset)
                || SqlJetUtility.get4byte(pPtrmap, offset + 1) != parent) {
            TRACE("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent);
            pDbPage.write();
            SqlJetUtility.putUnsignedByte(pPtrmap, offset, eType);
            SqlJetUtility.put4byte(pPtrmap, offset + 1, parent);
        }
        pDbPage.unref();
    }

    /**
     * Read an entry from the pointer map.
     *
     * This routine retrieves the pointer map entry for page 'key', writing the
     * type and parent page number to *pEType and *pPgno respectively. An error
     * code is returned if something goes wrong, otherwise SQLITE_OK.
     */
    public void ptrmapGet(int key, short[] pEType, int[] pPgno) throws SqlJetException {
        ISqlJetPage pDbPage; /* The pointer map page */
        int iPtrmap; /* Pointer map page index */
        ISqlJetMemoryPointer pPtrmap; /* Pointer map page data */
        int offset; /* Offset of entry in pointer map */

        assert (mutex.held());

        iPtrmap = PTRMAP_PAGENO(key);
        pDbPage = pPager.acquirePage(iPtrmap, true);
        pPtrmap = pDbPage.getData();

        offset = PTRMAP_PTROFFSET(iPtrmap, key);
        assert (pEType != null && pEType.length > 0);
        pEType[0] = (short) SqlJetUtility.getUnsignedByte(pPtrmap, offset);
        if (pPgno != null && pPgno.length > 0)
            pPgno[0] = SqlJetUtility.get4byte(pPtrmap, offset + 1);

        pDbPage.unref();

        if (pEType[0] < 1 || pEType[0] > 5)
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
    }

    /**
     * Convert a DbPage obtained from the pager into a MemPage used by the btree
     * layer.
     */
    private SqlJetMemPage pageFromDbPage(ISqlJetPage pDbPage, int pgno) {
        if (null == pDbPage.getExtra())
            pDbPage.setExtra(new SqlJetMemPage());
        SqlJetMemPage pPage = (SqlJetMemPage) pDbPage.getExtra();
        pPage.aData = pDbPage.getData();
        pPage.pDbPage = pDbPage;
        pPage.pBt = this;
        pPage.pgno = pgno;
        pPage.hdrOffset = (byte) (pPage.pgno == 1 ? 100 : 0);
        return pPage;
    }

    /**
     * Get a page from the pager. Initialize the MemPage.pBt and MemPage.aData
     * elements if needed.
     *
     * If the noContent flag is set, it means that we do not care about the
     * content of the page at this time. So do not go to the disk to fetch the
     * content. Just fill in the content with zeros for now. If in the future we
     * call sqlite3PagerWrite() on this page, that means we have started to be
     * concerned about content and the disk read should occur at that point.
     *
     * @param pgno
     *            Number of the page to fetch
     * @param noContent
     *            Do not load page content if true
     * @return
     * @throws SqlJetException
     */
    public SqlJetMemPage getPage(int pgno, boolean noContent) throws SqlJetException {
        ISqlJetPage pDbPage;
        assert (mutex.held());
        pDbPage = pPager.acquirePage(pgno, !noContent);
        return pageFromDbPage(pDbPage, pgno);
    }

    /**
     * Allocate a new page from the database file.
     *
     * The new page is marked as dirty. (In other words, sqlite3PagerWrite() has
     * already been called on the new page.) The new page has also been
     * referenced and the calling routine is responsible for calling
     * sqlite3PagerUnref() on the new page when it is done.
     *
     * SQLITE_OK is returned on success. Any other return value indicates an
     * error. *ppPage and *pPgno are undefined in the event of an error. Do not
     * invoke sqlite3PagerUnref() on *ppPage if an error is returned.
     *
     * If the "nearby" parameter is not 0, then a (feeble) effort is made to
     * locate a page close to the page number "nearby". This can be used in an
     * attempt to keep related pages close to each other in the database file,
     * which in turn can make database access faster.
     *
     * If the "exact" parameter is not 0, and the page-number nearby exists
     * anywhere on the free-list, then it is guarenteed to be returned. This is
     * only used by auto-vacuum databases when allocating a new table.
     *
     * @throws SqlJetException
     */
    public SqlJetMemPage allocatePage(int[] pPgno, int nearby, boolean exact) throws SqlJetException {
        SqlJetMemPage ppPage = null;
        long n; /* Number of pages on the freelist */
        int k; /* Number of leaves on the trunk of the freelist */
        SqlJetMemPage pTrunk = null;
        SqlJetMemPage pPrevTrunk = null;

        assert (mutex.held());
        n = SqlJetUtility.get4byteUnsigned(pPage1.aData, 36);
        try {
            if (n > 0) {
                /* There are pages on the freelist. Reuse one of those pages. */
                int iTrunk;
                /* If the free-list must be searched for 'nearby' */
                boolean searchList = false;

                /*
                 * If the 'exact' parameter was true and a query of the
                 * pointer-map shows that the page 'nearby' is somewhere on the
                 * free-list, then the entire-list will be searched for that
                 * page.
                 */
                if (exact && nearby <= getPageCount()) {
                    short[] eType = { 0 };
                    assert (nearby > 0);
                    assert (autoVacuum);
                    ptrmapGet(nearby, eType, null);
                    if (eType[0] == PTRMAP_FREEPAGE) {
                        searchList = true;
                    }
                    pPgno[0] = nearby;
                }

                /*
                 * Decrement the free-list count by 1. Set iTrunk to the index
                 * of the first free-list trunk page. iPrevTrunk is initially 1.
                 */
                pPage1.pDbPage.write();
                SqlJetUtility.put4byteUnsigned(pPage1.aData, 36, n - 1);

                /*
                 * The code within this loop is run only once if the
                 * 'searchList' variable is not true. Otherwise, it runs once
                 * for each trunk-page on the free-list until the page 'nearby'
                 * is located.
                 */

                do {
                    pPrevTrunk = pTrunk;
                    if (pPrevTrunk != null) {
                        iTrunk = SqlJetUtility.get4byte(pPrevTrunk.aData, 0);
                    } else {
                        iTrunk = SqlJetUtility.get4byte(pPage1.aData, 32);
                    }

                    try {
                        pTrunk = getPage(iTrunk, false);
                    } catch (SqlJetException e) {
                        pTrunk = null;
                        throw e;
                    }

                    k = SqlJetUtility.get4byte(pTrunk.aData, 4);
                    if (k == 0 && !searchList) {
                        /*
                         * The trunk has no leaves and the list is not being
                         * searched. So extract the trunk page itself and use it
                         * as the newly allocated page
                         */
                        assert (pPrevTrunk == null);
                        pTrunk.pDbPage.write();
                        pPgno[0] = iTrunk;
                        SqlJetUtility.memcpy(pPage1.aData, 32, pTrunk.aData, 0, 4);
                        ppPage = pTrunk;
                        pTrunk = null;
                        TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno[0], n - 1);
                    } else if (k > usableSize / 4 - 2) {
                        /* Value of k is out of range. Database corruption */
                        throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                    } else if (searchList && nearby == iTrunk) {
                        /*
                         * The list is being searched and this trunk page is the
                         * page to allocate, regardless of whether it has
                         * leaves.
                         */
                        assert (pPgno[0] == iTrunk);
                        ppPage = pTrunk;
                        searchList = false;
                        pTrunk.pDbPage.write();
                        if (k == 0) {
                            if (pPrevTrunk == null) {
                                SqlJetUtility.memcpy(pPage1.aData, 32, pTrunk.aData, 0, 4);
                            } else {
                                SqlJetUtility.memcpy(pPrevTrunk.aData, 0, pTrunk.aData, 0, 4);
                            }
                        } else {
                            /*
                             * The trunk page is required by the caller but it
                             * contains pointers to free-list leaves. The first
                             * leaf becomes a trunk page in this case.
                             */
                            SqlJetMemPage pNewTrunk;
                            int iNewTrunk = SqlJetUtility.get4byte(pTrunk.aData, 8);
                            pNewTrunk = getPage(iNewTrunk, false);
                            try {
                                pNewTrunk.pDbPage.write();
                            } catch (SqlJetException e) {
                                SqlJetMemPage.releasePage(pNewTrunk);
                                throw e;
                            }
                            SqlJetUtility.memcpy(pNewTrunk.aData, 0, pTrunk.aData, 0, 4);
                            SqlJetUtility.put4byte(pNewTrunk.aData, 4, k - 1);
                            SqlJetUtility.memcpy(pNewTrunk.aData, 8, pTrunk.aData, 12, (k - 1) * 4);
                            SqlJetMemPage.releasePage(pNewTrunk);
                            if (pPrevTrunk == null) {
                                SqlJetUtility.put4byte(pPage1.aData, 32, iNewTrunk);
                            } else {
                                pPrevTrunk.pDbPage.write();
                                SqlJetUtility.put4byte(pPrevTrunk.aData, 0, iNewTrunk);
                            }
                        }
                        pTrunk = null;
                        TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno[0], n - 1);
                    } else {
                        /* Extract a leaf from the trunk */
                        int closest;
                        int iPage;
                        ISqlJetMemoryPointer aData = pTrunk.aData;
                        pTrunk.pDbPage.write();
                        if (nearby > 0) {
                            int i, dist;
                            closest = 0;
                            dist = SqlJetUtility.get4byte(aData, 8) - nearby;
                            if (dist < 0)
                                dist = -dist;
                            for (i = 1; i < k; i++) {
                                int d2 = SqlJetUtility.get4byte(aData, 8 + i * 4) - nearby;
                                if (d2 < 0)
                                    d2 = -d2;
                                if (d2 < dist) {
                                    closest = i;
                                    dist = d2;
                                }
                            }
                        } else {
                            closest = 0;
                        }

                        iPage = SqlJetUtility.get4byte(aData, 8 + closest * 4);
                        if (!searchList || iPage == nearby) {
                            int nPage;
                            pPgno[0] = iPage;
                            nPage = getPageCount();
                            if (pPgno[0] > nPage) {
                                /* Free page off the end of the file */
                                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                            }
                            TRACE("ALLOCATE: %d was leaf %d of %d on trunk %d" + ": %d more free pages\n", pPgno[0],
                                    closest + 1, k, pTrunk.pgno, n - 1);
                            if (closest < k - 1) {
                                SqlJetUtility.memcpy(aData, 8 + closest * 4, aData, 4 + k * 4, 4);
                            }
                            SqlJetUtility.put4byte(aData, 4, k - 1);
                            ppPage = getPage(pPgno[0], true);
                            ppPage.pDbPage.dontRollback();
                            try {
                                ppPage.pDbPage.write();
                            } catch (SqlJetException e) {
                                SqlJetMemPage.releasePage(ppPage);
                            }
                            searchList = false;
                        }
                    }
                    SqlJetMemPage.releasePage(pPrevTrunk);
                    pPrevTrunk = null;
                } while (searchList);

            } else {
                /*
                 * There are no pages on the freelist, so create a new page at
                 * the end of the file
                 */
                int nPage = getPageCount();
                pPgno[0] = nPage + 1;

                if (autoVacuum && PTRMAP_ISPAGE(pPgno[0])) {
                    /*
                     * IfpPgno refers to a pointer-map page, allocate two new
                     * pages at the end of the file instead of one. The first
                     * allocated page becomes a new pointer-map page, the second
                     * is used by the caller.
                     */
                    TRACE("ALLOCATE: %d from end of file (pointer-map page)\n", pPgno[0]);
                    assert (pPgno[0] != PENDING_BYTE_PAGE());
                    pPgno[0]++;
                    if (pPgno[0] == PENDING_BYTE_PAGE()) {
                        pPgno[0]++;
                    }
                }

                assert (pPgno[0] != PENDING_BYTE_PAGE());
                ppPage = getPage(pPgno[0], false);
                try {
                    ppPage.pDbPage.write();
                } catch (SqlJetException e) {
                    SqlJetMemPage.releasePage(ppPage);
                }
                TRACE("ALLOCATE: %d from end of file\n", pPgno[0]);
            }

            assert (pPgno[0] != PENDING_BYTE_PAGE());

        } finally {
            // end_allocate_page:
            SqlJetMemPage.releasePage(pTrunk);
            SqlJetMemPage.releasePage(pPrevTrunk);
        }

        if (ppPage.pDbPage.getRefCount() > 1) {
            SqlJetMemPage.releasePage(ppPage);
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        ppPage.isInit = false;
        return ppPage;

    }

    /**
     * Move the open database page pDbPage to location iFreePage in the
     * database. The pDbPage reference remains valid.
     *
     * @param pDbPage
     *            Open page to move
     * @param s
     *            Pointer map 'type' entry for pDbPage
     * @param iPtrPage
     *            Pointer map 'page-no' entry for pDbPage
     * @param iFreePage
     *            The location to move pDbPage to
     * @param isCommit
     * @throws SqlJetException
     */
    public void relocatePage(SqlJetMemPage pDbPage, short s, int iPtrPage, int iFreePage, boolean isCommit)
            throws SqlJetException {
        /* The page that contains a pointer to pDbPage */
        SqlJetMemPage pPtrPage;
        int iDbPage = pDbPage.pgno;

        assert (s == PTRMAP_OVERFLOW2 || s == PTRMAP_OVERFLOW1 || s == PTRMAP_BTREE || s == PTRMAP_ROOTPAGE);
        assert (mutex.held());
        assert (pDbPage.pBt == this);

        /* Move page iDbPage from its current location to page number iFreePage */

        TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", iDbPage, iFreePage, iPtrPage, s);
        pDbPage.pDbPage.move(iFreePage, isCommit);
        pDbPage.pgno = iFreePage;

        /*
         * If pDbPage was a btree-page, then it may have child pages and/or
         * cells that point to overflow pages. The pointer map entries for all
         * these pages need to be changed.
         *
         * If pDbPage is an overflow page, then the first 4 bytes may store a
         * pointer to a subsequent overflow page. If this is the case, then the
         * pointer map needs to be updated for the subsequent overflow page.
         */
        if (s == PTRMAP_BTREE || s == PTRMAP_ROOTPAGE) {
            pDbPage.setChildPtrmaps();
        } else {
            int nextOvfl = SqlJetUtility.get4byte(pDbPage.aData);
            if (nextOvfl != 0) {
                ptrmapPut(nextOvfl, PTRMAP_OVERFLOW2, iFreePage);
            }
        }

        /*
         * Fix the database pointer on page iPtrPage that pointed at iDbPage so
         * that it points at iFreePage. Also fix the pointer map entry for
         * iPtrPage.
         */
        if (s != PTRMAP_ROOTPAGE) {
            pPtrPage = getPage(iPtrPage, false);
            try {
                pPtrPage.pDbPage.write();
            } catch (SqlJetException e) {
                SqlJetMemPage.releasePage(pPtrPage);
                throw e;
            }
            try {
                pPtrPage.modifyPagePointer(iDbPage, iFreePage, s);
            } finally {
                SqlJetMemPage.releasePage(pPtrPage);
            }
            ptrmapPut(iFreePage, s, iPtrPage);
        }
    }

    /**
     * Perform a single step of an incremental-vacuum. If successful, return
     * SQLITE_OK. If there is no work to do (and therefore no point in calling
     * this function again), return SQLITE_DONE.
     *
     * More specificly, this function attempts to re-organize the database so
     * that the last page of the file currently in use is no longer in use.
     *
     * If the nFin parameter is non-zero, the implementation assumes that the
     * caller will keep calling incrVacuumStep() until it returns SQLITE_DONE or
     * an error, and that nFin is the number of pages the database file will
     * contain after this process is complete.
     *
     * @throws SqlJetException
     */
    public void incrVacuumStep(int nFin, int iLastPg) throws SqlJetException {
        int nFreeList; /* Number of pages still on the free-list */

        assert (mutex.held());

        if (!PTRMAP_ISPAGE(iLastPg) && iLastPg != PENDING_BYTE_PAGE()) {
            short[] eType = { 0 };
            int[] iPtrPage = { 0 };

            nFreeList = SqlJetUtility.get4byte(pPage1.aData, 36);
            if (nFreeList == 0 || nFin == iLastPg) {
                throw new SqlJetException(SqlJetErrorCode.DONE);
            }

            ptrmapGet(iLastPg, eType, iPtrPage);
            if (eType[0] == PTRMAP_ROOTPAGE) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }

            if (eType[0] == PTRMAP_FREEPAGE) {
                if (nFin == 0) {
                    /*
                     * Remove the page from the files free-list. This is not
                     * required if nFin is non-zero. In that case, the free-list
                     * will be truncated to zero after this function returns, so
                     * it doesn't matter if it still contains some garbage
                     * entries.
                     */
                    int[] iFreePg = new int[1];
                    SqlJetMemPage pFreePg;
                    pFreePg = allocatePage(iFreePg, iLastPg, true);
                    assert (iFreePg[0] == iLastPg);
                    SqlJetMemPage.releasePage(pFreePg);
                }
            } else {
                /* Index of free page to move pLastPg to */
                int[] iFreePg = new int[1];
                SqlJetMemPage pLastPg;

                pLastPg = getPage(iLastPg, false);

                /*
                 * If nFin is zero, this loop runs exactly once and page pLastPg
                 * is swapped with the first free page pulled off the free list.
                 *
                 * On the other hand, if nFin is greater than zero, then keep
                 * looping until a free-page located within the first nFin pages
                 * of the file is found.
                 */
                do {
                    SqlJetMemPage pFreePg;
                    try {
                        pFreePg = allocatePage(iFreePg, 0, false);
                    } catch (SqlJetException e) {
                        SqlJetMemPage.releasePage(pLastPg);
                        throw e;
                    }
                    SqlJetMemPage.releasePage(pFreePg);
                } while (nFin != 0 && iFreePg[0] > nFin);
                assert (iFreePg[0] < iLastPg);
                pLastPg.pDbPage.write();
                try {
                    relocatePage(pLastPg, eType[0], iPtrPage[0], iFreePg[0], nFin != 0);
                } finally {
                    SqlJetMemPage.releasePage(pLastPg);
                }
            }
        }

        if (nFin == 0) {
            iLastPg--;
            while (iLastPg == PENDING_BYTE_PAGE() || PTRMAP_ISPAGE(iLastPg)) {
                iLastPg--;
            }
            pPager.truncateImage(iLastPg);
        }

    }

    /**
     * This routine is called prior to sqlite3PagerCommit when a transaction is
     * commited for an auto-vacuum database.
     *
     * If SQLITE_OK is returned, then *pnTrunc is set to the number of pages the
     * database file should be truncated to during the commit process. i.e. the
     * database has been reorganized so that only the first *pnTrunc pages are
     * in use.
     *
     */
    public void autoVacuumCommit() throws SqlJetException {

        int nref = pPager.getRefCount();

        assert (mutex.held());
        invalidateAllOverflowCache();
        assert (autoVacuum);
        if (!incrVacuum) {
            int nFin;
            int nFree;
            int nPtrmap;
            int iFree;
            final int pgsz = pageSize;
            int nOrig = getPageCount();

            if (PTRMAP_ISPAGE(nOrig)) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            if (nOrig == PENDING_BYTE_PAGE()) {
                nOrig--;
            }
            nFree = SqlJetUtility.get4byte(pPage1.aData, 36);
            nPtrmap = (nFree - nOrig + PTRMAP_PAGENO(nOrig) + pgsz / 5) / (pgsz / 5);
            nFin = nOrig - nFree - nPtrmap;
            if (nOrig > PENDING_BYTE_PAGE() && nFin <= PENDING_BYTE_PAGE()) {
                nFin--;
            }
            while (PTRMAP_ISPAGE(nFin) || nFin == PENDING_BYTE_PAGE()) {
                nFin--;
            }

            try {

                try {
                    for (iFree = nOrig; iFree > nFin; iFree--) {
                        incrVacuumStep(nFin, iFree);
                    }
                } catch (SqlJetException e) {
                    if (e.getErrorCode() != SqlJetErrorCode.DONE)
                        throw e;
                }

                if (nFree > 0) {
                    pPage1.pDbPage.write();
                    SqlJetUtility.put4byte(pPage1.aData, 32, 0);
                    SqlJetUtility.put4byte(pPage1.aData, 36, 0);
                    pPager.truncateImage(nFin);
                }

            } catch (SqlJetException e) {
                pPager.rollback();
                throw e;
            }

        }

        assert (nref == pPager.getRefCount());
    }

    /**
     * If there are no outstanding cursors and we are not in the middle of a
     * transaction but there is a read lock on the database, then this routine
     * unrefs the first page of the database file which has the effect of
     * releasing the read lock.
     *
     * If there are any outstanding cursors, this routine is a no-op.
     *
     * If there is a transaction in progress, this routine is a no-op.
     *
     * @throws SqlJetException
     */
    public void unlockBtreeIfUnused() throws SqlJetException {
        assert (mutex.held());
        if (inTransaction == TransMode.NONE && pCursor == null && pPage1 != null) {
            if (pPager.getRefCount() >= 1) {
                assert (pPage1.aData != null);
                SqlJetMemPage.releasePage(pPage1);
            }
            pPage1 = null;
            inStmt = false;
        }
    }

    /**
     * Save the positions of all cursors except pExcept open on the table with
     * root-page iRoot. Usually, this is called just before cursor pExcept is
     * used to modify the table (BtreeDelete() or BtreeInsert()).
     *
     * @param i
     * @param j
     * @throws SqlJetException
     */
    public boolean saveAllCursors(int iRoot, SqlJetBtreeCursor pExcept) throws SqlJetException {
        SqlJetBtreeCursor p;
        assert (mutex.held());
        assert (pExcept == null || pExcept.pBt == this);
        for (p = this.pCursor; p != null; p = p.pNext) {
            if (p != pExcept && (0 == iRoot || p.pgnoRoot == iRoot) && p.eState == SqlJetBtreeCursor.CursorState.VALID) {
                if (!p.saveCursorPosition())
                    return false;
            }
        }
        return true;
    }

    /**
     * Return the number of write-cursors open on this handle. This is for use
     * in assert() expressions, so it is only compiled if NDEBUG is not defined.
     *
     * For the purposes of this routine, a write-cursor is any cursor that is
     * capable of writing to the databse. That means the cursor was originally
     * opened for writing and the cursor has not be disabled by having its state
     * changed to CURSOR_FAULT.
     *
     * @return
     */
    public int countWriteCursors() {
        SqlJetBtreeCursor pCur;
        int r = 0;
        for (pCur = this.pCursor; pCur != null; pCur = pCur.pNext) {
            if (pCur.wrFlag && pCur.eState != SqlJetBtreeCursor.CursorState.FAULT)
                r++;
        }
        return r;
    }

    /**
     * Erase the given database page and all its children. Return the page to
     * the freelist.
     *
     * @param pgno
     * @param freePageFlag
     *            Page number to clear
     * @param pnChange
     *            Deallocate page if true
     *
     * @throws SqlJetException
     */
    public void clearDatabasePage(int pgno, boolean freePageFlag, int[] pnChange) throws SqlJetException {
        SqlJetMemPage pPage = null;
        ISqlJetMemoryPointer pCell;
        int i;

        assert (mutex.held());
        if (pgno > pPager.getPageCount()) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }

        try {

            pPage = getAndInitPage(pgno);
            for (i = 0; i < pPage.nCell; i++) {
                pCell = pPage.findCell(i);
                if (!pPage.leaf) {
                    clearDatabasePage(SqlJetUtility.get4byte(pCell), true, pnChange);
                }
                pPage.clearCell(pCell);
            }
            if (!pPage.leaf) {
                clearDatabasePage(SqlJetUtility.get4byte(pPage.aData, 8), true, pnChange);
            } else if (pnChange != null) {
                assert (pPage.intKey);
                pnChange[0] += pPage.nCell;
            }
            if (freePageFlag) {
                pPage.freePage();
            } else {
                pPage.pDbPage.write();
                pPage.zeroPage(SqlJetUtility.getUnsignedByte(pPage.aData, 0) | SqlJetMemPage.PTF_LEAF);
            }

        } finally {
            // cleardatabasepage_out:
            SqlJetMemPage.releasePage(pPage);
        }

    }

    /**
     * Get a page from the pager and initialize it. This routine* is just a
     * convenience wrapper around separate calls to* sqlite3BtreeGetPage() and
     * sqlite3BtreeInitPage().
     *
     * @param pgno
     *            Number of the page to get
     * @return
     * @throws SqlJetException
     */
    SqlJetMemPage getAndInitPage(int pgno) throws SqlJetException {

        ISqlJetPage pDbPage = null;
        SqlJetMemPage pPage = null;

        assert (mutex.held());
        if (pgno == 0) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }

        /*
         * It is often the case that the page we want is already in cache.* If
         * so, get it directly. This saves us from having to call*
         * pagerPagecount() to make sure pgno is within limits, which results*
         * in a measureable performance improvements.
         */
        try {
            pDbPage = pPager.lookupPage(pgno);
            if (pDbPage != null) {
                /* Page is already in cache */
                pPage = pageFromDbPage(pDbPage, pgno);
            } else {
                /* Page not in cache. Acquire it. */
                if (pgno > pPager.getPageCount()) {
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                pPage = getPage(pgno, false);
            }
            if (!pPage.isInit) {
                pPage.initPage();
            }
        } catch (SqlJetException e) {
            SqlJetMemPage.releasePage(pPage);
            throw e;
        }

        return pPage;

    }

    /**
     * Given the page number of an overflow page in the database (parameter
     * ovfl), this function finds the page number of the next page in the linked
     * list of overflow pages. If possible, it uses the auto-vacuum pointer-map
     * data instead of reading the content of page ovfl to do so.
     *
     * If an error occurs an SQLite error code is returned. Otherwise:
     *
     * Unless pPgnoNext is NULL, the page number of the next overflow page in
     * the linked list is written to *pPgnoNext. If page ovfl is the last page
     * in its linked list, *pPgnoNext is set to zero.
     *
     * If ppPage is not NULL, *ppPage is set to the MemPage* handle for page
     * ovfl. The underlying pager page may have been requested with the
     * noContent flag set, so the page data accessable via this handle may not
     * be trusted.
     *
     * @param ovfl
     *            Overflow page
     * @param ppPage
     *            OUT: MemPage handle
     * @param pPgnoNext
     *            OUT: Next overflow page number
     *
     * @throws SqlJetException
     */
    public void getOverflowPage(int ovfl, SqlJetMemPage[] ppPage, int[] pPgnoNext) throws SqlJetException {
        int next = 0;

        assert (mutex.held());
        /* One of these must not be NULL. Otherwise, why call this function? */
        assert ((ppPage != null && ppPage.length != 0) || (pPgnoNext != null && pPgnoNext.length != 0));

        /*
         * If pPgnoNext is NULL, then this function is being called to obtain a
         * MemPage reference only. No page-data is required in this case.
         */
        if (pPgnoNext == null || pPgnoNext.length == 0 || pPgnoNext[0] == 0) {
            ppPage[0] = getPage(ovfl, true);
            return;
        }

        /*
         * Try to find the next page in the overflow list using the autovacuum
         * pointer-map pages. Guess that the next page in the overflow list is
         * page number (ovfl+1). If that guess turns out to be wrong, fall back
         * to loading the data of page number ovfl to determine the next page
         * number.
         */
        if (autoVacuum) {

            int[] pgno = { 0 };
            int iGuess = ovfl + 1;
            short[] eType = { 0 };

            while (PTRMAP_ISPAGE(iGuess) || iGuess == PENDING_BYTE_PAGE()) {
                iGuess++;
            }

            if (iGuess <= pPager.getPageCount()) {
                ptrmapGet(iGuess, eType, pgno);
                if (eType[0] == PTRMAP_OVERFLOW2 && pgno[0] == ovfl) {
                    next = iGuess;
                }
            }
        }

        if (next == 0 || (ppPage != null && ppPage.length != 0)) {
            SqlJetMemPage pPage = null;

            try {
                pPage = getPage(ovfl, next != 0);
            } finally {
                if (next == 0 && pPage != null) {
                    next = SqlJetUtility.get4byte(pPage.aData, 0);
                }

                if (ppPage != null && ppPage.length != 0) {
                    ppPage[0] = pPage;
                } else {
                    SqlJetMemPage.releasePage(pPage);
                }
            }

        }

        pPgnoNext[0] = next;

    }

    /**
     * Make sure pBt->pTmpSpace points to an allocation of MX_CELL_SIZE(pBt)
     * bytes.
     *
     */
    public void allocateTempSpace() {
        if (pTmpSpace == null) {
            pTmpSpace = SqlJetUtility.allocatePtr(pageSize);
        }
    }

    /**
    * If the cell pCell, part of page pPage contains a pointer
    * to an overflow page, insert an entry into the pointer-map
    * for the overflow page.
    *
    * @throws SqlJetException
    */
	public void ptrmapPutOvflPtr(SqlJetMemPage pPage, ISqlJetMemoryPointer pCell) throws SqlJetException {
		  SqlJetBtreeCellInfo info;
		  assert( pCell!=null );
		  info = pPage.parseCellPtr(pCell);
		  assert( (info.nData+(pPage.intKey?0:info.nKey))==info.nPayload );
		  if( info.iOverflow>0 ){
		    int ovfl = SqlJetUtility.get4byte(pCell.getMoved(info.iOverflow));
		    pPage.pBt.ptrmapPut(ovfl, PTRMAP_OVERFLOW1, pPage.pgno);
		  }
	}

}
