/*
 * 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.codecs.uniformsplit.sharedterms;

import java.io.IOException;
import java.util.List;
import java.util.RandomAccess;
import org.apache.lucene.index.ImpactsEnum;
import org.apache.lucene.index.MergeState;
import org.apache.lucene.index.PostingsEnum;
import org.apache.lucene.index.TermState;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.util.AttributeSource;
import org.apache.lucene.util.BytesRef;

/**
 * Combines {@link PostingsEnum} for the same term for a given field from multiple segments. It is
 * used during segment merging.
 *
 * @lucene.experimental
 */
class STMergingTermsEnum extends TermsEnum {

  protected final String fieldName;
  protected final MultiSegmentsPostingsEnum multiPostingsEnum;
  protected BytesRef term;

  /** Constructs a {@link STMergingTermsEnum} for a given field. */
  protected STMergingTermsEnum(String fieldName, int numSegments) {
    this.fieldName = fieldName;
    multiPostingsEnum = new MultiSegmentsPostingsEnum(numSegments);
  }

  /**
   * Resets this {@link STMergingTermsEnum} with a new term and its list of {@link
   * STUniformSplitTermsWriter.SegmentPostings} to combine.
   *
   * @param segmentPostings List sorted by segment index.
   */
  protected void reset(
      BytesRef term, List<STUniformSplitTermsWriter.SegmentPostings> segmentPostings) {
    this.term = term;
    multiPostingsEnum.reset(segmentPostings);
  }

  @Override
  public AttributeSource attributes() {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean seekExact(BytesRef text) throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public SeekStatus seekCeil(BytesRef text) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void seekExact(long ord) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void seekExact(BytesRef term, TermState state) throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public BytesRef term() {
    return term;
  }

  @Override
  public long ord() {
    throw new UnsupportedOperationException();
  }

  @Override
  public int docFreq() {
    throw new UnsupportedOperationException();
  }

  @Override
  public long totalTermFreq() {
    throw new UnsupportedOperationException();
  }

  @Override
  public PostingsEnum postings(PostingsEnum reuse, int flags) {
    multiPostingsEnum.setPostingFlags(flags);
    return multiPostingsEnum;
  }

  @Override
  public ImpactsEnum impacts(int flags) {
    throw new UnsupportedOperationException();
  }

  @Override
  public TermState termState() {
    throw new UnsupportedOperationException();
  }

  @Override
  public BytesRef next() {
    throw new UnsupportedOperationException();
  }

  /**
   * Combines multiple segments {@link PostingsEnum} as a single {@link PostingsEnum}, for one field
   * and one term.
   *
   * <p>This {@link PostingsEnum} does not extend {@link
   * org.apache.lucene.index.FilterLeafReader.FilterPostingsEnum} because it updates the delegate
   * for each segment.
   */
  protected class MultiSegmentsPostingsEnum extends PostingsEnum {

    protected final PostingsEnum[] reusablePostingsEnums;
    protected List<STUniformSplitTermsWriter.SegmentPostings> segmentPostingsList;
    protected int segmentIndex;
    protected PostingsEnum postingsEnum;
    protected boolean postingsEnumExhausted;
    protected MergeState.DocMap docMap;
    protected int docId;
    protected int postingsFlags;

    protected MultiSegmentsPostingsEnum(int numSegments) {
      reusablePostingsEnums = new PostingsEnum[numSegments];
    }

    /**
     * Resets/reuse this {@link PostingsEnum}.
     *
     * @param segmentPostingsList List of segment postings ordered by segment index.
     */
    protected void reset(List<STUniformSplitTermsWriter.SegmentPostings> segmentPostingsList) {
      assert segmentPostingsList instanceof RandomAccess
          : "We optimize by accessing the list elements instead of creating an Iterator";
      this.segmentPostingsList = segmentPostingsList;
      segmentIndex = -1;
      postingsEnumExhausted = true;
      docId = -1;
    }

    protected void setPostingFlags(int flags) {
      this.postingsFlags = flags;
    }

    @Override
    public int freq() throws IOException {
      return postingsEnum.freq();
    }

    @Override
    public int nextPosition() throws IOException {
      return postingsEnum.nextPosition();
    }

    @Override
    public int startOffset() throws IOException {
      return postingsEnum.startOffset();
    }

    @Override
    public int endOffset() throws IOException {
      return postingsEnum.endOffset();
    }

    @Override
    public BytesRef getPayload() throws IOException {
      return postingsEnum.getPayload();
    }

    @Override
    public int docID() {
      return docId;
    }

    @Override
    public int nextDoc() throws IOException {
      assert segmentPostingsList != null : "reset not called";
      while (true) {
        if (postingsEnumExhausted) {
          if (segmentIndex == segmentPostingsList.size() - 1) {
            return docId = NO_MORE_DOCS;
          } else {
            segmentIndex++;
            STUniformSplitTermsWriter.SegmentPostings segmentPostings =
                segmentPostingsList.get(segmentIndex);
            postingsEnum = getPostings(segmentPostings);
            postingsEnumExhausted = false;
            docMap = segmentPostings.docMap;
          }
        }
        int docId = postingsEnum.nextDoc();
        if (docId == NO_MORE_DOCS) {
          postingsEnumExhausted = true;
        } else {
          docId = docMap.get(docId);
          if (docId != -1) {
            assert docId > this.docId : "next docId=" + docId + ", current docId=" + this.docId;
            return this.docId = docId;
          }
        }
      }
    }

    protected PostingsEnum getPostings(STUniformSplitTermsWriter.SegmentPostings segmentPostings)
        throws IOException {
      // The field is present in the segment because it is one of the segments provided in the
      // reset() method.
      return reusablePostingsEnums[segmentPostings.segmentIndex] =
          segmentPostings.getPostings(
              fieldName, reusablePostingsEnums[segmentPostings.segmentIndex], postingsFlags);
    }

    @Override
    public int advance(int target) {
      throw new UnsupportedOperationException();
    }

    @Override
    public long cost() {
      return 0; // Cost is not useful here.
    }
  }
}
