
package org.apache.accumulo.core.clientImpl;

import static java.util.Objects.requireNonNull;

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

import org.apache.accumulo.core.client.admin.DelegationTokenConfig;
import org.apache.accumulo.core.securityImpl.thrift.TAuthenticationTokenIdentifier;
import org.apache.accumulo.core.util.ByteBufferUtil;
import org.apache.accumulo.core.util.ThriftMessageUtil;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;

/**
 * Implementation that identifies the underlying {@link Token} for Accumulo.
 */
public class AuthenticationTokenIdentifier extends TokenIdentifier {
  public static final Text TOKEN_KIND = new Text("ACCUMULO_AUTH_TOKEN");

  private TAuthenticationTokenIdentifier impl = null;
  private DelegationTokenConfig cfg = null;

  public AuthenticationTokenIdentifier() {
    // noop for Writable
  }

  public AuthenticationTokenIdentifier(String principal) {
    this(principal, null);
  }

  public AuthenticationTokenIdentifier(String principal, DelegationTokenConfig cfg) {
    requireNonNull(principal);
    impl = new TAuthenticationTokenIdentifier(principal);
    this.cfg = cfg;
  }

  public AuthenticationTokenIdentifier(String principal, int keyId, long issueDate,
      long expirationDate, String instanceId) {
    requireNonNull(principal);
    impl = new TAuthenticationTokenIdentifier(principal);
    impl.setKeyId(keyId);
    impl.setIssueDate(issueDate);
    impl.setExpirationDate(expirationDate);
    impl.setInstanceId(instanceId);
  }

  public AuthenticationTokenIdentifier(AuthenticationTokenIdentifier identifier) {
    requireNonNull(identifier);
    impl = new TAuthenticationTokenIdentifier(identifier.getThriftIdentifier());
  }

  public AuthenticationTokenIdentifier(TAuthenticationTokenIdentifier identifier) {
    requireNonNull(identifier);
    impl = new TAuthenticationTokenIdentifier(identifier);
  }

  public void setKeyId(int keyId) {
    impl.setKeyId(keyId);
  }

  public int getKeyId() {
    requireNonNull(impl, "Identifier not initialized");
    return impl.getKeyId();
  }

  public void setIssueDate(long issueDate) {
    requireNonNull(impl, "Identifier not initialized");
    impl.setIssueDate(issueDate);
  }

  public long getIssueDate() {
    requireNonNull(impl, "Identifier not initialized");
    return impl.getIssueDate();
  }

  public void setExpirationDate(long expirationDate) {
    requireNonNull(impl, "Identifier not initialized");
    impl.setExpirationDate(expirationDate);
  }

  public long getExpirationDate() {
    requireNonNull(impl, "Identifier not initialized");
    return impl.getExpirationDate();
  }

  public void setInstanceId(String instanceId) {
    requireNonNull(impl, "Identifier not initialized");
    impl.setInstanceId(instanceId);
  }

  public String getInstanceId() {
    requireNonNull(impl, "Identifier not initialized");
    return impl.getInstanceId();
  }

  public TAuthenticationTokenIdentifier getThriftIdentifier() {
    requireNonNull(impl);
    return impl;
  }

  /**
   * A configuration from the requesting user, may be null.
   */
  public DelegationTokenConfig getConfig() {
    return cfg;
  }

  @Override
  public void write(DataOutput out) throws IOException {
    if (impl != null) {
      ThriftMessageUtil msgUtil = new ThriftMessageUtil();
      ByteBuffer serialized = msgUtil.serialize(impl);
      out.writeInt(serialized.limit());
      ByteBufferUtil.write(out, serialized);
    } else {
      out.writeInt(0);
    }
  }

  @Override
  public void readFields(DataInput in) throws IOException {
    int length = in.readInt();
    if (length > 0) {
      ThriftMessageUtil msgUtil = new ThriftMessageUtil();
      byte[] serialized = new byte[length];
      in.readFully(serialized);
      impl = new TAuthenticationTokenIdentifier();
      msgUtil.deserialize(serialized, impl);
    }
  }

  @Override
  public Text getKind() {
    return TOKEN_KIND;
  }

  @Override
  public UserGroupInformation getUser() {
    if (impl != null && impl.isSetPrincipal()) {
      return UserGroupInformation.createRemoteUser(impl.getPrincipal());
    }
    return null;
  }

  @Override
  public int hashCode() {
    if (impl == null) {
      return 0;
    }
    HashCodeBuilder hcb = new HashCodeBuilder(7, 11);
    if (impl.isSetPrincipal()) {
      hcb.append(impl.getPrincipal());
    }
    if (impl.isSetKeyId()) {
      hcb.append(impl.getKeyId());
    }
    if (impl.isSetIssueDate()) {
      hcb.append(impl.getIssueDate());
    }
    if (impl.isSetExpirationDate()) {
      hcb.append(impl.getExpirationDate());
    }
    if (impl.isSetInstanceId()) {
      hcb.append(impl.getInstanceId());
    }
    return hcb.toHashCode();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder(128);
    sb.append("AuthenticationTokenIdentifier(").append(impl).append(")");
    return sb.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (o == null) {
      return false;
    }
    if (o instanceof AuthenticationTokenIdentifier) {
      AuthenticationTokenIdentifier other = (AuthenticationTokenIdentifier) o;
      if (impl == null) {
        return other.impl == null;
      }
      return impl.equals(other.impl);
    }
    return false;
  }
}
