/**
 * 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.hadoop.hive.ql.io;

import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Gives the Record identifier information for the current record.
 */
public class RecordIdentifier implements WritableComparable<RecordIdentifier> {
  /**
   * This is in support of {@link org.apache.hadoop.hive.ql.metadata.VirtualColumn#ROWID}
   * Contains metadata about each field in RecordIdentifier that needs to be part of ROWID
   * which is represented as a struct {@link org.apache.hadoop.hive.ql.io.RecordIdentifier.StructInfo}.
   * Each field of RecordIdentifier which should be part of ROWID should be in this enum... which 
   * really means that it should be part of VirtualColumn (so make a subclass for rowid).
   */
  public enum Field {
    //note the enum names match field names in the struct
    transactionId(TypeInfoFactory.longTypeInfo,
      PrimitiveObjectInspectorFactory.javaLongObjectInspector),
    bucketId(TypeInfoFactory.intTypeInfo, PrimitiveObjectInspectorFactory.javaIntObjectInspector),
    rowId(TypeInfoFactory.longTypeInfo, PrimitiveObjectInspectorFactory.javaLongObjectInspector);
    public final TypeInfo fieldType;
    public final ObjectInspector fieldOI;
    Field(TypeInfo fieldType, ObjectInspector fieldOI) {
      this.fieldType = fieldType;
      this.fieldOI = fieldOI;
    }
  }
  /**
   * RecordIdentifier is passed along the operator tree as a struct.  This class contains a few
   * utilities for that.
   */
  public static final class StructInfo {
    private static final List<String> fieldNames = new ArrayList<String>(Field.values().length);
    private static final List<TypeInfo> fieldTypes = new ArrayList<TypeInfo>(fieldNames.size());
    private static final List<ObjectInspector> fieldOis = 
      new ArrayList<ObjectInspector>(fieldNames.size());
    static {
      for(Field f : Field.values()) {
        fieldNames.add(f.name());
        fieldTypes.add(f.fieldType);
        fieldOis.add(f.fieldOI);
      }
    }
    public static final TypeInfo typeInfo = 
      TypeInfoFactory.getStructTypeInfo(fieldNames, fieldTypes);
    public static final ObjectInspector oi = 
      ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOis);

    /**
     * Copies relevant fields from {@code ri} to {@code struct}
     * @param ri
     * @param struct must be of size Field.values().size()
     */
    public static void toArray(RecordIdentifier ri, Object[] struct) {
      assert struct != null && struct.length == Field.values().length;
      if(ri == null) {
        Arrays.fill(struct, null);
        return;
      }
      struct[Field.transactionId.ordinal()] = ri.getTransactionId();
      struct[Field.bucketId.ordinal()] = ri.getBucketId();
      struct[Field.rowId.ordinal()] = ri.getRowId();
    }
  }
  
  private long transactionId;
  private int bucketId;
  private long rowId;

  public RecordIdentifier() {
  }

  public RecordIdentifier(long transactionId, int bucket, long rowId) {
    this.transactionId = transactionId;
    this.bucketId = bucket;
    this.rowId = rowId;
  }

  /**
   * Set the identifier.
   * @param transactionId the transaction id
   * @param bucketId the bucket id
   * @param rowId the row id
   */
  public void setValues(long transactionId, int bucketId, long rowId) {
    this.transactionId = transactionId;
    this.bucketId = bucketId;
    this.rowId = rowId;
  }

  /**
   * Set this object to match the given object.
   * @param other the object to copy from
   */
  public void set(RecordIdentifier other) {
    this.transactionId = other.transactionId;
    this.bucketId = other.bucketId;
    this.rowId = other.rowId;
  }

  public void setRowId(long rowId) {
    this.rowId = rowId;
  }

  /**
   * What was the original transaction id for the last row?
   * @return the transaction id
   */
  public long getTransactionId() {
    return transactionId;
  }

  /**
   * What was the original bucket id for the last row?
   * @return the bucket id
   */
  public int getBucketId() {
    return bucketId;
  }

  /**
   * What was the original row id for the last row?
   * @return the row id
   */
  public long getRowId() {
    return rowId;
  }

  protected int compareToInternal(RecordIdentifier other) {
    if (other == null) {
      return -1;
    }
    if (transactionId != other.transactionId) {
      return transactionId < other.transactionId ? -1 : 1;
    }
    if (bucketId != other.bucketId) {
      return bucketId < other.bucketId ? - 1 : 1;
    }
    if (rowId != other.rowId) {
      return rowId < other.rowId ? -1 : 1;
    }
    return 0;
  }

  @Override
  public int compareTo(RecordIdentifier other) {
    if (other.getClass() != RecordIdentifier.class) {
      return -other.compareTo(this);
    }
    return compareToInternal(other);
  }

  @Override
  public void write(DataOutput dataOutput) throws IOException {
    dataOutput.writeLong(transactionId);
    dataOutput.writeInt(bucketId);
    dataOutput.writeLong(rowId);
  }

  @Override
  public void readFields(DataInput dataInput) throws IOException {
    transactionId = dataInput.readLong();
    bucketId = dataInput.readInt();
    rowId = dataInput.readLong();
  }

  @Override
  public boolean equals(Object other) {
    if(other == this) {
      return true;
    }
    if (other == null || other.getClass() != getClass()) {
      return false;
    }
    RecordIdentifier oth = (RecordIdentifier) other;
    return oth.transactionId == transactionId &&
        oth.bucketId == bucketId &&
        oth.rowId == rowId;
  }
  @Override
  public int hashCode() {
    int result = 17;
    result = 31 * result + (int)(transactionId ^ (transactionId >>> 32));
    result = 31 * result + bucketId;
    result = 31 * result + (int)(rowId ^ (rowId >>> 32));
    return result;
  }

  @Override
  public String toString() {
    return "{originalTxn: " + transactionId + ", bucket: " +
        bucketId + ", row: " + getRowId() + "}";
  }
}
