/**
 * SqlJetMemPage.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.SqlJetUtility.get2byte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.get4byte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.getVarint;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.getVarint32;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.memcpy;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.memset;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.movePtr;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.pointer;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.put2byte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.put4byte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.putVarint;
import static org.tmatesoft.sqljet.core.internal.btree.SqlJetBtree.TRACE;

import org.tmatesoft.sqljet.core.SqlJetErrorCode;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.internal.ISqlJetConfig;
import org.tmatesoft.sqljet.core.internal.ISqlJetLimits;
import org.tmatesoft.sqljet.core.internal.ISqlJetMemoryPointer;
import org.tmatesoft.sqljet.core.internal.ISqlJetPage;
import org.tmatesoft.sqljet.core.internal.SqlJetCloneable;
import org.tmatesoft.sqljet.core.internal.SqlJetUtility;

/**
 * As each page of the file is loaded into memory, an instance of the following
 * structure is appended and initialized to zero. This structure stores
 * information about the page that is decoded from the raw file page.
 *
 * The pParent field points back to the parent page. This allows us to walk up
 * the BTree from any leaf to the root. Care must be taken to unref() the parent
 * page pointer when this page is no longer referenced. The pageDestructor()
 * routine handles that chore.
 *
 * Access to all fields of this structure is controlled by the mutex stored in
 * MemPage.pBt->mutex.
 *
 * @author TMate Software Ltd.
 * @author Sergey Scherbina (sergey.scherbina@gmail.com)
 *
 */
public class SqlJetMemPage extends SqlJetCloneable {

    /**
     * Page type flags. An ORed combination of these flags appear as the first
     * byte of on-disk image of every BTree page.
     */
    public static final byte PTF_INTKEY = 0x01;
    public static final byte PTF_ZERODATA = 0x02;
    public static final byte PTF_LEAFDATA = 0x04;
    public static final byte PTF_LEAF = 0x08;

    /** True if previously initialized. MUST BE FIRST! */
    boolean isInit;

    /** Number of overflow cell bodies in aCell[] */
    int nOverflow;

    /** True if intkey flag is set */
    boolean intKey;

    /** True if leaf flag is set */
    boolean leaf;

    /** True if this page stores data */
    boolean hasData;

    /** 100 for page 1. 0 otherwise */
    byte hdrOffset;

    /** 0 if leaf==1. 4 if leaf==0 */
    byte childPtrSize;

    /** Copy of BtShared.maxLocal or BtShared.maxLeaf */
    int maxLocal;

    /** Copy of BtShared.minLocal or BtShared.minLeaf */
    int minLocal;

    /** Index in aData of first cell pointer */
    int cellOffset;

    /** Number of free bytes on the page */
    int nFree;

    /** Number of cells on this page, local and ovfl */
    int nCell;

    /** Mask for page offset */
    int maskPage;

    static class _OvflCell extends SqlJetCloneable {

        /** Pointers to the body of the overflow cell */
        ISqlJetMemoryPointer pCell;

        /** Insert this cell before idx-th non-overflow cell */
        int idx;

    }

    /** Cells that will not fit on aData[] */
    _OvflCell[] aOvfl = new _OvflCell[] { new _OvflCell(), new _OvflCell(), new _OvflCell(), new _OvflCell(),
            new _OvflCell() };

    /** Pointer to BtShared that this page is part of */
    SqlJetBtreeShared pBt;

    /** Pointer to disk image of the page data */
    ISqlJetMemoryPointer aData;

    /** Pager page handle */
    ISqlJetPage pDbPage;

    /** Page number for this page */
    int pgno;

    /**
     * The ISAUTOVACUUM macro is used within balance_nonroot() to determine if
     * the database supports auto-vacuum or not. Because it is used within an
     * expression that is an argument to another macro (sqliteMallocRaw), it is
     * not possible to use conditional compilation. So, this macro is defined
     * instead.
     *
     * @return
     */
    private boolean ISAUTOVACUUM() {
        return pBt.autoVacuum;
    }

    /**
     * Decode the flags byte (the first byte of the header) for a page and
     * initialize fields of the MemPage structure accordingly.
     *
     * Only the following combinations are supported. Anything different
     * indicates a corrupt database files:
     *
     * <p>
     * PTF_ZERODATA
     * </p>
     * <p>
     * PTF_ZERODATA | PTF_LEAF
     * </p>
     * <p>
     * PTF_LEAFDATA | PTF_INTKEY
     * </p>
     * <p>
     * PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF
     * </p>
     */
    public void decodeFlags(int flagByte) throws SqlJetException {
        assert (hdrOffset == (pgno == 1 ? 100 : 0));
        assert (pBt.mutex.held());
        leaf = (flagByte >> 3) > 0;
        flagByte &= ~PTF_LEAF;
        childPtrSize = (byte) (4 - 4 * (leaf ? 1 : 0));
        if (flagByte == (PTF_LEAFDATA | PTF_INTKEY)) {
            intKey = true;
            hasData = leaf;
            maxLocal = pBt.maxLeaf;
            minLocal = pBt.minLeaf;
        } else if (flagByte == PTF_ZERODATA) {
            intKey = false;
            hasData = false;
            maxLocal = pBt.maxLocal;
            minLocal = pBt.minLocal;
        } else {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        return;
    }

    /**
     * Initialize the auxiliary information for a disk block.
     *
     * Return SQLITE_OK on success. If we see that the page does not contain a
     * well-formed database page, then return SQLITE_CORRUPT. Note that a return
     * of SQLITE_OK does not guarantee that the page is well-formed. It only
     * shows that we failed to detect any corruption.
     */
    public void initPage() throws SqlJetException {

        assert (pBt != null);
        assert (pBt.mutex.held());
        assert (pgno == pDbPage.getPageNumber());
        assert (this == pDbPage.getExtra());
        assert (aData.getBuffer() == pDbPage.getData().getBuffer());

        if (!isInit) {
            int pc; /* Address of a freeblock within pPage->aData[] */
            byte hdr; /* Offset to beginning of page header */
            int usableSize; /* Amount of usable space on each page */
            int cellOffset; /* Offset from start of page to first cell pointer */
            int nFree; /* Number of unused bytes on the page */
            int top; /* First byte of the cell content area */

            hdr = hdrOffset;
            decodeFlags(SqlJetUtility.getUnsignedByte(aData, hdr));
            assert (pBt.pageSize >= 512 && pBt.pageSize <= 32768);
            maskPage = pBt.pageSize - 1;
            nOverflow = 0;
            usableSize = pBt.usableSize;
            this.cellOffset = cellOffset = hdr + 12 - 4 * (leaf ? 1 : 0);
            top = get2byte(aData, hdr + 5);
            nCell = get2byte(aData, hdr + 3);
            if (nCell > pBt.MX_CELL()) {
                /* To many cells for a single page. The page must be corrupt */
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            int iCellFirst = cellOffset + 2*this.nCell;

            /* Compute the total free space on the page */
            pc = get2byte(aData, hdr + 1);
            nFree = SqlJetUtility.getUnsignedByte(aData, hdr + 7) + top;// - (cellOffset + 2 * nCell);
            while (pc > 0) {
                int next, size;
                if (pc > usableSize - 4) {
                    /* Free block is off the page */
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                next = get2byte(aData, pc);
                size = get2byte(aData, pc + 2);
                if (next > 0 && next <= pc + size + 3) {
                    /* Free blocks must be in accending order */
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                nFree += size;
                pc = next;
            }
            if (nFree > usableSize) {
                /* Free space cannot exceed total page size */
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            this.nFree = (nFree - iCellFirst);
            isInit = true;
        }
    }

    /**
     * Release a MemPage. This should be called once for each prior call to
     * sqlite3BtreeGetPage.
     *
     * @throws SqlJetException
     */
    public static void releasePage(SqlJetMemPage pPage) throws SqlJetException {
        if (pPage != null) {
            assert (pPage.aData != null);
            assert (pPage.pBt != null);
            assert (pPage.pDbPage.getExtra() == pPage);
            assert (pPage.pDbPage.getData().getBuffer() == pPage.aData.getBuffer());
            assert (pPage.pBt.mutex.held());
            pPage.pDbPage.unref();
        }
    }

    /**
     * Set the pointer-map entries for all children of page pPage. Also, if
     * pPage contains cells that point to overflow pages, set the pointer map
     * entries for the overflow pages as well.
     *
     * @throws SqlJetException
     */
    public void setChildPtrmaps() throws SqlJetException {
        int i; /* Counter variable */
        int nCell; /* Number of cells in page pPage */

        boolean isInitOrig = isInit;

        assert (pBt.mutex.held());
        try {
            initPage();
            nCell = this.nCell;

            for (i = 0; i < nCell; i++) {
                ISqlJetMemoryPointer pCell = findCell(i);

                ptrmapPutOvflPtr(pCell);

                if (!leaf) {
                    int childPgno = get4byte(pCell);
                    pBt.ptrmapPut(childPgno, SqlJetBtreeShared.PTRMAP_BTREE, pgno);
                }
            }

            if (!leaf) {
                int childPgno = get4byte(aData, hdrOffset + 8);
                pBt.ptrmapPut(childPgno, SqlJetBtreeShared.PTRMAP_BTREE, pgno);
            }
        } catch (SqlJetException e) {
            // set_child_ptrmaps_out:
            isInit = isInitOrig;
            throw e;
        }
    }

    /**
     * Somewhere on pPage, which is guarenteed to be a btree page, not an
     * overflow page, is a pointer to page iFrom. Modify this pointer so that it
     * points to iTo. Parameter eType describes the type of pointer to be
     * modified, as follows:
     *
     * PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child page
     * of pPage.
     *
     * PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an
     * overflow page pointed to by one of the cells on pPage.
     *
     * PTRMAP_OVERFLOW2: pPage is an overflow-page. The pointer points at the
     * next overflow page in the list.
     *
     * @throws SqlJetExceptionRemove
     */
    public void modifyPagePointer(int iFrom, int iTo, short s) throws SqlJetException {
        assert (pBt.mutex.held());
        if (s == SqlJetBtreeShared.PTRMAP_OVERFLOW2) {
            /* The pointer is always the first 4 bytes of the page in this case. */
            if (get4byte(aData) != iFrom) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            put4byte(aData, iTo);
        } else {
            boolean isInitOrig = this.isInit;
            int i;
            int nCell;

            initPage();
            nCell = this.nCell;

            for (i = 0; i < nCell; i++) {
                ISqlJetMemoryPointer pCell = findCell(i);
                if (s == SqlJetBtreeShared.PTRMAP_OVERFLOW1) {
                    SqlJetBtreeCellInfo info;
                    info = parseCellPtr(pCell);
                    if (info.iOverflow > 0) {
                        if (iFrom == get4byte(pCell, info.iOverflow)) {
                            put4byte(pCell, info.iOverflow, iTo);
                            break;
                        }
                    }
                } else {
                    if (get4byte(pCell) == iFrom) {
                        put4byte(pCell, iTo);
                        break;
                    }
                }
            }

            if (i == nCell) {
                if (s != SqlJetBtreeShared.PTRMAP_BTREE || get4byte(aData, hdrOffset + 8) != iFrom) {
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                put4byte(aData, hdrOffset + 8, iTo);
            }

            this.isInit = isInitOrig;
        }
    }

    /**
     * Given a btree page and a cell index (0 means the first cell on the page,
     * 1 means the second cell, and so forth) return a pointer to the cell
     * content.
     *
     * This routine works only for pages that do not contain overflow cells.
     */
    public ISqlJetMemoryPointer findCell(int i) {
        return pointer(aData, maskPage & get2byte(aData, cellOffset + 2 * i));
    }

    /**
     * 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(ISqlJetMemoryPointer pCell) throws SqlJetException {
        assert (pCell != null);
        SqlJetBtreeCellInfo info = parseCellPtr(pCell);
        assert ((info.nData + (intKey ? 0 : info.nKey)) == info.nPayload);
        if ((info.nData + (intKey ? 0 : info.nKey)) > info.nLocal) {
            int ovfl = get4byte(pCell, info.iOverflow);
            pBt.ptrmapPut(ovfl, SqlJetBtreeShared.PTRMAP_OVERFLOW1, pgno);
        }
    }

    /**
     * Parse a cell content block and fill in the CellInfo structure. There are
     * two versions of this function. sqlite3BtreeParseCell() takes a cell index
     * as the second argument and sqlite3BtreeParseCellPtr() takes a pointer to
     * the body of the cell as its second argument.
     *
     * @param pCell
     *            Pointer to the cell text.
     * @return
     */
    SqlJetBtreeCellInfo parseCellPtr(ISqlJetMemoryPointer pCell) {

        int n; /* Number bytes in cell content header */
        int[] nPayload = new int[1]; /* Number of bytes of cell payload */

        assert (pBt.mutex.held());

        SqlJetBtreeCellInfo pInfo = new SqlJetBtreeCellInfo();
        pInfo.pCell = pCell;
        n = childPtrSize;
        assert (n == 4 - 4 * (leaf ? 1 : 0));
        if (intKey) {
            if (hasData) {
                n += getVarint32(pCell, n, nPayload);
            } else {
                nPayload[0] = 0;
            }
            long[] pInfo_nKey = new long[1];
            n += getVarint(pCell, n, pInfo_nKey);
            pInfo.nKey = pInfo_nKey[0];
            pInfo.nData = nPayload[0];
        } else {
            pInfo.nData = 0;
            n += getVarint32(pCell, n, nPayload);
            pInfo.nKey = nPayload[0];
        }
        pInfo.nPayload = nPayload[0];
        pInfo.nHeader = n;
        if (nPayload[0] <= this.maxLocal) {
            /*
             * This is the (easy) common case where the entire payload fits on
             * the local page. No overflow is required.
             */
            int nSize; /* Total size of cell content in bytes */
            nSize = nPayload[0] + n;
            pInfo.nLocal = nPayload[0];
            pInfo.iOverflow = 0;
            if ((nSize & ~3) == 0) {
                nSize = 4; /* Minimum cell size is 4 */
            }
            pInfo.nSize = nSize;
        } else {
            /*
             * If the payload will not fit completely on the local page, we have
             * to decide how much to store locally and how much to spill onto
             * overflow pages. The strategy is to minimize the amount of unused
             * space on overflow pages while keeping the amount of local storage
             * in between minLocal and maxLocal.
             *
             * Warning: changing the way overflow payload is distributed in any
             * way will result in an incompatible file format.
             */
            int minLocal; /* Minimum amount of payload held locally */
            int maxLocal; /* Maximum amount of payload held locally */
            int surplus; /* Overflow payload available for local storage */

            minLocal = this.minLocal;
            maxLocal = this.maxLocal;
            surplus = minLocal + (nPayload[0] - minLocal) % (pBt.usableSize - 4);
            if (surplus <= maxLocal) {
                pInfo.nLocal = surplus;
            } else {
                pInfo.nLocal = minLocal;
            }
            pInfo.iOverflow = pInfo.nLocal + n;
            pInfo.nSize = pInfo.iOverflow + 4;
        }

        return pInfo;

    }

    /**
     * @param iCell
     *            The cell index. First cell is 0
     * @return
     */
    public SqlJetBtreeCellInfo parseCell(int iCell) {
        return parseCellPtr(findCell(iCell));
    }

    /**
     * Set up a raw page so that it looks like a database page holding no
     * entries.
     *
     * @param sqlJetBtree
     * @param flags
     * @throws SqlJetException
     */
    void zeroPage(int flags) throws SqlJetException {
        ISqlJetMemoryPointer data = aData;
        byte hdr = hdrOffset;
        int first;

        assert (pDbPage.getPageNumber() == pgno);
        assert (pDbPage.getExtra() == this);
        assert (pDbPage.getData().getBuffer() == data.getBuffer());
        assert (pBt.mutex.held());

        SqlJetUtility.putUnsignedByte(data, hdr, (short) flags);
        first = hdr + 8 + 4 * ((flags & SqlJetMemPage.PTF_LEAF) == 0 ? 1 : 0);
        // SqlJetUtility.memset(data, hdr + 1, (byte) 0, 4);
        SqlJetUtility.put4byte(data, hdr + 1, 0);
        //
        SqlJetUtility.putUnsignedByte(data, hdr + 7, (short) 0);
        SqlJetUtility.put2byte(data, hdr + 5, pBt.usableSize);
        nFree = pBt.usableSize - first;
        decodeFlags(flags);
        hdrOffset = hdr;
        cellOffset = first;
        nOverflow = 0;
        assert (pBt.pageSize >= 512 && pBt.pageSize <= 32768);
        maskPage = pBt.pageSize - 1;
        nCell = 0;
        isInit = true;
    }

    /**
     * Add a page of the database file to the freelist. unref() is NOT called
     * for pPage.
     */
    public void freePage() throws SqlJetException {
        SqlJetMemPage pPage1 = pBt.pPage1;
        int n, k;

        /* Prepare the page for freeing */
        assert (pBt.mutex.held());
        assert (this.pgno > 1);
        this.isInit = false;

        /* Increment the free page count on pPage1 */
        pPage1.pDbPage.write();
        n = get4byte(pPage1.aData, 36);
        put4byte(pPage1.aData, 36, n + 1);

        if (ISqlJetConfig.SECURE_DELETE) {
            /*
             * If the SQLITE_SECURE_DELETE compile-time option is enabled, then
             * always fully overwrite deleted information with zeros.
             */
            pDbPage.write();
            memset(aData, (byte) 0, pBt.pageSize);
        }

        /*
         * If the database supports auto-vacuum, write an entry in the
         * pointer-map to indicate that the page is free.
         */
        if (ISAUTOVACUUM()) {
            pBt.ptrmapPut(pgno, SqlJetBtreeShared.PTRMAP_FREEPAGE, 0);
        }

        if (n == 0) {
            /* This is the first free page */
            pDbPage.write();
            memset(aData, (byte) 0, 8);
            put4byte(pPage1.aData, 32, pgno);
            TRACE("FREE-PAGE: %d first\n", this.pgno);
        } else {
            /*
             * Other free pages already exist. Retrive the first trunk page* of
             * the freelist and find out how many leaves it has.
             */
            SqlJetMemPage pTrunk;
            pTrunk = pBt.getPage(get4byte(pPage1.aData, 32), false);
            k = get4byte(pTrunk.aData, 4);
            if (k >= pBt.usableSize / 4 - 8) {
                /*
                 * The trunk is full. Turn the page being freed into a new*
                 * trunk page with no leaves.** Note that the trunk page is not
                 * really full until it contains* usableSize/4 - 2 entries, not
                 * usableSize/4 - 8 entries as we have* coded. But due to a
                 * coding error in versions of SQLite prior to* 3.6.0, databases
                 * with freelist trunk pages holding more than* usableSize/4 - 8
                 * entries will be reported as corrupt. In order* to maintain
                 * backwards compatibility with older versions of SQLite,* we
                 * will contain to restrict the number of entries to
                 * usableSize/4 - 8* for now. At some point in the future (once
                 * everyone has upgraded* to 3.6.0 or later) we should consider
                 * fixing the conditional above* to read "usableSize/4-2"
                 * instead of "usableSize/4-8".
                 */
                pDbPage.write();
                put4byte(aData, pTrunk.pgno);
                put4byte(aData, 4, 0);
                put4byte(pPage1.aData, 32, pgno);
                TRACE("FREE-PAGE: %d new trunk page replacing %d\n", this.pgno, pTrunk.pgno);
            } else if (k < 0) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            } else {
                /* Add the newly freed page as a leaf on the current trunk */
                pTrunk.pDbPage.write();
                put4byte(pTrunk.aData, 4, k + 1);
                put4byte(pTrunk.aData, 8 + k * 4, pgno);
                if (ISqlJetConfig.SECURE_DELETE) {
                    pDbPage.dontWrite();
                }
                TRACE("FREE-PAGE: %d leaf on trunk page %d\n", this.pgno, pTrunk.pgno);
            }
            releasePage(pTrunk);
        }
    }

    /**
     ** Free any overflow pages associated with the given Cell.
     */
    public void clearCell(ISqlJetMemoryPointer pCell) throws SqlJetException {
        SqlJetBtreeCellInfo info;
        int[] ovflPgno = new int[1];
        int nOvfl;
        int ovflPageSize;

        assert (pBt.mutex.held());
        info = parseCellPtr(pCell);
        if (info.iOverflow == 0) {
            return; /* No overflow pages. Return without doing anything */
        }
        ovflPgno[0] = get4byte(pCell, info.iOverflow);
        ovflPageSize = pBt.usableSize - 4;
        nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1) / ovflPageSize;
        assert (ovflPgno[0] == 0 || nOvfl > 0);
        while (nOvfl-- != 0) {
            SqlJetMemPage[] pOvfl = new SqlJetMemPage[1];
            if (ovflPgno[0] <2 || ovflPgno[0] > pBt.pPager.getPageCount()) {
                /* 0 is not a legal page number and page 1 cannot be an
                 ** overflow page. Therefore if ovflPgno<2 or past the end of the
                 ** file the database must be corrupt. */
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }

            pBt.getOverflowPage(ovflPgno[0], pOvfl, (nOvfl == 0) ? null : ovflPgno);
            pOvfl[0].freePage();
            pOvfl[0].pDbPage.unref();
        }
    }

    /**
     ** Compute the total number of bytes that a Cell needs in the cell data area
     * of the btree-page. The return number includes the cell data header and
     * the local payload, but not any overflow page or the space used by the
     * cell pointer.
     */
    int cellSize(int iCell) {
        //SqlJetBtreeCellInfo info = parseCell(iCell);
        //return info.nSize;
    	return cellSizePtr(findCell(iCell));
    }

	int cellSizePtr(ISqlJetMemoryPointer pCell) {
		// SqlJetBtreeCellInfo info = parseCellPtr(pCell);
		// return info.nSize;

		final SqlJetMemPage pPage = this;

		final ISqlJetMemoryPointer pIter = pCell.getMoved(pPage.childPtrSize);
		int[] nSize = { 0 };

		if (pPage.intKey) {
			if (pPage.hasData) {
				pIter.movePointer(SqlJetUtility.getVarint32(pIter, nSize));
			} else {
				nSize[0] = 0;
			}

			/*
			 * pIter now points at the 64-bit integer key value, a variable
			 * length* integer. The following block moves pIter to point at the
			 * first byte* past the end of the key value.
			 */
			int pEnd = pIter.getPointer() + 9;
			// while( (*pIter++)&0x80 && pIter<pEnd );
			while (pIter.getPointer() < pEnd) {
				int b = pIter.getByteUnsigned();
				pIter.movePointer(1);
				if ((b & 0x80) == 0) {
					break;
				}
			}
		} else {
			pIter.movePointer(SqlJetUtility.getVarint32(pIter, nSize));
		}

		if (nSize[0] > pPage.maxLocal) {
			int minLocal = pPage.minLocal;
			nSize[0] = minLocal + (nSize[0] - minLocal)
					% (pPage.pBt.usableSize - 4);
			if (nSize[0] > pPage.maxLocal) {
				nSize[0] = minLocal;
			}
			nSize[0] += 4;
		}
		nSize[0] += (pIter.getPointer() - pCell.getPointer());

		/* The minimum size of any cell is 4 bytes. */
		if (nSize[0] < 4) {
			nSize[0] = 4;
		}

		return nSize[0];
	}


    /**
     * Remove the i-th cell from pPage. This routine effects pPage only. The
     * cell content is not freed or deallocated. It is assumed that the cell
     * content has been copied someplace else. This routine just removes the
     * reference to the cell from pPage.
     *
     * "sz" must be the number of bytes in the cell.
     *
     * @param idx
     * @param sz
     * @throws SqlJetException
     */
    public void dropCell(int idx, int sz) throws SqlJetException {

        final SqlJetMemPage pPage = this;

        int pc; /* Offset to cell content of cell being deleted */
        ISqlJetMemoryPointer data; /* pPage->aData */
        ISqlJetMemoryPointer ptr; /* Used to move bytes around within data[] */

        assert (idx >= 0 && idx < pPage.nCell);
        assert (sz == pPage.cellSize(idx));
        assert (pPage.pBt.mutex.held());
        data = pPage.aData;
        ptr = pointer(data, pPage.cellOffset + 2 * idx);
        pc = get2byte(ptr);
        if ((pc < pPage.hdrOffset + 6 + (pPage.leaf ? 0 : 4)) || (pc + sz > pPage.pBt.usableSize)) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        pPage.freeSpace(pc, sz);
        final ISqlJetMemoryPointer endPtr = pointer(data, pPage.cellOffset + 2*pPage.nCell - 2);
        while( ptr.getPointer() < endPtr.getPointer() ){
            put2byte(ptr, get2byte(ptr, 2));
            movePtr(ptr, 2);
        }
        //put2byte(endPtr, 0);
        pPage.nCell--;
        put2byte(data, pPage.hdrOffset + 3, pPage.nCell);
        pPage.nFree += 2;
    }

    /*
     * * Return a section of the pPage->aData to the freelist.* The first byte
     * of the new free block is pPage->aDisk[start]* and the size of the block
     * is "size" bytes.** Most of the effort here is involved in coalesing
     * adjacent* free blocks into a single big free block.
     */
    private void freeSpace(int start, int size) throws SqlJetException {

        SqlJetMemPage pPage = this;

        int addr, pbegin, hdr;
        ISqlJetMemoryPointer data = pPage.aData;

        assert (pPage.pBt != null);
        assert (start >= pPage.hdrOffset + 6 + (pPage.leaf ? 0 : 4));
        assert ((start + size) <= pPage.pBt.usableSize);
        assert (pPage.pBt.mutex.held());
        assert (size >= 0); /* Minimum cell size is 4 */

        if (ISqlJetConfig.SECURE_DELETE) {
            /*
             * Overwrite deleted information with zeros when the SECURE_DELETE*
             * option is enabled at compile-time
             */
            memset(data, start, (byte) 0, size);
        }

        /* Add the space back into the linked list of freeblocks */
        hdr = pPage.hdrOffset;
        addr = hdr + 1;
        while ((pbegin = get2byte(data, addr)) < start && pbegin > 0) {
            assert (pbegin <= pPage.pBt.usableSize - 4);
            if (pbegin <= addr) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            addr = pbegin;
        }
        if (pbegin > pPage.pBt.usableSize - 4) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        assert (pbegin > addr || pbegin == 0);
        put2byte(data, addr, start);
        put2byte(data, start, pbegin);
        put2byte(data, start + 2, size);
        pPage.nFree += size;

        /* Coalesce adjacent free blocks */
        addr = pPage.hdrOffset + 1;
        while ((pbegin = get2byte(data, addr)) > 0) {
            int pnext, psize, x;
            assert (pbegin > addr);
            assert (pbegin <= pPage.pBt.usableSize - 4);
            pnext = get2byte(data, pbegin);
            psize = get2byte(data, pbegin + 2);
            if (pbegin + psize + 3 >= pnext && pnext > 0) {
                int frag = pnext - (pbegin + psize);
                if ((frag < 0) || (frag > (int) SqlJetUtility.getUnsignedByte(data, pPage.hdrOffset + 7))) {
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                SqlJetUtility.putUnsignedByte(data, pPage.hdrOffset + 7, (byte) (SqlJetUtility.getUnsignedByte(data,
                        pPage.hdrOffset + 7) - (byte) frag));
                x = get2byte(data, pnext);
                put2byte(data, pbegin, x);
                x = pnext + get2byte(data, pnext + 2) - pbegin;
                put2byte(data, pbegin + 2, x);
            } else {
                addr = pbegin;
            }
        }

        /* If the cell content area begins with a freeblock, remove it. */
        if (SqlJetUtility.getUnsignedByte(data, hdr + 1) == SqlJetUtility.getUnsignedByte(data, hdr + 5)
                && SqlJetUtility.getUnsignedByte(data, hdr + 2) == SqlJetUtility.getUnsignedByte(data, hdr + 6)) {
            int top;
            pbegin = get2byte(data, hdr + 1);
            memcpy(data, hdr + 1, data, pbegin, 2);
            top = get2byte(data, hdr + 5) + get2byte(data, pbegin + 2);
            put2byte(data, hdr + 5, top);
        }
        assert (pPage.pDbPage.isWriteable());
    }

    /**
     * Insert a new cell on pPage at cell index "i". pCell points to the content
     * of the cell.
     *
     * If the cell content will fit on the page, then put it there. If it will
     * not fit, then make a copy of the cell content into pTemp if pTemp is not
     * null. Regardless of pTemp, allocate a new entry in pPage->aOvfl[] and
     * make it point to the cell content (either in pTemp or the original pCell)
     * and also record its index. Allocating a new entry in pPage->aCell[]
     * implies that pPage->nOverflow is incremented.
     *
     * If nSkip is non-zero, then do not copy the first nSkip bytes of the cell.
     * The caller will overwrite them after this function returns. If nSkip is
     * non-zero, then pCell may not point to an invalid memory location (but
     * pCell+nSkip is always valid).
     *
     * @param i
     *            New cell becomes the i-th cell of the page
     * @param pCell
     *            Content of the new cell
     * @param sz
     *            Bytes of content in pCell
     * @param pTemp
     *            Temp storage space for pCell, if needed
     * @param nSkip
     *            Do not write the first nSkip bytes of the cell
     *
     * @throws SqlJetException
     */
    public void insertCell(int i, ISqlJetMemoryPointer pCell, int sz, ISqlJetMemoryPointer pTemp, int iChild)
            throws SqlJetException {

    	int nSkip = (iChild>0 ? 4 : 0);

        final SqlJetMemPage pPage = this;

        int idx; /* Where to write new cell content in data[] */
        int j; /* Loop counter */
        int top; /* First byte of content for any cell in data[] */
        int end; /* First byte past the last cell pointer in data[] */
        int ins; /* Index in data[] where new cell pointer is inserted */
        int hdr; /* Offset into data[] of the page header */
        int cellOffset; /* Address of first cell pointer in data[] */
        ISqlJetMemoryPointer data; /* The content of the whole page */

        assert (i >= 0 && i <= pPage.nCell + pPage.nOverflow);
        assert (pPage.nCell <= pPage.pBt.MX_CELL() && pPage.pBt.MX_CELL() <= 5460);
        assert (pPage.nOverflow <= pPage.aOvfl.length);
        assert (sz == pPage.cellSizePtr(pCell) || (sz==8 && iChild>0) );
        assert (pPage.pBt.mutex.held());
        if (pPage.nOverflow != 0 || sz + 2 > pPage.nFree) {
            if (pTemp != null) {
                memcpy(pTemp, nSkip, pCell, nSkip, sz - nSkip);
                pCell = pTemp;
            }
            if( iChild>0 ) {
                put4byte(pCell, iChild);
            }
            j = pPage.nOverflow++;
            // assert( j<(int)(sizeof(pPage.aOvfl)/sizeof(pPage.aOvfl[0])) );
            pPage.aOvfl[j].pCell = pCell;
            pPage.aOvfl[j].idx = i;
        } else {
            pPage.pDbPage.write();
            assert (pPage.pDbPage.isWriteable());
            data = pPage.aData;
            hdr = pPage.hdrOffset;
            top = get2byte(data, hdr + 5);
            cellOffset = pPage.cellOffset;
            end = cellOffset + 2 * pPage.nCell + 2;
            ins = cellOffset + 2 * i;
            if (end > top - sz) {
                pPage.defragmentPage();
                top = get2byte(data, hdr + 5);
                assert (end + sz <= top);
            }
            idx = pPage.allocateSpace(sz);
            assert (idx > 0);
            assert (end <= get2byte(data, hdr + 5));
            if (idx + sz > pPage.pBt.usableSize) {
                throw new SqlJetException(SqlJetErrorCode.CORRUPT);
            }
            pPage.nCell++;
            pPage.nFree -= (2 + sz);
            memcpy(data, idx + nSkip, pCell, nSkip, sz - nSkip);
            if( iChild>0 ) {
                put4byte(data, idx, iChild);
            }
            for (j = end - 2; j > ins; j -= 2) {
                SqlJetUtility.putUnsignedByte(data, j, SqlJetUtility.getUnsignedByte(data, j - 2));
                SqlJetUtility.putUnsignedByte(data, j + 1, SqlJetUtility.getUnsignedByte(data, j - 1));
            }
            put2byte(data, ins, idx);
            put2byte(data, hdr + 3, pPage.nCell);
            if (pPage.pBt.autoVacuum) {
                /*
                 * The cell may contain a pointer to an overflow page. If so,
                 * write* the entry for the overflow page into the pointer map.
                 */
                SqlJetBtreeCellInfo info = pPage.parseCellPtr(pCell);
                assert ((info.nData + (pPage.intKey ? 0 : info.nKey)) == info.nPayload);
                if ((info.nData + (pPage.intKey ? 0 : info.nKey)) > info.nLocal) {
                    int pgnoOvfl = get4byte(pCell, info.iOverflow);
                    pPage.pBt.ptrmapPut(pgnoOvfl, SqlJetBtreeShared.PTRMAP_OVERFLOW1, pPage.pgno);
                }
            }
        }
    }

    /**
     * Allocate nByte bytes of space on a page.
     *
     * Return the index into pPage->aData[] of the first byte of the new
     * allocation. The caller guarantees that there is enough space. This
     * routine will never fail.
     *
     * If the page contains nBytes of free space but does not contain nBytes of
     * contiguous free space, then this routine automatically calls
     * defragementPage() to consolidate all free space before allocating the new
     * chunk.
     *
     * @param nByte
     * @return
     *
     * @throws SqlJetException
     */
    private int allocateSpace(int nByte) throws SqlJetException {

        final SqlJetMemPage pPage = this;

        int addr, pc, hdr;
        int size;
        int nFrag;
        ISqlJetMemoryPointer data;

        data = pPage.aData;
        assert (pPage.pDbPage.isWriteable());
        assert (pPage.pBt != null);
        assert (pPage.pBt.mutex.held());
        
        assert (nByte >= 0); /* Minimum cell size is 4 */
        assert (pPage.nFree >= nByte);
        assert (pPage.nOverflow == 0);
        int usableSize = pPage.pBt.usableSize;
        assert (nByte < usableSize - 8);
        
        hdr = pPage.hdrOffset;

        nFrag = SqlJetUtility.getUnsignedByte(data, hdr + 7);
        assert (pPage.cellOffset == hdr + 12 - 4*(pPage.leaf ? 1 : 0));
        int gap = pPage.cellOffset + 2*pPage.nCell;
        int top = get2byte(data.getMoved(hdr + 5));
        if (gap > top) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
        if (nFrag >= 60) {
            pPage.defragmentPage();
            top = get2byte(data.getMoved(hdr + 5));
        } else if (gap + 2 <= top) {
            addr = hdr + 1; 
            while((pc = get2byte(data, addr)) > 0) {
                if (pc > usableSize - 4 || pc < addr + 4) {
                    throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                }
                size = get2byte(data, pc + 2);
                if (size >= nByte) {
                    int x = size - nByte;
                    if (x < 4) {
                        memcpy(data, addr, data, pc, 2);
                        SqlJetUtility.putUnsignedByte(data, hdr + 7, nFrag + x);
                    } else if (size + pc > usableSize) {
                        throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                    } else {
                        put2byte(data, pc +2 , x);
                    }
                    return pc + x;
                }
                addr = pc;
            }
        }
        if (gap + 2 + nByte > top) {
            pPage.defragmentPage();
            top = get2byte(data, hdr + 5);
            assert (gap + nByte <= top);
        }
        top -= nByte;
        put2byte(data, hdr + 5, top);
        assert (top + nByte <= pPage.pBt.usableSize);
        return top;
    }

    /**
     * Defragment the page given. All Cells are moved to the end of the page and
     * all free space is collected into one big FreeBlk that occurs in between
     * the header and cell pointer array and the cell content area.
     *
     * @throws SqlJetException
     */
    private void defragmentPage() throws SqlJetException {

        final SqlJetMemPage pPage = this;

        int i; /* Loop counter */
        int pc; /* Address of a i-th cell */
        int hdr; /* Offset to the page header */
        int size; /* Size of a cell */
        int usableSize; /* Number of usable bytes on a page */
        int cellOffset; /* Offset to the cell pointer array */
        int cbrk; /* Offset to the cell content area */
        int nCell; /* Number of cells on the page */
        ISqlJetMemoryPointer data; /* The page data */
        ISqlJetMemoryPointer temp; /* Temp area for cell content */
        int iCellFirst;            /* First allowable cell index */
        int iCellLast;             /* Last possible cell index */

        assert (pPage.pDbPage.isWriteable());
        assert (pPage.pBt != null);
        assert (pPage.pBt.usableSize <= ISqlJetLimits.SQLJET_MAX_PAGE_SIZE);
        assert (pPage.nOverflow == 0);
        assert (pPage.pBt.mutex.held());
        temp = pPage.pBt.pPager.getTempSpace();
        data = pPage.aData;
        hdr = pPage.hdrOffset;
        cellOffset = pPage.cellOffset;
        nCell = pPage.nCell;
        assert (nCell == get2byte(data, hdr + 3));
        usableSize = pPage.pBt.usableSize;
        cbrk = get2byte(data, hdr + 5);
        memcpy(temp, cbrk, data, cbrk, usableSize - cbrk);
        cbrk = usableSize;
        iCellFirst = cellOffset + 2*nCell;
        iCellLast = usableSize - 4;
        for(i=0; i<nCell; i++){
          final ISqlJetMemoryPointer pAddr = data.getBuffer().getPointer(cellOffset + i*2); /* The i-th cell pointer */
          pc = get2byte(pAddr);
          if( pc<iCellFirst || pc>iCellLast ){
              throw new SqlJetException(SqlJetErrorCode.CORRUPT);
          }
          assert( pc>=iCellFirst && pc<=iCellLast );
          size = pPage.cellSizePtr(temp.getBuffer().getPointer(pc));
          cbrk -= size;
          if( cbrk<iCellFirst || pc+size>usableSize ){
              throw new SqlJetException(SqlJetErrorCode.CORRUPT);
          }
          assert( cbrk+size<=usableSize && cbrk>=iCellFirst );
          memcpy(data, cbrk, temp, pc, size);
          put2byte(pAddr, cbrk);
        }
        assert( cbrk>=iCellFirst );
        put2byte(data, hdr + 5, cbrk);
        SqlJetUtility.putUnsignedByte(data, hdr + 1, (byte) 0);
        SqlJetUtility.putUnsignedByte(data, hdr + 2, (byte) 0);
        SqlJetUtility.putUnsignedByte(data, hdr + 7, (byte) 0);
        memset(data, iCellFirst, (byte) 0, cbrk - iCellFirst);
        assert (pPage.pDbPage.isWriteable());
        if (cbrk - iCellFirst != pPage.nFree) {
            throw new SqlJetException(SqlJetErrorCode.CORRUPT);
        }
    }

    /**
     * This a more complex version of findCell() that works for pages that do
     * contain overflow cells. See insert
     *
     * @param iCell
     * @return
     */
    public ISqlJetMemoryPointer findOverflowCell(int iCell) {
        final SqlJetMemPage pPage = this;
        int i;
        assert (pPage.pBt.mutex.held());
        for (i = pPage.nOverflow - 1; i >= 0; i--) {
            int k;
            _OvflCell pOvfl = pPage.aOvfl[i];
            k = pOvfl.idx;
            if (k <= iCell) {
                if (k == iCell) {
                    return pOvfl.pCell;
                }
                iCell--;
            }
        }
        return pPage.findCell(iCell);
    }

    /**
     * Add a list of cells to a page. The page should be initially empty. The
     * cells are guaranteed to fit on the page.
     *
     * @param nCell
     *            The number of cells to add to this page
     * @param apCell
     *            Pointers to cell bodies
     * @param aSize
     *            Sizes of the cells
     *
     * @throws SqlJetException
     */
    public void assemblePage(int nCell, ISqlJetMemoryPointer[] apCell, int apCellPos, int[] aSize, int aSizePos) throws SqlJetException {
        final SqlJetMemPage pPage = this;

        int i; /* Loop counter */
        int hdr = pPage.hdrOffset; /* Index of page header */
        
        ISqlJetMemoryPointer data = pPage.aData; /* Data for the page */
        int nUsable = pPage.pBt.usableSize;

        assert (pPage.nOverflow == 0);
        assert (pPage.pBt.mutex.held());
        assert (nCell >= 0 && nCell <= pPage.pBt.MX_CELL() && pPage.pBt.MX_CELL() <= 10921);
        assert (pPage.pDbPage.isWriteable());
        assert (pPage.nCell == 0);
        assert (get2byte(data.getMoved(hdr + 5)) == nUsable);
        
        ISqlJetMemoryPointer pCellPtr = data.getMoved(pPage.cellOffset + nCell*2);
        int cellbody = nUsable;
        for(i = nCell - 1; i >= 0; i--) {
            int sz = aSize[apCellPos + i];
            pCellPtr = pCellPtr.getMoved(-2);
            cellbody -= sz;
            put2byte(pCellPtr, cellbody);
            memcpy(data.getMoved(cellbody), apCell[apCellPos + i], sz);
        }
        put2byte(data.getMoved(hdr + 3), nCell);
        put2byte(data.getMoved(hdr + 5), cellbody);
        pPage.nFree -= (2 * nCell + pPage.pBt.usableSize - cellbody);
        pPage.nCell = nCell;
    }

    /**
     * Page pParent is an internal (non-leaf) tree page. This function asserts
     * that page number iChild is the left-child if the iIdx'th cell in page
     * pParent. Or, if iIdx is equal to the total number of cells in pParent,
     * that page number iChild is the right-child of the page.
     *
     * @param iIdx
     * @param iChild
     */
    public void assertParentIndex(int iIdx, int iChild) {
        final SqlJetMemPage pParent = this;
        assert (iIdx <= pParent.nCell);
        if (iIdx == pParent.nCell) {
            assert (get4byte(pParent.aData, pParent.hdrOffset + 8) == iChild);
        } else {
            assert (get4byte(pParent.findCell(iIdx)) == iChild);
        }
    }

    /**
     * Create the byte sequence used to represent a cell on page pPage and write
     * that byte sequence into pCell[]. Overflow pages are allocated and filled
     * in as necessary. The calling procedure is responsible for making sure
     * sufficient space has been allocated for pCell[].
     *
     * Note that pCell does not necessary need to point to the pPage->aData
     * area. pCell might point to some temporary storage. The cell will be
     * constructed in this temporary area then copied into pPage->aData later.
     *
     * @param pCell
     *            Complete text of the cell
     * @param pKey
     *            The key
     * @param nKey
     *            The key
     * @param pData
     *            The data
     * @param nData
     *            The data
     * @param nZero
     *            Extra zero bytes to append to pData
     *
     * @return cell size
     *
     * @throws SqlJetException
     */
    public int fillInCell(ISqlJetMemoryPointer pCell, ISqlJetMemoryPointer pKey, long nKey, ISqlJetMemoryPointer pData,
            int nData, int nZero) throws SqlJetException {

        final SqlJetMemPage pPage = this;
        int pnSize = 0;

        int nPayload;
        ISqlJetMemoryPointer pSrc;
        int nSrc, n;
        int spaceLeft;
        SqlJetMemPage pOvfl = null;
        SqlJetMemPage pToRelease = null;
        ISqlJetMemoryPointer pPrior;
        ISqlJetMemoryPointer pPayload;
        SqlJetBtreeShared pBt = pPage.pBt;
        int[] pgnoOvfl = { 0 };
        int nHeader;
        SqlJetBtreeCellInfo info;

        assert (pPage.pBt.mutex.held());

        /*
         * pPage is not necessarily writeable since pCell might be auxiliary*
         * buffer space that is separate from the pPage buffer area
         */
        assert (pCell.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable());

        /* Fill in the header. */
        nHeader = 0;
        if (!pPage.leaf) {
            nHeader += 4;
        }
        if (pPage.hasData) {
            nHeader += putVarint(pointer(pCell, nHeader), nData + nZero);
        } else {
            nData = nZero = 0;
        }
        nHeader += putVarint(pointer(pCell, nHeader), nKey);
        info = pPage.parseCellPtr(pCell);
        assert (info.nHeader == nHeader);
        assert (info.nKey == nKey);
        assert (info.nData == nData + nZero);

        /* Fill in the payload */
        nPayload = nData + nZero;
        if (pPage.intKey) {
            pSrc = pData;
            nSrc = nData;
            nData = 0;
        } else {
            /* TBD: Perhaps raise SQLITE_CORRUPT if nKey is larger than 31 bits? */
            nPayload += (int) nKey;
            pSrc = pKey;
            nSrc = (int) nKey;
        }
        pnSize = info.nSize;
        spaceLeft = info.nLocal;
        pPayload = pointer(pCell, nHeader);
        pPrior = pointer(pCell, info.iOverflow);

        while (nPayload > 0) {
            if (spaceLeft == 0) {
                int pgnoPtrmap = pgnoOvfl[0]; /*
                                               * Overflow page pointer-map entry
                                               * page
                                               */
                if (pBt.autoVacuum) {
                    do {
                        pgnoOvfl[0]++;
                    } while (pBt.PTRMAP_ISPAGE(pgnoOvfl[0]) || pgnoOvfl[0] == pBt.PENDING_BYTE_PAGE());
                }
                try {
                    pOvfl = pBt.allocatePage(pgnoOvfl, pgnoOvfl[0], false);
                    /*
                     * If the database supports auto-vacuum, and the second or
                     * subsequent* overflow page is being allocated, add an
                     * entry to the pointer-map* for that page now.** If this is
                     * the first overflow page, then write a partial entry* to
                     * the pointer-map. If we write nothing to this pointer-map
                     * slot,* then the optimistic overflow chain processing in
                     * clearCell()* may misinterpret the uninitialised values
                     * and delete the* wrong pages from the database.
                     */
                    if (pBt.autoVacuum) {
                        byte eType = (pgnoPtrmap != 0 ? SqlJetBtreeShared.PTRMAP_OVERFLOW2
                                : SqlJetBtreeShared.PTRMAP_OVERFLOW1);
                        try {
                            pBt.ptrmapPut(pgnoOvfl[0], eType, pgnoPtrmap);
                        } catch (SqlJetException e) {
                            releasePage(pOvfl);
                        }
                    }
                } catch (SqlJetException e) {
                    releasePage(pToRelease);
                    throw e;
                }

                /*
                 * If pToRelease is not zero than pPrior points into the data
                 * area* of pToRelease. Make sure pToRelease is still writeable.
                 */
                assert (pToRelease == null || pToRelease.pDbPage.isWriteable());

                /*
                 * If pPrior is part of the data area of pPage, then make sure
                 * pPage* is still writeable
                 */
                assert (pPrior.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable());

                put4byte(pPrior, pgnoOvfl[0]);
                releasePage(pToRelease);
                pToRelease = pOvfl;
                pPrior = pOvfl.aData;
                put4byte(pPrior, 0);
                pPayload = pointer(pOvfl.aData, 4);
                spaceLeft = pBt.usableSize - 4;
            }
            n = nPayload;
            if (n > spaceLeft)
                n = spaceLeft;

            /*
             * If pToRelease is not zero than pPayload points into the data area
             * * of pToRelease. Make sure pToRelease is still writeable.
             */
            assert (pToRelease == null || pToRelease.pDbPage.isWriteable());

            /*
             * If pPayload is part of the data area of pPage, then make sure
             * pPage* is still writeable
             */
            assert (pPayload.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable());

            if (nSrc > 0) {
                if (n > nSrc)
                    n = nSrc;
                assert (pSrc != null);
                memcpy(pPayload, pSrc, n);
            } else {
                memset(pPayload, (byte) 0, n);
            }
            nPayload -= n;
            movePtr(pPayload, n);
            pSrc = pointer(pSrc, n);
            nSrc -= n;
            spaceLeft -= n;
            if (nSrc == 0) {
                nSrc = nData;
                pSrc = pData;
            }
        }
        releasePage(pToRelease);

        return pnSize;
    }

    /**
     * If the cell with index iCell on page pPage contains a pointer to an
     * overflow page, insert an entry into the pointer-map for the overflow
     * page.
     *
     * @param iCell
     *
     * @throws SqlJetException
     */
    public void ptrmapPutOvfl(int iCell) throws SqlJetException {
        SqlJetMemPage pPage = this;
        ISqlJetMemoryPointer pCell;
        assert (pPage.pBt.mutex.held());
        pCell = pPage.findOverflowCell(iCell);
        pPage.ptrmapPutOvflPtr(pCell);
    }

	public void copyNodeContent(final SqlJetMemPage pTo) throws SqlJetException {
		final SqlJetMemPage pFrom = this;
	    final SqlJetBtreeShared pBt = pFrom.pBt;
	    final ISqlJetMemoryPointer aFrom = pFrom.aData;
	    final ISqlJetMemoryPointer aTo = pTo.aData;
	    int iFromHdr = pFrom.hdrOffset;
	    int iToHdr = ((pTo.pgno==1) ? 100 : 0);
	    int iData;

	    assert( pFrom.isInit );
	    assert( pFrom.nFree>=iToHdr );
	    assert( get2byte(aFrom.getMoved(iFromHdr+5)) <= (int)pBt.usableSize );

	    /* Copy the b-tree node content from page pFrom to page pTo. */
	    iData = get2byte(aFrom.getMoved(iFromHdr+5));
	    memcpy(aTo.getMoved(iData), aFrom.getMoved(iData), pBt.usableSize-iData);
	    memcpy(aTo.getMoved(iToHdr), aFrom.getMoved(iFromHdr), pFrom.cellOffset + 2*pFrom.nCell);

	    /* Reinitialize page pTo so that the contents of the MemPage structure
	    ** match the new data. The initialization of pTo can actually fail under
	    ** fairly obscure circumstances, even though it is a copy of initialized
	    ** page pFrom.
	    */
	    pTo.isInit = false;
	    pTo.initPage();

	    /* If this is an auto-vacuum database, update the pointer-map entries
	    ** for any b-tree or overflow pages that pTo now contains the pointers to.
	    */
	    if( ISAUTOVACUUM() ){
	    	pTo.setChildPtrmaps();
	    }
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		final SqlJetMemPage clone = (SqlJetMemPage) super.clone();
		clone.aData = SqlJetUtility.allocatePtr(clone.pBt.pageSize);
		clone.aOvfl = aOvfl.clone();
		for(int i=0; i< aOvfl.length; i++) {
			clone.aOvfl[i]=(_OvflCell) aOvfl[i].clone();
		}
		return clone;
	}
}
