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

import java.io.IOException;
import java.util.Set;
import org.apache.lucene.backward_codecs.store.EndiannessReverserUtil;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.index.SegmentInfo;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedNumericSelector;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.search.SortedSetSortField;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.Version;

/** Writable version of Lucene70SegmentInfoFormat for testing */
public class Lucene70RWSegmentInfoFormat extends Lucene70SegmentInfoFormat {

  @Override
  public void write(Directory dir, SegmentInfo si, IOContext ioContext) throws IOException {
    final String fileName =
        IndexFileNames.segmentFileName(si.name, "", Lucene70SegmentInfoFormat.SI_EXTENSION);

    try (IndexOutput output = EndiannessReverserUtil.createOutput(dir, fileName, ioContext)) {
      // Only add the file once we've successfully created it, else IFD assert can trip:
      si.addFile(fileName);
      CodecUtil.writeIndexHeader(
          output,
          Lucene70SegmentInfoFormat.CODEC_NAME,
          Lucene70SegmentInfoFormat.VERSION_CURRENT,
          si.getId(),
          "");

      writeSegmentInfo(output, si);

      CodecUtil.writeFooter(output);
    }
  }

  private void writeSegmentInfo(IndexOutput output, SegmentInfo si) throws IOException {
    Version version = si.getVersion();
    if (version.major < 7) {
      throw new IllegalArgumentException(
          "invalid major version: should be >= 7 but got: " + version.major + " segment=" + si);
    }
    // Write the Lucene version that created this segment, since 3.1
    output.writeInt(version.major);
    output.writeInt(version.minor);
    output.writeInt(version.bugfix);

    // Write the min Lucene version that contributed docs to the segment, since 7.0
    if (si.getMinVersion() != null) {
      output.writeByte((byte) 1);
      Version minVersion = si.getMinVersion();
      output.writeInt(minVersion.major);
      output.writeInt(minVersion.minor);
      output.writeInt(minVersion.bugfix);
    } else {
      output.writeByte((byte) 0);
    }

    assert version.prerelease == 0;
    output.writeInt(si.maxDoc());

    output.writeByte((byte) (si.getUseCompoundFile() ? SegmentInfo.YES : SegmentInfo.NO));
    output.writeMapOfStrings(si.getDiagnostics());
    Set<String> files = si.files();
    for (String file : files) {
      if (!IndexFileNames.parseSegmentName(file).equals(si.name)) {
        throw new IllegalArgumentException(
            "invalid files: expected segment=" + si.name + ", got=" + files);
      }
    }
    output.writeSetOfStrings(files);
    output.writeMapOfStrings(si.getAttributes());

    Sort indexSort = si.getIndexSort();
    int numSortFields = indexSort == null ? 0 : indexSort.getSort().length;
    output.writeVInt(numSortFields);
    for (int i = 0; i < numSortFields; ++i) {
      SortField sortField = indexSort.getSort()[i];
      SortField.Type sortType = sortField.getType();
      output.writeString(sortField.getField());
      int sortTypeID;
      switch (sortField.getType()) {
        case STRING:
          sortTypeID = 0;
          break;
        case LONG:
          sortTypeID = 1;
          break;
        case INT:
          sortTypeID = 2;
          break;
        case DOUBLE:
          sortTypeID = 3;
          break;
        case FLOAT:
          sortTypeID = 4;
          break;
        case CUSTOM:
          if (sortField instanceof SortedSetSortField) {
            sortTypeID = 5;
            sortType = SortField.Type.STRING;
          } else if (sortField instanceof SortedNumericSortField) {
            sortTypeID = 6;
            sortType = ((SortedNumericSortField) sortField).getNumericType();
          } else {
            throw new IllegalStateException("Unexpected SortedNumericSortField " + sortField);
          }
          break;
        case DOC:
        case REWRITEABLE:
        case STRING_VAL:
        case SCORE:
        default:
          throw new IllegalStateException("Unexpected sort type: " + sortField.getType());
      }
      output.writeVInt(sortTypeID);
      if (sortTypeID == 5) {
        SortedSetSortField ssf = (SortedSetSortField) sortField;
        if (ssf.getSelector() == SortedSetSelector.Type.MIN) {
          output.writeByte((byte) 0);
        } else if (ssf.getSelector() == SortedSetSelector.Type.MAX) {
          output.writeByte((byte) 1);
        } else if (ssf.getSelector() == SortedSetSelector.Type.MIDDLE_MIN) {
          output.writeByte((byte) 2);
        } else if (ssf.getSelector() == SortedSetSelector.Type.MIDDLE_MAX) {
          output.writeByte((byte) 3);
        } else {
          throw new IllegalStateException(
              "Unexpected SortedSetSelector type: " + ssf.getSelector());
        }
      } else if (sortTypeID == 6) {
        SortedNumericSortField snsf = (SortedNumericSortField) sortField;
        if (snsf.getNumericType() == SortField.Type.LONG) {
          output.writeByte((byte) 0);
        } else if (snsf.getNumericType() == SortField.Type.INT) {
          output.writeByte((byte) 1);
        } else if (snsf.getNumericType() == SortField.Type.DOUBLE) {
          output.writeByte((byte) 2);
        } else if (snsf.getNumericType() == SortField.Type.FLOAT) {
          output.writeByte((byte) 3);
        } else {
          throw new IllegalStateException(
              "Unexpected SortedNumericSelector type: " + snsf.getNumericType());
        }
        if (snsf.getSelector() == SortedNumericSelector.Type.MIN) {
          output.writeByte((byte) 0);
        } else if (snsf.getSelector() == SortedNumericSelector.Type.MAX) {
          output.writeByte((byte) 1);
        } else {
          throw new IllegalStateException(
              "Unexpected sorted numeric selector type: " + snsf.getSelector());
        }
      }
      output.writeByte((byte) (sortField.getReverse() ? 0 : 1));

      // write missing value
      Object missingValue = sortField.getMissingValue();
      if (missingValue == null) {
        output.writeByte((byte) 0);
      } else {
        switch (sortType) {
          case STRING:
            if (missingValue == SortField.STRING_LAST) {
              output.writeByte((byte) 1);
            } else if (missingValue == SortField.STRING_FIRST) {
              output.writeByte((byte) 2);
            } else {
              throw new AssertionError(
                  "unrecognized missing value for STRING field \""
                      + sortField.getField()
                      + "\": "
                      + missingValue);
            }
            break;
          case LONG:
            output.writeByte((byte) 1);
            output.writeLong(((Long) missingValue).longValue());
            break;
          case INT:
            output.writeByte((byte) 1);
            output.writeInt(((Integer) missingValue).intValue());
            break;
          case DOUBLE:
            output.writeByte((byte) 1);
            output.writeLong(Double.doubleToLongBits(((Double) missingValue).doubleValue()));
            break;
          case FLOAT:
            output.writeByte((byte) 1);
            output.writeInt(Float.floatToIntBits(((Float) missingValue).floatValue()));
            break;
          case CUSTOM:
          case DOC:
          case REWRITEABLE:
          case STRING_VAL:
          case SCORE:
          default:
            throw new IllegalStateException("Unexpected sort type: " + sortField.getType());
        }
      }
    }
  }
}
