/**
 * SqlJetMem.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.vdbe;

import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.getUnsignedByte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.memcpy;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.memmove;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.memset;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.mutex_held;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.pointer;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.putUnsignedByte;
import static org.tmatesoft.sqljet.core.internal.SqlJetUtility.strlen30;

import java.util.EnumSet;
import java.util.Iterator;
import java.util.Set;

import org.tmatesoft.sqljet.core.SqlJetEncoding;
import org.tmatesoft.sqljet.core.SqlJetErrorCode;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetValueType;
import org.tmatesoft.sqljet.core.internal.ISqlJetBtreeCursor;
import org.tmatesoft.sqljet.core.internal.ISqlJetCallback;
import org.tmatesoft.sqljet.core.internal.ISqlJetCollSeq;
import org.tmatesoft.sqljet.core.internal.ISqlJetDbHandle;
import org.tmatesoft.sqljet.core.internal.ISqlJetFuncDef;
import org.tmatesoft.sqljet.core.internal.ISqlJetLimits;
import org.tmatesoft.sqljet.core.internal.ISqlJetMemoryPointer;
import org.tmatesoft.sqljet.core.internal.ISqlJetRowSet;
import org.tmatesoft.sqljet.core.internal.ISqlJetVdbeMem;
import org.tmatesoft.sqljet.core.internal.SqlJetCloneable;
import org.tmatesoft.sqljet.core.internal.SqlJetUtility;
import org.tmatesoft.sqljet.core.schema.SqlJetTypeAffinity;

/**
 * Internally, the vdbe manipulates nearly all SQL values as Mem structures.
 * Each Mem struct may cache multiple representations (string, integer etc.) of
 * the same value. A value (and therefore Mem structure) has the following
 * properties:
 *
 * Each value has a manifest type. The manifest type of the value stored in a
 * Mem struct is returned by the MemType(Mem*) macro. The type is one of
 * SQLITE_NULL, SQLITE_INTEGER, SQLITE_REAL, SQLITE_TEXT or SQLITE_BLOB.
 *
 *
 * @author TMate Software Ltd.
 * @author Sergey Scherbina (sergey.scherbina@gmail.com)
 *
 */
public class SqlJetVdbeMem extends SqlJetCloneable implements ISqlJetVdbeMem {

    // union {

    /** Integer value. */
    long i;

    /** Used when bit MEM_Zero is set in flags */
    int nZero;

    /** Used only when flags==MEM_Agg */
    ISqlJetFuncDef pDef;

    /** Used only when flags==MEM_RowSet */
    ISqlJetRowSet pRowSet;

    // } u;

    /** Real value */
    double r;

    /** The associated database connection */
    ISqlJetDbHandle db;

    /** String or BLOB value */
    ISqlJetMemoryPointer z;

    /** Number of characters in string value, excluding '\0' */
    int n;

    /** Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
    EnumSet<SqlJetVdbeMemFlags> flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Null);

    /** One of SQLITE_NULL, SQLITE_TEXT, SQLITE_INTEGER, etc */
    SqlJetValueType type = SqlJetValueType.NULL;

    /** SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
    SqlJetEncoding enc;

    /** If not null, call this function to delete Mem.z */
    ISqlJetCallback xDel;

    /** Dynamic buffer allocated by sqlite3_malloc() */
    ISqlJetMemoryPointer zMalloc;

    public SqlJetVdbeMem() {
        this.db = null;
    }

    public SqlJetVdbeMem(ISqlJetDbHandle db) {
        this.db = db;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.vdbe.ISqlJetVdbeMem#release()
     */
    public void release() {
        // releaseExternal();
        // sqlite3DbFree(p->db, p->zMalloc);
        z = null;
        zMalloc = null;
        xDel = null;
    }

    /**
     * Compare the values contained by the two memory cells, returning negative,
     * zero or positive if pMem1 is less than, equal to, or greater than pMem2.
     * Sorting order is NULL's first, followed by numbers (integers and reals)
     * sorted numerically, followed by text ordered by the collating sequence
     * pColl and finally blob's ordered by memcmp().Two NULL values are
     * considered equal by this function.
     */
    public static int compare(SqlJetVdbeMem pMem1, SqlJetVdbeMem pMem2, ISqlJetCollSeq pColl) throws SqlJetException {

        int rc;

        /*
         * Interchange pMem1 and pMem2 if the collating sequence specifies* DESC
         * order.
         */
        EnumSet<SqlJetVdbeMemFlags> f1 = pMem1.flags;
        EnumSet<SqlJetVdbeMemFlags> f2 = pMem2.flags;
        EnumSet<SqlJetVdbeMemFlags> combined_flags = EnumSet.copyOf(f1);
        combined_flags.addAll(f2);
        assert (!combined_flags.contains(SqlJetVdbeMemFlags.RowSet));

        /*
         * If one value is NULL, it is less than the other. If both values* are
         * NULL, return 0.
         */
        if (combined_flags.contains(SqlJetVdbeMemFlags.Null)) {
            return (f2.contains(SqlJetVdbeMemFlags.Null) ? 1 : 0) - (f1.contains(SqlJetVdbeMemFlags.Null) ? 1 : 0);
        }

        /*
         * If one value is a number and the other is not, the number is less.*
         * If both are numbers, compare as reals if one is a real, or as
         * integers* if both values are integers.
         */
        if (combined_flags.contains(SqlJetVdbeMemFlags.Int) || combined_flags.contains(SqlJetVdbeMemFlags.Real)) {
            if (!(f1.contains(SqlJetVdbeMemFlags.Int) || f1.contains(SqlJetVdbeMemFlags.Real))) {
                return 1;
            }
            if (!(f2.contains(SqlJetVdbeMemFlags.Int) || f2.contains(SqlJetVdbeMemFlags.Real))) {
                return -1;
            }
            if (f1.contains(SqlJetVdbeMemFlags.Real) || f2.contains(SqlJetVdbeMemFlags.Real)) {
                // one is real.
                double r1, r2;
                if (!f1.contains(SqlJetVdbeMemFlags.Real)) {
                    r1 = (double) pMem1.i;
                } else {
                    r1 = pMem1.r;
                }
                if (!f2.contains(SqlJetVdbeMemFlags.Real)) {
                    r2 = (double) pMem2.i;
                } else {
                    r2 = pMem2.r;
                }
                if (r1 < r2)
                    return -1;
                if (r1 > r2)
                    return 1;
                return 0;
            } else {
                assert (f1.contains(SqlJetVdbeMemFlags.Int));
                assert (f2.contains(SqlJetVdbeMemFlags.Int));
                if (pMem1.i < pMem2.i)
                    return -1;
                if (pMem1.i > pMem2.i)
                    return 1;
                return 0;
            }
        }

        /*
         * If one value is a string and the other is a blob, the string is less.
         * * If both are strings, compare using the collating functions.
         */
        if (combined_flags.contains(SqlJetVdbeMemFlags.Str)) {
            if (!f1.contains(SqlJetVdbeMemFlags.Str)) {
                return 1;
            }
            if (!f2.contains(SqlJetVdbeMemFlags.Str)) {
                return -1;
            }

            assert (pMem1.enc == pMem2.enc);
            assert (pMem1.enc == SqlJetEncoding.UTF8 || pMem1.enc == SqlJetEncoding.UTF16LE || pMem1.enc == SqlJetEncoding.UTF16BE);

            /*
             * The collation sequence must be defined at this point, even if*
             * the user deletes the collation sequence after the vdbe program is
             * * compiled (this was not always the case).
             */
            if (pColl != null) {
                if (pMem1.enc == pColl.getEnc()) {
                    /*
                     * The strings are already in the correct encoding. Call the
                     * * comparison function directly
                     */
                    return pColl.cmp(pColl.getUserData(), pMem1.n, pMem1.z, pMem2.n, pMem2.z);
                } else {

                    ISqlJetMemoryPointer v1, v2;
                    int n1, n2;

                    SqlJetVdbeMem c1 = (SqlJetVdbeMem) pMem1.shallowCopy(SqlJetVdbeMemFlags.Ephem);
                    SqlJetVdbeMem c2 = (SqlJetVdbeMem) pMem2.shallowCopy(SqlJetVdbeMemFlags.Ephem);
                    v1 = c1.valueText(pColl.getEnc());
                    n1 = v1 == null ? 0 : c1.n;
                    v2 = c2.valueText(pColl.getEnc());
                    n2 = v2 == null ? 0 : c2.n;
                    c1.release();
                    c2.release();
                    return pColl.cmp(pColl.getUserData(), n1, v1, n2, v2);

                }
            }
            /*
             * If a NULL pointer was passed as the collate function, fall
             * through to the blob case and use memcmp().
             */
        }

        /* Both values must be blobs. Compare using memcmp(). */
        rc = SqlJetUtility.memcmp(pMem1.z, pMem2.z, (pMem1.n > pMem2.n) ? pMem2.n : pMem1.n);
        if (rc == 0) {
            rc = pMem1.n - pMem2.n;
        }
        return rc;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#shallowCopy(org.tmatesoft.sqljet
     * .core.internal.vdbe.SqlJetVdbeMemFlags)
     */
    public ISqlJetVdbeMem shallowCopy(SqlJetVdbeMemFlags srcType) throws SqlJetException {
        final SqlJetVdbeMem pFrom = this;
        assert (!pFrom.flags.contains(SqlJetVdbeMemFlags.RowSet));
        final SqlJetVdbeMem pTo = memcpy(pFrom);
        if (pFrom.flags.contains(SqlJetVdbeMemFlags.Dyn) || pFrom.z == pFrom.zMalloc) {
            pTo.flags.removeAll(SqlJetUtility.of(SqlJetVdbeMemFlags.Dyn, SqlJetVdbeMemFlags.Static,
                    SqlJetVdbeMemFlags.Ephem));
            assert (srcType == SqlJetVdbeMemFlags.Ephem || srcType == SqlJetVdbeMemFlags.Static);
            pTo.flags.add(srcType);
        }
        return pTo;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#copy()
     */
    public ISqlJetVdbeMem copy() throws SqlJetException {
        final SqlJetVdbeMem pFrom = this;
        assert (!pFrom.flags.contains(SqlJetVdbeMemFlags.RowSet));
        final SqlJetVdbeMem pTo = SqlJetUtility.memcpy(pFrom);
        pTo.flags.remove(SqlJetVdbeMemFlags.Dyn);
        if (pTo.flags.contains(SqlJetVdbeMemFlags.Str) || pTo.flags.contains(SqlJetVdbeMemFlags.Blob)) {
            if (!pFrom.flags.contains(SqlJetVdbeMemFlags.Static)) {
                pTo.flags.add(SqlJetVdbeMemFlags.Ephem);
                pTo.makeWriteable();
            }
        }
        return pTo;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#move(org.tmatesoft.sqljet.core
     * .ISqlJetVdbeMem, org.tmatesoft.sqljet.core.ISqlJetVdbeMem)
     */
    public ISqlJetVdbeMem move() throws SqlJetException {
        assert (db == null || mutex_held(db.getMutex()));
        SqlJetVdbeMem pTo = SqlJetUtility.memcpy(this);
        this.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Null);
        this.xDel = null;
        this.zMalloc = null;
        return pTo;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.internal.vdbe.ISqlJetVdbeMem#valueText(org.
     * tmatesoft.sqljet.core.SqlJetEncoding)
     */
    public ISqlJetMemoryPointer valueText(SqlJetEncoding enc) throws SqlJetException {
        // if( !pVal ) return 0;

        final SqlJetVdbeMem pVal = this;

        assert (pVal.db == null || mutex_held(pVal.db.getMutex()));
        // assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
        assert (!pVal.flags.contains(SqlJetVdbeMemFlags.RowSet));

        if (pVal.flags.contains(SqlJetVdbeMemFlags.Null)) {
            return null;
        }
        // assert( (MEM_Blob>>3) == MEM_Str );
        // pVal.flags |= (pVal.flags & MEM_Blob)>>3;
        if (pVal.flags.contains(SqlJetVdbeMemFlags.Blob)) {
            pVal.flags.add(SqlJetVdbeMemFlags.Str);
        }
        pVal.expandBlob();
        if (pVal.flags.contains(SqlJetVdbeMemFlags.Str)) {
            pVal.changeEncoding(enc);
            /*
             * if( (enc & SQLITE_UTF16_ALIGNED)!=0 &&
             * 1==(1&SQLITE_PTR_TO_INT(pVal->z)) ){ assert( (pVal->flags &
             * (MEM_Ephem|MEM_Static))!=0 ); if(
             * sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){ return 0; } }
             */
            pVal.makeWriteable();
            pVal.nulTerminate();
        } else {
            assert (!pVal.flags.contains(SqlJetVdbeMemFlags.Blob));
            pVal.stringify(enc);
            // assert( 0==(1&SQLITE_PTR_TO_INT(pVal->z)) );
        }
        /*
         * assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 ||
         * pVal->db->mallocFailed ); if( pVal->enc==(enc &
         * ~SQLITE_UTF16_ALIGNED) ){ return pVal->z; }else{ return 0; }
         */
        return pVal.z;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#stringify(org.tmatesoft.sqljet
     * .core.SqlJetEncoding)
     */
    public void stringify(SqlJetEncoding enc) throws SqlJetException {

        final SqlJetVdbeMem pMem = this;

        final Set<SqlJetVdbeMemFlags> fg = pMem.flags;
        final int nByte = 32;

        assert (pMem.db == null || mutex_held(pMem.db.getMutex()));
        assert (!fg.contains(SqlJetVdbeMemFlags.Zero));
        assert (!(fg.contains(SqlJetVdbeMemFlags.Str) || fg.contains(SqlJetVdbeMemFlags.Blob)));
        assert (fg.contains(SqlJetVdbeMemFlags.Int) || fg.contains(SqlJetVdbeMemFlags.Real));
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));

        pMem.grow(nByte, false);

        /*
         * For a Real or Integer, use sqlite3_mprintf() to produce the UTF-8*
         * string representation of the value. Then, if the required encoding*
         * is UTF-16le or UTF-16be do a translation.** FIX ME: It would be
         * better if sqlite3_snprintf() could do UTF-16.
         */
        if (fg.contains(SqlJetVdbeMemFlags.Int)) {
            // sqlite3_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
            pMem.z.putBytes(Long.toString(pMem.i).getBytes());
        } else {
            assert (fg.contains(SqlJetVdbeMemFlags.Real));
            // sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->r);
            pMem.z.putBytes(Double.toString(pMem.r).getBytes());
        }
        pMem.n = strlen30(pMem.z);
        pMem.enc = SqlJetEncoding.UTF8;
        pMem.flags.add(SqlJetVdbeMemFlags.Str);
        pMem.flags.add(SqlJetVdbeMemFlags.Term);
        pMem.changeEncoding(enc);
        type = SqlJetValueType.TEXT;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.vdbe.ISqlJetVdbeMem#grow(int,
     * boolean)
     */
    public void grow(int n, boolean preserve) {

        final SqlJetVdbeMem pMem = this;

        assert (1 >= ((pMem.zMalloc != null && pMem.zMalloc == pMem.z) ? 1 : 0)
                + ((pMem.flags.contains(SqlJetVdbeMemFlags.Dyn) && pMem.xDel != null) ? 1 : 0)
                + (pMem.flags.contains(SqlJetVdbeMemFlags.Ephem) ? 1 : 0)
                + (pMem.flags.contains(SqlJetVdbeMemFlags.Static) ? 1 : 0));
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));

        if (n < 32)
            n = 32;
        /*
         * if( sqlite3DbMallocSize(pMem->db, pMem->zMalloc)<n ){ if( preserve &&
         * pMem->z==pMem->zMalloc ){ pMem->z = pMem->zMalloc =
         * sqlite3DbReallocOrFree(pMem->db, pMem->z, n); preserve = 0; }else{
         * sqlite3DbFree(pMem->db, pMem->zMalloc); pMem->zMalloc =
         * sqlite3DbMallocRaw(pMem->db, n); } }
         */

        pMem.zMalloc = SqlJetUtility.allocatePtr(n);

        if (preserve && pMem.z != null) {
            memcpy(pMem.zMalloc, pMem.z, pMem.n);
        }
        if (pMem.flags.contains(SqlJetVdbeMemFlags.Dyn) && pMem.xDel != null) {
            pMem.xDel.call(pMem.z);
        }
        pMem.z = pMem.zMalloc;
        if (pMem.z == null) { // WTF? /sergey/
            pMem.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Null);
        } else {
            pMem.flags.remove(SqlJetVdbeMemFlags.Ephem);
            pMem.flags.remove(SqlJetVdbeMemFlags.Static);
        }
        pMem.xDel = null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#nulTerminate()
     */
    public void nulTerminate() {
        final SqlJetVdbeMem pMem = this;
        assert (pMem.db == null || mutex_held(pMem.db.getMutex()));
        if (pMem.flags.contains(SqlJetVdbeMemFlags.Term) || !pMem.flags.contains(SqlJetVdbeMemFlags.Str)) {
            return; /* Nothing to do */
        }
        pMem.grow(pMem.n + 2, true);
        pMem.z.putByte(pMem.n, (byte) 0);
        pMem.z.putByte(pMem.n + 1, (byte) 0);
        pMem.flags.add(SqlJetVdbeMemFlags.Term);
        pMem.z.limit(pMem.n);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#changeEncoding(org.tmatesoft
     * .sqljet.core.SqlJetEncoding)
     */
    public void changeEncoding(SqlJetEncoding desiredEnc) throws SqlJetException {
        final SqlJetVdbeMem pMem = this;
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));
        assert (desiredEnc == SqlJetEncoding.UTF8 || desiredEnc == SqlJetEncoding.UTF16LE || desiredEnc == SqlJetEncoding.UTF16BE);
        if (!pMem.flags.contains(SqlJetVdbeMemFlags.Str) || pMem.enc == desiredEnc) {
            return;
        }
        assert (pMem.db == null || mutex_held(pMem.db.getMutex()));

        /*
         * MemTranslate() may return SQLITE_OK or SQLITE_NOMEM. If NOMEM is
         * returned, then the encoding of the value may not have changed.
         */
        pMem.translate(desiredEnc);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#translate(org.tmatesoft.sqljet
     * .core.SqlJetEncoding)
     */
    public void translate(SqlJetEncoding desiredEnc) throws SqlJetException {

        final SqlJetVdbeMem pMem = this;

        int len; /* Maximum length of output string in bytes */

        ISqlJetMemoryPointer zOut; /* Output buffer */
        int zIn; /* Input iterator */
        int zTerm; /* End of input */
        // int z; /* Output iterator */

        // long c;

        assert (pMem.db == null || mutex_held(pMem.db.getMutex()));
        assert (pMem.flags.contains(SqlJetVdbeMemFlags.Str));
        assert (pMem.enc != desiredEnc);
        assert (pMem.enc != null);
        assert (pMem.n >= 0);

        /*
         * If the translation is between UTF-16 little and big endian, then* all
         * that is required is to swap the byte order. This case is handled*
         * differently from the others.
         */
        if (pMem.enc != SqlJetEncoding.UTF8 && desiredEnc != SqlJetEncoding.UTF8) {
            short temp;
            pMem.makeWriteable();
            zIn = 0;
            zTerm = pMem.n & ~1;
            while (zIn < zTerm) {
                temp = (short) SqlJetUtility.getUnsignedByte(pMem.z, zIn);
                SqlJetUtility.putUnsignedByte(pMem.z, zIn, SqlJetUtility.getUnsignedByte(pMem.z, zIn + 1));
                zIn++;
                SqlJetUtility.putUnsignedByte(pMem.z, zIn++, temp);
            }
            pMem.enc = desiredEnc;
            return;
        }

        /* Set len to the maximum number of bytes required in the output buffer. */
        if (desiredEnc == SqlJetEncoding.UTF8) {
            /*
             * When converting from UTF-16, the maximum growth results from*
             * translating a 2-byte character to a 4-byte UTF-8 character.* A
             * single byte is required for the output string* nul-terminator.
             */
            pMem.n &= ~1;
            len = pMem.n * 2 + 1;
        } else {
            /*
             * When converting from UTF-8 to UTF-16 the maximum growth is caused
             * * when a 1-byte UTF-8 character is translated into a 2-byte
             * UTF-16* character. Two bytes are required in the output buffer
             * for the* nul-terminator.
             */
            len = pMem.n * 2 + 2;
        }

        /*
         * Set zIn to point at the start of the input buffer and zTerm to point
         * 1* byte past the end.** Variable zOut is set to point at the output
         * buffer, space obtained* from sqlite3_malloc().
         */
        zOut = SqlJetUtility.translate(pMem.z, pMem.enc, desiredEnc);
        pMem.n = zOut.remaining();

        assert ((pMem.n + (desiredEnc == SqlJetEncoding.UTF8 ? 1 : 2)) <= len);

        pMem.release();
        pMem.flags.removeAll(SqlJetUtility.of(SqlJetVdbeMemFlags.Static, SqlJetVdbeMemFlags.Dyn,
                SqlJetVdbeMemFlags.Ephem));
        pMem.enc = desiredEnc;
        pMem.flags.addAll(SqlJetUtility.of(SqlJetVdbeMemFlags.Term, SqlJetVdbeMemFlags.Dyn));
        pMem.z = zOut;
        pMem.zMalloc = pMem.z;

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#expandBlob()
     */
    public void expandBlob() {
        final SqlJetVdbeMem pMem = this;
        if (pMem.flags.contains(SqlJetVdbeMemFlags.Zero)) {
            int nByte;
            assert (pMem.flags.contains(SqlJetVdbeMemFlags.Blob));
            assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));
            assert (pMem.db == null || mutex_held(pMem.db.getMutex()));

            /*
             * Set nByte to the number of bytes required to store the expanded
             * blob.
             */
            nByte = pMem.n + pMem.nZero;
            if (nByte <= 0) {
                nByte = 1;
            }
            pMem.grow(nByte, true);
            memset(pMem.z, pMem.n, (byte) 0, pMem.nZero);
            pMem.n += pMem.nZero;
            pMem.flags.removeAll(SqlJetUtility.of(SqlJetVdbeMemFlags.Zero, SqlJetVdbeMemFlags.Term));
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.internal.vdbe.ISqlJetVdbeMem#fromBtree(org.
     * tmatesoft.sqljet.core.ISqlJetBtreeCursor, int, int, boolean)
     */
    public void fromBtree(ISqlJetBtreeCursor pCur, int offset, int amt, boolean key) throws SqlJetException {

        assert (mutex_held(pCur.getCursorDb().getMutex()));

        SqlJetVdbeMem pMem = this;

        /* Data from the btree layer */
        ISqlJetMemoryPointer zData;
        /* Number of bytes available on the local btree page */
        int[] available = { 0 };

        if (key) {
            zData = pCur.keyFetch(available);
        } else {
            zData = pCur.dataFetch(available);
        }
        assert (zData != null);

        if (offset + amt <= available[0]) {
            pMem.release();
            pMem.z = pointer(zData, offset);
            pMem.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Blob, SqlJetVdbeMemFlags.Ephem);
        } else {
            pMem.grow(amt + 2, false);
            pMem.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Blob, SqlJetVdbeMemFlags.Dyn, SqlJetVdbeMemFlags.Term);
            pMem.enc = null;
            pMem.type = SqlJetValueType.BLOB;
            try {
                if (key) {
                    pCur.key(offset, amt, pMem.z);
                } else {
                    pCur.data(offset, amt, pMem.z);
                }
            } catch (SqlJetException e) {
                pMem.release();
                throw e;
            } finally {
                if (pMem.z != null) {
                    SqlJetUtility.putUnsignedByte(pMem.z, amt, (byte) 0);
                    SqlJetUtility.putUnsignedByte(pMem.z, amt + 1, (byte) 0);
                }
            }
        }
        pMem.n = amt;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.internal.vdbe.ISqlJetVdbeMem#makeWriteable()
     */
    public void makeWriteable() {
        SqlJetVdbeMem pMem = this;
        assert (pMem.db == null || mutex_held(pMem.db.getMutex()));
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));
        pMem.expandBlob();
        if ((pMem.flags.contains(SqlJetVdbeMemFlags.Str) || pMem.flags.contains(SqlJetVdbeMemFlags.Blob))
                && pMem.z != pMem.zMalloc) {
            pMem.grow(pMem.n + 2, true);
            putUnsignedByte(pMem.z, pMem.n, (byte) 0);
            putUnsignedByte(pMem.z, pMem.n + 1, (byte) 0);
            pMem.flags.add(SqlJetVdbeMemFlags.Term);
            pMem.z.limit(pMem.n);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#intValue()
     */
    public long intValue() {
        assert (db == null || mutex_held(db.getMutex()));
        if (flags.contains(SqlJetVdbeMemFlags.Int)) {
            return i;
        } else if (flags.contains(SqlJetVdbeMemFlags.Real)) {
            return (long) r;
        } else if (flags.contains(SqlJetVdbeMemFlags.Str) || flags.contains(SqlJetVdbeMemFlags.Blob)) {
            long value = 0;
            /*
             * pMem->flags |= MEM_Str; if( sqlite3VdbeChangeEncoding(pMem,
             * SQLITE_UTF8) || sqlite3VdbeMemNulTerminate(pMem) ){ return 0; }
             * assert( pMem->z ); sqlite3Atoi64(pMem->z, &value);
             */
            flags.add(SqlJetVdbeMemFlags.Str);
            try {
                changeEncoding(SqlJetEncoding.UTF8);
                nulTerminate();
            } catch (SqlJetException e) {
                return 0;
            }
            value = SqlJetUtility.atoi64(SqlJetUtility.toString(this.z));
            return value;
        } else {
            return 0;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setNull()
     */
    public void setNull() {
        if (flags.contains(SqlJetVdbeMemFlags.RowSet)) {
            // sqlite3RowSetClear(pMem->u.pRowSet);
        }
        flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Null);
        type = SqlJetValueType.NULL;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setStr(java.nio.ByteBuffer,
     * org.tmatesoft.sqljet.core.SqlJetEncoding)
     */
    public void setStr(ISqlJetMemoryPointer z, SqlJetEncoding enc) throws SqlJetException {

        assert (db == null || mutex_held(db.getMutex()));
        assert (!flags.contains(SqlJetVdbeMemFlags.RowSet));

        /* If z is a NULL pointer, set pMem to contain an SQL NULL. */
        if (z == null) {
            setNull();
            return;
        }

        int nByte = z.remaining(); /* New value for pMem->n */
        /* Maximum allowed string or blob size */
        int iLimit = ISqlJetLimits.SQLJET_MAX_LENGTH;
        /* New value for pMem->flags */
        flags = SqlJetUtility.noneOf(SqlJetVdbeMemFlags.class);

        flags.add(enc == null ? SqlJetVdbeMemFlags.Blob : SqlJetVdbeMemFlags.Str);

        if (nByte > iLimit) {
            throw new SqlJetException(SqlJetErrorCode.TOOBIG);
        }

        this.z = z;
        this.n = nByte;
        this.enc = (enc == null ? SqlJetEncoding.UTF8 : enc);
        this.type = (enc == null ? SqlJetValueType.BLOB : SqlJetValueType.TEXT);

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setInt64(long)
     */
    public void setInt64(long val) {
        release();
        i = val;
        flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Int);
        type = SqlJetValueType.INTEGER;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#realValue()
     */
    public double realValue() {
        final SqlJetVdbeMem pMem = this;
        assert (pMem.db == null || pMem.db.getMutex().held());
        if (pMem.flags.contains(SqlJetVdbeMemFlags.Real)) {
            return pMem.r;
        } else if (pMem.flags.contains(SqlJetVdbeMemFlags.Int)) {
            return (double) pMem.i;
        } else if (pMem.flags.contains(SqlJetVdbeMemFlags.Str) || pMem.flags.contains(SqlJetVdbeMemFlags.Blob)) {
            double val = 0.0;
            pMem.flags.add(SqlJetVdbeMemFlags.Str);
            try {
                pMem.changeEncoding(SqlJetEncoding.UTF8);
                pMem.nulTerminate();
            } catch (SqlJetException e) {
                return 0.0;
            }
            assert (pMem.z != null);
            val = SqlJetUtility.atof(pMem.z);
            return val;
        } else {
            return 0.0;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#integerAffinity()
     */
    public void integerAffinity() {
        final SqlJetVdbeMem pMem = this;
        assert (pMem.flags.contains(SqlJetVdbeMemFlags.Real));
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));
        assert (pMem.db == null || pMem.db.getMutex().held());
        pMem.i = (long) pMem.r;
        if (pMem.r == (double) pMem.i) {
            pMem.flags.add(SqlJetVdbeMemFlags.Int);
            type = SqlJetValueType.INTEGER;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#integerify()
     */
    public void integerify() {
        final SqlJetVdbeMem pMem = this;
        assert (pMem.db == null || pMem.db.getMutex().held());
        assert (!pMem.flags.contains(SqlJetVdbeMemFlags.RowSet));
        pMem.i = pMem.intValue();
        pMem.setTypeFlag(SqlJetVdbeMemFlags.Int);
        type = SqlJetValueType.INTEGER;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#realify()
     */
    public void realify() {
        final SqlJetVdbeMem pMem = this;
        assert (pMem.db == null || pMem.db.getMutex().held());
        pMem.r = pMem.realValue();
        pMem.setTypeFlag(SqlJetVdbeMemFlags.Real);
        type = SqlJetValueType.FLOAT;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#numerify()
     */
    public void numerify() {
        final SqlJetVdbeMem pMem = this;
        double r1, r2;
        long i;
        assert (!(pMem.flags.contains(SqlJetVdbeMemFlags.Int) || pMem.flags.contains(SqlJetVdbeMemFlags.Real) || pMem.flags
                .contains(SqlJetVdbeMemFlags.Null)));
        assert (pMem.flags.contains(SqlJetVdbeMemFlags.Str) || pMem.flags.contains(SqlJetVdbeMemFlags.Blob));
        assert (pMem.db == null || pMem.db.getMutex().held());
        r1 = pMem.realValue();
        i = (long) r1;
        r2 = (double) i;
        if (r1 == r2) {
            pMem.integerify();
        } else {
            pMem.r = r1;
            pMem.setTypeFlag(SqlJetVdbeMemFlags.Real);
            type = SqlJetValueType.FLOAT;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setTypeFlag(org.tmatesoft.sqljet
     * .core.internal.vdbe.SqlJetVdbeMemFlags)
     */
    public void setTypeFlag(SqlJetVdbeMemFlags f) {
        final Iterator<SqlJetVdbeMemFlags> iterator = flags.iterator();
        while (iterator.hasNext()) {
            final SqlJetVdbeMemFlags flag = iterator.next();
            if (flag.ordinal() < SqlJetVdbeMemFlags.TypeMask.ordinal() || flag == SqlJetVdbeMemFlags.Zero)
                iterator.remove();
        }
        flags.add(f);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setZeroBlob(int)
     */
    public void setZeroBlob(int n) {
        final SqlJetVdbeMem pMem = this;
        pMem.release();
        pMem.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Blob, SqlJetVdbeMemFlags.Zero);
        pMem.type = SqlJetValueType.BLOB;
        pMem.n = 0;
        if (n < 0)
            n = 0;
        pMem.nZero = n;
        pMem.enc = SqlJetEncoding.UTF8;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setDouble(double)
     */
    public void setDouble(double val) {
        final SqlJetVdbeMem pMem = this;
        if (Double.isNaN(val)) {
            pMem.setNull();
        } else {
            pMem.release();
            pMem.r = val;
            pMem.flags = SqlJetUtility.of(SqlJetVdbeMemFlags.Real);
            pMem.type = SqlJetValueType.FLOAT;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#setRowSet()
     */
    public void setRowSet() {
        final SqlJetVdbeMem pMem = this;
        final ISqlJetDbHandle db = pMem.db;
        assert (db != null);
        if (pMem.flags.contains(SqlJetVdbeMemFlags.RowSet)) {
            pMem.pRowSet.clear();
        } else {
            pMem.release();
            pMem.pRowSet = new SqlJetRowSet(db);
            pMem.flags.add(SqlJetVdbeMemFlags.RowSet);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#isTooBig()
     */
    public boolean isTooBig() {
        final SqlJetVdbeMem p = this;
        assert (p.db != null);
        if (p.flags.contains(SqlJetVdbeMemFlags.Str) || p.flags.contains(SqlJetVdbeMemFlags.Blob)) {
            int n = p.n;
            if (p.flags.contains(SqlJetVdbeMemFlags.Zero)) {
                n += p.nZero;
            }
            return n > ISqlJetLimits.SQLJET_MAX_LENGTH;
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#sanity()
     */
    public void sanity() {
        final SqlJetVdbeMem pMem = this;
        final Set<SqlJetVdbeMemFlags> flags = pMem.flags;

        assert (flags != null && flags.size() > 0); /* Must define some type */
        if (flags.contains(SqlJetVdbeMemFlags.Str) || flags.contains(SqlJetVdbeMemFlags.Blob)) {
            int x = (flags.contains(SqlJetVdbeMemFlags.Static) ? 1 : 0)
                    + (flags.contains(SqlJetVdbeMemFlags.Dyn) ? 1 : 0)
                    + (flags.contains(SqlJetVdbeMemFlags.Ephem) ? 1 : 0);
            /* Strings must define a string subtype */
            /* Only one string subtype can be defined */
            assert (x == 1);
            assert (pMem.z != null); /* Strings must have a value */
            /* No destructor unless there is MEM_Dyn */
            assert (pMem.xDel == null || flags.contains(SqlJetVdbeMemFlags.Dyn));

            if (flags.contains(SqlJetVdbeMemFlags.Str)) {
                assert (pMem.enc == SqlJetEncoding.UTF8 || pMem.enc == SqlJetEncoding.UTF16BE || pMem.enc == SqlJetEncoding.UTF16LE);
                /*
                 * If the string is UTF-8 encoded and nul terminated, then
                 * pMem->n* must be the length of the string. (Later:) If the
                 * database file* has been corrupted, null characters might have
                 * been inserted* into the middle of the string. In that case,
                 * the sqlite3Strlen30()* might be less.
                 */
                if (pMem.enc == SqlJetEncoding.UTF8 && flags.contains(SqlJetVdbeMemFlags.Term)) {
                    assert (SqlJetUtility.strlen30(pMem.z) <= pMem.n);
                    assert (SqlJetUtility.getUnsignedByte(pMem.z, pMem.n) == 0);
                }
            }
        } else {
            /* Cannot define a string subtype for non-string objects */
            assert (!(pMem.flags.contains(SqlJetVdbeMemFlags.Static) || pMem.flags.contains(SqlJetVdbeMemFlags.Dyn) || pMem.flags
                    .contains(SqlJetVdbeMemFlags.Ephem)));
            assert (pMem.xDel == null);
        }
        /* MEM_Null excludes all other types */
        assert (!(pMem.flags.contains(SqlJetVdbeMemFlags.Static) || pMem.flags.contains(SqlJetVdbeMemFlags.Dyn) || pMem.flags
                .contains(SqlJetVdbeMemFlags.Ephem)) || !pMem.flags.contains(SqlJetVdbeMemFlags.Null));
        /* If the MEM is both real and integer, the values are equal */
        assert (pMem.flags.contains(SqlJetVdbeMemFlags.Int) && pMem.flags.contains(SqlJetVdbeMemFlags.Real) && pMem.r == pMem.i);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetVdbeMem#valueBytes(org.tmatesoft.sqljet
     * .core.SqlJetEncoding)
     */
    public int valueBytes(SqlJetEncoding enc) throws SqlJetException {
        SqlJetVdbeMem p = this;
        if (p.flags.contains(SqlJetVdbeMemFlags.Blob) || p.valueText(enc) != null) {
            if (p.flags.contains(SqlJetVdbeMemFlags.Zero)) {
                return p.n + p.nZero;
            } else {
                return p.n;
            }
        }
        return 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#handleBom()
     */
    public void handleBom() {

        final SqlJetVdbeMem pMem = this;

        SqlJetEncoding bom = null;

        if (pMem.n < 0 || pMem.n > 1) {
            short b1 = (short) getUnsignedByte(pMem.z, 0);
            short b2 = (short) getUnsignedByte(pMem.z, 1);
            if (b1 == 0xFE && b2 == 0xFF) {
                bom = SqlJetEncoding.UTF16BE;
            }
            if (b1 == 0xFF && b2 == 0xFE) {
                bom = SqlJetEncoding.UTF16LE;
            }
        }

        if (null != bom) {
            pMem.makeWriteable();
            pMem.n -= 2;
            memmove(pMem.z, 0, pMem.z, 2, pMem.n);
            putUnsignedByte(pMem.z, pMem.n, (byte) 0);
            putUnsignedByte(pMem.z, pMem.n + 1, (byte) 0);
            pMem.flags.add(SqlJetVdbeMemFlags.Term);
            pMem.enc = bom;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetVdbeMem#getFlags()
     */
    public Set<SqlJetVdbeMemFlags> getFlags() {
        return flags;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.ISqlJetVdbeMem#isNull()
     */
    public boolean isNull() {
        if (null == flags)
            return true;
        return flags.contains(SqlJetVdbeMemFlags.Null);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.ISqlJetVdbeMem#getType()
     */
    public SqlJetValueType getType() {
        return type;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.ISqlJetVdbeMem#valueBlob()
     */
    public ISqlJetMemoryPointer valueBlob() throws SqlJetException {
        if (flags.contains(SqlJetVdbeMemFlags.Str) || flags.contains(SqlJetVdbeMemFlags.Blob)) {
            expandBlob();
            flags.remove(SqlJetVdbeMemFlags.Str);
            flags.add(SqlJetVdbeMemFlags.Blob);
            z.limit(n);
            return z;
        } else {
            return valueText(SqlJetEncoding.UTF8);
        }
    }

    /**
     * Processing is determine by the affinity parameter:
     *
     * <table>
     * <tr>
     * <td>
     * <ul>
     * <li>AFF_INTEGER:</li>
     * <li>AFF_REAL:</li>
     * <li>AFF_NUMERIC:</li>
     * </ul>
     * </td>
     * <td></td>
     * </tr>
     * <tr>
     * <td></td>
     * <td>
     * Try to convert value to an integer representation or a floating-point
     * representation if an integer representation is not possible. Note that
     * the integer representation is always preferred, even if the affinity is
     * REAL, because an integer representation is more space efficient on disk.</td>
     * </tr>
     * <tr>
     * <td>
     * <ul>
     * <li>AFF_TEXT:</li>
     * </ul>
     * </td>
     * <td></td>
     * </tr>
     * <tr>
     * <td></td>
     * <td>Convert value to a text representation.</td>
     * </tr>
     *
     * <tr>
     * <td>
     * <ul>
     * <li>AFF_NONE:</li>
     * </ul>
     * </td>
     * <td></td>
     * </tr>
     * <tr>
     * <td></td>
     * <td>No-op. value is unchanged.</td>
     * </tr>
     * </table>
     *
     * @param affinity
     *            The affinity to be applied
     * @param enc
     *            Use this text encoding
     *
     * @throws SqlJetException
     */
    public void applyAffinity(SqlJetTypeAffinity affinity, SqlJetEncoding enc) throws SqlJetException {
        if (affinity == SqlJetTypeAffinity.TEXT) {
            /*
             * Only attempt the conversion to TEXT if there is an integer or
             * real representation (blob and NULL do not get converted) but no
             * string representation.
             */
            if (!flags.contains(SqlJetVdbeMemFlags.Str)
                    && (flags.contains(SqlJetVdbeMemFlags.Real) || flags.contains(SqlJetVdbeMemFlags.Int))) {
                stringify(enc);
            }
            flags.remove(SqlJetVdbeMemFlags.Real);
            flags.remove(SqlJetVdbeMemFlags.Int);
        } else if (affinity != SqlJetTypeAffinity.NONE) {
            assert (affinity == SqlJetTypeAffinity.INTEGER || affinity == SqlJetTypeAffinity.REAL || affinity == SqlJetTypeAffinity.NUMERIC);
            applyNumericAffinity();
            /*
            if (flags.contains(SqlJetVdbeMemFlags.Real)) {
                applyIntegerAffinity();
            }
            */
        }
    }

    /**
     * Try to convert a value into a numeric representation if we can do so
     * without loss of information. In other words, if the string looks like a
     * number, convert it into a number. If it does not look like a number,
     * leave it alone.
     *
     * @throws SqlJetException
     */
    public void applyNumericAffinity() throws SqlJetException {
        if (!flags.contains(SqlJetVdbeMemFlags.Real) && !flags.contains(SqlJetVdbeMemFlags.Int)) {
            boolean[] realnum = { false };
            nulTerminate();
            if (flags.contains(SqlJetVdbeMemFlags.Str)
                    && SqlJetUtility.isNumber(SqlJetUtility.toString(z, enc), realnum)) {
                Long value;
                changeEncoding(SqlJetEncoding.UTF8);
                if (!realnum[0] && (value = SqlJetUtility.atoi64(SqlJetUtility.toString(z))) != null) {
                    i = value;
                    setTypeFlag(SqlJetVdbeMemFlags.Int);
                    type = SqlJetValueType.INTEGER;
                } else {
                    realify();
                }
            }
        } else if (type != SqlJetValueType.INTEGER && type != SqlJetValueType.FLOAT) {
            if (flags.contains(SqlJetVdbeMemFlags.Int)) {
                type = SqlJetValueType.INTEGER;
            } else if (flags.contains(SqlJetVdbeMemFlags.Real)) {
                type = SqlJetValueType.FLOAT;
            }
        }
    }

    /**
     * The MEM structure is already a MEM_Real. Try to also make it a MEM_Int if
     * we can.
     */
    void applyIntegerAffinity() {
        assert (flags.contains(SqlJetVdbeMemFlags.Real));
        assert (!flags.contains(SqlJetVdbeMemFlags.RowSet));
        assert (db == null || SqlJetUtility.mutex_held(db.getMutex()));
        final Long l = SqlJetUtility.doubleToInt64(r);
        if (l != null) {
            i = l;
            flags.add(SqlJetVdbeMemFlags.Int);
            type = SqlJetValueType.INTEGER;
        }
    }
}
