/*
 * 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) 2013 Digia Plc and/or its subsidiary(-ies).
*/
#include "CLucene/StdHeader.h"
#include "SegmentInfos.h"

#include "CLucene/store/Directory.h"
#include "CLucene/util/Misc.h"

CL_NS_USE(store)
CL_NS_USE(util)
CL_NS_DEF(index)

SegmentInfo::SegmentInfo(const QString& Name, const int32_t DocCount,
    CL_NS(store)::Directory* Dir)
    : docCount(DocCount)
    , dir(Dir)
{
    //Func - Constructor. Initialises SegmentInfo.
    //Pre  - Name holds the unique name in the directory Dir
    //       DocCount holds the number of documents in the segment
    //       Dir holds the Directory where the segment resides
    //Post - The instance has been created. name contains the duplicated string
    //        Name. docCount = DocCount and dir references Dir
    name = Name;
}

SegmentInfo::~SegmentInfo()
{
}

SegmentInfos::SegmentInfos(bool _deleteMembers)
    : deleteMembers(_deleteMembers)
{
    //Func - Constructor
    //Pre  - deleteMembers indicates if the instance to be created must delete
    //       all SegmentInfo instances it manages when the instance is destroyed
    //       or not true -> must delete, false may not delete
    //Post - An instance of SegmentInfos has been created.

    //initialize counter to 0
    counter = 0;
    version = Misc::currentTimeMillis();
}

SegmentInfos::~SegmentInfos()
{
    //Func - Destructor
    //Pre  - true
    //Post - The instance has been destroyed. Depending on the constructor used
    //       the SegmentInfo instances that this instance managed have been
    //       deleted or not.

    if (deleteMembers) {
        segmentInfosType::iterator it;
        for (it = infos.begin(); it != infos.end(); ++it)
            _CLLDELETE(*it);
    }
    //Clear the list of SegmentInfo instances - make sure everything is deleted
    infos.clear();
}
  
SegmentInfo* SegmentInfos::info(int32_t i) const
{
    //Func - Returns a reference to the i-th SegmentInfo in the list.
    //Pre  - i >= 0
    //Post - A reference to the i-th SegmentInfo instance has been returned

    CND_PRECONDITION(i >= 0, "i contains negative number");

    //Get the i-th SegmentInfo instance
    SegmentInfo *ret = infos.value(i, 0);

    //Condition check to see if the i-th SegmentInfo has been retrieved
    CND_CONDITION(ret != NULL, "No SegmentInfo instance found");

    return ret;
}

void SegmentInfos::clearto(size_t _min)
{
    // Make sure we actually need to remove
    if (infos.size() > _min) {
        segmentInfosType::iterator itr;
        segmentInfosType::iterator eitr = infos.end();
        segmentInfosType::iterator bitr = infos.begin() + _min;

        for(itr = bitr; itr != eitr; ++itr)
            _CLLDELETE((*itr));
        infos.erase(bitr, eitr);
    }
}

void SegmentInfos::add(SegmentInfo* info)
{
    infos.push_back(info);
}

int32_t SegmentInfos::size() const
{
    return infos.size();
}

void SegmentInfos::read(Directory* directory)
{
    //Func - Reads segments file that resides in directory. 
    //Pre  - directory contains a valid reference
    //Post - The segments file has been read and for each segment found
    //       a SegmentsInfo intance has been created and stored.

    //Open an IndexInput to the segments file and check if valid
    IndexInput* input = directory->openInput(QLatin1String("segments"));
    if (input) {
        try {
            int32_t format = input->readInt();
            // file contains explicit format info
            if (format < 0) {
                // check that it is a format we can understand
                if (format < FORMAT) {
                    TCHAR err[30];
                    _sntprintf(err, 30, _T("Unknown format version: %d"), format);
                    _CLTHROWT(CL_ERR_Runtime, err);
                }
                // read version
                version = input->readLong();
                // read counter
                counter = input->readInt();
            } else {
                // file is in old format without explicit format info
                counter = format;
            }

            //Temporary variable for storing the name of the segment
            char aname[CL_MAX_PATH] = { 0 };
            TCHAR tname[CL_MAX_PATH] = { 0 };

            //read segmentInfos
            for (int32_t i = input->readInt(); i > 0; --i) { 
                // read the name of the segment
                input->readString(tname, CL_MAX_PATH); 
                STRCPY_TtoA(aname, tname, CL_MAX_PATH);

                //Instantiate a new SegmentInfo Instance
                SegmentInfo* si = _CLNEW SegmentInfo(QLatin1String(aname),
                    input->readInt(), directory);

                //Condition check to see if si points to an instance
                CND_CONDITION(si != NULL, "Memory allocation for si failed")	;

                //store SegmentInfo si
                infos.push_back(si);
            } 

            if (format >= 0) {
                // in old format the version number may be at the end of the file
                if (input->getFilePointer() >= input->length()) {
                    // old file format without version number
                    version = Misc::currentTimeMillis();
                } else {
                    // read version
                    version = input->readLong();
                }
            }
        } _CLFINALLY (
            //destroy the inputStream input. The destructor of IndexInput will 
            //also close the Inputstream input
            _CLDELETE(input);
        );
    }
}

void SegmentInfos::write(Directory* directory)
{
    //Func - Writes a new segments file based upon the SegmentInfo instances it manages
    //Pre  - directory is a valid reference to a Directory
    //Post - The new segment has been written to disk

    //Open an IndexOutput to the segments file and check if valid
    IndexOutput* output = directory->createOutput(QLatin1String("segments.new"));
    if (output) {
        try {
            // write FORMAT
            output->writeInt(FORMAT); 
            // every write changes the index
            output->writeLong(++version);
             // Write the counter
            output->writeInt(counter);

            // Write the number of SegmentInfo Instances which is equal to the number
            // of segments in directory as each SegmentInfo manages a single segment
            output->writeInt(infos.size());			  

            //temporary value for wide segment name
            TCHAR tname[CL_MAX_PATH];

            //Iterate through all the SegmentInfo instances
            for (uint32_t i = 0; i < infos.size(); ++i) {
                //Retrieve the SegmentInfo
                SegmentInfo *si = infos.value(i, 0);
                //Condition check to see if si has been retrieved
                CND_CONDITION(si != NULL, "No SegmentInfo instance found");

                //Write the name of the current segment
                int32_t count = si->name.toWCharArray(tname);
                tname[count] = '\0';
                output->writeString(tname, _tcslen(tname));

                //Write the number of documents in the segment 
                output->writeInt(si->docCount);
            }
        } _CLFINALLY(
            output->close();
            _CLDELETE(output);
        );

        // install new segment info
        directory->renameFile(QLatin1String("segments.new"),
            QLatin1String("segments"));
    }
}

  
int64_t SegmentInfos::readCurrentVersion(Directory* directory)
{
    int32_t format = 0;
    int64_t version = 0;
    IndexInput* input = directory->openInput(QLatin1String("segments"));
    try {
        format = input->readInt();
        if (format < 0){
            if (format < FORMAT) {
                TCHAR err[30];
                _sntprintf(err, 30, _T("Unknown format version: %d"), format);
                _CLTHROWT(CL_ERR_Runtime, err);
            }
            // read version
            version = input->readLong();
        }
    } _CLFINALLY (
        input->close(); 
        _CLDELETE(input);
    );

    if (format < 0)
        return version;

    // We cannot be sure about the format of the file. Therefore we have to
    // read the whole file and cannot simply seek to the version entry.
    SegmentInfos segmentInfos;
    segmentInfos.read(directory);
    return segmentInfos.getVersion();
}

CL_NS_END
