/*
 * 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.lucene.queries.function;

import java.io.IOException;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.mutable.MutableValue;
import org.apache.lucene.util.mutable.MutableValueFloat;

/**
 * Represents field values as different types.
 * Normally created via a {@link ValueSource} for a particular field and reader.
 *
 *
 */

// FunctionValues is distinct from ValueSource because
// there needs to be an object created at query evaluation time that
// is not referenced by the query itself because:
// - Query objects should be MT safe
// - For caching, Query objects are often used as keys... you don't
//   want the Query carrying around big objects
public abstract class FunctionValues {

  public byte byteVal(int doc) throws IOException { throw new UnsupportedOperationException(); }
  public short shortVal(int doc) throws IOException { throw new UnsupportedOperationException(); }

  public float floatVal(int doc) throws IOException { throw new UnsupportedOperationException(); }
  public int intVal(int doc) throws IOException { throw new UnsupportedOperationException(); }
  public long longVal(int doc) throws IOException { throw new UnsupportedOperationException(); }
  public double doubleVal(int doc) throws IOException { throw new UnsupportedOperationException(); }
  // TODO: should we make a termVal, returns BytesRef?
  public String strVal(int doc) throws IOException { throw new UnsupportedOperationException(); }

  public boolean boolVal(int doc) throws IOException {
    return intVal(doc) != 0;
  }

  /** returns the bytes representation of the string val - TODO: should this return the indexed raw bytes not? */
  public boolean bytesVal(int doc, BytesRefBuilder target) throws IOException {
    String s = strVal(doc);
    if (s==null) {
      target.clear();
      return false;
    }
    target.copyChars(s);
    return true;
  }

  /** Native Java Object representation of the value */
  public Object objectVal(int doc) throws IOException {
    // most FunctionValues are functions, so by default return a Float()
    return floatVal(doc);
  }

  /** Returns true if there is a value for this document */
  public boolean exists(int doc) throws IOException {
    return true;
  }

  /**
   * @param doc The doc to retrieve to sort ordinal for
   * @return the sort ordinal for the specified doc
   * TODO: Maybe we can just use intVal for this...
   */
  public int ordVal(int doc) throws IOException {
    throw new UnsupportedOperationException();
  }

  /**
   * @return the number of unique sort ordinals this instance has
   */
  public int numOrd() { throw new UnsupportedOperationException(); }

  /**
   * An estimate of the expected cost to return a value for a document.
   * It's intended to be used by TwoPhaseIterator.matchCost implementations.
   * Returns an expected cost in number of simple operations like addition, multiplication,
   * comparing two numbers and indexing an array.
   * The returned value must be positive.
   */
  public float cost() { return 100; }

  public abstract String toString(int doc) throws IOException;

  /**
   * Abstraction of the logic required to fill the value of a specified doc into
   * a reusable {@link MutableValue}.  Implementations of {@link FunctionValues}
   * are encouraged to define their own implementations of ValueFiller if their
   * value is not a float.
   *
   * @lucene.experimental
   */
  public static abstract class ValueFiller {
    /** MutableValue will be reused across calls */
    public abstract MutableValue getValue();

    /** MutableValue will be reused across calls.  Returns true if the value exists. */
    public abstract void fillValue(int doc) throws IOException;
  }

  /** @lucene.experimental  */
  public ValueFiller getValueFiller() {
    return new ValueFiller() {
      private final MutableValueFloat mval = new MutableValueFloat();

      @Override
      public MutableValue getValue() {
        return mval;
      }

      @Override
      public void fillValue(int doc) throws IOException {
        mval.value = floatVal(doc);
      }
    };
  }

  //For Functions that can work with multiple values from the same document.  This does not apply to all functions
  public void byteVal(int doc, byte [] vals) throws IOException { throw new UnsupportedOperationException(); }
  public void shortVal(int doc, short [] vals) throws IOException { throw new UnsupportedOperationException(); }

  public void floatVal(int doc, float [] vals) throws IOException { throw new UnsupportedOperationException(); }
  public void intVal(int doc, int [] vals) throws IOException { throw new UnsupportedOperationException(); }
  public void longVal(int doc, long [] vals) throws IOException { throw new UnsupportedOperationException(); }
  public void doubleVal(int doc, double [] vals) throws IOException { throw new UnsupportedOperationException(); }

  // TODO: should we make a termVal, fills BytesRef[]?
  public void strVal(int doc, String [] vals) throws IOException { throw new UnsupportedOperationException(); }

  public Explanation explain(int doc) throws IOException {
    return Explanation.match(floatVal(doc), toString(doc));
  }

  /**
   * Yields a {@link Scorer} that matches all documents,
   * and that which produces scores equal to {@link #floatVal(int)}.
   */
  public ValueSourceScorer getScorer(Weight weight, LeafReaderContext readerContext) {
    return new ValueSourceScorer(weight, readerContext, this) {
      @Override
      public boolean matches(int doc) {
        return true;
      }
      @Override
      public float matchCost() {
        return 0f;
      }
    };
  }

  /**
   * Yields a {@link Scorer} that matches documents with values between the specified range,
   * and that which produces scores equal to {@link #floatVal(int)}.
   */
  // A RangeValueSource can't easily be a ValueSource that takes another ValueSource
  // because it needs different behavior depending on the type of fields.  There is also
  // a setup cost - parsing and normalizing params, and doing a binary search on the StringIndex.
  // TODO: change "reader" to LeafReaderContext
  public ValueSourceScorer getRangeScorer(Weight weight, LeafReaderContext readerContext, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper) throws IOException {
    float lower;
    float upper;

    if (lowerVal == null) {
      lower = Float.NEGATIVE_INFINITY;
    } else {
      lower = Float.parseFloat(lowerVal);
    }
    if (upperVal == null) {
      upper = Float.POSITIVE_INFINITY;
    } else {
      upper = Float.parseFloat(upperVal);
    }

    final float l = lower;
    final float u = upper;

    if (includeLower && includeUpper) {
      return new ValueSourceScorer(weight, readerContext, this) {
        @Override
        public boolean matches(int doc) throws IOException {
          if (!exists(doc)) return false;
          float docVal = floatVal(doc);
          return docVal >= l && docVal <= u;
        }
      };
    }
    else if (includeLower && !includeUpper) {
       return new ValueSourceScorer(weight, readerContext, this) {
        @Override
        public boolean matches(int doc) throws IOException {
          if (!exists(doc)) return false;
          float docVal = floatVal(doc);
          return docVal >= l && docVal < u;
        }
      };
    }
    else if (!includeLower && includeUpper) {
       return new ValueSourceScorer(weight, readerContext, this) {
        @Override
        public boolean matches(int doc) throws IOException {
          if (!exists(doc)) return false;
          float docVal = floatVal(doc);
          return docVal > l && docVal <= u;
        }
      };
    }
    else {
       return new ValueSourceScorer(weight, readerContext, this) {
        @Override
        public boolean matches(int doc) throws IOException {
          if (!exists(doc)) return false;
          float docVal = floatVal(doc);
          return docVal > l && docVal < u;
        }
      };
    }
  }
}