/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.core.query.lucene;

import java.io.IOException;

import org.apache.jackrabbit.core.query.lucene.directory.DirectoryManager;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;

/**
 * Implements a lucene index which is based on a
 * {@link org.apache.jackrabbit.core.fs.FileSystem}.
 */
class PersistentIndex extends AbstractIndex {

    /** The name of this persistent index */
    private final String name;

    /**
     * If non <code>null</code>, <code>listener</code> needs to be informed
     * when a document is deleted.
     */
    private IndexListener listener;

    /**
     * The index deletion policy. Old index generations are deleted when they
     * reach a certain age.
     */
    private final IndexDeletionPolicyImpl indexDelPolicy;

    /**
     * The current generation of this persistent index.
     */
    private long generation;

    /**
     * Creates a new <code>PersistentIndex</code>.
     *
     * @param name the name of this index.
     * @param analyzer the analyzer for text tokenizing.
     * @param similarity the similarity implementation.
     * @param cache the document number cache
     * @param indexingQueue the indexing queue.
     * @param directoryManager the directory manager.
     * @param generationMaxAge age in seconds after which an index generation is
     *          deleted.
     * @throws IOException if an error occurs while opening / creating the
     *  index.
     */
    PersistentIndex(String name, Analyzer analyzer,
                    Similarity similarity, DocNumberCache cache,
                    IndexingQueue indexingQueue,
                    DirectoryManager directoryManager, long generationMaxAge)
            throws IOException {
        super(analyzer, similarity, directoryManager.getDirectory(name),
                cache, indexingQueue);
        this.name = name;
        this.indexDelPolicy = new IndexDeletionPolicyImpl(this,
                generationMaxAge * 1000);
        if (isExisting()) {
            IndexMigration.migrate(this, directoryManager, '\uFFFF');
        }
    }

    /**
     * @inheritDoc
     */
    int removeDocument(Term idTerm) throws IOException {
        int num = super.removeDocument(idTerm);
        if (num > 0 && listener != null) {
            listener.documentDeleted(idTerm);
        }
        return num;
    }

    /**
     * @return the index deletion policy of this index.
     */
    protected IndexDeletionPolicy getIndexDeletionPolicy() {
        return indexDelPolicy;
    }

    /**
     * Merges the provided indexes into this index. After this completes, the
     * index is optimized.
     * <p/>
     * The provided IndexReaders are not closed.
     *
     * @param readers the readers of indexes to add.
     * @throws IOException if an error occurs while adding indexes.
     */
    void addIndexes(IndexReader[] readers) throws IOException {
        getIndexWriter().addIndexes(readers);
        getIndexWriter().optimize();
    }

    /**
     * Copies <code>index</code> into this persistent index. This method should
     * only be called when <code>this</code> index is empty otherwise the
     * behaviour is undefined.
     *
     * @param index the index to copy from.
     * @throws IOException if an error occurs while copying.
     */
    void copyIndex(AbstractIndex index) throws IOException {
        // commit changes to directory on other index.
        index.commit(true);
        // simply copy over the files
        byte[] buffer = new byte[1024];
        Directory dir = index.getDirectory();
        Directory dest = getDirectory();
        String[] files = dir.listAll();
        for (String file : files) {
            IndexInput in = dir.openInput(file);
            try {
                IndexOutput out = dest.createOutput(file);
                try {
                    long remaining = in.length();
                    while (remaining > 0) {
                        int num = (int) Math.min(remaining, buffer.length);
                        in.readBytes(buffer, 0, num);
                        out.writeBytes(buffer, num);
                        remaining -= num;
                    }
                } finally {
                    out.close();
                }
            } finally {
                in.close();
            }
        }
        // refresh current generation
        indexDelPolicy.readCurrentGeneration();
    }

    /**
     * Returns a <code>ReadOnlyIndexReader</code> and registeres
     * <code>listener</code> to send notifications when documents are deleted on
     * <code>this</code> index.
     *
     * @param listener the listener to notify when documents are deleted.
     * @return a <code>ReadOnlyIndexReader</code>.
     * @throws IOException if the reader cannot be obtained.
     */
    synchronized ReadOnlyIndexReader getReadOnlyIndexReader(IndexListener listener)
            throws IOException {
        ReadOnlyIndexReader reader = getReadOnlyIndexReader();
        this.listener = listener;
        return reader;
    }

    /**
     * Removes a potentially registered {@link IndexListener}.
     */
    synchronized void resetListener() {
        this.listener = null;
    }

    /**
     * Returns the number of documents in this persistent index.
     *
     * @return the number of documents in this persistent index.
     * @throws IOException if an error occurs while reading from the index.
     */
    int getNumDocuments() throws IOException {
        return getIndexReader().numDocs();
    }

    /**
     * Returns the name of this index.
     * @return the name of this index.
     */
    String getName() {
        return name;
    }

    /**
     * @return the current generation of this index.
     */
    long getCurrentGeneration() {
        return generation;
    }

    /**
     * Sets the current generation of this index. This method should only be
     * called by {@link IndexDeletionPolicyImpl}.
     *
     * @param generation the current generation.
     */
    void setCurrentGeneration(long generation) {
        this.generation = generation;
    }
}
