/*
 * BEGIN FLOCK GPL
 *
 * Copyright Flock Inc. 2005-2009
 * http://flock.com
 *
 * This file may be used under the terms of the
 * GNU General Public License Version 2 or later (the "GPL"),
 * http://www.gnu.org/licenses/gpl.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * END FLOCK GPL
 */


#pragma GCC visibility push(default)
#include <stdexcept>
#pragma GCC visibility pop

#include "flockLuceneImpl.h"

#include "nsIClassInfoImpl.h"
#include "nsIProgrammingLanguage.h"

#include "nsTHashtable.h"
#include "nsHashKeys.h"

#include "nsCOMArray.h"

#include "nsMemory.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsICharsetConverterManager.h"
#include "nsIFile.h"

#include "prlog.h"

#include "CLucene/queryParser/MultiFieldQueryParser.h"

#ifdef PR_LOGGING
static const PRLogModuleInfo *gLuceneLog = NULL;
#define LOG_DEBUG(args) PR_LOG(gLuceneLog, PR_LOG_DEBUG, args);
#define LOG_ERROR(args) PR_LOG(gLuceneLog, PR_LOG_ERROR, args);
#else
#define LOG_DEBUG(args)
#define LOG_ERROR(args)
#endif

static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);

#define NC_BASE             "http://home.netscape.com/NC-rdf#"
#define FLOCK_BASE          "http://flock.com/rdf#"

#define FIELD_URI           NC_BASE    "URL"
#define FIELD_URL           FLOCK_BASE "URL"
#define FIELD_TITLE         NC_BASE    "Name"
#define FIELD_TAGS          FLOCK_BASE "tags"
#define FIELD_DESCRIPTION   NC_BASE    "Description"
#define FIELD_TEXT          FLOCK_BASE "contents"
#define FIELD_TYPE          FLOCK_BASE "type"

#define LUCENE_URI          mConversions[LOOKUP_FIELD_URI]
#define LUCENE_URL          mConversions[LOOKUP_FIELD_URL]
#define LUCENE_TITLE        mConversions[LOOKUP_FIELD_TITLE]
#define LUCENE_TAGS         mConversions[LOOKUP_FIELD_TAGS]
#define LUCENE_DESCRIPTION  mConversions[LOOKUP_FIELD_DESCRIPTION]
#define LUCENE_TEXT         mConversions[LOOKUP_FIELD_TEXT]
#define LUCENE_TYPE         mConversions[LOOKUP_FIELD_TYPE]

#ifdef XP_WIN

#define ConvertUTF16toTCHAR(str) ((TCHAR*)(str).BeginReading())
#define ConvertASCIItoTCHAR(str) ((TCHAR*)NS_ConvertASCIItoUTF16(str).get())
#define ConvertTCHARtoUTF16(str) ((PRUnichar*)(str))
#define ConvertTCHARtoASCII(str) NS_LossyConvertUTF16toASCII((PRUnichar*)(str))

#else /* !XP_WIN */

#define ConvertUTF16toTCHAR(str) Flock_ConvertUTF16toTCHAR(str, mEncoder).get()
#define ConvertASCIItoTCHAR(str) Flock_ConvertASCIItoTCHAR(str).get()
#define ConvertTCHARtoUTF16(str) Flock_ConvertTCHARtoUTF16(str, mDecoder).get()
#define ConvertTCHARtoASCII(str) nsDependentCString(Flock_ConvertTCHARtoASCII(str).get())

typedef PRUint32 ActualUnichar;

#ifdef IS_LITTLE_ENDIAN
#define NATIVE_WCHAR_T_ENCODING "UTF-32LE"
#else
#define NATIVE_WCHAR_T_ENCODING "UTF-32BE"
#endif

#endif


using namespace lucene::analysis;
using namespace lucene::analysis::standard;
using namespace lucene::document;
using namespace lucene::index;
using namespace lucene::queryParser;
using namespace lucene::search;
using namespace lucene::store;
using namespace lucene::util;


#ifndef XP_WIN32
class Flock_EncodingConverter {
protected:
  enum { kBufferSize = 1024 };

public:
  Flock_EncodingConverter() : mBufferPtr(mBuffer) { }

  ~Flock_EncodingConverter() {
    DeleteBuffer();
  }

protected:
  PRBool EnsureCapacity(size_t capacity) {
    if (capacity <= kBufferSize)
      return PR_TRUE;

    mBufferPtr = (char*) nsMemory::Alloc(capacity);

    if (!mBufferPtr) {
      mBufferPtr = mBuffer;
      return PR_FALSE;
    } else
      return PR_TRUE;
  }

  void DeleteBuffer(void) {
    if (mBufferPtr != mBuffer) {
      nsMemory::Free(mBufferPtr);
      mBufferPtr = mBuffer;
    }
  }

protected:
  char  mBuffer[kBufferSize];
  char* mBufferPtr;
};

class Flock_ConvertUTF16toTCHAR : Flock_EncodingConverter {
public:
  explicit
  Flock_ConvertUTF16toTCHAR(const nsAString& aString,
                            nsIUnicodeEncoder* encoder) {
    nsresult rv = NS_OK;

    encoder->Reset();

    PRInt32 inLength, outLength;
    inLength = aString.Length();
    rv = encoder->GetMaxLength(aString.BeginReading(), inLength, &outLength);
    if (NS_FAILED(rv)) return;

    if (!EnsureCapacity(outLength + sizeof(ActualUnichar))) {
      memset(mBufferPtr, 0, sizeof(ActualUnichar));
      return;
    }

    rv = encoder->Convert(aString.BeginReading(), &inLength,
                          mBufferPtr, &outLength);
    if (NS_FAILED(rv)) {
      DeleteBuffer();
      memset(mBufferPtr, 0, sizeof(ActualUnichar));
      return;
    }

    memset(&mBufferPtr[outLength], 0, sizeof(ActualUnichar));
  }

  TCHAR* get(void) {
    return (TCHAR*) mBufferPtr;
  }
};

class Flock_ConvertASCIItoTCHAR : Flock_EncodingConverter {
public:
  explicit
  Flock_ConvertASCIItoTCHAR(const char* str) {
    size_t length = strlen(str);

    if (!EnsureCapacity((length + 1) * sizeof(ActualUnichar))) {
      memset(mBufferPtr, 0, sizeof(ActualUnichar));
      return;
    }

    ActualUnichar *s = (ActualUnichar*) mBufferPtr;
    while (length--) {
      *s = *str;
      s++; str++;
    }

    *s = 0;
  }

  TCHAR* get(void) {
    return (TCHAR*) mBufferPtr;
  }
};

class Flock_ConvertTCHARtoUTF16 : Flock_EncodingConverter {
public:
  explicit
  Flock_ConvertTCHARtoUTF16(const TCHAR* aTString,
                            nsIUnicodeDecoder* decoder) {
    nsresult rv = NS_OK;

    decoder->Reset();

    const char* str = (const char*) aTString;

    PRInt32 inLength = 0;
    ActualUnichar* s = (ActualUnichar*) str;
    while (*s++) inLength++;
    inLength *= sizeof(ActualUnichar);

    PRInt32 outLength;
    rv = decoder->GetMaxLength(str, inLength, &outLength);
    if (NS_FAILED(rv)) return;

    if (!EnsureCapacity(outLength + sizeof(PRUnichar))) {
      memset(mBufferPtr, 0, sizeof(PRUnichar));
      return;
    }

    rv = decoder->Convert(str, &inLength, (PRUnichar*) mBufferPtr, &outLength);
    if (NS_FAILED(rv)) {
      DeleteBuffer();
      memset(mBufferPtr, 0, sizeof(PRUnichar));
      return;
    }

    memset(&mBufferPtr[outLength * sizeof(PRUnichar)], 0, sizeof(PRUnichar));
  }

  PRUnichar* get(void) {
    return (PRUnichar*) mBufferPtr;
  }
};

class Flock_ConvertTCHARtoASCII : Flock_EncodingConverter {
public:
  explicit
  Flock_ConvertTCHARtoASCII(const TCHAR* aTString) {
    const char* str = (const char*) aTString;

    PRInt32 length = 0;
    ActualUnichar* s = (ActualUnichar*) str;
    while (*s++) length++;

    if (!EnsureCapacity(length + 1)) {
      mBufferPtr[0] = 0;
      return;
    }

    char* d = mBufferPtr;

    s = (ActualUnichar*) str;
    for (int i = 0; i < length; i++) *d++ = *s++;

    mBufferPtr[length] = 0;
  }

  char* get(void) {
    return mBufferPtr;
  }
};
#endif

class FlockAnalyzer : public Analyzer {
public:
  FlockAnalyzer() { }
  ~FlockAnalyzer() { }

  TokenStream* tokenStream(const TCHAR* fieldName, Reader* reader) {
    TokenStream* ret = new StandardTokenizer(reader);
    ret = new StandardFilter(ret, true);
    ret = new LowerCaseFilter(ret, true);
    return ret;
  }
};


class flockLuceneResult : public flockILuceneResult,
                          public nsIClassInfo
{
public:
  flockLuceneResult(const nsACString & aURI, const nsACString & aType,
                    const PRUnichar *aURL, const PRUnichar *aTitle,
                    const double aScore)
    : mURI(aURI), mType(aType), mURL(aURL), mTitle(aTitle), mScore(aScore) { }

  NS_DECL_ISUPPORTS
  NS_DECL_NSICLASSINFO
  NS_DECL_FLOCKILUCENERESULT

protected:
  virtual ~flockLuceneResult() { }

  nsCString mURI;
  nsCString mType;
  nsString mURL;
  nsString mTitle;
  double mScore;
};

NS_IMPL_THREADSAFE_ADDREF(flockLuceneResult)
NS_IMPL_THREADSAFE_RELEASE(flockLuceneResult)
NS_IMPL_QUERY_INTERFACE2(flockLuceneResult, flockILuceneResult, nsIClassInfo)
NS_IMPL_CI_INTERFACE_GETTER1(flockLuceneResult, flockILuceneResult)
NS_IMPL_THREADSAFE_CI(flockLuceneResult)

NS_IMETHODIMP
flockLuceneResult::GetURI(char **aURI)
{
  NS_ENSURE_ARG_POINTER(aURI);
  *aURI = ToNewCString(mURI);
  return *aURI ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
flockLuceneResult::GetType(char **aType)
{
  NS_ENSURE_ARG_POINTER(aType);
  *aType = ToNewCString(mType);
  return *aType ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
flockLuceneResult::GetURL(PRUnichar **aURL)
{
  NS_ENSURE_ARG_POINTER(aURL);
  *aURL = ToNewUnicode(mURL);
  return *aURL ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
flockLuceneResult::GetTitle(PRUnichar **aTitle)
{
  NS_ENSURE_ARG_POINTER(aTitle);
  *aTitle = ToNewUnicode(mTitle);
  return *aTitle ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
flockLuceneResult::GetScore(double *aScore)
{
  NS_ENSURE_ARG_POINTER(aScore);
  *aScore = mScore;
  return NS_OK;
}


class flockLuceneResultEnumerator : public nsISimpleEnumerator {
public:
  flockLuceneResultEnumerator() : mIndex(0) { }

  NS_DECL_ISUPPORTS
  NS_DECL_NSISIMPLEENUMERATOR

  nsresult AddResult(flockILuceneResult *aResult)
  {
    return mArray.AppendObject(aResult) ? NS_OK: NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult InitCapacity(PRUint32 aCapacity)
  {
    return mArray.SetCapacity(aCapacity) ? NS_OK: NS_ERROR_OUT_OF_MEMORY;
  }

protected:
  virtual ~flockLuceneResultEnumerator() { }

  nsCOMArray<flockILuceneResult> mArray;
  PRInt32 mIndex;
};

NS_IMPL_THREADSAFE_ISUPPORTS1(flockLuceneResultEnumerator, nsISimpleEnumerator)

NS_IMETHODIMP
flockLuceneResultEnumerator::HasMoreElements(PRBool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  if (!aResult) {
     return NS_ERROR_NULL_POINTER;
  }

  *aResult = (mIndex < mArray.Count());
  return NS_OK;
}

NS_IMETHODIMP
flockLuceneResultEnumerator::GetNext(nsISupports** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  if (!aResult) {
     return NS_ERROR_NULL_POINTER;
  }

  *aResult = mArray.SafeObjectAt(mIndex++);

  if (*aResult) {
    NS_ADDREF(*aResult);
    return NS_OK;
  } else {
    return NS_ERROR_UNEXPECTED;
  }
}


NS_IMPL_THREADSAFE_ISUPPORTS1(flockLuceneImpl, flockILucene)

flockLuceneImpl::flockLuceneImpl()
{
#ifdef PR_LOGGING
  if (!gLuceneLog)
    gLuceneLog = PR_NewLogModule("Lucene");
#endif

  mShuttingDown = PR_FALSE;

  mAnalyzer = NULL;
  mDirectory = NULL;
  mReader = NULL;
  
  mAddDocumentCount = 0;

#ifndef XP_WIN
  mEncoder = NULL;
  mDecoder = NULL;

  for (int i = 0; i < NUM_CONVERSIONS; i++)
    mConversions[i] = NULL;
#endif
}

flockLuceneImpl::~flockLuceneImpl()
{
  if (mReader) {
    mReader->close();
    delete mReader;
    mReader = NULL;
  }

  if (mDirectory) {
    mDirectory->close();
    mDirectory = NULL;
  }

  if (mAnalyzer) {
    delete mAnalyzer;
    mAnalyzer = NULL;
  }

#ifndef XP_WIN
  mEncoder = NULL;
  mDecoder = NULL;

  for (int i = 0; i < NUM_CONVERSIONS; i++) {
    if (mConversions[i]) {
      nsMemory::Free(mConversions[i]);
      mConversions[i] = NULL;
    }
  }
#endif
}

NS_IMETHODIMP
flockLuceneImpl::Init(nsIFile *aIndexDir)
{
  nsresult rv = NS_OK;
  bool create;

  nsCAutoString path;
  rv = aIndexDir->GetNativePath(path);
  if (NS_FAILED(rv)) return rv;

  LOG_DEBUG(("flockLuceneImpl::Init (\"%s\")\n", path.get()));

#ifndef XP_WIN
  nsCOMPtr<nsICharsetConverterManager> ccm;
  ccm = do_GetService(kCharsetConverterManagerCID, &rv);
  NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get charset conversion service");
  if (NS_FAILED(rv)) return rv;

  rv = ccm->GetUnicodeEncoderRaw(NATIVE_WCHAR_T_ENCODING,
                                 getter_AddRefs(mEncoder));
  NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get a " NATIVE_WCHAR_T_ENCODING 
                                 " encoder");
  if (NS_FAILED(rv)) return rv;

  rv = ccm->GetUnicodeDecoderRaw(NATIVE_WCHAR_T_ENCODING,
                                 getter_AddRefs(mDecoder));
  NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get a " NATIVE_WCHAR_T_ENCODING 
                                 " decoder");
  if (NS_FAILED(rv)) return rv;
#endif

  rv = CreateConversions();
  if (NS_FAILED(rv)) return rv;

  try {
    mAnalyzer = new PerFieldAnalyzerWrapper(new FlockAnalyzer());
    mAnalyzer->addAnalyzer(LUCENE_TEXT, new StandardAnalyzer());

    if (IndexReader::indexExists(path.get())) {
      LOG_DEBUG(("flockLuceneImpl::Init - the index exists\n"));

      mDirectory = FSDirectory::getDirectory(path.get(), false);

      if (IndexReader::isLocked(path.get())) {
        IndexReader::unlock(mDirectory);
      }

      create = false;
    } else {
      LOG_DEBUG(("flockLuceneImpl::Init - the index doesn't exist\n"));
      // create the directory
      mDirectory = FSDirectory::getDirectory(path.get(), true);

      create = true;
    }
  } catch (CLuceneError& e) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception removing old locks "
               "(e=%s)\n", e.what()));
    return NS_ERROR_FAILURE;
  } catch (const char* str) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception removing old locks "
               "(%s)\n", str));
    return NS_ERROR_FAILURE;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception removing old locks\n"));
    return NS_ERROR_FAILURE;
  }

  try {
    LOG_DEBUG(("flockLuceneImpl::Init creating writer and optimizing...\n"));
    IndexWriter writer(mDirectory, mAnalyzer, create);
    //writer.optimize();
  } catch (CLuceneError& e) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception creating writer and "
               "optimizing (e=%s)\n", e.what()));
    return NS_ERROR_FAILURE;
  } catch (const char* str) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception creating writer and "
               "optimizing (%s)\n", str));
    return NS_ERROR_FAILURE;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::Init caught an exception creating writer and "
               "optimizing\n"));
    return NS_ERROR_FAILURE;
  }

  rv = EnsureReader();
  NS_ENSURE_SUCCESS(rv, rv);

  mAddDocumentCount = 0;

  return NS_OK;
}

NS_IMETHODIMP 
flockLuceneImpl::AddDocument(const char *aURI, 
                             const char *aType,
                             const nsAString & aURL, 
                             const nsAString & aTitle,
                             const nsAString & aTags, 
                             const nsAString & aDescription, 
                             const nsAString & aText,
                             flockILuceneIndexListener *aIndexListener)
{
  LOG_DEBUG(("flockLuceneImpl::AddDocument aURI = \"%s\"\n", aURI));

  if (mShuttingDown) {
    LOG_DEBUG(("flockLuceneImpl::AddDocument called while shutting down\n"));
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv = EnsureReader();
  NS_ENSURE_SUCCESS(rv, rv);

  try {
    Term t(LUCENE_URI, ConvertASCIItoTCHAR(aURI), true);
    mReader->deleteDocuments(&t);
    mReader->close();
    delete mReader;
    mReader = NULL;
  } catch (CLuceneError& e) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception removing "
               "old entries (e=%s)\n", e.what()));
    return NS_ERROR_FAILURE;
  } catch (const char* str) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception removing "
               "old entries (%s)\n", str));
    return NS_ERROR_FAILURE;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception removing "
               "old entries\n"));
    return NS_ERROR_FAILURE;
  }

  try {
    Document doc;

    doc.add(*Field::Keyword(LUCENE_URI, ConvertASCIItoTCHAR(aURI)));
    doc.add(*Field::Keyword(LUCENE_TYPE, ConvertASCIItoTCHAR(aType)));

    doc.add(*Field::Text(LUCENE_URL, ConvertUTF16toTCHAR(aURL)));
    LOG_DEBUG(("flockLuceneImpl::AddDocument aURL = \"%s\"\n",
               NS_ConvertUTF16toUTF8(aURL).get()));

    doc.add(*Field::Text(LUCENE_TITLE, ConvertUTF16toTCHAR(aTitle)));
    LOG_DEBUG(("flockLuceneImpl::AddDocument aTitle = \"%s\"\n",
               NS_ConvertUTF16toUTF8(aTitle).get()));

    doc.add(*Field::UnStored(LUCENE_TAGS, ConvertUTF16toTCHAR(aTags)));
    LOG_DEBUG(("flockLuceneImpl::AddDocument aTags = \"%s\"\n",
               NS_ConvertUTF16toUTF8(aTags).get()));

    doc.add(*Field::UnStored(LUCENE_DESCRIPTION, ConvertUTF16toTCHAR(aDescription)));
    LOG_DEBUG(("flockLuceneImpl::AddDocument aDescription = \"%s\"\n",
               NS_ConvertUTF16toUTF8(aDescription).get()));

    doc.add(*Field::UnStored(LUCENE_TEXT, ConvertUTF16toTCHAR(aText)));
    /*
    LOG_DEBUG(("flockLuceneImpl::AddDocument aText = \"%s\"\n",
               NS_ConvertUTF16toUTF8(aText).get()));
    */

    LOG_DEBUG(("flockLuceneImpl::AddDocument adding document\n"));

    IndexWriter writer(mDirectory, mAnalyzer, false);
    writer.addDocument(&doc);
    if (mAddDocumentCount == 0) {
      writer.optimize();
    }
  } catch (CLuceneError& e) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception adding "
               "new entry (e=%s)\n", e.what()));
    return NS_ERROR_FAILURE;
  } catch (const char* str) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception adding "
               "new entry (%s)\n", str));
    return NS_ERROR_FAILURE;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::AddDocument caught an exception adding "
               "new entry\n"));
    return NS_ERROR_FAILURE;
  }

  if (aIndexListener != NULL) {
    aIndexListener->OnAddDocumentComplete(aURI);
  }
  
  mAddDocumentCount = (mAddDocumentCount + 1) % 100;

  LOG_DEBUG(("flockLuceneImpl::AddDocument complete\n"));

  return NS_OK;
}

NS_IMETHODIMP 
flockLuceneImpl::AddDocumentSync(const char *aURI, 
                                 const char *aType,
                                 const nsAString & aURL, 
                                 const nsAString & aTitle,
                                 const nsAString & aTags, 
                                 const nsAString & aDescription, 
                                 const nsAString & aText)
{
  LOG_DEBUG(("flockLuceneImpl::AddDocumentSync aURI = \"%s\"\n", aURI));
  return AddDocument(aURI, aType, aURL, aTitle, aTags, aDescription, aText,
                     NULL);
}


NS_IMETHODIMP
flockLuceneImpl::DeleteDocument(const char *aURI,
                                flockILuceneIndexListener *aIndexListener)
{
  LOG_DEBUG(("flockLuceneImpl::DeleteDocument\n"));

  if (mShuttingDown) {
    LOG_DEBUG(("flockLuceneImpl::DeleteDocument called while shutting down\n"));
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv = EnsureReader();
  NS_ENSURE_SUCCESS(rv, rv);

  try {
    Term t(LUCENE_URI, ConvertASCIItoTCHAR(aURI), true);
    TermQuery q(&t);
    IndexSearcher s(mReader);

    struct HitsWrapper {
      HitsWrapper() : hits(NULL) { }
      ~HitsWrapper() { if (hits) delete hits; }
      Hits *hits;
    } h;

    h.hits = s.search(&q);
    LOG_DEBUG(("flockLuceneImpl::DeleteDocument - found %d\n",
               h.hits->length()));

    for (int i = 0; i < h.hits->length(); i++) {
      try {
        mReader->deleteDoc(h.hits->id(i));
      } catch (...) {
        LOG_ERROR(("flockLuceneImpl::DeleteDocument - error deleting %d\n", i));
      }
    }

    s.close();

    // close the reader to force a flush
    mReader->close();
    delete mReader;
    mReader = NULL;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::DeleteDocument caught an exception!\n"));
    return NS_ERROR_FAILURE;
  }

  if (aIndexListener != NULL) {
    aIndexListener->OnDeleteDocumentComplete(aURI);
  }

  return NS_OK;
}


NS_IMETHODIMP
flockLuceneImpl::DeleteDocumentSync(const char *aURI)
{
  LOG_DEBUG(("flockLuceneImpl::DeleteDocumentSync\n"));
  return DeleteDocument(aURI, NULL);
}


NS_IMETHODIMP
flockLuceneImpl::Search(const nsAString &aQuery,
                        const char *aType,
                        PRInt32 aMaxResults,
                        flockILuceneSearchCallback *aSearchCallback)
{
  nsresult rv = NS_OK;

  LOG_DEBUG(("flockLuceneImpl::Search\n"));

  if (mShuttingDown) {
    LOG_DEBUG(("flockLuceneImpl::Search called while shutting down\n"));
    return NS_ERROR_NOT_INITIALIZED;
  }

  rv = EnsureReader();
  NS_ENSURE_SUCCESS(rv, rv);

  PRUint32 numResults = 0;
  nsCOMPtr<nsISimpleEnumerator> results = NULL;

  try {
    static const TCHAR *fields[] = {
      LUCENE_URL,
      LUCENE_TITLE,
      LUCENE_TAGS,
      LUCENE_DESCRIPTION,
      LUCENE_TEXT,
      NULL
    };

    struct SearchWrapper {
      SearchWrapper() : userQuery(NULL), hits(NULL),
                        typeTerm(NULL), typeQuery(NULL) { }
      ~SearchWrapper() { 
        if (hits)      delete hits;
        if (typeQuery) delete typeQuery;
        if (typeTerm)  delete typeTerm;
        if (userQuery) delete userQuery;
      }

      Query     *userQuery;
      Hits      *hits;
      Term      *typeTerm;
      TermQuery *typeQuery;
    } s;

    BooleanQuery query;

    if (aQuery.Length() > 0) {
      s.userQuery = MultiFieldQueryParser::parse(ConvertUTF16toTCHAR(aQuery),
                                                 fields, mAnalyzer);
      query.add(s.userQuery, true, false);
    }

    if (aType) {
      s.typeTerm = new Term(LUCENE_TYPE, ConvertASCIItoTCHAR(aType), true);
      s.typeQuery = new TermQuery(s.typeTerm);
      query.add(s.typeQuery, true, false);
    }

    IndexSearcher search(mReader);

    s.hits = search.search(&query);

    LOG_DEBUG(("flockLuceneImpl::Search found %d hits\n", s.hits->length()));

    if (s.hits->length() == 0) {
      rv = NS_OK;
    } else {
      rv = ReturnResults(s.hits, aMaxResults, &numResults,
                         getter_AddRefs(results));
    }

    search.close();
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl::Search caught an exception!\n"));
  }

  if (!results) {
    results = new flockLuceneResultEnumerator();
    NS_ENSURE_TRUE(results, NS_ERROR_OUT_OF_MEMORY);
  }

  // flockResultEnumerator doesn't do any data structure locking, but
  // that's OK because we don't do anything with it after calling
  // HandleResults here. If this ever changes, we need to take
  // appropriate action in flockResultEnumerator as well.
  aSearchCallback->HandleResults(numResults, results);

  return rv;
}

nsresult
flockLuceneImpl::ReturnResults(Hits *aHits,
                               PRInt32 aMaxResults,
                               PRUint32 *aNumResults,
                               nsISimpleEnumerator **aResults)
{
  NS_ENSURE_ARG_POINTER(aResults);

  nsresult rv;

  nsTHashtable<nsCStringHashKey> resultSet;

  nsCOMPtr<flockLuceneResultEnumerator> results =
    new flockLuceneResultEnumerator();
  NS_ENSURE_TRUE(results, NS_ERROR_OUT_OF_MEMORY);

  PRUint32 resultSetInitialSize = PL_DHASH_MIN_SIZE;

  if (aMaxResults > 0) {
    resultSetInitialSize = PRUint32(aMaxResults);

    rv = results->InitCapacity(resultSetInitialSize);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (!resultSet.Init(resultSetInitialSize)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (PRInt32 i = 0; i < aHits->length(); i++) {
    // Don't bother processing more than what we've been asked for
    if (aMaxResults > 0 && resultSet.Count() >= PRUint32(aMaxResults)) {
      // we've got enough
      break;
    }

    Document& doc = aHits->doc(i);

    double score = aHits->score(i);

    LOG_DEBUG(("flockLuceneImpl::ReturnResults i=%d, aHits->length()=%d\n",
               i, aHits->length()));
    LOG_DEBUG(("flockLuceneImpl::ReturnResults found uri=\"%s\"\n",
               ConvertTCHARtoASCII(doc.get(LUCENE_URI)).get()));
    LOG_DEBUG(("flockLuceneImpl::ReturnResults       title=\"%s\"\n",
               ConvertTCHARtoASCII(doc.get(LUCENE_TITLE)).get()));
    LOG_DEBUG(("flockLuceneImpl::ReturnResults score = %06.6f\n",
               score));

    nsCAutoString hitURI;
    hitURI.Assign(ConvertTCHARtoASCII(doc.get(LUCENE_URI)));

    // eliminate dupes
    if (resultSet.GetEntry(hitURI)) {
      LOG_DEBUG(("flockLuceneImpl::ReturnResults IT'S A DUPE!\n"));
      continue;
    }

    nsCOMPtr<flockILuceneResult> result =
      new flockLuceneResult(hitURI,
                            ConvertTCHARtoASCII(doc.get(LUCENE_TYPE)),
                            ConvertTCHARtoUTF16(doc.get(LUCENE_URL)),
                            ConvertTCHARtoUTF16(doc.get(LUCENE_TITLE)),
                            score);

    NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);

    rv = results->AddResult(result);
    NS_ENSURE_SUCCESS(rv, rv);

    resultSet.PutEntry(hitURI);
  }

  if (aNumResults) {
    *aNumResults = resultSet.Count();
  }

  NS_ADDREF(*aResults = results);
  return NS_OK;
}

nsresult
flockLuceneImpl::CreateConversions (void)
{
  LOG_DEBUG(("flockLuceneImpl::CreateConversions\n"));

  nsresult rv = NS_OK;

#ifdef XP_WIN
#define CONVERT_FIELD(field) do {                                           \
  NS_NAMED_LITERAL_CSTRING(fieldString, FIELD_ ## field);                   \
  LUCENE_ ## field =                                                        \
    (TCHAR*) ToNewUnicode(NS_ConvertUTF8toUTF16(fieldString));              \
} while (0)
#else
#define CONVERT_FIELD(field) do {                                           \
  NS_NAMED_LITERAL_STRING(fieldString, FIELD_ ## field);                    \
                                                                            \
  mEncoder->Reset();                                                        \
                                                                            \
  PRInt32 inLength, outLength;                                              \
  inLength = fieldString.Length();                                          \
  rv = mEncoder->GetMaxLength(fieldString.get(), inLength, &outLength);     \
  NS_ENSURE_SUCCESS(rv, rv);                                                \
                                                                            \
  char *buf = (char*) nsMemory::Alloc(outLength + sizeof(ActualUnichar));   \
  NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);                              \
                                                                            \
  rv = mEncoder->Convert(fieldString.get(), &inLength, buf, &outLength);    \
  NS_ASSERTION(NS_SUCCEEDED(rv), "unable to convert FIELD_" #field);        \
  if (NS_FAILED(rv)) return rv;                                             \
                                                                            \
  memset(buf + outLength, 0, sizeof(ActualUnichar));                        \
  LUCENE_ ## field = (TCHAR*) buf;                                          \
} while (0)
#endif

  CONVERT_FIELD (URI);
  CONVERT_FIELD (URL);
  CONVERT_FIELD (TITLE);
  CONVERT_FIELD (TAGS);
  CONVERT_FIELD (DESCRIPTION);
  CONVERT_FIELD (TEXT);
  CONVERT_FIELD (TYPE);

#undef CONVERT_FIELD

  return rv;
}

nsresult
flockLuceneImpl::EnsureReader(void)
{
  if (mReader) {
    return NS_OK;
  }

  try {
    LOG_DEBUG(("flockLuceneImpl creating reader...\n"));
    mReader = IndexReader::open(mDirectory, false);
  } catch (CLuceneError& e) {
    LOG_ERROR(("flockLuceneImpl caught an exception creating reader (e=%s)\n",
               e.what()));
    return NS_ERROR_FAILURE;
  } catch (const char* str) {
    LOG_ERROR(("flockLuceneImpl caught an exception creating reader (%s)\n",
               str));
    return NS_ERROR_FAILURE;
  } catch (...) {
    LOG_ERROR(("flockLuceneImpl caught an exception creating reader\n"));
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}
