/*
 * 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.
*/
#ifndef _lucene_index_compoundfile_h
#define _lucene_index_compoundfile_h

#if defined(_LUCENE_PRAGMA_ONCE)
#   pragma once
#endif

#include <QtCore/QString>
#include <QtCore/QStringList>

#include "CLucene/store/IndexInput.h"
#include "CLucene/store/IndexOutput.h"
#include "CLucene/store/Directory.h"
#include "CLucene/store/Lock.h"
#include "CLucene/util/VoidList.h"
#include "CLucene/util/VoidMap.h"

CL_NS_DEF(index)

// Class for accessing a compound stream.
// This class implements a directory, but is limited to only read operations.
// Directory methods that would normally modify data throw an exception.
class CompoundFileReader : public CL_NS(store)::Directory
{
private:
    /** Implementation of an IndexInput that reads from a portion of the
    *  compound file. The visibility is left as "package" *only* because
    *  this helps with testing since JUnit test cases in a different class
    *  can then access package fields of this class.
    */
    class CSIndexInput : public CL_NS(store)::BufferedIndexInput
    {
    private:
        CL_NS(store)::IndexInput* base;
        int64_t fileOffset;
        int64_t _length;
    protected:
        /** Expert: implements buffer refill.  Reads uint8_ts from the current
        *  position in the input.
        * @param b the array to read uint8_ts into
        * @param length the number of uint8_ts to read
        */
        void readInternal(uint8_t* b, const int32_t len);
        void seekInternal(const int64_t pos) {}

    public:
        CSIndexInput(CL_NS(store)::IndexInput* base, const int64_t fileOffset,
            const int64_t length);
        CSIndexInput(const CSIndexInput& clone);
        ~CSIndexInput();

        /** Closes the stream to futher operations. */
        void close();
        CL_NS(store)::IndexInput* clone() const;

        int64_t length() { return _length; }

        QString getDirectoryType() const {
            return CompoundFileReader::DirectoryType(); }
    };

    class FileEntry : LUCENE_BASE
    {
    public:
        FileEntry()
            : offset(0), length(0) {}
        FileEntry(int64_t _offset)
            : offset(_offset), length(0) {}
        ~FileEntry() {}

        int64_t offset;
        int64_t length;
    };

    // Base info
    CL_NS(store)::Directory* directory;
    QString fileName;

    CL_NS(store)::IndexInput* stream;

    typedef CL_NS(util)::CLHashMap<QString, FileEntry*,
        CL_NS(util)::Compare::Qstring,
        CL_NS(util)::Equals::Qstring,
        CL_NS(util)::Deletor::DummyQString,
        CL_NS(util)::Deletor::Object<FileEntry> > EntriesType;
    EntriesType entries;
protected:
    // Removes an existing file in the directory->
    bool doDeleteFile(const QString& name);

public:
    CompoundFileReader(CL_NS(store)::Directory* dir, const QString& name);
    ~CompoundFileReader();
    CL_NS(store)::Directory* getDirectory();
    QString getName() const;

    void close();
    CL_NS(store)::IndexInput* openInput(const QString& id);

    /** Returns an array of strings, one for each file in the directory-> */
    QStringList list() const;
    /** Returns true iff a file with the given name exists. */
    bool fileExists(const QString& name) const;
    /** Returns the time the named file was last modified. */
    int64_t fileModified(const QString& name) const;
    /** Set the modified time of an existing file to now. */
    void touchFile(const QString& name);
    /** Renames an existing file in the directory->
    If a file already exists with the new name, then it is replaced.
    This replacement should be atomic. */
    void renameFile(const QString& from, const QString& to);
    /** Returns the length of a file in the directory.
    * @throws IOException if the file does not exist */
    int64_t fileLength(const QString& name) const;
    /** Not implemented
    * @throws UnsupportedOperationException */
    CL_NS(store)::IndexOutput* createOutput(const QString& name);
    /** Not implemented
    * @throws UnsupportedOperationException */
    CL_NS(store)::LuceneLock* makeLock(const QString& name);

    QString toString() const;

    static QString DirectoryType() { return QLatin1String("CFS"); }
    QString getDirectoryType() const { return DirectoryType(); }
};



// Combines multiple files into a single compound file.
// The file format:<br>
// <ul>
//    <li>VInt fileCount</li>
//    <li>{Directory}
//        fileCount entries with the following structure:</li>
//        <ul>
//            <li>int64_t dataOffset</li>
//            <li>UTFString extension</li>
//        </ul>
//    <li>{File Data}
//        fileCount entries with the raw data of the corresponding file</li>
// </ul>
// The fileCount integer indicates how many files are contained in this compound
// file. The {directory} that follows has that many entries. Each directory entry
// contains an encoding identifier, an int64_t pointer to the start of this file's
// data section, and a UTF String with that file's extension.
class CompoundFileWriter : LUCENE_BASE
{
    class WriterFileEntry : LUCENE_BASE {
    public:
        WriterFileEntry()
            : dataOffset(0), directoryOffset(0) {}
        WriterFileEntry(const QString& _file)
            : file(_file), dataOffset(0), directoryOffset(0) {}
        ~WriterFileEntry() {}

        QString file;
        // temporary holder for the start of this file's data section
        int64_t dataOffset;
        // temporary holder for the start of directory entry for this file
        int64_t directoryOffset;
    };

    bool merged;
    QString fileName;
    CL_NS(store)::Directory* directory;
    
    CL_NS(util)::CLHashSet<QString, CL_NS(util)::Compare::Qstring,
        CL_NS(util)::Deletor::DummyQString> ids;

    CL_NS(util)::CLLinkedList<WriterFileEntry*,
        CL_NS(util)::Deletor::Object<WriterFileEntry> > entries;
    
    // Copy the contents of the file with specified extension into the
    // provided output stream. Use the provided buffer for moving data
    // to reduce memory allocation.
    void copyFile(WriterFileEntry* source, CL_NS(store)::IndexOutput* os,
        uint8_t* buffer, int32_t bufferLength);

public:
    // Create the compound stream in the specified file. The file name is the
    //  entire name (no extensions are added).
    CompoundFileWriter(CL_NS(store)::Directory* dir, const QString& name);
    ~CompoundFileWriter();
    
    // Returns the directory of the compound file.
    CL_NS(store)::Directory* getDirectory();
    
    QString getName() const ;
    /* Add a source stream. <code>file</code> is the string by which the 
    *  sub-stream will be known in the compound stream.
    * 
    *  @throws IllegalStateException if this writer is closed
    *  @throws NullPointerException if <code>file</code> is null
    *  @throws IllegalArgumentException if a file with the same name
    *   has been added already
    */
    void addFile(const QString& file);
    /* Merge files with the extensions added up to now.
    *  All files with these extensions are combined sequentially into the
    *  compound stream. After successful merge, the source files
    *  @throws IllegalStateException if close() had been called before or
    *   if no file has been added to this object
    *  are deleted.
    */
    void close();
};

CL_NS_END

#endif
