/*
 * 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.accumulo.tserver.logger;

import static org.apache.accumulo.tserver.logger.LogEvents.DEFINE_TABLET;
import static org.apache.accumulo.tserver.logger.LogEvents.MANY_MUTATIONS;
import static org.apache.accumulo.tserver.logger.LogEvents.MUTATION;
import static org.apache.accumulo.tserver.logger.LogEvents.OPEN;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.accumulo.core.data.impl.KeyExtent;
import org.apache.hadoop.io.WritableComparable;

public class LogFileKey implements WritableComparable<LogFileKey> {

  public LogEvents event;
  public String filename = null;
  public KeyExtent tablet = null;
  public long seq = -1;
  public int tid = -1;
  public static final int VERSION = 2;
  public String tserverSession;

  @Override
  public void readFields(DataInput in) throws IOException {
    int value = in.readByte();
    if (value >= LogEvents.values().length) {
      throw new IOException("Invalid LogEvent type, got ordinal " + value + ", but only know about " + LogEvents.values().length + " possible types.");
    }
    event = LogEvents.values()[value];
    switch (event) {
      case OPEN:
        tid = in.readInt();
        tserverSession = in.readUTF();
        if (tid != VERSION) {
          throw new RuntimeException(String.format("Bad version number for log file: expected %d, but saw %d", VERSION, tid));
        }
        break;
      case COMPACTION_FINISH:
        seq = in.readLong();
        tid = in.readInt();
        break;
      case COMPACTION_START:
        seq = in.readLong();
        tid = in.readInt();
        filename = in.readUTF();
        break;
      case DEFINE_TABLET:
        seq = in.readLong();
        tid = in.readInt();
        tablet = new KeyExtent();
        tablet.readFields(in);
        break;
      case MANY_MUTATIONS:
        seq = in.readLong();
        tid = in.readInt();
        break;
      case MUTATION:
        seq = in.readLong();
        tid = in.readInt();
        break;
      default:
        throw new RuntimeException("Unknown log event type: " + event);
    }

  }

  @Override
  public void write(DataOutput out) throws IOException {
    out.writeByte(event.ordinal());
    switch (event) {
      case OPEN:
        seq = -1;
        tid = -1;
        out.writeInt(VERSION);
        out.writeUTF(tserverSession);
        // out.writeUTF(Accumulo.getInstanceID());
        break;
      case COMPACTION_FINISH:
        out.writeLong(seq);
        out.writeInt(tid);
        break;
      case COMPACTION_START:
        out.writeLong(seq);
        out.writeInt(tid);
        out.writeUTF(filename);
        break;
      case DEFINE_TABLET:
        out.writeLong(seq);
        out.writeInt(tid);
        tablet.write(out);
        break;
      case MANY_MUTATIONS:
        out.writeLong(seq);
        out.writeInt(tid);
        break;
      case MUTATION:
        out.writeLong(seq);
        out.writeInt(tid);
        break;
      default:
        throw new IllegalArgumentException("Bad value for LogFileEntry type");
    }
  }

  static int eventType(LogEvents event) {
    // Order logs by START, TABLET_DEFINITIONS, COMPACTIONS and then MUTATIONS
    if (event == MUTATION || event == MANY_MUTATIONS) {
      return 3;
    }
    if (event == DEFINE_TABLET) {
      return 1;
    }
    if (event == OPEN) {
      return 0;
    }
    return 2;
  }

  private static int sign(long l) {
    if (l < 0)
      return -1;
    if (l > 0)
      return 1;
    return 0;
  }

  @Override
  public int compareTo(LogFileKey o) {
    if (eventType(this.event) != eventType(o.event)) {
      return eventType(this.event) - eventType(o.event);
    }
    if (this.event == OPEN)
      return 0;
    if (this.tid != o.tid) {
      return this.tid - o.tid;
    }
    return sign(this.seq - o.seq);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof LogFileKey) {
      return this.compareTo((LogFileKey) obj) == 0;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return (int) seq;
  }

  @Override
  public String toString() {
    switch (event) {
      case OPEN:
        return String.format("OPEN %s", tserverSession);
      case COMPACTION_FINISH:
        return String.format("COMPACTION_FINISH %d %d", tid, seq);
      case COMPACTION_START:
        return String.format("COMPACTION_START %d %d %s", tid, seq, filename);
      case MUTATION:
        return String.format("MUTATION %d %d", tid, seq);
      case MANY_MUTATIONS:
        return String.format("MANY_MUTATIONS %d %d", tid, seq);
      case DEFINE_TABLET:
        return String.format("DEFINE_TABLET %d %d %s", tid, seq, tablet);
    }
    throw new RuntimeException("Unknown type of entry: " + event);
  }
}
