/*
 * Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
 *
 * Distributable under the terms of either the Apache License (Version 2.0) or 
 * the GNU Lesser General Public License, as specified in the COPYING file.
 *
 * Changes are Copyright (C) 2015 The Qt Company Ltd.
*/
#include "CLucene/StdHeader.h"
#include "SegmentHeader.h"

#include "FieldInfos.h"
#include "FieldsReader.h"
#include "IndexReader.h"
#include "TermInfosReader.h"
#include "Terms.h"
#include "CLucene/search/Similarity.h"

CL_NS_USE(util)
CL_NS_USE(store)
CL_NS_USE(document)
CL_NS_USE(search)
CL_NS_DEF(index)

SegmentReader::Norm::Norm(IndexInput* instrm, int32_t n, SegmentReader* r,
    const QString& seg)
    : number(n)
    , reader(r)
    , segment(seg)
    , in(instrm)
    , bytes(NULL)
    , dirty(false)
{
    //Func - Constructor
    //Pre  - instrm is a valid reference to an IndexInput
    //Post - A Norm instance has been created with an empty bytes array
    bytes = NULL;
    dirty = false;
}

SegmentReader::Norm::~Norm()
{
    //Func - Destructor
    //Pre  - true
    //Post - The IndexInput in has been deleted (and closed by its destructor) 
    //       and the array too.

    //Close and destroy the inputstream in-> The inputstream will be closed
    // by its destructor. Note that the IndexInput 'in' actually is a pointer!!!!!  
    _CLDELETE(in);

    //Delete the bytes array
    _CLDELETE_ARRAY(bytes);

}

void SegmentReader::Norm::reWrite()
{
    QString buf(segment + QLatin1String(".tmp"));

    // NOTE: norms are re-written in regular directory, not cfs
    IndexOutput* out = reader->getDirectory()->createOutput(buf);
    try {
        out->writeBytes(bytes, reader->maxDoc());
    } _CLFINALLY (
        out->close();
        _CLDELETE(out)
        );

    QString fileName(segment);
    if (reader->cfsReader == NULL)
        fileName.append(QLatin1String(".f%1")).arg(number);
    else // use a different file name if we have compound format
        fileName.append(QLatin1String(".s%1")).arg(number);

    reader->getDirectory()->renameFile(buf, fileName);
    this->dirty = false;
}

SegmentReader::SegmentReader(SegmentInfo* si)
    : IndexReader(si->getDir())
    , _norms(false, false)
{ 
    initialize(si);
}

SegmentReader::SegmentReader(SegmentInfos* sis, SegmentInfo* si)
    : IndexReader(si->getDir(), sis, false)
    , _norms(false, false)
{ 
    initialize(si);
}

void SegmentReader::initialize(SegmentInfo* si)
{
    //Pre  - si-> is a valid reference to SegmentInfo instance
    //       identified by si->
    //Post - All files of the segment have been read

    deletedDocs      = NULL;
    ones			   = NULL;
    //There are no documents yet marked as deleted
    deletedDocsDirty = false;

    normsDirty=false;
    undeleteAll=false;

    //Duplicate the name of the segment from SegmentInfo to segment
    segment          = si->name;
    // make sure that all index files have been read or are kept open
    // so that if an index update removes them we'll still have them
    freqStream       = NULL;
    proxStream       = NULL;

    //instantiate a buffer large enough to hold a directory path
    QString buf;

    // Use compound file directory for some files, if it exists
    Directory* cfsDir = getDirectory();
    SegmentName(buf, CL_MAX_PATH, QLatin1String(".cfs"));
    if (cfsDir->fileExists(buf)) {
        cfsReader = _CLNEW CompoundFileReader(cfsDir, buf);
        cfsDir = cfsReader;
    }else
        cfsReader = NULL;

    // Create the name of the field info file with suffix .fnm in buf
    SegmentName(buf, CL_MAX_PATH, QLatin1String(".fnm"));
    fieldInfos = _CLNEW FieldInfos(cfsDir, buf );

    // Condition check to see if fieldInfos points to a valid instance
    CND_CONDITION(fieldInfos != NULL,
        "No memory could be allocated for fieldInfos");

    // Create the name of the frequence file with suffix .frq in buf
    SegmentName(buf ,CL_MAX_PATH, QLatin1String(".frq"));

    // Open an IndexInput freqStream to the frequency file
    freqStream = cfsDir->openInput( buf );

    // Condition check to see if freqStream points to a valid instance and was
    // able to open the frequency file
    CND_CONDITION(freqStream != NULL,
        "IndexInput freqStream could not open the frequency file");

    // Create the name of the prox file with suffix .prx in buf
    SegmentName(buf, CL_MAX_PATH, QLatin1String(".prx"));

    // Open an IndexInput proxStream to the prox file
    proxStream = cfsDir->openInput( buf );

    // Condition check to see if proxStream points to a valid instance and was
    // able to open the prox file
    CND_CONDITION(proxStream != NULL,
        "IndexInput proxStream could not open proximity file");

    // Instantiate a FieldsReader for reading the Field Info File
    fieldsReader = _CLNEW FieldsReader(cfsDir, segment, fieldInfos);

    // Condition check to see if fieldsReader points to a valid instance 
    CND_CONDITION(fieldsReader != NULL,
        "No memory could be allocated for fieldsReader");

    //Instantiate a TermInfosReader for reading the Term Dictionary .tis file
    tis = _CLNEW TermInfosReader(cfsDir, segment, fieldInfos);

    //Condition check to see if tis points to a valid instance 
    CND_CONDITION(tis != NULL,"No memory could be allocated for tis");

    // Check if the segment has deletion according to the SegmentInfo instance
    // si-> NOTE: the bitvector is stored using the regular directory, not cfs
    if (hasDeletions(si)) {
        //Create a deletion file with suffix .del          
        SegmentName(buf, CL_MAX_PATH, QLatin1String(".del"));
        // Instantiate a BitVector that manages which documents have been deleted
        deletedDocs = _CLNEW BitSet(getDirectory(), buf);
    }

    // Open the norm file. There's a norm file for each indexed field with a
    // byte for each document. The .f[0-9]* file contains, for each document,
    // a byte that encodes a value that is multiplied into the score for hits
    // on that field
    openNorms(cfsDir);

    termVectorsReaderOrig = NULL;
    if (fieldInfos->hasVectors()) // open term vector files only as needed
        termVectorsReaderOrig = _CLNEW TermVectorsReader(cfsDir, segment, fieldInfos); 
}

SegmentReader::~SegmentReader()
{
    //Func - Destructor.
    //Pre  - doClose has been invoked!
    //Post - the instance has been destroyed

    doClose(); //this means that index reader doesn't need to be closed manually

    _CLDELETE(fieldInfos);
    _CLDELETE(fieldsReader);
    _CLDELETE(tis);	      
    _CLDELETE(freqStream);
    _CLDELETE(proxStream);
    _CLDELETE(deletedDocs);
    _CLDELETE_ARRAY(ones);
    _CLDELETE(termVectorsReaderOrig);
    _CLDECDELETE(cfsReader);
}

void SegmentReader::doCommit()
{
    QString bufdel(segment + QLatin1String(".del"));

    if (deletedDocsDirty) {               // re-write deleted 
        QString buftmp(segment + QLatin1String(".tmp"));
        deletedDocs->write(getDirectory(), buftmp);
        getDirectory()->renameFile(buftmp, bufdel);
    }

    if(undeleteAll && getDirectory()->fileExists(bufdel))
        getDirectory()->deleteFile(bufdel, true);

    if (normsDirty) {               // re-write norms 
        NormsType::iterator itr = _norms.begin();
        while (itr != _norms.end()) {
            Norm* norm = itr->second;
            if (norm->dirty) {
                norm->reWrite();
            }
            ++itr;
        }
    }
    deletedDocsDirty = false;
    normsDirty = false;
    undeleteAll = false;
}

void SegmentReader::doClose()
{
    //Func - Closes all streams to the files of a single segment
    //Pre  - fieldsReader != NULL
    //       tis != NULL
    //Post - All streams to files have been closed

    CND_PRECONDITION(fieldsReader != NULL, "fieldsReader is NULL");
    CND_PRECONDITION(tis != NULL, "tis is NULL");

    //Close the fieldsReader
    fieldsReader->close();
    //Close the TermInfosReader
    tis->close();

    //Close the frequency stream
    if (freqStream != NULL){
        freqStream->close();
    }
    //Close the prox stream
    if (proxStream != NULL){
        proxStream->close();
    }

    //Close the norm file
    closeNorms();

    if (termVectorsReaderOrig != NULL) 
        termVectorsReaderOrig->close();

    if (cfsReader != NULL)
        cfsReader->close();
}

bool SegmentReader::hasDeletions() const
{
    return deletedDocs != NULL;
}

//static 
bool SegmentReader::usesCompoundFile(SegmentInfo* si)
{
    return si->getDir()->fileExists(si->name + QLatin1String(".cfs"));
}

//static
bool SegmentReader::hasSeparateNorms(SegmentInfo* si)
{
    QString pattern(si->name);
    pattern.append(QLatin1String(".s"));
    size_t patternLength = pattern.length();

    QStringList names = si->getDir()->list();
    foreach (const QString& name, names) {
        int length = name.length();
        if (length > patternLength && name.left(patternLength) == pattern) {
            if (name.at(patternLength) >= QLatin1Char('0')
                && name.at(patternLength) <= QLatin1Char('9')) {
                return true;
            }
        }
    }
    return false;
}

bool SegmentReader::hasDeletions(const SegmentInfo* si)
{
    //Func - Static method
    //       Checks if a segment managed by SegmentInfo si-> has deletions
    //Pre  - si-> holds a valid reference to an SegmentInfo instance
    //Post - if the segement contains deleteions true is returned otherwise flas

    //Check if the deletion file exists and return the result
    QString f;
    Misc::segmentname(f, CL_MAX_PATH, si->name, QLatin1String(".del"), -1);
    return si->getDir()->fileExists(f);
}

//synchronized
void SegmentReader::doDelete(const int32_t docNum)
{
    //Func - Marks document docNum as deleted
    //Pre  - docNum >=0 and DocNum < maxDoc() 
    //       docNum contains the number of the document that must be 
    //       marked deleted
    //Post - The document identified by docNum has been marked deleted

    SCOPED_LOCK_MUTEX(THIS_LOCK)

    CND_PRECONDITION(docNum >= 0, "docNum is a negative number");
    CND_PRECONDITION(docNum < maxDoc(),
        "docNum is bigger than the total number of documents");

    //Check if deletedDocs exists
    if (deletedDocs == NULL) {
        deletedDocs = _CLNEW BitSet(maxDoc());

        //Condition check to see if deletedDocs points to a valid instance
        CND_CONDITION(deletedDocs != NULL,
            "No memory could be allocated for deletedDocs");
    }
    //Flag that there are documents marked deleted
    deletedDocsDirty = true;
    undeleteAll = false;
    //Mark document identified by docNum as deleted
    deletedDocs->set(docNum);

}

void SegmentReader::doUndeleteAll()
{
    _CLDELETE(deletedDocs);
    deletedDocsDirty = false;
    undeleteAll = true;
}

void SegmentReader::files(QStringList& retarray)
{
    //Func - Returns all file names managed by this SegmentReader
    //Pre  - segment != NULL
    //Post - All filenames managed by this SegmentRead have been returned

    CND_PRECONDITION(segment != NULL, "segment is NULL");

    QString temp;
    #define _ADD_SEGMENT(ext) \
        temp = SegmentName(ext); \
        if (getDirectory()->fileExists(temp)) \
            retarray.push_back(temp);

    //Add the name of the Field Info file
    _ADD_SEGMENT(QLatin1String(".cfs"));
    _ADD_SEGMENT(QLatin1String(".fnm"));
    _ADD_SEGMENT(QLatin1String(".fdx"));
    _ADD_SEGMENT(QLatin1String(".fdt"));
    _ADD_SEGMENT(QLatin1String(".tii"));
    _ADD_SEGMENT(QLatin1String(".tis"));
    _ADD_SEGMENT(QLatin1String(".frq"));
    _ADD_SEGMENT(QLatin1String(".prx"));
    _ADD_SEGMENT(QLatin1String(".del"));
    _ADD_SEGMENT(QLatin1String(".tvx"));
    _ADD_SEGMENT(QLatin1String(".tvd"));
    _ADD_SEGMENT(QLatin1String(".tvf"));
    _ADD_SEGMENT(QLatin1String(".tvp"));

    //iterate through the field infos
    for (int32_t i = 0; i < fieldInfos->size(); ++i) {
        //Get the field info for the i-th field   
        FieldInfo* fi = fieldInfos->fieldInfo(i);
        //Check if the field has been indexed
        if (fi->isIndexed && !fi->omitNorms) {
            QString name;
            if (cfsReader == NULL)
                name = SegmentName(QLatin1String(".f"), i);
            else
                name = SegmentName(QLatin1String(".s"), i);
            
            //The field has been indexed so add its norm file
            if (getDirectory()->fileExists(name))
                retarray.push_back(name);
        }
    }
}

TermEnum* SegmentReader::terms() const
{
    //Func - Returns an enumeration of all the Terms and TermInfos in the set. 
    //Pre  - tis != NULL
    //Post - An enumeration of all the Terms and TermInfos in the set has been returned

    CND_PRECONDITION(tis != NULL, "tis is NULL");

    return tis->terms();
}

TermEnum* SegmentReader::terms(const Term* t) const
{
    //Func - Returns an enumeration of terms starting at or after the named term t 
    //Pre  - t != NULL
    //       tis != NULL
    //Post - An enumeration of terms starting at or after the named term t 

    CND_PRECONDITION(t   != NULL, "t is NULL");
    CND_PRECONDITION(tis != NULL, "tis is NULL");

    return tis->terms(t);
}

bool SegmentReader::document(int32_t n, Document* doc)
{
    //Func - Returns a document identified by n
    //Pre  - n >=0 and identifies the document n
    //Post - if the document has been deleted then an exception has been thrown
    //       otherwise a reference to the found document has been returned

    SCOPED_LOCK_MUTEX(THIS_LOCK)

    CND_PRECONDITION(n >= 0, "n is a negative number");

    //Check if the n-th document has been marked deleted
    if (isDeleted(n))
        _CLTHROWA(CL_ERR_InvalidState, "attempt to access a deleted document" );

    //Retrieve the n-th document
    return fieldsReader->doc(n, doc);
}

bool SegmentReader::isDeleted(const int32_t n)
{
    //Func - Checks if the n-th document has been marked deleted
    //Pre  - n >=0 and identifies the document n
    //Post - true has been returned if document n has been deleted otherwise fralse

    SCOPED_LOCK_MUTEX(THIS_LOCK)

    CND_PRECONDITION(n >= 0, "n is a negative number");

    //Is document n deleted
    return (deletedDocs != NULL && deletedDocs->get(n));
}

TermDocs* SegmentReader::termDocs() const
{
    //Func - Returns an unpositioned TermDocs enumerator. 
    //Pre  - true
    //Post - An unpositioned TermDocs enumerator has been returned

    return _CLNEW SegmentTermDocs(this);
}

TermPositions* SegmentReader::termPositions() const
{
    //Func - Returns an unpositioned TermPositions enumerator. 
    //Pre  - true
    //Post - An unpositioned TermPositions enumerator has been returned

    return _CLNEW SegmentTermPositions(this);
}

int32_t SegmentReader::docFreq(const Term* t) const
{
    //Func - Returns the number of documents which contain the term t
    //Pre  - t holds a valid reference to a Term
    //Post - The number of documents which contain term t has been returned

    //Get the TermInfo ti for Term  t in the set
    TermInfo* ti = tis->get(t);
    //Check if an TermInfo has been returned
    if (ti) {
        //Get the frequency of the term
        int32_t ret = ti->docFreq;
        //TermInfo ti is not needed anymore so delete it
        _CLDELETE( ti );
        //return the number of documents which containt term t
        return ret;
    }
        
    //No TermInfo returned so return 0
    return 0;
}

int32_t SegmentReader::numDocs()
{
    //Func - Returns the actual number of documents in the segment
    //Pre  - true
    //Post - The actual number of documents in the segments

    //Get the number of all the documents in the segment including the ones that have 
    //been marked deleted
    int32_t n = maxDoc();

    //Check if there any deleted docs
    if (deletedDocs != NULL)
        //Substract the number of deleted docs from the number returned by maxDoc
        n -= deletedDocs->count();

    //return the actual number of documents in the segment
    return n;
}

int32_t SegmentReader::maxDoc() const
{
    //Func - Returns the number of  all the documents in the segment including
    //       the ones that have been marked deleted
    //Pre  - true
    //Post - The total number of documents in the segment has been returned

    return fieldsReader->size();
}

void SegmentReader::getFieldNames(FieldOption fldOption,
    StringArrayWithDeletor& retarray)
{
    size_t len = fieldInfos->size();
    for (size_t i = 0; i < len; i++) {
        bool v = false;
        FieldInfo* fi = fieldInfos->fieldInfo(i);
        if (fldOption & IndexReader::ALL) {
            v = true;
        } else {
            if (!fi->isIndexed && (fldOption & IndexReader::UNINDEXED)) {
                v = true;
            }
            
            if (fi->isIndexed && (fldOption & IndexReader::INDEXED)) {
                v = true;
            }

            if (fi->isIndexed && fi->storeTermVector == false
                && (fldOption & IndexReader::INDEXED_NO_TERMVECTOR)) {
                v = true;
            }
            
            if ((fldOption & IndexReader::TERMVECTOR)
                && fi->storeTermVector == true
                && fi->storePositionWithTermVector == false
                && fi->storeOffsetWithTermVector == false) {
                v = true;
            }

            if (fi->isIndexed && fi->storeTermVector
                && (fldOption & IndexReader::INDEXED_WITH_TERMVECTOR)) {
                v = true;
            }

            if (fi->storePositionWithTermVector
                && fi->storeOffsetWithTermVector == false
                && (fldOption & IndexReader::TERMVECTOR_WITH_POSITION)) {
                v = true;
            }

            if (fi->storeOffsetWithTermVector
                && fi->storePositionWithTermVector == false
                && (fldOption & IndexReader::TERMVECTOR_WITH_OFFSET)) {
                v = true;
            }

            if ((fi->storeOffsetWithTermVector && fi->storePositionWithTermVector)
                && (fldOption & IndexReader::TERMVECTOR_WITH_POSITION_OFFSET)) {
                v = true;
            }
        }

        if (v)
            retarray.push_back(STRDUP_TtoT(fi->name));
    }
}

bool SegmentReader::hasNorms(const TCHAR* field) const
{
    return _norms.find(field) != _norms.end();
}


void SegmentReader::norms(const TCHAR* field, uint8_t* bytes)
{
    //Func - Reads the Norms for field from disk starting at offset in the inputstream
    //Pre  - field != NULL
    //       bytes != NULL is an array of bytes which is to be used to read the norms into.
    //       it is advisable to have bytes initalized by zeroes!
    //Post - The if an inputstream to the norm file could be retrieved the bytes have been read
    //       You are never sure whether or not the norms have been read into bytes properly!!!!!!!!!!!!!!!!!

    CND_PRECONDITION(field != NULL, "field is NULL");
    CND_PRECONDITION(bytes != NULL, "field is NULL");

    SCOPED_LOCK_MUTEX(THIS_LOCK)

    Norm* norm = _norms.get(field);
    if ( norm == NULL ){
        memcpy(bytes, fakeNorms(), maxDoc());
        return;
    }
    
    if (norm->bytes != NULL) { // can copy from cache
        memcpy(bytes, norm->bytes, maxDoc());
        return;
    }

    IndexInput* _normStream = norm->in->clone();
    CND_PRECONDITION(_normStream != NULL, "normStream==NULL")

    // read from disk
    try { 
        _normStream->seek(0);
        _normStream->readBytes(bytes, maxDoc());
    } _CLFINALLY (
        //Have the normstream closed
        _normStream->close();
        //Destroy the normstream
        _CLDELETE( _normStream );
    );	
}

uint8_t* SegmentReader::createFakeNorms(int32_t size)
{
    uint8_t* ones = _CL_NEWARRAY(uint8_t,size);
    memset(ones, DefaultSimilarity::encodeNorm(1.0f), size);
    return ones;
}

uint8_t* SegmentReader::fakeNorms()
{
    if (ones == NULL) 
        ones = createFakeNorms(maxDoc());
    return ones;
}

// can return null if norms aren't stored
uint8_t* SegmentReader::getNorms(const TCHAR* field)
{
    SCOPED_LOCK_MUTEX(THIS_LOCK)
    Norm* norm = _norms.get(field);
    if (norm == NULL) 
        return NULL;  // not indexed, or norms not stored

    if (norm->bytes == NULL) {                     // value not yet read
        uint8_t* bytes = _CL_NEWARRAY(uint8_t, maxDoc());
        norms(field, bytes);
        norm->bytes = bytes;                         // cache it
    }
    return norm->bytes;
}

uint8_t* SegmentReader::norms(const TCHAR* field)
{
    //Func - Returns the bytes array that holds the norms of a named field
    //Pre  - field != NULL and contains the name of the field for which the norms 
    //       must be retrieved
    //Post - If there was norm for the named field then a bytes array has been allocated 
    //       and returned containing the norms for that field. If the named field is unknown NULL is returned.

    CND_PRECONDITION(field != NULL, "field is NULL");
    
    SCOPED_LOCK_MUTEX(THIS_LOCK)

    uint8_t* bytes = getNorms(field);
    if (bytes == NULL) 
        bytes = fakeNorms();
    return bytes;
}

void SegmentReader::doSetNorm(int32_t doc, const TCHAR* field, uint8_t value)
{
    Norm* norm = _norms.get(field);
    if (norm == NULL)                             // not an indexed field
        return;

    norm->dirty = true;                            // mark it dirty
    normsDirty = true;

    uint8_t* bits = norms(field);
    bits[doc] = value;                    // set the value
}

QString SegmentReader::SegmentName(const QString& ext, const int32_t x)
{
    //Func - Returns an allocated buffer in which it creates a filename by 
    //       concatenating segment with ext and x
    //Pre    ext != NULL and holds the extension
    //       x contains a number
    //Post - A buffer has been instantiated an when x = -1 buffer contains the concatenation of 
    //       segment and ext otherwise buffer contains the contentation of segment, ext and x

    CND_PRECONDITION(!ext.isEmpty(), "ext is NULL");

    QString buf;
    SegmentName(buf, CL_MAX_PATH, ext, x);
    return buf;
}

void SegmentReader::SegmentName(QString& buffer, int32_t bufferLen,
    const QString& ext, const int32_t x)
{
    //Func - Creates a filename in buffer by concatenating segment with ext and x
    //Pre  - buffer != NULL
    //       ext    != NULL
    //       x contains a number
    //Post - When x = -1 buffer contains the concatenation of segment and ext otherwise
    //       buffer contains the contentation of segment, ext and x

    CND_PRECONDITION(!segment.isEmpty(), "Segment is NULL");

    Misc::segmentname(buffer, bufferLen, segment, ext, x);
}

void SegmentReader::openNorms(Directory* cfsDir)
{
    //Func - Open all norms files for all fields
    //       Creates for each field a norm Instance with an open inputstream to 
    //       a corresponding norm file ready to be read
    //Pre  - true
    //Post - For each field a norm instance has been created with an open inputstream to
    //       a corresponding norm file ready to be read

    //Iterate through all the fields
    for (int32_t i = 0; i < fieldInfos->size(); i++) {
        //Get the FieldInfo for the i-th field
        FieldInfo* fi = fieldInfos->fieldInfo(i);
        //Check if the field is indexed
        if (fi->isIndexed && !fi->omitNorms ) {
            //Allocate a buffer
            QString fileName;

            // look first if there are separate norms in compound format
            SegmentName(fileName, CL_MAX_PATH, QLatin1String(".s"), fi->number);
            Directory* d = getDirectory();
            if(!d->fileExists(fileName)){
                SegmentName(fileName, CL_MAX_PATH, QLatin1String(".f"), fi->number);
                d = cfsDir;
            }

            _norms.put(fi->name, _CLNEW Norm(d->openInput(fileName),
                fi->number, this, segment));
        }
    }
}

void SegmentReader::closeNorms()
{
    //Func - Close all the norms stored in norms
    //Pre  - true
    //Post - All the norms have been destroyed

    SCOPED_LOCK_MUTEX(_norms.THIS_LOCK)
    
    //Create an interator initialized at the beginning of norms
    NormsType::iterator itr = _norms.begin();
    //Iterate through all the norms
    while (itr != _norms.end()) {
        // Get, delete the norm
        _CLDELETE(itr->second);
        // Move the interator to the next norm in the norms collection.
        // Note ++ is an overloaded operator
        ++itr;
    }
    _norms.clear(); //bvk: they're deleted, so clear them so that they are not re-used
}

TermVectorsReader* SegmentReader::getTermVectorsReader()
{
    TermVectorsReader* tvReader = termVectorsLocal.get();
    if (tvReader == NULL) {
        tvReader = termVectorsReaderOrig->clone();
        termVectorsLocal.set(tvReader);
    }
    return tvReader;
}

TermFreqVector* SegmentReader::getTermFreqVector(int32_t docNumber,
    const TCHAR* field)
{
    if (field) {
        FieldInfo* fi = fieldInfos->fieldInfo(field);
        // Check if this field is invalid or has no stored term vector
        if (fi == NULL || !fi->storeTermVector || termVectorsReaderOrig == NULL) 
            return NULL;
    }

    TermVectorsReader* termVectorsReader = getTermVectorsReader();
    if (termVectorsReader == NULL)
        return NULL;

    return termVectorsReader->get(docNumber, field);
}

bool SegmentReader::getTermFreqVectors(int32_t docNumber,
    Array<TermFreqVector*>& result)
{
    if (termVectorsReaderOrig == NULL)
        return false;

    TermVectorsReader* termVectorsReader = getTermVectorsReader();
    if (termVectorsReader == NULL)
        return false;

    return termVectorsReader->get(docNumber, result);
}

CL_NS_END
