/*
 * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.search.lucene;

import com.arsdigita.util.UncheckedWrapperException;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateField;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Locale;

/**
 *
 * LuceneSearch is a wrapper for the Lucene search facilities. It contains
 * constructors to create new searches and methods to iterate over and to get
 * information for each hit.
 *
 * @author Richard Su (richard.su@alum.mit.edu)
 * @version $Id: //core-platform/dev/src/com/arsdigita/search/lucene/LuceneSearch.java#3 $
 *
 **/

public class LuceneSearch {

    private static final Logger LOG = Logger.getLogger(LuceneSearch.class);

    private IndexSearcher m_index;
    private Hits m_hits;
    private int m_hitIndex;
    private org.apache.lucene.document.Document m_doc;
    private float m_score;

    private static final LuceneLock LOCK = LuceneLock.getInstance();

    /**
     * Search over all objects in the system. Returns objects that matches
     * the search string.
     *
     * @param searchString user specified search string
     **/
    public LuceneSearch(String searchString) {
        try {
            synchronized(LOCK) {
                m_index = new IndexSearcher(Index.getLocation());
                Query query = QueryParser.parse(searchString,
                                                Document.CONTENT,
                                                new StandardAnalyzer());
                m_hits = m_index.search(query);
            }
            m_hitIndex = 0;
        } catch (IOException ex) {
            LOG.fatal("failed the search for " + searchString, ex);
        } catch (ParseException ex) {
            LOG.fatal("failed the search for " + searchString, ex);
        }
    }

    /**
     * Search for a specific ACS object and search string.
     *
     * @param searchString user specified search string
     * @param objectType ACS object type
     **/
    public LuceneSearch(String searchString, String objectType) {
        this(searchString, new ObjectTypeFilter(objectType));
    }

    /**
     * Search over all objects in the system using a filter
     *
     * @param searchString user specified search string
     * @param f a filter
     **/
    public LuceneSearch(String searchString, Filter f) {
        try {
            synchronized(LOCK) {
                m_index = new IndexSearcher(Index.getLocation());
                Query query = QueryParser.parse(searchString,
                                                Document.CONTENT,
                                                new StandardAnalyzer());
                m_hits = m_index.search(query, f);
            }
            m_hitIndex = 0;
        } catch (IOException ex) {
            LOG.fatal("failed the search for " + searchString, ex);
        } catch (ParseException ex) {
            LOG.fatal("failed the search for " + searchString, ex);
        }
    }

    /**
     * Search given a preformed query.
     *
     * @param q a performed query
     **/
    public LuceneSearch(Query q) {
        try {
            synchronized(LOCK) {
                m_index = new IndexSearcher(Index.getLocation());
                m_hits = m_index.search(q);
            }
            m_hitIndex = 0;
        } catch (IOException e) {
            LOG.fatal("search failed", e);
        }
    }

    /**
     * Returns the number of hits in this query.
     **/

    public int size() {
        return m_hits == null ? 0 : m_hits.length();
    }

    /**
     * Returns true if the search has more results
     *
     * @return true if the search has more results
     **/
    public boolean next() {
        if (m_hits == null) {
            return false;
        }

        if (m_hitIndex < m_hits.length()) {
            try {
                m_doc = m_hits.doc(m_hitIndex);
                m_score = m_hits.score(m_hitIndex);
                m_hitIndex++;
                return true;
            } catch (IOException e) {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Closes this search, freeing associated resources.
     **/
    public void close() {
        try {
            if (m_index != null) {
                m_index.close();
            }
        } catch (IOException ioe) {
            throw new UncheckedWrapperException(ioe);
        }
    }

    /**
     * Returns the score of this hit.
     **/

    public float getScore() {
        return m_score;
    }

    /**
     * Returns the object ID
     *
     * @return the object id
     **/
    public BigDecimal getID() {
        return new BigDecimal(m_doc.get(Document.ID));
    }

    /**
     * Returns the locale the content is in
     *
     * @return the locale the content is in
     **/
    public Locale getLocale() {
        String language = m_doc.get(Document.LANGUAGE);
        String country = m_doc.get(Document.COUNTRY);
        if ("".equals(language) && "".equals(country)) {
            return null;
        } else {
            return new Locale(language, country);
        }
    }

    /**
     * Returns the object type.
     *
     * @return the object type
     **/
    public String getType() {
        return m_doc.get(Document.TYPE);
    }

    /**
     * Returns type-specific info.
     *
     * @return the type-specific info
     **/
    public String getTypeSpecificInfo() {
        return m_doc.get(Document.TYPE_SPECIFIC_INFO);
    }

    /**
     * Returns the link text for the object
     *
     * @return the link text for the object
     **/
    public String getTitle() {
        return m_doc.get(Document.TITLE);
    }

    /**
     * Returns a summary for the hit
     *
     * @return a summary for the hit
     **/
    public String getSummary() {
        return m_doc.get(Document.SUMMARY);
    }

    /**
     * Returns the content
     *
     * @return the content
     **/
    public String getContent() {
        return m_doc.get(Document.CONTENT);
    }
 
    public Date getCreationDate() {
        return toDate(m_doc.get(Document.CREATION_DATE));
    }

    public Date getLastModifiedDate() {
        return toDate(m_doc.get(Document.LAST_MODIFIED_DATE));
    }

    private Date toDate(String date) {
        if (date == null || date.equals("")) {
            return null;
        } else {
            return DateField.stringToDate(date);
        }
    }

}
