/*
 * 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 com.datastax.oss.driver.internal.core.metadata.token;

import com.datastax.oss.driver.api.core.metadata.token.Token;
import com.datastax.oss.driver.api.core.metadata.token.TokenRange;
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import net.jcip.annotations.ThreadSafe;

@ThreadSafe
public class RandomTokenFactory implements TokenFactory {

  public static final String PARTITIONER_NAME = "org.apache.cassandra.dht.RandomPartitioner";

  private static final BigInteger MIN_VALUE = BigInteger.ONE.negate();
  static final BigInteger MAX_VALUE = BigInteger.valueOf(2).pow(127);
  public static final RandomToken MIN_TOKEN = new RandomToken(MIN_VALUE);
  public static final RandomToken MAX_TOKEN = new RandomToken(MAX_VALUE);

  private final MessageDigest prototype;
  private final boolean supportsClone;

  public RandomTokenFactory() {
    prototype = createMessageDigest();
    boolean supportsClone;
    try {
      prototype.clone();
      supportsClone = true;
    } catch (CloneNotSupportedException e) {
      supportsClone = false;
    }
    this.supportsClone = supportsClone;
  }

  @Override
  public String getPartitionerName() {
    return PARTITIONER_NAME;
  }

  @Override
  public Token hash(ByteBuffer partitionKey) {
    return new RandomToken(md5(partitionKey));
  }

  @Override
  public Token parse(String tokenString) {
    return new RandomToken(new BigInteger(tokenString));
  }

  @Override
  public String format(Token token) {
    Preconditions.checkArgument(
        token instanceof RandomToken, "Can only format RandomToken instances");
    return ((RandomToken) token).getValue().toString();
  }

  @Override
  public Token minToken() {
    return MIN_TOKEN;
  }

  @Override
  public TokenRange range(Token start, Token end) {
    Preconditions.checkArgument(
        start instanceof RandomToken && end instanceof RandomToken,
        "Can only build ranges of RandomToken instances");
    return new RandomTokenRange((RandomToken) start, (RandomToken) end);
  }

  private static MessageDigest createMessageDigest() {
    try {
      return MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("MD5 doesn't seem to be available on this JVM", e);
    }
  }

  private BigInteger md5(ByteBuffer data) {
    MessageDigest digest = newMessageDigest();
    digest.update(data.duplicate());
    return new BigInteger(digest.digest()).abs();
  }

  private MessageDigest newMessageDigest() {
    if (supportsClone) {
      try {
        return (MessageDigest) prototype.clone();
      } catch (CloneNotSupportedException ignored) {
      }
    }
    return createMessageDigest();
  }
}
