/*
 * 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.geode.internal.cache;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.nio.ByteBuffer;

import org.apache.geode.annotations.Immutable;
import org.apache.geode.internal.serialization.DSCODE;
import org.apache.geode.internal.serialization.DataSerializableFixedID;
import org.apache.geode.internal.serialization.DeserializationContext;
import org.apache.geode.internal.serialization.KnownVersion;
import org.apache.geode.internal.serialization.SerializationContext;

/**
 * Internal tokens used as region values. These tokens are never seen from the public API.
 *
 * <p>
 * These classes are Serializable and implement readResolve to support canonicalization in the face
 * of copysharing.
 */
@Immutable
public abstract class Token {

  @Immutable
  public static final Invalid INVALID = new Invalid();
  @Immutable
  public static final LocalInvalid LOCAL_INVALID = new LocalInvalid();

  // DESTROYED token, used during getInitialImage only
  // and only for identity comparisons
  @Immutable
  public static final Destroyed DESTROYED = new Destroyed();

  /**
   * Tombstone is used to hold onto destroyed entries in regions supporting entry versioning.
   * DESTROYED and REMOVED_PHASE2 tokens transition to TOMBSTONE during GII and normal entry
   * removal.
   */
  @Immutable
  public static final Tombstone TOMBSTONE = new Tombstone();

  /**
   * Used when a RegionEntry is removed from a RegionMap. In phase 1 of removal, the destroy()
   * operation is applied to the entry under synchronization. The lock is then released while
   * distribution is done. During this period, the entry may be modified, preventing the entry from
   * being removed from its map.
   */
  @Immutable
  public static final Removed REMOVED_PHASE1 = new Removed();

  /**
   * Used when a RegionEntry is removed from a RegionMap. In phase 2 of removal, callbacks for the
   * entry are invoked under synchronization and the entry is then removed from the map. If an entry
   * is seen in this state, you should wait in a loop for the entry to be removed from the map.
   */
  @Immutable
  public static final Removed2 REMOVED_PHASE2 = new Removed2();

  /**
   * Used to designate end of stream in StreamingOperation
   */
  @Immutable
  public static final EndOfStream END_OF_STREAM = new EndOfStream();

  /**
   * Indicates that a decision was made to not provide some information that is normally available.
   */
  @Immutable
  public static final NotAvailable NOT_AVAILABLE = new NotAvailable();

  // !!! NOTICE !!!
  // If you add a new Token to this class then add
  // support in OffHeapRegionEntryHelper to encode that
  // token as an address.
  // See OffHeapRegionEntryHelper.objectToAddress.

  /**
   * A token used to represent a value that is not a token.
   */
  @Immutable
  public static final NotAToken NOT_A_TOKEN = new NotAToken();

  /**
   * Returns true if o is INVALID, LOCAL_INVALID, DESTROYED, or REMOVED.
   */
  public static boolean isInvalidOrRemoved(Object o) {
    return isInvalid(o) || isRemoved(o);
  }

  public static boolean isInvalid(Object o) {
    return o == INVALID || o == LOCAL_INVALID;
  }

  public static boolean isRemoved(Object o) {
    return o == DESTROYED || o == REMOVED_PHASE1 || o == REMOVED_PHASE2 || o == TOMBSTONE;
  }

  public static boolean isRemovedFromDisk(Object o) {
    return o == DESTROYED || o == REMOVED_PHASE1 || o == REMOVED_PHASE2;
  }

  public static boolean isDestroyed(Object o) {
    return o == DESTROYED;
  }

  public static boolean isToken(Object o) {
    return o instanceof Token;
  }

  /**
   * Singleton token indicating an Invalid Entry.
   */
  public static class Invalid extends Token implements DataSerializableFixedID, Serializable {
    private static final long serialVersionUID = -4133205114649525169L;

    protected Invalid() {}

    @Override
    public String toString() {
      return "INVALID";
    }

    private Object readResolve() throws ObjectStreamException {
      return INVALID;
    }

    @Override
    public int getDSFID() {
      return TOKEN_INVALID;
    }

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    public boolean isSerializedValue(byte[] value) {
      ByteBuffer buf = ByteBuffer.wrap(value);
      return buf.capacity() == 3 && buf.get() == DSCODE.DS_FIXED_ID_SHORT.toByte()
          && buf.getShort() == getDSFID();
    }

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

  public static class LocalInvalid extends Invalid {
    private static final long serialVersionUID = 4110159168041217249L;

    protected LocalInvalid() {}

    @Override
    public String toString() {
      return "LOCAL_INVALID";
    }

    private Object readResolve() throws ObjectStreamException {
      return LOCAL_INVALID;
    }

    @Override
    public int getDSFID() {
      return TOKEN_LOCAL_INVALID;
    }
  }

  public static class Destroyed extends Token implements DataSerializableFixedID, Serializable {
    private static final long serialVersionUID = -1922513819482668368L;

    protected Destroyed() {}

    @Override
    public String toString() {
      return "DESTROYED";
    }

    private Object readResolve() throws ObjectStreamException {
      return DESTROYED;
    }

    @Override
    public int getDSFID() {
      return TOKEN_DESTROYED;
    }

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

  public static class Tombstone extends Token implements DataSerializableFixedID, Serializable {
    static final long serialVersionUID = -6388232623019450170L;

    protected Tombstone() {}

    @Override
    public String toString() {
      return "TOMBSTONE";
    }

    private Object readResolve() throws ObjectStreamException {
      return TOMBSTONE;
    }

    @Override
    public int getDSFID() {
      return TOKEN_TOMBSTONE;
    }

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

  public static class Removed extends Token implements DataSerializableFixedID, Serializable {
    private static final long serialVersionUID = -1999836887955504653L;

    protected Removed() {}

    @Override
    public String toString() {
      return "REMOVED_PHASE1";
    }

    private Object readResolve() throws ObjectStreamException {
      return REMOVED_PHASE1;
    }

    @Override
    public int getDSFID() {
      return TOKEN_REMOVED;
    }

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

  public static class Removed2 extends Token implements DataSerializableFixedID, Serializable {
    private static final long serialVersionUID = 5122235867167804597L;

    protected Removed2() {}

    @Override
    public String toString() {
      return "REMOVED_PHASE2";
    }

    private Object readResolve() throws ObjectStreamException {
      return REMOVED_PHASE2;
    }

    @Override
    public int getDSFID() {
      return TOKEN_REMOVED2;
    }

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

  public static class NotAvailable extends Token {
    protected NotAvailable() {}

    @Override
    public String toString() {
      return "NOT_AVAILABLE";
    }
    // to fix bug 50200 no longer serializable
  }

  public static class NotAToken extends Token {
    protected NotAToken() {}

    @Override
    public String toString() {
      return "NOT_A_TOKEN";
    }
    // to fix bug 50200 no longer serializable
  }

  /** Token used in StreamingOperation, StreamingPartitionOperation */
  public static class EndOfStream extends Token implements DataSerializableFixedID {
    public EndOfStream() {}

    @Override
    public String toString() {
      return "EndOfStream";
    }

    private Object readResolve() throws ObjectStreamException {
      return END_OF_STREAM;
    }

    @Override
    public int getDSFID() {
      return END_OF_STREAM_TOKEN;
    }

    @Override
    public void fromData(DataInput in,
        DeserializationContext context) throws IOException, ClassNotFoundException {}

    @Override
    public void toData(DataOutput out,
        SerializationContext context) throws IOException {}

    @Override
    public KnownVersion[] getSerializationVersions() {
      return null;
    }
  }

}
