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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * Shuffles field numbers around to try to trip bugs where field numbers
 * are assumed to always be consistent across segments.
 */
public class MismatchedLeafReader extends FilterLeafReader {
  final FieldInfos shuffled;
  
  /** Creates a new reader which will renumber fields in {@code in} */
  public MismatchedLeafReader(LeafReader in, Random random) {
    super(in);
    shuffled = shuffleInfos(in.getFieldInfos(), random);
  }
  
  @Override
  public FieldInfos getFieldInfos() {
    return shuffled;
  }

  @Override
  public void document(int docID, StoredFieldVisitor visitor) throws IOException {
    in.document(docID, new MismatchedVisitor(visitor));
  }

  @Override
  public CacheHelper getCoreCacheHelper() {
    return in.getCoreCacheHelper();
  }

  @Override
  public CacheHelper getReaderCacheHelper() {
    return in.getReaderCacheHelper();
  }

  static FieldInfos shuffleInfos(FieldInfos infos, Random random) {
    // first, shuffle the order
    List<FieldInfo> shuffled = new ArrayList<>();
    for (FieldInfo info : infos) {
      shuffled.add(info);
    }
    Collections.shuffle(shuffled, random);
    
    // now renumber:
    for (int i = 0; i < shuffled.size(); i++) {
      FieldInfo oldInfo = shuffled.get(i);
      // TODO: should we introduce "gaps" too?
      FieldInfo newInfo = new FieldInfo(oldInfo.name,                // name
                                        i,                           // number
                                        oldInfo.hasVectors(),        // storeTermVector
                                        oldInfo.omitsNorms(),        // omitNorms
                                        oldInfo.hasPayloads(),       // storePayloads
                                        oldInfo.getIndexOptions(),   // indexOptions
                                        oldInfo.getDocValuesType(),  // docValuesType
                                        oldInfo.getDocValuesGen(),   // dvGen
                                        oldInfo.attributes(),        // attributes
                                        oldInfo.getPointDimensionCount(),      // data dimension count
                                        oldInfo.getPointIndexDimensionCount(),      // index dimension count
                                        oldInfo.getPointNumBytes(),  // dimension numBytes
                                        oldInfo.isSoftDeletesField()); // used as soft-deletes field
      shuffled.set(i, newInfo);
    }
    
    return new FieldInfos(shuffled.toArray(new FieldInfo[shuffled.size()]));
  }
  
  /**
   * StoredFieldsVisitor that remaps actual field numbers
   * to our new shuffled ones.
   */
  // TODO: its strange this part of our IR api exposes FieldInfo,
  // no other "user-accessible" codec apis do this?
  class MismatchedVisitor extends StoredFieldVisitor {
    final StoredFieldVisitor in;
    
    MismatchedVisitor(StoredFieldVisitor in) {
      this.in = in;
    }

    @Override
    public void binaryField(FieldInfo fieldInfo, byte[] value) throws IOException {
      in.binaryField(renumber(fieldInfo), value);
    }

    @Override
    public void stringField(FieldInfo fieldInfo, byte[] value) throws IOException {
      in.stringField(renumber(fieldInfo), value);
    }

    @Override
    public void intField(FieldInfo fieldInfo, int value) throws IOException {
      in.intField(renumber(fieldInfo), value);
    }

    @Override
    public void longField(FieldInfo fieldInfo, long value) throws IOException {
      in.longField(renumber(fieldInfo), value);
    }

    @Override
    public void floatField(FieldInfo fieldInfo, float value) throws IOException {
      in.floatField(renumber(fieldInfo), value);
    }

    @Override
    public void doubleField(FieldInfo fieldInfo, double value) throws IOException {
      in.doubleField(renumber(fieldInfo), value);
    }

    @Override
    public Status needsField(FieldInfo fieldInfo) throws IOException {
      return in.needsField(renumber(fieldInfo));
    }
    
    FieldInfo renumber(FieldInfo original) {
      FieldInfo renumbered = shuffled.fieldInfo(original.name);
      if (renumbered == null) {
        throw new AssertionError("stored fields sending bogus infos!");
      }
      return renumbered;
    }
  }
}
