/*
** 2006 September 30
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Implementation of the full-text-search tokenizer that implements
** a Porter stemmer.
**
*/

/*
 * This file is based on the SQLite FTS3 Porter Stemmer implementation.
 *
 * This is an attempt to provide some level of full-text search to users of
 *  Thunderbird who use languages that are not space/punctuation delimited.
 *  This is accomplished by performing bi-gram indexing of characters fall
 *  into the unicode space occupied by character sets used in such languages.
 *
 * Bi-gram indexing means that given the string "12345" we would index the
 *  pairs "12", "23", "34", and "45" (with position information).  We do this
 *  because we are not sure where the word/semantic boundaries are in that
 *  string.  Then, when a user searches for "234" the FTS3 engine tokenizes the
 *  search query into "23" and "34".  Using special phrase-logic FTS3 requires
 *  the matches to have the tokens "23" and "34" adjacent to each other and in
 *  that order.  In theory if the user searched for "2345" we we could just
 *  search for "23 NEAR/2 34".  Unfortunately, NEAR does not imply ordering,
 *  so even though that would be more efficient, we would lose correctness
 *  and cannot do it.
 *
 * The efficiency and usability of bi-gram search assumes that the character
 *  space is large enough and actually observed bi-grams sufficiently
 *  distributed throughout the potential space so that the search bi-grams
 *  generated when the user issues a query find a 'reasonable' number of
 *  documents for each bi-gram match.
 *
 * Mozilla contributors:
 *   Makoto Kato <m_kato@ga2.so-net.ne.jp>
 *   Andrew Sutherland <asutherland@asutherland.org>
 */

/*
** The code in this file is only compiled if:
**
**     * The FTS3 module is being built as an extension
**       (in which case SQLITE_CORE is not defined), or
**
**     * The FTS3 module is being built into the core of
**       SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
*/
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)


#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "fts3_tokenizer.h"

/* need some defined to compile without sqlite3 code */

#define sqlite3_malloc malloc
#define sqlite3_free free
#define sqlite3_realloc realloc

static const unsigned char sqlite3Utf8Trans1[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
};

typedef unsigned char u8;

/**
 * SQLite helper macro from sqlite3.c (really utf.c) to encode a unicode
 * character into utf8.
 *
 * @param zOut A pointer to the current write position that is updated by
 *     the routine.  At entry it should point to one-past the last valid
 *     encoded byte.  The same holds true at exit.
 * @param c The character to encode; this should be an unsigned int.
 */
#define WRITE_UTF8(zOut, c) {                          \
  if( c<0x0080 ){                                      \
    *zOut++ = (u8)(c&0xff);                            \
  }                                                    \
  else if( c<0x0800 ){                                 \
    *zOut++ = 0xC0 + (u8)((c>>6) & 0x1F);              \
    *zOut++ = 0x80 + (u8)(c & 0x3F);                   \
  }                                                    \
  else if( c<0x10000 ){                                \
    *zOut++ = 0xE0 + (u8)((c>>12) & 0x0F);             \
    *zOut++ = 0x80 + (u8)((c>>6) & 0x3F);              \
    *zOut++ = 0x80 + (u8)(c & 0x3F);                   \
  }else{                                               \
    *zOut++ = 0xf0 + (u8)((c>>18) & 0x07);             \
    *zOut++ = 0x80 + (u8)((c>>12) & 0x3F);             \
    *zOut++ = 0x80 + (u8)((c>>6) & 0x3F);              \
    *zOut++ = 0x80 + (u8)(c & 0x3F);                   \
  }                                                    \
}

/**
 * Fudge factor to avoid buffer overwrites when WRITE_UTF8 is involved.
 *
 * Our normalization table includes entries that may result in a larger
 *  utf-8 encoding.  Namely, 023a maps to 2c65.  This is a growth from 2 bytes
 *  as utf-8 encoded to 3 bytes.  This is currently the only transition possible
 *  because 1-byte encodings are known to stay 1-byte and our normalization
 *  table is 16-bit and so can't generate a 4-byte encoded output.
 *
 * For simplicity, we just multiple by 2 which covers the current case and
 *  potential growth for 2-byte to 4-byte growth.  We can afford to do this
 *  because we're not talking about a lot of memory here as a rule.
 */
#define MAX_UTF8_GROWTH_FACTOR 2

/**
 * Helper from sqlite3.c to read a single UTF8 character.
 *
 * The clever bit with multi-byte reading is that you keep going until you find
 *  a byte whose top bits are not '10'.  A single-byte UTF8 character will have
 *  '00' or '01', and a multi-byte UTF8 character must start with '11'.
 *
 * In the event of illegal UTF-8 this macro may read an arbitrary number of
 *  characters but will never read past zTerm.  The resulting character value
 *  of illegal UTF-8 can be anything, although efforts are made to return the
 *  illegal character (0xfffd) for UTF-16 surrogates.
 *
 * @param zIn A pointer to the current position that is updated by the routine,
 *     pointing at the start of the next character when the routine returns.
 * @param zTerm A pointer one past the end of the buffer.
 * @param c The 'unsigned int' to hold the resulting character value.  Do not
 *      use a short or a char.
 */
#define READ_UTF8(zIn, zTerm, c) {                         \
  c = *(zIn++);                                            \
  if( c>=0xc0 ){                                           \
    c = sqlite3Utf8Trans1[c-0xc0];                         \
    while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){            \
      c = (c<<6) + (0x3f & *(zIn++));                      \
    }                                                      \
    if( c<0x80                                             \
        || (c&0xFFFFF800)==0xD800                          \
        || (c&0xFFFFFFFE)==0xFFFE ){  c = 0xFFFD; }        \
  }                                                        \
}

/* end of compatible block to complie codes */

/*
** Class derived from sqlite3_tokenizer
*/
typedef struct porter_tokenizer {
  sqlite3_tokenizer base;      /* Base class */
} porter_tokenizer;

/*
** Class derived from sqlit3_tokenizer_cursor
*/
typedef struct porter_tokenizer_cursor {
  sqlite3_tokenizer_cursor base;
  const char *zInput;          /* input we are tokenizing */
  int nInput;                  /* size of the input */
  int iOffset;                 /* current position in zInput */
  int iToken;                  /* index of next token to be returned */
  unsigned char *zToken;       /* storage for current token */
  int nAllocated;              /* space allocated to zToken buffer */
  /**
   * Store the offset of the second character in the bi-gram pair that we just
   *  emitted so that we can consider it being the first character in a bi-gram
   *  pair.
   * The value 0 indicates that there is no previous such character.  This is
   *  an acceptable sentinel value because the 0th offset can never be the
   *  offset of the second in a bi-gram pair.
   *
   * For example, let us say we are tokenizing a string of 4 CJK characters 
   *  represented by the byte-string "11223344" where each repeated digit
   *  indicates 2-bytes of storage used to encode the character in UTF-8.
   *  (It actually takes 3, btw.)  Then on the passes to emit each token,
   *  the iOffset and iPrevGigramOffset values at entry will be:
   *
   * 1122: iOffset = 0, iPrevBigramOffset = 0
   * 2233: iOffset = 4, iPrevBigramOffset = 2
   * 3344: iOffset = 6, iPrevBigramOffset = 4
   * (nothing will be emitted): iOffset = 8, iPrevBigramOffset = 6
   */
  int iPrevBigramOffset;       /* previous result was bi-gram */
} porter_tokenizer_cursor;


/* Forward declaration */
static const sqlite3_tokenizer_module porterTokenizerModule;

/* from normalize.c */
extern unsigned int normalize_character(const unsigned int c);

/*
** Create a new tokenizer instance.
*/
static int porterCreate(
  int argc, const char * const *argv,
  sqlite3_tokenizer **ppTokenizer
){
  porter_tokenizer *t;
  t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t));
  if( t==NULL ) return SQLITE_NOMEM;
  memset(t, 0, sizeof(*t));
  *ppTokenizer = &t->base;
  return SQLITE_OK;
}

/*
** Destroy a tokenizer
*/
static int porterDestroy(sqlite3_tokenizer *pTokenizer){
  sqlite3_free(pTokenizer);
  return SQLITE_OK;
}

/*
** Prepare to begin tokenizing a particular string.  The input
** string to be tokenized is zInput[0..nInput-1].  A cursor
** used to incrementally tokenize this string is returned in 
** *ppCursor.
*/
static int porterOpen(
  sqlite3_tokenizer *pTokenizer,         /* The tokenizer */
  const char *zInput, int nInput,        /* String to be tokenized */
  sqlite3_tokenizer_cursor **ppCursor    /* OUT: Tokenization cursor */
){
  porter_tokenizer_cursor *c;

  c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
  if( c==NULL ) return SQLITE_NOMEM;

  c->zInput = zInput;
  if( zInput==0 ){
    c->nInput = 0;
  }else if( nInput<0 ){
    c->nInput = (int)strlen(zInput);
  }else{
    c->nInput = nInput;
  }
  c->iOffset = 0;                 /* start tokenizing at the beginning */
  c->iToken = 0;
  c->zToken = NULL;               /* no space allocated, yet. */
  c->nAllocated = 0;
  c->iPrevBigramOffset = 0;

  *ppCursor = &c->base;
  return SQLITE_OK;
}

/*
** Close a tokenization cursor previously opened by a call to
** porterOpen() above.
*/
static int porterClose(sqlite3_tokenizer_cursor *pCursor){
  porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
  sqlite3_free(c->zToken);
  sqlite3_free(c);
  return SQLITE_OK;
}
/*
** Vowel or consonant
*/
static const char cType[] = {
   0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
   1, 1, 1, 2, 1
};

/*
** isConsonant() and isVowel() determine if their first character in
** the string they point to is a consonant or a vowel, according
** to Porter ruls.  
**
** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'.
** 'Y' is a consonant unless it follows another consonant,
** in which case it is a vowel.
**
** In these routine, the letters are in reverse order.  So the 'y' rule
** is that 'y' is a consonant unless it is followed by another
** consonent.
*/
static int isVowel(const char*);
static int isConsonant(const char *z){
  int j;
  char x = *z;
  if( x==0 ) return 0;
  assert( x>='a' && x<='z' );
  j = cType[x-'a'];
  if( j<2 ) return j;
  return z[1]==0 || isVowel(z + 1);
}
static int isVowel(const char *z){
  int j;
  char x = *z;
  if( x==0 ) return 0;
  assert( x>='a' && x<='z' );
  j = cType[x-'a'];
  if( j<2 ) return 1-j;
  return isConsonant(z + 1);
}

/*
** Let any sequence of one or more vowels be represented by V and let
** C be sequence of one or more consonants.  Then every word can be
** represented as:
**
**           [C] (VC){m} [V]
**
** In prose:  A word is an optional consonant followed by zero or
** vowel-consonant pairs followed by an optional vowel.  "m" is the
** number of vowel consonant pairs.  This routine computes the value
** of m for the first i bytes of a word.
**
** Return true if the m-value for z is 1 or more.  In other words,
** return true if z contains at least one vowel that is followed
** by a consonant.
**
** In this routine z[] is in reverse order.  So we are really looking
** for an instance of of a consonant followed by a vowel.
*/
static int m_gt_0(const char *z){
  while( isVowel(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isConsonant(z) ){ z++; }
  return *z!=0;
}

/* Like mgt0 above except we are looking for a value of m which is
** exactly 1
*/
static int m_eq_1(const char *z){
  while( isVowel(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isConsonant(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isVowel(z) ){ z++; }
  if( *z==0 ) return 1;
  while( isConsonant(z) ){ z++; }
  return *z==0;
}

/* Like mgt0 above except we are looking for a value of m>1 instead
** or m>0
*/
static int m_gt_1(const char *z){
  while( isVowel(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isConsonant(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isVowel(z) ){ z++; }
  if( *z==0 ) return 0;
  while( isConsonant(z) ){ z++; }
  return *z!=0;
}

/*
** Return TRUE if there is a vowel anywhere within z[0..n-1]
*/
static int hasVowel(const char *z){
  while( isConsonant(z) ){ z++; }
  return *z!=0;
}

/*
** Return TRUE if the word ends in a double consonant.
**
** The text is reversed here. So we are really looking at
** the first two characters of z[].
*/
static int doubleConsonant(const char *z){
  return isConsonant(z) && z[0]==z[1] && isConsonant(z+1);
}

/*
** Return TRUE if the word ends with three letters which
** are consonant-vowel-consonent and where the final consonant
** is not 'w', 'x', or 'y'.
**
** The word is reversed here.  So we are really checking the
** first three letters and the first one cannot be in [wxy].
*/
static int star_oh(const char *z){
  return
    z[0]!=0 && isConsonant(z) &&
    z[0]!='w' && z[0]!='x' && z[0]!='y' &&
    z[1]!=0 && isVowel(z+1) &&
    z[2]!=0 && isConsonant(z+2);
}

/*
** If the word ends with zFrom and xCond() is true for the stem
** of the word that preceeds the zFrom ending, then change the 
** ending to zTo.
**
** The input word *pz and zFrom are both in reverse order.  zTo
** is in normal order. 
**
** Return TRUE if zFrom matches.  Return FALSE if zFrom does not
** match.  Not that TRUE is returned even if xCond() fails and
** no substitution occurs.
*/
static int stem(
  char **pz,             /* The word being stemmed (Reversed) */
  const char *zFrom,     /* If the ending matches this... (Reversed) */
  const char *zTo,       /* ... change the ending to this (not reversed) */
  int (*xCond)(const char*)   /* Condition that must be true */
){
  char *z = *pz;
  while( *zFrom && *zFrom==*z ){ z++; zFrom++; }
  if( *zFrom!=0 ) return 0;
  if( xCond && !xCond(z) ) return 1;
  while( *zTo ){
    *(--z) = *(zTo++);
  }
  *pz = z;
  return 1;
}

/**
 * Voiced sound mark is only on Japanese.  It is like accent.  It combines with
 * previous character.  Example, "サ" (Katakana) with "゛" (voiced sound mark) is
 * "ザ".  Although full-width character mapping has combined character like "ザ",
 * there is no combined character on half-width Katanaka character mapping.
 */
static int isVoicedSoundMark(const unsigned int c)
{
  if (c == 0xff9e || c == 0xff9f || c == 0x3099 || c == 0x309a)
    return 1;
  return 0;
}

/**
 * How many unicode characters to take from the front and back of a term in
 * |copy_stemmer|.
 */
#define COPY_STEMMER_COPY_HALF_LEN 10

/**
 * Normalizing but non-stemming term copying.
 *
 * The original function would take 10 bytes from the front and 10 bytes from
 * the back if there were no digits in the string and it was more than 20
 * bytes long.  If there were digits involved that would decrease to 3 bytes
 * from the front and 3 from the back.  This would potentially corrupt utf-8
 * encoded characters, which is fine from the perspective of the FTS3 logic.
 *
 * In our revised form we now operate on a unicode character basis rather than
 * a byte basis.  Additionally we use the same length limit even if there are
 * digits involved because it's not clear digit token-space reduction is saving
 * us from anything and could be hurting.  Specifically, if no one is ever
 * going to search on things with digits, then we should just remove them.
 * Right now, the space reduction is going to increase false positives when
 * people do search on them and increase the number of collisions sufficiently
 * to make it really expensive.  The caveat is there will be some increase in
 * index size which could be meaningful if people are receiving lots of emails
 * full of distinct numbers.
 *
 * In order to do the copy-from-the-front and copy-from-the-back trick, once
 * we reach N characters in, we set zFrontEnd to the current value of zOut
 * (which represents the termination of the first part of the result string)
 * and set zBackStart to the value of zOutStart.  We then advanced zBackStart
 * along a character at a time as we write more characters.  Once we have
 * traversed the entire string, if zBackStart > zFrontEnd, then we know
 * the string should be shrunk using the characters in the two ranges.
 *
 * (It would be faster to scan from the back with specialized logic but that
 * particular logic seems easy to screw up and we don't have unit tests in here
 * to the extent required.)
 *
 * @param zIn Input string to normalize and potentially shrink.
 * @param nBytesIn The number of bytes in zIn, distinct from the number of
 *     unicode characters encoded in zIn.
 * @param zOut The string to write our output into.  This must have at least
 *     nBytesIn * MAX_UTF8_GROWTH_FACTOR in order to compensate for
 *     normalization that results in a larger utf-8 encoding.
 * @param pnBytesOut Integer to write the number of bytes in zOut into.
 */
static void copy_stemmer(const unsigned char *zIn, const int nBytesIn,
                         unsigned char *zOut, int *pnBytesOut){
  const unsigned char *zInTerm = zIn + nBytesIn;
  unsigned char *zOutStart = zOut;
  unsigned int c;
  unsigned int charCount = 0;
  unsigned char *zFrontEnd = NULL, *zBackStart = NULL;
  unsigned int trashC;

  /* copy normalized character */
  while (zIn < zInTerm) {
    READ_UTF8(zIn, zInTerm, c);
    c = normalize_character(c);

    /* ignore voiced/semi-voiced sound mark */
    if (!isVoicedSoundMark(c)) {
      /* advance one non-voiced sound mark character. */
      if (zBackStart)
        READ_UTF8(zBackStart, zOut, trashC);

      WRITE_UTF8(zOut, c);
      charCount++;
      if (charCount == COPY_STEMMER_COPY_HALF_LEN) {
        zFrontEnd = zOut;
        zBackStart = zOutStart;
      }
    }
  }

  /* if we need to shrink the string, transplant the back bytes */
  if (zBackStart > zFrontEnd) { /* this handles when both are null too */
    size_t backBytes = zOut - zBackStart;
    memmove(zFrontEnd, zBackStart, backBytes);
    zOut = zFrontEnd + backBytes;
  }
  *zOut = 0;
  *pnBytesOut = zOut - zOutStart;
}


/*
** Stem the input word zIn[0..nIn-1].  Store the output in zOut.
** zOut is at least big enough to hold nIn bytes.  Write the actual
** size of the output word (exclusive of the '\0' terminator) into *pnOut.
**
** Any upper-case characters in the US-ASCII character set ([A-Z])
** are converted to lower case.  Upper-case UTF characters are
** unchanged.
**
** Words that are longer than about 20 bytes are stemmed by retaining
** a few bytes from the beginning and the end of the word.  If the
** word contains digits, 3 bytes are taken from the beginning and
** 3 bytes from the end.  For long words without digits, 10 bytes
** are taken from each end.  US-ASCII case folding still applies.
** 
** If the input word contains not digits but does characters not 
** in [a-zA-Z] then no stemming is attempted and this routine just 
** copies the input into the input into the output with US-ASCII
** case folding.
**
** Stemming never increases the length of the word.  So there is
** no chance of overflowing the zOut buffer.
*/
static void porter_stemmer(
  const unsigned char *zIn,
  unsigned int nIn,
  unsigned char *zOut,
  int *pnOut
){
  unsigned int i, j, c;
  char zReverse[28];
  char *z, *z2;
  const unsigned char *zTerm = zIn + nIn;
  const unsigned char *zTmp = zIn;

  if( nIn<3 || nIn>=sizeof(zReverse)-7 ){
    /* The word is too big or too small for the porter stemmer.
    ** Fallback to the copy stemmer */
    copy_stemmer(zIn, nIn, zOut, pnOut);
    return;
  }
  for (j = sizeof(zReverse) - 6; zTmp < zTerm; j--) {
    READ_UTF8(zTmp, zTerm, c);
    c = normalize_character(c);
    if( c>='a' && c<='z' ){
      zReverse[j] = c;
    }else{
      /* The use of a character not in [a-zA-Z] means that we fallback
      ** to the copy stemmer */
      copy_stemmer(zIn, nIn, zOut, pnOut);
      return;
    }
  }
  memset(&zReverse[sizeof(zReverse)-5], 0, 5);
  z = &zReverse[j+1];


  /* Step 1a */
  if( z[0]=='s' ){
    if(
     !stem(&z, "sess", "ss", 0) &&
     !stem(&z, "sei", "i", 0)  &&
     !stem(&z, "ss", "ss", 0)
    ){
      z++;
    }
  }

  /* Step 1b */  
  z2 = z;
  if( stem(&z, "dee", "ee", m_gt_0) ){
    /* Do nothing.  The work was all in the test */
  }else if( 
     (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel))
      && z!=z2
  ){
     if( stem(&z, "ta", "ate", 0) ||
         stem(&z, "lb", "ble", 0) ||
         stem(&z, "zi", "ize", 0) ){
       /* Do nothing.  The work was all in the test */
     }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){
       z++;
     }else if( m_eq_1(z) && star_oh(z) ){
       *(--z) = 'e';
     }
  }

  /* Step 1c */
  if( z[0]=='y' && hasVowel(z+1) ){
    z[0] = 'i';
  }

  /* Step 2 */
  switch( z[1] ){
   case 'a':
     (void) (stem(&z, "lanoita", "ate", m_gt_0) ||
             stem(&z, "lanoit", "tion", m_gt_0));
     break;
   case 'c':
     (void) (stem(&z, "icne", "ence", m_gt_0) ||
             stem(&z, "icna", "ance", m_gt_0));
     break;
   case 'e':
     (void) (stem(&z, "rezi", "ize", m_gt_0));
     break;
   case 'g':
     (void) (stem(&z, "igol", "log", m_gt_0));
     break;
   case 'l':
     (void) (stem(&z, "ilb", "ble", m_gt_0) ||
             stem(&z, "illa", "al", m_gt_0) ||
             stem(&z, "iltne", "ent", m_gt_0) ||
             stem(&z, "ile", "e", m_gt_0) ||
             stem(&z, "ilsuo", "ous", m_gt_0));
     break;
   case 'o':
     (void) (stem(&z, "noitazi", "ize", m_gt_0) ||
             stem(&z, "noita", "ate", m_gt_0) ||
             stem(&z, "rota", "ate", m_gt_0));
     break;
   case 's':
     (void) (stem(&z, "msila", "al", m_gt_0) ||
             stem(&z, "ssenevi", "ive", m_gt_0) ||
             stem(&z, "ssenluf", "ful", m_gt_0) ||
             stem(&z, "ssensuo", "ous", m_gt_0));
     break;
   case 't':
     (void) (stem(&z, "itila", "al", m_gt_0) ||
             stem(&z, "itivi", "ive", m_gt_0) ||
             stem(&z, "itilib", "ble", m_gt_0));
     break;
  }

  /* Step 3 */
  switch( z[0] ){
   case 'e':
     (void) (stem(&z, "etaci", "ic", m_gt_0) ||
             stem(&z, "evita", "", m_gt_0)   ||
             stem(&z, "ezila", "al", m_gt_0));
     break;
   case 'i':
     (void) (stem(&z, "itici", "ic", m_gt_0));
     break;
   case 'l':
     (void) (stem(&z, "laci", "ic", m_gt_0) ||
             stem(&z, "luf", "", m_gt_0));
     break;
   case 's':
     (void) (stem(&z, "ssen", "", m_gt_0));
     break;
  }

  /* Step 4 */
  switch( z[1] ){
   case 'a':
     if( z[0]=='l' && m_gt_1(z+2) ){
       z += 2;
     }
     break;
   case 'c':
     if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e')  && m_gt_1(z+4)  ){
       z += 4;
     }
     break;
   case 'e':
     if( z[0]=='r' && m_gt_1(z+2) ){
       z += 2;
     }
     break;
   case 'i':
     if( z[0]=='c' && m_gt_1(z+2) ){
       z += 2;
     }
     break;
   case 'l':
     if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){
       z += 4;
     }
     break;
   case 'n':
     if( z[0]=='t' ){
       if( z[2]=='a' ){
         if( m_gt_1(z+3) ){
           z += 3;
         }
       }else if( z[2]=='e' ){
         (void) (stem(&z, "tneme", "", m_gt_1) ||
                 stem(&z, "tnem", "", m_gt_1) ||
                 stem(&z, "tne", "", m_gt_1));
       }
     }
     break;
   case 'o':
     if( z[0]=='u' ){
       if( m_gt_1(z+2) ){
         z += 2;
       }
     }else if( z[3]=='s' || z[3]=='t' ){
       (void) (stem(&z, "noi", "", m_gt_1));
     }
     break;
   case 's':
     if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){
       z += 3;
     }
     break;
   case 't':
     (void) (stem(&z, "eta", "", m_gt_1) ||
             stem(&z, "iti", "", m_gt_1));
     break;
   case 'u':
     if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){
       z += 3;
     }
     break;
   case 'v':
   case 'z':
     if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){
       z += 3;
     }
     break;
  }

  /* Step 5a */
  if( z[0]=='e' ){
    if( m_gt_1(z+1) ){
      z++;
    }else if( m_eq_1(z+1) && !star_oh(z+1) ){
      z++;
    }
  }

  /* Step 5b */
  if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){
    z++;
  }

  /* z[] is now the stemmed word in reverse order.  Flip it back
  ** around into forward order and return.
  */
  *pnOut = i = strlen(z);
  zOut[i] = 0;
  while( *z ){
    zOut[--i] = *(z++);
  }
}

/**
 * Indicate whether characters in the 0x30 - 0x7f region can be part of a token.
 * Letters and numbers can; punctuation (and 'del') can't.
 */
static const char porterIdChar[] = {
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,  /* 3x */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 4x */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,  /* 5x */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 6x */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,  /* 7x */
};

/**
 * Test whether a character is a (non-ascii) space character or not.  isDelim
 *  uses the existing porter stemmer logic for anything in the ASCII (< 0x80)
 *  space which covers 0x20.
 *
 * 0x2000-0x206F is the general punctuation table.  0x2000 - 0x200b are spaces.
 *  The spaces 0x2000 - 0x200a are all defined as roughly equivalent to a
 *  standard 0x20 space.  0x200b is a "zero width space" (ZWSP) and not like an
 *  0x20 space.  0x202f is a narrow no-break space and roughly equivalent to an
 *  0x20 space.  0x205f is a "medium mathematical space" and defined as roughly
 *  equivalent to an 0x20 space.
 */
#define IS_UNI_SPACE(x) (((x)>=0x2000&&(x)<=0x200a) || (x)==0x202f || (x)==0x205f)
/**
 * What we are checking for:
 * - 0x3001: Ideographic comma (-> 0x2c ',')
 * - 0x3002: Ideographic full stop (-> 0x2e '.')
 * - 0xff0c: fullwidth comma (~ wide 0x2c ',')
 * - 0xff0e: fullwidth full stop (~ wide 0x2e '.')
 * - 0xff61: halfwidth ideographic full stop (~ narrow 0x3002)
 * - 0xff64: halfwidth ideographic comma (~ narrow 0x3001)
 *
 * It is possible we should be treating other things as delimiters!
 */
#define IS_JA_DELIM(x) (((x)==0x3001)||((x)==0xFF64)||((x)==0xFF0E)||((x)==0x3002)||((x)==0xFF61)||((x)==0xFF0C))

/**
 * The previous character was a delimeter (which includes the start of the
 *  string).
 */
#define BIGRAM_RESET   0
/**
 * The previous character was a CJK character and we have only seen one of them.
 *  If we had seen more than one in a row it would be the BIGRAM_USE state.
 */
#define BIGRAM_UNKNOWN 1
/**
 * We have seen two or more CJK characters in a row.
 */
#define BIGRAM_USE     2
/**
 * The previous character was ASCII or something in the unicode general scripts
 *  area that we do not believe is a delimeter.  We call it 'alpha' as in
 *  alphabetic/alphanumeric and something that should be tokenized based on
 *  delimiters rather than on a bi-gram basis.
 */
#define BIGRAM_ALPHA   3

static int isDelim(
  const unsigned char *zCur,    /* IN: current pointer of token */
  const unsigned char *zTerm,   /* IN: one character beyond end of token */
  int *len,                     /* OUT: analyzed bytes in this token */
  int *state                    /* IN/OUT: analyze state */
){
  const unsigned char *zIn = zCur;
  unsigned int c;
  int delim;

  /* get the unicode character to analyze */
  READ_UTF8(zIn, zTerm, c);
  c = normalize_character(c);
  *len = zIn - zCur;

  /* ASCII character range has rule */
  if( c < 0x80 ){
    // This is original porter stemmer isDelim logic.
    // 0x0 - 0x1f are all control characters, 0x20 is space, 0x21-0x2f are
    //  punctuation.
    delim = (c < 0x30 || !porterIdChar[c - 0x30]);
    // cases: "&a", "&."
    if (*state == BIGRAM_USE || *state == BIGRAM_UNKNOWN ){
      /* previous maybe CJK and current is ascii */
      *state = BIGRAM_ALPHA; /*ascii*/
      delim = 1; /* must break */
    } else if (delim == 1) {
      // cases: "a.", ".."
      /* this is delimiter character */
      *state = BIGRAM_RESET; /*reset*/
    } else {
      // cases: "aa", ".a"
      *state = BIGRAM_ALPHA; /*ascii*/
    }
    return delim;
  }

  // (at this point we must be a non-ASCII character)

  /* voiced/semi-voiced sound mark is ignore */
  if (isVoicedSoundMark(c) && *state != BIGRAM_ALPHA) {
    /* ignore this because it is combined with previous char */
   return 0;
  }

  /* this isn't CJK range, so return as no delim */
  // Anything less than 0x2000 (except to U+0E00-U+0EFF and  U+1780-U+17FF) 
  // is the general scripts area and should not be bi-gram indexed.
  // 0xa000 - 0a4cf is the Yi area.  It is apparently a phonetic language whose
  //  usage does not appear to have simple delimeter rules, so we're leaving it
  //  as bigram processed.  This is a guess, if you know better, let us know.
  //  (We previously bailed on this range too.)
  // Addition, U+0E00-U+0E7F is Thai, U+0E80-U+0EFF is Laos,
  // and U+1780-U+17FF is Khmer.  It is no easy way to break each word.
  // So these should use bi-gram too. 
  // cases: "aa", ".a", "&a"
  if (c < 0xe00 ||
     (c >= 0xf00 && c < 0x1780) ||
     (c >= 0x1800 && c < 0x2000)) {
    *state = BIGRAM_ALPHA; /* not really ASCII but same idea; tokenize it */
    return 0;
  }

  // (at this point we must be a bi-grammable char or delimiter)

  /* this is space character or delim character */
  // cases: "a.", "..", "&."
  if( IS_UNI_SPACE(c) || IS_JA_DELIM(c) ){
    *state = BIGRAM_RESET; /* reset */
    return 1; /* it actually is a delimiter; report as such */
  }

  // (at this point we must be a bi-grammable char)

  // cases: "a&"
  if( *state==BIGRAM_ALPHA ){
    /* Previous is ascii and current maybe CJK */
    *state = BIGRAM_UNKNOWN; /* mark as unknown */
    return 1; /* break to emit the ASCII token*/
  }

  /* We have no rule for CJK!. use bi-gram */
  // cases: "&&"
  if( *state==BIGRAM_UNKNOWN || *state==BIGRAM_USE ){
    /* previous state is unknown.  mark as bi-gram */
    *state = BIGRAM_USE;
    return 1; /* break to emit the digram */
  }

  // cases: ".&" (*state == BIGRAM_RESET)
  *state = BIGRAM_UNKNOWN; /* mark as unknown */
  return 0; /* no need to break; nothing to emit */
}

/**
 * Generate a new token.  There are basically three types of token we can
 *  generate:
 * - A porter stemmed token.  This is a word entirely comprised of ASCII
 *    characters.  We run the porter stemmer algorithm against the word.
 *    Because we have no way to know what is and is not an English word
 *    (the only language for which the porter stemmer was designed), this
 *    could theoretically map multiple words that are not variations of the
 *    same word down to the same root, resulting in potentially unexpected
 *    result inclusions in the search results.  We accept this result because
 *    there's not a lot we can do about it and false positives are much
 *    better than false negatives.
 * - A copied token; case/accent-folded but not stemmed.  We call the porter
 *    stemmer for all non-CJK cases and it diverts to the copy stemmer if it
 *    sees any non-ASCII characters (after folding) or if the string is too
 *    long.  The copy stemmer will shrink the string if it is deemed too long.
 * - A bi-gram token; two CJK-ish characters.  For query reasons we generate a
 *    series of overlapping bi-grams.  (We can't require the user to start their
 *    search based on the arbitrary context of the indexed documents.)
 *
 * It may be useful to think of this function as operating at the points between
 *  characters.  While we are considering the 'current' character (the one after
 *  the 'point'), we are also interested in the 'previous' character (the one
 *  preceding the point).
 * At any 'point', there are a number of possible situations which I will
 *  illustrate with pairs of characters. 'a' means alphanumeric ASCII or a
 *  non-ASCII character that is not bi-grammable or a delimeter, '.'
 *  means a delimiter (space or punctuation), '&' means a bi-grammable
 *  character.
 * - aa: We are in the midst of a token.  State remains BIGRAM_ALPHA.
 * - a.: We will generate a porter stemmed or copied token.  State was
 *        BIGRAM_ALPHA, gets set to BIGRAM_RESET.
 * - a&: We will generate a porter stemmed or copied token; we will set our
 *        state to BIGRAM_UNKNOWN to indicate we have seen one bigram character
 *        but that it is not yet time to emit a bigram.
 * - .a: We are starting a token.  State was BIGRAM_RESET, gets set to
 *        BIGRAM_ALPHA.
 * - ..: We skip/eat the delimeters.  State stays BIGRAM_RESET.
 * - .&: State set to BIGRAM_UNKNOWN to indicate we have seen one bigram char.
 * - &a: If the state was BIGRAM_USE, we generate a bi-gram token.  If the state
 *        was BIGRAM_UNKNOWN we had only seen one CJK character and so don't do
 *        anything.  State is set to BIGRAM_ALPHA.
 * - &.: Same as the "&a" case, but state is set to BIGRAM_RESET.
 * - &&: We will generate a bi-gram token.  State was either BIGRAM_UNKNOWN or
 *        BIGRAM_USE, gets set to BIGRAM_USE.
 */
static int porterNext(
  sqlite3_tokenizer_cursor *pCursor,  /* Cursor returned by porterOpen */
  const char **pzToken,               /* OUT: *pzToken is the token text */
  int *pnBytes,                       /* OUT: Number of bytes in token */
  int *piStartOffset,                 /* OUT: Starting offset of token */
  int *piEndOffset,                   /* OUT: Ending offset of token */
  int *piPosition                     /* OUT: Position integer of token */
){
  porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
  const unsigned char *z = (unsigned char *) c->zInput;
  int len = 0;
  int state;

  while( c->iOffset < c->nInput ){
    int iStartOffset, numChars;

    /*
     * This loop basically has two modes of operation:
     * - general processing (iPrevBigramOffset == 0 here)
     * - CJK processing (iPrevBigramOffset != 0 here)
     *
     * In an general processing pass we skip over all the delimiters, leaving us
     *  at a character that promises to produce a token.  This could be a CJK
     *  token (state == BIGRAM_USE) or an ALPHA token (state == BIGRAM_ALPHA).
     * If it was a CJK token, we transition into CJK state for the next loop.
     * If it was an alpha token, our current offset is pointing at a delimiter
     *  (which could be a CJK character), so it is good that our next pass
     *  through the function and loop will skip over any delimiters.  If the
     *  delimiter we hit was a CJK character, the next time through we will
     *  not treat it as a delimiter though; the entry state for that scan is
     *  BIGRAM_RESET so the transition is not treated as a delimiter!
     * 
     * The CJK pass always starts with the second character in a bi-gram emitted
     *  as a token in the previous step.  No delimiter skipping is required
     *  because we know that first character might produce a token for us.  It
     *  only 'might' produce a token because the previous pass performed no
     *  lookahead and cannot be sure it is followed by another CJK character.
     *  This is why 
     */

    // If we have a previous bigram offset
    if (c->iPrevBigramOffset == 0) {
      /* Scan past delimiter characters */
      state = BIGRAM_RESET; /* reset */
      while (c->iOffset < c->nInput &&
             isDelim(z + c->iOffset, z + c->nInput, &len, &state)) {
        c->iOffset += len;
      }

    } else {
      /* for bigram indexing, use previous offset */
      c->iOffset = c->iPrevBigramOffset;
    }

    /* Count non-delimiter characters. */
    iStartOffset = c->iOffset;
    numChars = 0;

    // Start from a reset state.  This means the first character we see
    //  (which will not be a delimiter) determines which of ALPHA or CJK modes
    //  we are operating in.  (It won't be a delimiter because in a 'general'
    //  pass as defined above, we will have eaten all the delimiters, and in
    //  a CJK pass we are guaranteed that the first character is CJK.)
    state = BIGRAM_RESET; /* state is reset */
    // Advance until it is time to emit a token.
    // For ALPHA characters, this means advancing until we encounter a delimiter
    //  or a CJK character.  iOffset will be pointing at the delimiter or CJK
    //  character, aka one beyond the last ALPHA character.
    // For CJK characters this means advancing until we encounter an ALPHA
    //  character, a delimiter, or we have seen two consecutive CJK
    //  characters.  iOffset points at the ALPHA/delimiter in the first 2 cases
    //  and the second of two CJK characters in the last case.
    // Because of the way this loop is structured, iOffset is only updated
    //  when we don't terminate.  However, if we terminate, len still contains
    //  the number of bytes in the character found at iOffset.  (This is useful
    //  in the CJK case.)
    while (c->iOffset < c->nInput &&
           !isDelim(z + c->iOffset, z + c->nInput, &len, &state)) {
      c->iOffset += len;
      numChars++;
    }

    if (state == BIGRAM_USE) {
      /* Split word by bigram */
      // Right now iOffset is pointing at the second character in a pair.
      //  Save this offset so next-time through we start with that as the
      //  first character.
      c->iPrevBigramOffset = c->iOffset;
      // And now advance so that iOffset is pointing at the character after
      //  the second character in the bi-gram pair.  Also count the char.
      c->iOffset += len;
      numChars++;
    } else {
      /* Reset bigram offset */
      c->iPrevBigramOffset = 0;
    }

    /* We emit a token if:
     *  - there are two ideograms together,
     *  - there are three chars or more,
     *  - we think this is a query and wildcard magic is desired.
     * We think is a wildcard query when we have a single character, it starts
     *  at the start of the buffer, it's CJK, our current offset is one shy of
     *  nInput and the character at iOffset is '*'.  Because the state gets
     *  clobbered by the incidence of '*' our requirement for CJK is that the
     *  implied character length is at least 3 given that it takes at least 3
     *  bytes to encode to 0x2000.
     */
    // It is possible we have no token to emit here if iPrevBigramOffset was not
    //  0 on entry and there was no second CJK character.  iPrevBigramOffset
    //  will now be 0 if that is the case (and c->iOffset == iStartOffset).
    if (// allow two-character words only if in bigram
        (numChars == 2 && state == BIGRAM_USE) ||
        // otherwise, drop two-letter words (considered stop-words)
        (numChars >=3) ||
        // wildcard case:
        (numChars == 1 && iStartOffset == 0 &&
         (c->iOffset >= 3) &&
         (c->iOffset == c->nInput - 1) &&
         (z[c->iOffset] == '*'))) {
      /* figure out the number of bytes to copy/stem */
      int n = c->iOffset - iStartOffset;
      /* make sure there is enough buffer space */
      if (n * MAX_UTF8_GROWTH_FACTOR > c->nAllocated) {
        c->nAllocated = n * MAX_UTF8_GROWTH_FACTOR + 20;
        c->zToken = sqlite3_realloc(c->zToken, c->nAllocated);
        if (c->zToken == NULL)
          return SQLITE_NOMEM;
      }

      if (state == BIGRAM_USE) {
        /* This is by bigram. So it is unnecessary to convert word */
        copy_stemmer(&z[iStartOffset], n, c->zToken, pnBytes);
      } else {
        porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes);
      }
      *pzToken = c->zToken;
      *piStartOffset = iStartOffset;
      *piEndOffset = c->iOffset;
      *piPosition = c->iToken++;
      return SQLITE_OK;
    }
  }
  return SQLITE_DONE;
}

/*
** The set of routines that implement the porter-stemmer tokenizer
*/
static const sqlite3_tokenizer_module porterTokenizerModule = {
  0,
  porterCreate,
  porterDestroy,
  porterOpen,
  porterClose,
  porterNext,
};

/*
** Allocate a new porter tokenizer.  Return a pointer to the new
** tokenizer in *ppModule
*/
void sqlite3Fts3PorterTokenizerModule(
  sqlite3_tokenizer_module const**ppModule
){
  *ppModule = &porterTokenizerModule;
}

#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
