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

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

CompoundFileReader::CSIndexInput::CSIndexInput(CL_NS(store)::IndexInput* base,
    const int64_t fileOffset, const int64_t length)
{
    this->base = base;
    this->fileOffset = fileOffset;
    this->_length = length;
}

void CompoundFileReader::CSIndexInput::readInternal(uint8_t* b, const int32_t len)
{
    SCOPED_LOCK_MUTEX(base->THIS_LOCK)

    int64_t start = getFilePointer();
    if(start + len > _length)
        _CLTHROWA(CL_ERR_IO, "read past EOF");
    base->seek(fileOffset + start);
    base->readBytes(b, len);
}

CompoundFileReader::CSIndexInput::~CSIndexInput()
{
}

IndexInput* CompoundFileReader::CSIndexInput::clone() const
{
    return _CLNEW CSIndexInput(*this);
}

CompoundFileReader::CSIndexInput::CSIndexInput(const CSIndexInput& clone)
    : BufferedIndexInput(clone)
{
    this->base = clone.base; //no need to clone this..
    this->fileOffset = clone.fileOffset;
    this->_length = clone._length;
}

void CompoundFileReader::CSIndexInput::close()
{
}

CompoundFileReader::CompoundFileReader(Directory* dir, const QString& name)
    : entries(false, true)
{
    directory = dir;
    fileName = name;

    bool success = false;
    try {
        stream = dir->openInput(name);

        // read the directory and init files
        int32_t count = stream->readVInt();
        FileEntry* entry = NULL;
        TCHAR tid[CL_MAX_PATH];
        for (int32_t i = 0; i < count; i++) {
            int64_t offset = stream->readLong();
            int32_t read = stream->readString(tid, CL_MAX_PATH);
            QString aid(QString::fromWCharArray(tid, read));

            // set length of the previous entry
            if (entry != NULL)
                entry->length = offset - entry->offset;

            entry = _CLNEW FileEntry(offset);
            entries.put(aid, entry);
        }

        // set the length of the final entry
        if (entry != NULL)
            entry->length = stream->length() - entry->offset;
        success = true;
    } _CLFINALLY (
        if (!success && (stream != NULL)) {
            try {
                stream->close();
                _CLDELETE(stream);
            } catch (CLuceneError& err) {
                if (err.number() != CL_ERR_IO)
                    throw err;
            }
        }
    )
}

CompoundFileReader::~CompoundFileReader()
{
    close();
}

Directory* CompoundFileReader::getDirectory()
{
    return directory;
}

QString CompoundFileReader::getName() const
{
    return fileName;
}

void CompoundFileReader::close()
{
    SCOPED_LOCK_MUTEX(THIS_LOCK)

    if (stream != NULL) {
        entries.clear();
        stream->close();
        _CLDELETE(stream);
    }
}

IndexInput* CompoundFileReader::openInput(const QString& id)
{
    SCOPED_LOCK_MUTEX(THIS_LOCK)

    if (stream == NULL)
        _CLTHROWA(CL_ERR_IO, "Stream closed");

    const FileEntry* entry = entries.get(id);
    if (entry == NULL) {
        char buf[CL_MAX_PATH + 30];
        strcpy(buf,"No sub-file with id ");
        strncat(buf, id.toLocal8Bit().constData(), CL_MAX_PATH);
        strcat(buf, " found");
        _CLTHROWA(CL_ERR_IO,buf);
    }
    return _CLNEW CSIndexInput(stream, entry->offset, entry->length);
}

QStringList CompoundFileReader::list() const
{
    // for (EntriesType::const_iterator i=entries.begin();i!=entries.end();i++){
    //     names->push_back(i->first);
    //     ++i;
    // }

    QStringList names;
    EntriesType::const_iterator itr;
    // TODO: verify this, see old code above ???
    for (itr = entries.begin(); itr != entries.end(); ++itr)
        names.push_back(itr->first);

    return names;
}

bool CompoundFileReader::fileExists(const QString& name) const
{
    return entries.exists(name);
}

int64_t CompoundFileReader::fileModified(const QString& name) const
{
    return directory->fileModified(fileName);
}

void CompoundFileReader::touchFile(const QString& name)
{
    directory->touchFile(fileName);
}

bool CompoundFileReader::doDeleteFile(const QString& name)
{
    _CLTHROWA(CL_ERR_UnsupportedOperation,
        "UnsupportedOperationException: CompoundFileReader::doDeleteFile");
}

void CompoundFileReader::renameFile(const QString& from, const QString& to)
{
    _CLTHROWA(CL_ERR_UnsupportedOperation,
        "UnsupportedOperationException: CompoundFileReader::renameFile");
}

int64_t CompoundFileReader::fileLength(const QString& name) const
{
    FileEntry* e = entries.get(name);
    if (e == NULL) {
        char buf[CL_MAX_PATH + 30];
        strcpy(buf,"File ");
        strncat(buf, name.toLocal8Bit().constData(), CL_MAX_PATH);
        strcat(buf," does not exist");
        _CLTHROWA(CL_ERR_IO,buf);
    }
    return e->length;
}

IndexOutput* CompoundFileReader::createOutput(const QString& name)
{
    _CLTHROWA(CL_ERR_UnsupportedOperation,
        "UnsupportedOperationException: CompoundFileReader::createOutput");
}

LuceneLock* CompoundFileReader::makeLock(const QString& name)
{
    _CLTHROWA(CL_ERR_UnsupportedOperation,
        "UnsupportedOperationException: CompoundFileReader::makeLock");
}

QString CompoundFileReader::toString() const
{
    QString ret(QLatin1String("CompoundFileReader@"));
    return ret.append(fileName);
}

CompoundFileWriter::CompoundFileWriter(Directory* dir, const QString& name)
    : ids(false)
    , entries(true)
{
    if (dir == NULL)
        _CLTHROWA(CL_ERR_NullPointer, "directory cannot be null");

    if (name.isEmpty())
        _CLTHROWA(CL_ERR_NullPointer, "name cannot be null");

    merged = false;
    directory = dir;
    fileName = name;
}

CompoundFileWriter::~CompoundFileWriter()
{
}

Directory* CompoundFileWriter::getDirectory()
{
    return directory;
}

/** Returns the name of the compound file. */
QString CompoundFileWriter::getName() const
{
    return fileName;
}

void CompoundFileWriter::addFile(const QString& file)
{
    if (merged)
        _CLTHROWA(CL_ERR_IO, "Can't add extensions after merge has been called");

    if (file.isEmpty())
        _CLTHROWA(CL_ERR_NullPointer, "file cannot be null");

    if (ids.find(file) != ids.end()) {
        char buf[CL_MAX_PATH + 30];
        strcpy(buf, "File ");
        strncat(buf, file.toLocal8Bit().constData(), CL_MAX_PATH);
        strcat(buf," already added");
        _CLTHROWA(CL_ERR_IO,buf);
    }
    ids.insert(file);
    entries.push_back(_CLNEW WriterFileEntry(file));
}

void CompoundFileWriter::close()
{
    if (merged)
        _CLTHROWA(CL_ERR_IO, "Merge already performed");

    if (entries.size() == 0) // isEmpty()
        _CLTHROWA(CL_ERR_IO, "No entries to merge have been defined");

    merged = true;

    // open the compound stream
    IndexOutput* os = NULL;
    try {
        os = directory->createOutput(fileName);

        // Write the number of entries
        os->writeVInt(entries.size());

        // Write the directory with all offsets at 0.
        // Remember the positions of directory entries so that we can
        // adjust the offsets later
        { //msvc6 for scope fix
            TCHAR tfile[CL_MAX_PATH];
            for (CLLinkedList<WriterFileEntry*>::iterator i = entries.begin();
                i != entries.end(); i++) {
                WriterFileEntry* fe = *i;
                fe->directoryOffset = os->getFilePointer();
                os->writeLong(0);    // for now
                tfile[fe->file.toWCharArray(tfile)] = '\0';
                os->writeString(tfile, _tcslen(tfile));
            }
        }

        // Open the files and copy their data into the stream.
        // Remember the locations of each file's data section.
        { //msvc6 for scope fix
            int32_t bufferLength = 1024;
            uint8_t buffer[1024];
            for (CLLinkedList<WriterFileEntry*>::iterator i = entries.begin();
                i != entries.end(); i++) {
                WriterFileEntry* fe = *i;
                fe->dataOffset = os->getFilePointer();
                copyFile(fe, os, buffer, bufferLength);
            }
        }

        { //msvc6 for scope fix
            // Write the data offsets into the directory of the compound stream
            for (CLLinkedList<WriterFileEntry*>::iterator i = entries.begin();
                i != entries.end(); i++) {
                WriterFileEntry* fe = *i;
                os->seek(fe->directoryOffset);
                os->writeLong(fe->dataOffset);
            }
        }


    } _CLFINALLY (
        if (os != NULL) {
            try {
                os->close();
                _CLDELETE(os);
            } catch (...) { }
        }
    );
}

void CompoundFileWriter::copyFile(WriterFileEntry* source, IndexOutput* os,
    uint8_t* buffer, int32_t bufferLength)
{
    IndexInput* is = NULL;
    try {
        int64_t startPtr = os->getFilePointer();

        is = directory->openInput(source->file);
        int64_t length = is->length();
        int64_t remainder = length;
        int32_t chunk = bufferLength;

        while(remainder > 0) {
            int32_t len = (int32_t)min((int64_t)chunk, remainder);
            is->readBytes(buffer, len);
            os->writeBytes(buffer, len);
            remainder -= len;
        }

        // Verify that remainder is 0
        if (remainder != 0) {
            TCHAR buf[CL_MAX_PATH+100];
            _sntprintf(buf, CL_MAX_PATH + 100, _T("Non-zero remainder length ")
                _T("after copying: %d (id: %s, length: %d, buffer size: %d)"),
                remainder, source->file.toLocal8Bit().constData(), length, chunk);
            _CLTHROWT(CL_ERR_IO, buf);
        }

        // Verify that the output length diff is equal to original file
        int64_t endPtr = os->getFilePointer();
        int64_t diff = endPtr - startPtr;
        if (diff != length) {
            TCHAR buf[100];
            _sntprintf(buf, 100, _T("Difference in the output file offsets %d ")
                _T("does not match the original file length %d"), diff, length);
            _CLTHROWT(CL_ERR_IO,buf);
        }
    } _CLFINALLY (
        if (is != NULL) {
            is->close();
            _CLDELETE(is);
        }
    );
}

CL_NS_END
