/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.client.rest;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.concurrent.NotThreadSafe;
import javax.validation.constraints.NotNull;
import javax.ws.rs.core.MediaType;

/**
 * Method options for creating a REST API test case.
 */
// TODO(jiri): consolidate input stream and body fields
@NotThreadSafe
public final class TestCaseOptions {
  /* Supported content types */
  public static final String JSON_CONTENT_TYPE = MediaType.APPLICATION_JSON;
  public static final String OCTET_STREAM_CONTENT_TYPE = MediaType.APPLICATION_OCTET_STREAM;
  public static final String XML_CONTENT_TYPE = MediaType.APPLICATION_XML;
  public static final String TEXT_PLAIN_CONTENT_TYPE = MediaType.TEXT_PLAIN;

  /* Headers */
  public static final String AUTHORIZATION_HEADER = "Authorization";
  public static final String CONTENT_TYPE_HEADER = "Content-Type";
  public static final String CONTENT_MD5_HEADER = "Content-MD5";
  private String mAuthorization;
  private String mContentType;
  private String mMD5;

  // mHeaders contains the previously defined headers
  // - Users may add additional headers
  private final Map<String, String> mHeaders;

  private Object mBody;
  private Charset mCharset; // used when converting byte data into strings
  private boolean mPrettyPrint; // used for ObjectMapper when printing strings

  /**
   * @return the default {@link TestCaseOptions}
   */
  public static TestCaseOptions defaults() {
    TestCaseOptions options =  new TestCaseOptions();
    return options;
  }

  private TestCaseOptions() {
    mAuthorization = null;
    mBody = null;
    mContentType = OCTET_STREAM_CONTENT_TYPE;
    mCharset = StandardCharsets.UTF_8;
    mHeaders = new HashMap<>();
    mMD5 = null;
    mPrettyPrint = false;
  }

  /**
   * @return the object representing the data to be sent to the web server
   */
  public Object getBody() {
    return mBody;
  }

  /**
   * @return the pretty print flag
   */
  public boolean isPrettyPrint() {
    return mPrettyPrint;
  }

  /**
   * @return the content type
   */
  public String getContentType() {
    return mContentType;
  }

  /**
   * @return the Base64 encoded MD5 digest of the request body
   */
  public String getMD5() {
    return mMD5;
  }

  /**
   * @return the authorization header
   */
  public String getAuthorization() {
    return mAuthorization;
  }

  /**
   * @return the charset map
   */
  public Charset getCharset() {
    return mCharset;
  }

  /**
   * @return the headers map
   */
  public Map<String, String> getHeaders() {
    return mHeaders;
  }

  /**
   * @param body the body to use
   * @return the updated options object
   */
  public TestCaseOptions setBody(Object body) {
    mBody = body;
    return this;
  }

  /**
   * @param prettyPrint the pretty print flag value to use
   * @return the updated options object
   */
  public TestCaseOptions setPrettyPrint(boolean prettyPrint) {
    mPrettyPrint = prettyPrint;
    return this;
  }

  /**
   * @param contentType the content type to set
   * @return the updated options object
   */
  public TestCaseOptions setContentType(String contentType) {
    mContentType = contentType;
    mHeaders.put(CONTENT_TYPE_HEADER, contentType);
    return this;
  }

  /**
   * @param md5 the Base64 encoded MD5 digest of the request body
   * @return the updated options object
   */
  public TestCaseOptions setMD5(String md5) {
    mMD5 = md5;
    mHeaders.put(CONTENT_MD5_HEADER, md5);
    return this;
  }

  /**
   * @param authorization the authorization header
   * @return the updated options object
   */
  public TestCaseOptions setAuthorization(String authorization) {
    mAuthorization = authorization;
    mHeaders.put(AUTHORIZATION_HEADER, authorization);
    return this;
  }

  /**
   * @param charset the charset to use
   * @return the updated options object
   */
  public TestCaseOptions setCharset(@NotNull Charset charset) {
    mCharset = charset;
    return this;
  }

  /**
   * Adds the provided headers to the existing headers map. Overwrites duplicate keys.
   * Note that this does not update the class header fields if you overwrite them.
   * @param headers headers map
   * @return the updated options object
   */
  public TestCaseOptions addHeaders(@NotNull Map<String, String> headers) {
    mHeaders.putAll(headers);
    return this;
  }

  /**
   * Adds the provided header to the existing headers map. Overwrites duplicate keys.
   * Note that this does not update the class header fields if you overwrite them.
   * @param key header key
   * @param value header value
   * @return the updated options object
   */
  public TestCaseOptions addHeader(String key, String value) {
    mHeaders.put(key, value);
    return this;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof TestCaseOptions)) {
      return false;
    }
    TestCaseOptions that = (TestCaseOptions) o;
    return Objects.equal(mAuthorization, that.mAuthorization)
        && Objects.equal(mBody, that.mBody)
        && Objects.equal(mCharset, that.mCharset)
        && Objects.equal(mContentType, that.mContentType)
        && Objects.equal(mHeaders, that.mHeaders)
        && Objects.equal(mMD5, that.mMD5)
        && mPrettyPrint == that.mPrettyPrint;
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(mAuthorization, mBody, mCharset, mContentType, mHeaders, mMD5,
        mPrettyPrint);
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("authorization", mAuthorization)
        .add("body", mBody)
        .add("charset", mCharset)
        .add("content type", mContentType)
        .add("headers", mHeaders)
        .add("MD5", mMD5)
        .add("pretty print", mPrettyPrint)
        .toString();
  }
}
