/*
 * 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.backward_codecs.lucene102;

import static org.apache.lucene.backward_codecs.lucene102.Lucene102BinaryQuantizedVectorsFormat.QUERY_BITS;
import static org.apache.lucene.backward_codecs.lucene102.Lucene102BinaryQuantizedVectorsFormat.VECTOR_DATA_EXTENSION;
import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readSimilarityFunction;
import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readVectorEncoding;
import static org.apache.lucene.index.VectorSimilarityFunction.COSINE;
import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
import static org.apache.lucene.util.quantization.OptimizedScalarQuantizer.discretize;
import static org.apache.lucene.util.quantization.OptimizedScalarQuantizer.transposeHalfByte;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.codecs.KnnVectorsReader;
import org.apache.lucene.codecs.hnsw.FlatVectorsReader;
import org.apache.lucene.codecs.hnsw.FlatVectorsScorer;
import org.apache.lucene.codecs.lucene95.OrdToDocDISIReaderConfiguration;
import org.apache.lucene.index.ByteVectorValues;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.DocsWithFieldSet;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.FloatVectorValues;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.index.KnnVectorValues;
import org.apache.lucene.index.SegmentReadState;
import org.apache.lucene.index.SegmentWriteState;
import org.apache.lucene.index.VectorEncoding;
import org.apache.lucene.index.VectorSimilarityFunction;
import org.apache.lucene.search.AcceptDocs;
import org.apache.lucene.search.KnnCollector;
import org.apache.lucene.search.VectorScorer;
import org.apache.lucene.store.ChecksumIndexInput;
import org.apache.lucene.store.DataAccessHint;
import org.apache.lucene.store.FileDataHint;
import org.apache.lucene.store.FileTypeHint;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.RamUsageEstimator;
import org.apache.lucene.util.VectorUtil;
import org.apache.lucene.util.hnsw.CloseableRandomVectorScorerSupplier;
import org.apache.lucene.util.hnsw.OrdinalTranslatedKnnCollector;
import org.apache.lucene.util.hnsw.RandomVectorScorer;
import org.apache.lucene.util.hnsw.RandomVectorScorerSupplier;
import org.apache.lucene.util.quantization.BaseQuantizedByteVectorValues;
import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;
import org.apache.lucene.util.quantization.QuantizedVectorsReader;
import org.apache.lucene.util.quantization.ScalarQuantizer;

/** Reader for binary quantized vectors in the Lucene 10.2 format. */
public class Lucene102BinaryQuantizedVectorsReader extends FlatVectorsReader
    implements QuantizedVectorsReader {

  private static final long SHALLOW_SIZE =
      RamUsageEstimator.shallowSizeOfInstance(Lucene102BinaryQuantizedVectorsReader.class);

  private final Map<String, FieldEntry> fields = new HashMap<>();
  private final IndexInput quantizedVectorData;
  private final FlatVectorsReader rawVectorsReader;
  private final Lucene102BinaryFlatVectorsScorer vectorScorer;

  /**
   * Creates a new reader for binary quantized vectors.
   *
   * @param state the segment read state
   * @param rawVectorsReader the reader for the raw (non-quantized) vectors
   * @param vectorsScorer the scorer for binary quantized vectors
   * @throws IOException if an I/O error occurs
   */
  public Lucene102BinaryQuantizedVectorsReader(
      SegmentReadState state,
      FlatVectorsReader rawVectorsReader,
      Lucene102BinaryFlatVectorsScorer vectorsScorer)
      throws IOException {
    this.vectorScorer = vectorsScorer;
    this.rawVectorsReader = rawVectorsReader;
    int versionMeta = -1;
    String metaFileName =
        IndexFileNames.segmentFileName(
            state.segmentInfo.name,
            state.segmentSuffix,
            Lucene102BinaryQuantizedVectorsFormat.META_EXTENSION);
    boolean success = false;
    try (ChecksumIndexInput meta = state.directory.openChecksumInput(metaFileName)) {
      Throwable priorE = null;
      try {
        versionMeta =
            CodecUtil.checkIndexHeader(
                meta,
                Lucene102BinaryQuantizedVectorsFormat.META_CODEC_NAME,
                Lucene102BinaryQuantizedVectorsFormat.VERSION_START,
                Lucene102BinaryQuantizedVectorsFormat.VERSION_CURRENT,
                state.segmentInfo.getId(),
                state.segmentSuffix);
        readFields(meta, state.fieldInfos);
      } catch (Throwable exception) {
        priorE = exception;
      } finally {
        CodecUtil.checkFooter(meta, priorE);
      }
      quantizedVectorData =
          openDataInput(
              state,
              versionMeta,
              VECTOR_DATA_EXTENSION,
              Lucene102BinaryQuantizedVectorsFormat.VECTOR_DATA_CODEC_NAME,
              // Quantized vectors are accessed randomly from their node ID stored in the HNSW
              // graph.
              state.context.withHints(
                  FileTypeHint.DATA, FileDataHint.KNN_VECTORS, DataAccessHint.RANDOM));
      success = true;
    } finally {
      if (success == false) {
        IOUtils.closeWhileHandlingException(this);
      }
    }
  }

  private void readFields(ChecksumIndexInput meta, FieldInfos infos) throws IOException {
    for (int fieldNumber = meta.readInt(); fieldNumber != -1; fieldNumber = meta.readInt()) {
      FieldInfo info = infos.fieldInfo(fieldNumber);
      if (info == null) {
        throw new CorruptIndexException("Invalid field number: " + fieldNumber, meta);
      }
      FieldEntry fieldEntry = readField(meta, info);
      validateFieldEntry(info, fieldEntry);
      fields.put(info.name, fieldEntry);
    }
  }

  static void validateFieldEntry(FieldInfo info, FieldEntry fieldEntry) {
    int dimension = info.getVectorDimension();
    if (dimension != fieldEntry.dimension) {
      throw new IllegalStateException(
          "Inconsistent vector dimension for field=\""
              + info.name
              + "\"; "
              + dimension
              + " != "
              + fieldEntry.dimension);
    }

    int binaryDims = discretize(dimension, Long.SIZE) / Byte.SIZE;
    long numQuantizedVectorBytes =
        Math.multiplyExact((binaryDims + (Float.BYTES * 3) + Short.BYTES), (long) fieldEntry.size);
    if (numQuantizedVectorBytes != fieldEntry.vectorDataLength) {
      throw new IllegalStateException(
          "Binarized vector data length "
              + fieldEntry.vectorDataLength
              + " not matching size = "
              + fieldEntry.size
              + " * (binaryBytes="
              + binaryDims
              + " + 14"
              + ") = "
              + numQuantizedVectorBytes);
    }
  }

  @Override
  public FlatVectorsScorer getFlatVectorScorer(String field) throws IOException {
    return vectorScorer;
  }

  @Override
  public RandomVectorScorer getRandomVectorScorer(String field, float[] target) throws IOException {
    FieldEntry fi = fields.get(field);
    if (fi == null) {
      return null;
    }
    return vectorScorer.getRandomVectorScorer(
        fi.similarityFunction,
        OffHeapBinarizedVectorValues.load(
            fi.ordToDocDISIReaderConfiguration,
            fi.dimension,
            fi.size,
            new OptimizedScalarQuantizer(fi.similarityFunction),
            fi.similarityFunction,
            vectorScorer,
            fi.centroid,
            fi.centroidDP,
            fi.vectorDataOffset,
            fi.vectorDataLength,
            quantizedVectorData),
        target);
  }

  @Override
  public RandomVectorScorer getRandomVectorScorer(String field, byte[] target) throws IOException {
    return rawVectorsReader.getRandomVectorScorer(field, target);
  }

  @Override
  public void checkIntegrity() throws IOException {
    rawVectorsReader.checkIntegrity();
    CodecUtil.checksumEntireFile(quantizedVectorData);
  }

  @Override
  public BinarizedVectorValues getFloatVectorValues(String field) throws IOException {
    FieldEntry fi = fields.get(field);
    if (fi == null) {
      return null;
    }
    if (fi.vectorEncoding != VectorEncoding.FLOAT32) {
      throw new IllegalArgumentException(
          "field=\""
              + field
              + "\" is encoded as: "
              + fi.vectorEncoding
              + " expected: "
              + VectorEncoding.FLOAT32);
    }
    OffHeapBinarizedVectorValues bvv =
        OffHeapBinarizedVectorValues.load(
            fi.ordToDocDISIReaderConfiguration,
            fi.dimension,
            fi.size,
            new OptimizedScalarQuantizer(fi.similarityFunction),
            fi.similarityFunction,
            vectorScorer,
            fi.centroid,
            fi.centroidDP,
            fi.vectorDataOffset,
            fi.vectorDataLength,
            quantizedVectorData);
    return new BinarizedVectorValues(rawVectorsReader.getFloatVectorValues(field), bvv);
  }

  @Override
  public ByteVectorValues getByteVectorValues(String field) throws IOException {
    return rawVectorsReader.getByteVectorValues(field);
  }

  @Override
  public void search(String field, byte[] target, KnnCollector knnCollector, AcceptDocs acceptDocs)
      throws IOException {
    rawVectorsReader.search(field, target, knnCollector, acceptDocs);
  }

  @Override
  public void search(String field, float[] target, KnnCollector knnCollector, AcceptDocs acceptDocs)
      throws IOException {
    if (knnCollector.k() == 0) return;
    final RandomVectorScorer scorer = getRandomVectorScorer(field, target);
    if (scorer == null) return;
    OrdinalTranslatedKnnCollector collector =
        new OrdinalTranslatedKnnCollector(knnCollector, scorer::ordToDoc);
    Bits acceptedOrds = scorer.getAcceptOrds(acceptDocs.bits());
    for (int i = 0; i < scorer.maxOrd(); i++) {
      if (acceptedOrds == null || acceptedOrds.get(i)) {
        collector.collect(i, scorer.score(i));
        collector.incVisitedCount(1);
      }
    }
  }

  @Override
  public void close() throws IOException {
    IOUtils.close(quantizedVectorData, rawVectorsReader);
  }

  @Override
  public long ramBytesUsed() {
    long size = SHALLOW_SIZE;
    size +=
        RamUsageEstimator.sizeOfMap(
            fields, RamUsageEstimator.shallowSizeOfInstance(FieldEntry.class));
    size += rawVectorsReader.ramBytesUsed();
    return size;
  }

  @Override
  public Map<String, Long> getOffHeapByteSize(FieldInfo fieldInfo) {
    Objects.requireNonNull(fieldInfo);
    var raw = rawVectorsReader.getOffHeapByteSize(fieldInfo);
    var fieldEntry = fields.get(fieldInfo.name);
    if (fieldEntry == null) {
      assert fieldInfo.getVectorEncoding() == VectorEncoding.BYTE;
      return raw;
    }
    var quant = Map.of(VECTOR_DATA_EXTENSION, fieldEntry.vectorDataLength());
    return KnnVectorsReader.mergeOffHeapByteSizeMaps(raw, quant);
  }

  float[] getCentroid(String field) {
    FieldEntry fieldEntry = fields.get(field);
    if (fieldEntry != null) {
      return fieldEntry.centroid;
    }
    return null;
  }

  private static IndexInput openDataInput(
      SegmentReadState state,
      int versionMeta,
      String fileExtension,
      String codecName,
      IOContext context)
      throws IOException {
    String fileName =
        IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, fileExtension);
    IndexInput in = state.directory.openInput(fileName, context);
    boolean success = false;
    try {
      int versionVectorData =
          CodecUtil.checkIndexHeader(
              in,
              codecName,
              Lucene102BinaryQuantizedVectorsFormat.VERSION_START,
              Lucene102BinaryQuantizedVectorsFormat.VERSION_CURRENT,
              state.segmentInfo.getId(),
              state.segmentSuffix);
      if (versionMeta != versionVectorData) {
        throw new CorruptIndexException(
            "Format versions mismatch: meta="
                + versionMeta
                + ", "
                + codecName
                + "="
                + versionVectorData,
            in);
      }
      CodecUtil.retrieveChecksum(in);
      success = true;
      return in;
    } finally {
      if (success == false) {
        IOUtils.closeWhileHandlingException(in);
      }
    }
  }

  private FieldEntry readField(IndexInput input, FieldInfo info) throws IOException {
    VectorEncoding vectorEncoding = readVectorEncoding(input);
    VectorSimilarityFunction similarityFunction = readSimilarityFunction(input);
    if (similarityFunction != info.getVectorSimilarityFunction()) {
      throw new IllegalStateException(
          "Inconsistent vector similarity function for field=\""
              + info.name
              + "\"; "
              + similarityFunction
              + " != "
              + info.getVectorSimilarityFunction());
    }
    return FieldEntry.create(input, vectorEncoding, info.getVectorSimilarityFunction());
  }

  @Override
  public BaseQuantizedByteVectorValues getQuantizedVectorValues(String fieldName) {
    return null;
  }

  @Override
  public ScalarQuantizer getQuantizationState(String fieldName) {
    return null;
  }

  @Override
  public CloseableRandomVectorScorerSupplier getRandomVectorScorerSupplierForMerge(
      FieldInfo fieldInfo, SegmentWriteState segmentWriteState) throws IOException {
    assert fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32);
    OptimizedScalarQuantizer quantizer =
        new OptimizedScalarQuantizer(fieldInfo.getVectorSimilarityFunction());
    String tempScoreQuantizedVectorName = null;
    float[] centroid = getCentroid(fieldInfo.name);
    BinarizedVectorValues vectorValues = getFloatVectorValues(fieldInfo.name);
    FloatVectorValues floatVectorValues = vectorValues.rawVectorValues;
    if (fieldInfo.getVectorSimilarityFunction() == COSINE) {
      floatVectorValues = new NormalizedFloatVectorValues(floatVectorValues);
    }

    DocsWithFieldSet docsWithField;
    try (IndexOutput tempScoreQuantizedVector =
        segmentWriteState.directory.createTempOutput(
            segmentWriteState.segmentInfo.name, "queries", segmentWriteState.context)) {
      tempScoreQuantizedVectorName = tempScoreQuantizedVector.getName();
      docsWithField =
          writeBinarizedQueryData(tempScoreQuantizedVector, floatVectorValues, centroid, quantizer);
      CodecUtil.writeFooter(tempScoreQuantizedVector);
    } catch (Throwable t) {
      if (tempScoreQuantizedVectorName != null) {
        IOUtils.deleteFilesIgnoringExceptions(
            segmentWriteState.directory, tempScoreQuantizedVectorName);
      }
      throw t;
    }
    IndexInput quantizedScoreDataInput =
        segmentWriteState.directory.openInput(
            tempScoreQuantizedVectorName, segmentWriteState.context);
    try {
      RandomVectorScorerSupplier scorerSupplier =
          vectorScorer.getRandomVectorScorerSupplier(
              fieldInfo.getVectorSimilarityFunction(),
              new OffHeapBinarizedQueryVectorValues(
                  quantizedScoreDataInput,
                  fieldInfo.getVectorDimension(),
                  docsWithField.cardinality()),
              vectorValues.quantizedVectorValues);
      final String finalTempScoreQuantizedVectorName = tempScoreQuantizedVectorName;
      return CloseableRandomVectorScorerSupplier.create(
          scorerSupplier,
          vectorValues.size(),
          () -> {
            IOUtils.close(quantizedScoreDataInput);
            IOUtils.deleteFilesIgnoringExceptions(
                segmentWriteState.directory, finalTempScoreQuantizedVectorName);
          });
    } catch (Throwable t) {
      IOUtils.closeWhileSuppressingExceptions(t, quantizedScoreDataInput);
      IOUtils.deleteFilesIgnoringExceptions(
          segmentWriteState.directory, tempScoreQuantizedVectorName);
      throw t;
    }
  }

  static DocsWithFieldSet writeBinarizedQueryData(
      IndexOutput binarizedQueryData,
      FloatVectorValues floatVectorValues,
      float[] centroid,
      OptimizedScalarQuantizer binaryQuantizer)
      throws IOException {
    int discretizedDimension = discretize(floatVectorValues.dimension(), 64);
    DocsWithFieldSet docsWithField = new DocsWithFieldSet();
    byte[] quantizationScratch = new byte[floatVectorValues.dimension()];
    byte[] toQuery = new byte[(discretizedDimension / 8) * QUERY_BITS];
    KnnVectorValues.DocIndexIterator iterator = floatVectorValues.iterator();
    for (int docV = iterator.nextDoc(); docV != NO_MORE_DOCS; docV = iterator.nextDoc()) {
      // write index vector
      OptimizedScalarQuantizer.QuantizationResult r =
          binaryQuantizer.scalarQuantize(
              floatVectorValues.vectorValue(iterator.index()),
              quantizationScratch,
              QUERY_BITS,
              centroid);
      docsWithField.add(docV);

      // pack and store the 4bit query vector
      transposeHalfByte(quantizationScratch, toQuery);
      binarizedQueryData.writeBytes(toQuery, toQuery.length);
      binarizedQueryData.writeInt(Float.floatToIntBits(r.lowerInterval()));
      binarizedQueryData.writeInt(Float.floatToIntBits(r.upperInterval()));
      binarizedQueryData.writeInt(Float.floatToIntBits(r.additionalCorrection()));
      assert r.quantizedComponentSum() >= 0 && r.quantizedComponentSum() <= 0xffff;
      binarizedQueryData.writeShort((short) r.quantizedComponentSum());
    }
    return docsWithField;
  }

  private record FieldEntry(
      VectorSimilarityFunction similarityFunction,
      VectorEncoding vectorEncoding,
      int dimension,
      int descritizedDimension,
      long vectorDataOffset,
      long vectorDataLength,
      int size,
      float[] centroid,
      float centroidDP,
      OrdToDocDISIReaderConfiguration ordToDocDISIReaderConfiguration) {

    static FieldEntry create(
        IndexInput input,
        VectorEncoding vectorEncoding,
        VectorSimilarityFunction similarityFunction)
        throws IOException {
      int dimension = input.readVInt();
      long vectorDataOffset = input.readVLong();
      long vectorDataLength = input.readVLong();
      int size = input.readVInt();
      final float[] centroid;
      float centroidDP = 0;
      if (size > 0) {
        centroid = new float[dimension];
        input.readFloats(centroid, 0, dimension);
        centroidDP = Float.intBitsToFloat(input.readInt());
      } else {
        centroid = null;
      }
      OrdToDocDISIReaderConfiguration conf =
          OrdToDocDISIReaderConfiguration.fromStoredMeta(input, size);
      return new FieldEntry(
          similarityFunction,
          vectorEncoding,
          dimension,
          discretize(dimension, 64),
          vectorDataOffset,
          vectorDataLength,
          size,
          centroid,
          centroidDP,
          conf);
    }
  }

  /** Binarized vector values holding row and quantized vector values */
  protected static final class BinarizedVectorValues extends FloatVectorValues {
    private final FloatVectorValues rawVectorValues;
    private final BinarizedByteVectorValues quantizedVectorValues;

    BinarizedVectorValues(
        FloatVectorValues rawVectorValues, BinarizedByteVectorValues quantizedVectorValues) {
      this.rawVectorValues = rawVectorValues;
      this.quantizedVectorValues = quantizedVectorValues;
    }

    @Override
    public int dimension() {
      return rawVectorValues.dimension();
    }

    @Override
    public int size() {
      return rawVectorValues.size();
    }

    @Override
    public float[] vectorValue(int ord) throws IOException {
      return rawVectorValues.vectorValue(ord);
    }

    @Override
    public BinarizedVectorValues copy() throws IOException {
      return new BinarizedVectorValues(rawVectorValues.copy(), quantizedVectorValues.copy());
    }

    @Override
    public Bits getAcceptOrds(Bits acceptDocs) {
      return rawVectorValues.getAcceptOrds(acceptDocs);
    }

    @Override
    public int ordToDoc(int ord) {
      return rawVectorValues.ordToDoc(ord);
    }

    @Override
    public DocIndexIterator iterator() {
      return rawVectorValues.iterator();
    }

    @Override
    public VectorScorer scorer(float[] query) throws IOException {
      return quantizedVectorValues.scorer(query);
    }

    @Override
    public VectorScorer rescorer(float[] query) throws IOException {
      return rawVectorValues.rescorer(query);
    }

    BinarizedByteVectorValues getQuantizedVectorValues() throws IOException {
      return quantizedVectorValues;
    }
  }

  // When accessing vectorValue method, targetOrd here means a row ordinal.
  static class OffHeapBinarizedQueryVectorValues {
    private final IndexInput slice;
    private final int dimension;
    private final int size;
    protected final byte[] binaryValue;
    protected final ByteBuffer byteBuffer;
    private final int byteSize;
    protected final float[] correctiveValues;
    private int lastOrd = -1;
    private int quantizedComponentSum;

    OffHeapBinarizedQueryVectorValues(IndexInput data, int dimension, int size) {
      this.slice = data;
      this.dimension = dimension;
      this.size = size;
      // 4x the quantized binary dimensions
      int binaryDimensions = (discretize(dimension, Long.SIZE) / Byte.SIZE) * QUERY_BITS;
      this.byteBuffer = ByteBuffer.allocate(binaryDimensions);
      this.binaryValue = byteBuffer.array();
      // + 1 for the quantized sum
      this.correctiveValues = new float[3];
      this.byteSize = binaryDimensions + Float.BYTES * 3 + Short.BYTES;
    }

    public OptimizedScalarQuantizer.QuantizationResult getCorrectiveTerms(int targetOrd)
        throws IOException {
      if (lastOrd == targetOrd) {
        return new OptimizedScalarQuantizer.QuantizationResult(
            correctiveValues[0], correctiveValues[1], correctiveValues[2], quantizedComponentSum);
      }
      vectorValue(targetOrd);
      return new OptimizedScalarQuantizer.QuantizationResult(
          correctiveValues[0], correctiveValues[1], correctiveValues[2], quantizedComponentSum);
    }

    public int size() {
      return size;
    }

    public int quantizedLength() {
      return binaryValue.length;
    }

    public int dimension() {
      return dimension;
    }

    public OffHeapBinarizedQueryVectorValues copy() throws IOException {
      return new OffHeapBinarizedQueryVectorValues(slice.clone(), dimension, size);
    }

    public IndexInput getSlice() {
      return slice;
    }

    public byte[] vectorValue(int targetOrd) throws IOException {
      if (lastOrd == targetOrd) {
        return binaryValue;
      }
      slice.seek((long) targetOrd * byteSize);
      slice.readBytes(binaryValue, 0, binaryValue.length);
      slice.readFloats(correctiveValues, 0, 3);
      quantizedComponentSum = Short.toUnsignedInt(slice.readShort());
      lastOrd = targetOrd;
      return binaryValue;
    }
  }

  static final class NormalizedFloatVectorValues extends FloatVectorValues {
    private final FloatVectorValues values;
    private final float[] normalizedVector;

    NormalizedFloatVectorValues(FloatVectorValues values) {
      this.values = values;
      this.normalizedVector = new float[values.dimension()];
    }

    @Override
    public int dimension() {
      return values.dimension();
    }

    @Override
    public int size() {
      return values.size();
    }

    @Override
    public int ordToDoc(int ord) {
      return values.ordToDoc(ord);
    }

    @Override
    public float[] vectorValue(int ord) throws IOException {
      System.arraycopy(values.vectorValue(ord), 0, normalizedVector, 0, normalizedVector.length);
      VectorUtil.l2normalize(normalizedVector);
      return normalizedVector;
    }

    @Override
    public DocIndexIterator iterator() {
      return values.iterator();
    }

    @Override
    public NormalizedFloatVectorValues copy() throws IOException {
      return new NormalizedFloatVectorValues(values.copy());
    }
  }
}
