package org.apache.lucene.search;

/**
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed 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.
 */

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;

import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.HashMap;

/**
 * Expert: The default cache implementation, storing all values in memory.
 * A WeakHashMap is used for storage.
 *
 * <p>Created: May 19, 2004 4:40:36 PM
 *
 * @author  Tim Jones (Nacimiento Software)
 * @since   lucene 1.4
 * @version $Id: FieldCacheImpl.java,v 1.1 2006/01/12 21:15:21 alexander Exp $
 */
class FieldCacheImpl
implements FieldCache {

  /** Expert: Every key in the internal cache is of this type. */
  static class Entry {
    final String field;        // which Field
    final int type;            // which SortField type
    final Object custom;       // which custom comparator

    /** Creates one of these objects. */
    Entry (String field, int type) {
      this.field = field.intern();
      this.type = type;
      this.custom = null;
    }

    /** Creates one of these objects for a custom comparator. */
    Entry (String field, Object custom) {
      this.field = field.intern();
      this.type = SortField.CUSTOM;
      this.custom = custom;
    }

    /** Two of these are equal iff they reference the same field and type. */
    public boolean equals (Object o) {
      if (o instanceof Entry) {
        Entry other = (Entry) o;
        if (other.field == field && other.type == type) {
          if (other.custom == null) {
            if (custom == null) return true;
          } else if (other.custom.equals (custom)) {
            return true;
          }
        }
      }
      return false;
    }

    /** Composes a hashcode based on the field and type. */
    public int hashCode() {
      return field.hashCode() ^ type ^ (custom==null ? 0 : custom.hashCode());
    }
  }


  /** The internal cache. Maps Entry to array of interpreted term values. **/
  final Map cache = new WeakHashMap();

  /** See if an object is in the cache. */
  Object lookup (IndexReader reader, String field, int type) {
    Entry entry = new Entry (field, type);
    synchronized (this) {
      HashMap readerCache = (HashMap)cache.get(reader);
      if (readerCache == null) return null;
      return readerCache.get (entry);
    }
  }

  /** See if a custom object is in the cache. */
  Object lookup (IndexReader reader, String field, Object comparer) {
    Entry entry = new Entry (field, comparer);
    synchronized (this) {
      HashMap readerCache = (HashMap)cache.get(reader);
      if (readerCache == null) return null;
      return readerCache.get (entry);
    }
  }

  /** Put an object into the cache. */
  Object store (IndexReader reader, String field, int type, Object value) {
    Entry entry = new Entry (field, type);
    synchronized (this) {
      HashMap readerCache = (HashMap)cache.get(reader);
      if (readerCache == null) {
        readerCache = new HashMap();
        cache.put(reader,readerCache);
      }
      return readerCache.put (entry, value);
    }
  }

  /** Put a custom object into the cache. */
  Object store (IndexReader reader, String field, Object comparer, Object value) {
    Entry entry = new Entry (field, comparer);
    synchronized (this) {
      HashMap readerCache = (HashMap)cache.get(reader);
      if (readerCache == null) {
        readerCache = new HashMap();
        cache.put(reader, readerCache);
      }
      return readerCache.put (entry, value);
    }
  }

  // inherit javadocs
  public int[] getInts (IndexReader reader, String field)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, SortField.INT);
    if (ret == null) {
      final int[] retArray = new int[reader.maxDoc()];
      if (retArray.length > 0) {
        TermDocs termDocs = reader.termDocs();
        TermEnum termEnum = reader.terms (new Term (field, ""));
        try {
          if (termEnum.term() == null) {
            throw new RuntimeException ("no terms in field " + field);
          }
          do {
            Term term = termEnum.term();
            if (term.field() != field) break;
            int termval = Integer.parseInt (term.text());
            termDocs.seek (termEnum);
            while (termDocs.next()) {
              retArray[termDocs.doc()] = termval;
            }
          } while (termEnum.next());
        } finally {
          termDocs.close();
          termEnum.close();
        }
      }
      store (reader, field, SortField.INT, retArray);
      return retArray;
    }
    return (int[]) ret;
  }

  // inherit javadocs
  public float[] getFloats (IndexReader reader, String field)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, SortField.FLOAT);
    if (ret == null) {
      final float[] retArray = new float[reader.maxDoc()];
      if (retArray.length > 0) {
        TermDocs termDocs = reader.termDocs();
        TermEnum termEnum = reader.terms (new Term (field, ""));
        try {
          if (termEnum.term() == null) {
            throw new RuntimeException ("no terms in field " + field);
          }
          do {
            Term term = termEnum.term();
            if (term.field() != field) break;
            float termval = Float.parseFloat (term.text());
            termDocs.seek (termEnum);
            while (termDocs.next()) {
              retArray[termDocs.doc()] = termval;
            }
          } while (termEnum.next());
        } finally {
          termDocs.close();
          termEnum.close();
        }
      }
      store (reader, field, SortField.FLOAT, retArray);
      return retArray;
    }
    return (float[]) ret;
  }

  // inherit javadocs
  public String[] getStrings (IndexReader reader, String field)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, SortField.STRING);
    if (ret == null) {
      final String[] retArray = new String[reader.maxDoc()];
      if (retArray.length > 0) {
        TermDocs termDocs = reader.termDocs();
        TermEnum termEnum = reader.terms (new Term (field, ""));
        try {
          if (termEnum.term() == null) {
            throw new RuntimeException ("no terms in field " + field);
          }
          do {
            Term term = termEnum.term();
            if (term.field() != field) break;
            String termval = term.text();
            termDocs.seek (termEnum);
            while (termDocs.next()) {
              retArray[termDocs.doc()] = termval;
            }
          } while (termEnum.next());
        } finally {
          termDocs.close();
          termEnum.close();
        }
      }
      store (reader, field, SortField.STRING, retArray);
      return retArray;
    }
    return (String[]) ret;
  }

  // inherit javadocs
  public StringIndex getStringIndex (IndexReader reader, String field)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, STRING_INDEX);
    if (ret == null) {
      final int[] retArray = new int[reader.maxDoc()];
      String[] mterms = new String[reader.maxDoc()+1];
      if (retArray.length > 0) {
        TermDocs termDocs = reader.termDocs();
        TermEnum termEnum = reader.terms (new Term (field, ""));
        int t = 0;  // current term number

        // an entry for documents that have no terms in this field
        // should a document with no terms be at top or bottom?
        // this puts them at the top - if it is changed, FieldDocSortedHitQueue
        // needs to change as well.
        mterms[t++] = null;

        try {
          if (termEnum.term() == null) {
            throw new RuntimeException ("no terms in field " + field);
          }
          do {
            Term term = termEnum.term();
            if (term.field() != field) break;

            // store term text
            // we expect that there is at most one term per document
            if (t >= mterms.length) throw new RuntimeException ("there are more terms than documents in field \"" + field + "\"");
            mterms[t] = term.text();

            termDocs.seek (termEnum);
            while (termDocs.next()) {
              retArray[termDocs.doc()] = t;
            }

            t++;
          } while (termEnum.next());
        } finally {
          termDocs.close();
          termEnum.close();
        }

        if (t == 0) {
          // if there are no terms, make the term array
          // have a single null entry
          mterms = new String[1];
        } else if (t < mterms.length) {
          // if there are less terms than documents,
          // trim off the dead array space
          String[] terms = new String[t];
          System.arraycopy (mterms, 0, terms, 0, t);
          mterms = terms;
        }
      }
      StringIndex value = new StringIndex (retArray, mterms);
      store (reader, field, STRING_INDEX, value);
      return value;
    }
    return (StringIndex) ret;
  }

  /** The pattern used to detect integer values in a field */
  /** removed for java 1.3 compatibility
   protected static final Pattern pIntegers = Pattern.compile ("[0-9\\-]+");
   **/

  /** The pattern used to detect float values in a field */
  /**
   * removed for java 1.3 compatibility
   * protected static final Object pFloats = Pattern.compile ("[0-9+\\-\\.eEfFdD]+");
   */

  // inherit javadocs
  public Object getAuto (IndexReader reader, String field)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, SortField.AUTO);
    if (ret == null) {
      TermEnum enumerator = reader.terms (new Term (field, ""));
      try {
        Term term = enumerator.term();
        if (term == null) {
          throw new RuntimeException ("no terms in field " + field + " - cannot determine sort type");
        }
        if (term.field() == field) {
          String termtext = term.text().trim();

          /**
           * Java 1.4 level code:

           if (pIntegers.matcher(termtext).matches())
           return IntegerSortedHitQueue.comparator (reader, enumerator, field);

           else if (pFloats.matcher(termtext).matches())
           return FloatSortedHitQueue.comparator (reader, enumerator, field);
           */

          // Java 1.3 level code:
          try {
            Integer.parseInt (termtext);
            ret = getInts (reader, field);
          } catch (NumberFormatException nfe1) {
            try {
              Float.parseFloat (termtext);
              ret = getFloats (reader, field);
            } catch (NumberFormatException nfe2) {
              ret = getStringIndex (reader, field);
            }
          }
          if (ret != null) {
            store (reader, field, SortField.AUTO, ret);
          }
        } else {
          throw new RuntimeException ("field \"" + field + "\" does not appear to be indexed");
        }
      } finally {
        enumerator.close();
      }

    }
    return ret;
  }

  // inherit javadocs
  public Comparable[] getCustom (IndexReader reader, String field, SortComparator comparator)
  throws IOException {
    field = field.intern();
    Object ret = lookup (reader, field, comparator);
    if (ret == null) {
      final Comparable[] retArray = new Comparable[reader.maxDoc()];
      if (retArray.length > 0) {
        TermDocs termDocs = reader.termDocs();
        TermEnum termEnum = reader.terms (new Term (field, ""));
        try {
          if (termEnum.term() == null) {
            throw new RuntimeException ("no terms in field " + field);
          }
          do {
            Term term = termEnum.term();
            if (term.field() != field) break;
            Comparable termval = comparator.getComparable (term.text());
            termDocs.seek (termEnum);
            while (termDocs.next()) {
              retArray[termDocs.doc()] = termval;
            }
          } while (termEnum.next());
        } finally {
          termDocs.close();
          termEnum.close();
        }
      }
      store (reader, field, SortField.CUSTOM, retArray);
      return retArray;
    }
    return (Comparable[]) ret;
  }

}

