/*
 * Licensed 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.google.api.client.googleapis.services;

import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.util.ObjectParser;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.Strings;
import java.io.IOException;
import java.util.logging.Logger;

/**
 * Abstract thread-safe Google client.
 *
 * @since 1.12
 * @author Yaniv Inbar
 */
public abstract class AbstractGoogleClient {

  private static final Logger logger = Logger.getLogger(AbstractGoogleClient.class.getName());

  /** The request factory for connections to the server. */
  private final HttpRequestFactory requestFactory;

  /**
   * Initializer to use when creating an {@link AbstractGoogleClientRequest} or {@code null} for
   * none.
   */
  private final GoogleClientRequestInitializer googleClientRequestInitializer;

  /**
   * Root URL of the service, for example {@code "https://www.googleapis.com/"}. Must be URL-encoded
   * and must end with a "/".
   */
  private final String rootUrl;

  /**
   * Service path, for example {@code "tasks/v1/"}. Must be URL-encoded and must end with a "/".
   */
  private final String servicePath;

  /**
   * Batch path, for example {@code "batch/tasks"}.  Must be URL-encoded.
   */
  private final String batchPath;

  /**
   * Application name to be sent in the User-Agent header of each request or {@code null} for none.
   */
  private final String applicationName;

  /** Object parser or {@code null} for none. */
  private final ObjectParser objectParser;

  /** Whether discovery pattern checks should be suppressed on required parameters. */
  private final boolean suppressPatternChecks;

  /** Whether discovery required parameter checks should be suppressed. */
  private final boolean suppressRequiredParameterChecks;

  /**
   * @param builder builder
   *
   * @since 1.14
   */
  protected AbstractGoogleClient(Builder builder) {
    googleClientRequestInitializer = builder.googleClientRequestInitializer;
    rootUrl = normalizeRootUrl(builder.rootUrl);
    servicePath = normalizeServicePath(builder.servicePath);
    batchPath = builder.batchPath;
    if (Strings.isNullOrEmpty(builder.applicationName)) {
      logger.warning("Application name is not set. Call Builder#setApplicationName.");
    }
    applicationName = builder.applicationName;
    requestFactory = builder.httpRequestInitializer == null
        ? builder.transport.createRequestFactory()
        : builder.transport.createRequestFactory(builder.httpRequestInitializer);
    objectParser = builder.objectParser;
    suppressPatternChecks = builder.suppressPatternChecks;
    suppressRequiredParameterChecks = builder.suppressRequiredParameterChecks;
  }

  /**
   * Returns the URL-encoded root URL of the service, for example
   * {@code "https://www.googleapis.com/"}.
   *
   * <p>
   * Must end with a "/".
   * </p>
   */
  public final String getRootUrl() {
    return rootUrl;
  }

  /**
   * Returns the URL-encoded service path of the service, for example {@code "tasks/v1/"}.
   *
   * <p>
   * Must end with a "/" and not begin with a "/". It is allowed to be an empty string {@code ""} or
   * a forward slash {@code "/"}, if it is a forward slash then it is treated as an empty string
   * </p>
   */
  public final String getServicePath() {
    return servicePath;
  }

  /**
   * Returns the URL-encoded base URL of the service, for example
   * {@code "https://www.googleapis.com/tasks/v1/"}.
   *
   * <p>
   * Must end with a "/". It is guaranteed to be equal to {@code getRootUrl() + getServicePath()}.
   * </p>
   */
  public final String getBaseUrl() {
    return rootUrl + servicePath;
  }

  /**
   * Returns the application name to be sent in the User-Agent header of each request or
   * {@code null} for none.
   */
  public final String getApplicationName() {
    return applicationName;
  }

  /** Returns the HTTP request factory. */
  public final HttpRequestFactory getRequestFactory() {
    return requestFactory;
  }

  /** Returns the Google client request initializer or {@code null} for none. */
  public final GoogleClientRequestInitializer getGoogleClientRequestInitializer() {
    return googleClientRequestInitializer;
  }

  /**
   * Returns the object parser or {@code null} for none.
   *
   * <p>
   * Overriding is only supported for the purpose of calling the super implementation and changing
   * the return type, but nothing else.
   * </p>
   */
  public ObjectParser getObjectParser() {
    return objectParser;
  }

  /**
   * Initializes a {@link AbstractGoogleClientRequest} using a
   * {@link GoogleClientRequestInitializer}.
   *
   * <p>
   * Must be called before the Google client request is executed, preferably right after the request
   * is instantiated. Sample usage:
   * </p>
   *
   * <pre>
    public class Get extends HttpClientRequest {
      ...
    }

    public Get get(String userId) throws IOException {
      Get result = new Get(userId);
      initialize(result);
      return result;
    }
   * </pre>
   *
   * <p>
   * Subclasses may override by calling the super implementation.
   * </p>
   *
   * @param httpClientRequest Google client request type
   */
  protected void initialize(AbstractGoogleClientRequest<?> httpClientRequest) throws IOException {
    if (getGoogleClientRequestInitializer() != null) {
      getGoogleClientRequestInitializer().initialize(httpClientRequest);
    }
  }

  /**
   * Create an {@link BatchRequest} object from this Google API client instance.
   *
   * <p>
   * Sample usage:
   * </p>
   *
   * <pre>
     client.batch()
         .queue(...)
         .queue(...)
         .execute();
   * </pre>
   *
   * @return newly created Batch request
   */
  public final BatchRequest batch() {
    return batch(null);
  }

  /**
   * Create an {@link BatchRequest} object from this Google API client instance.
   *
   * <p>
   * Sample usage:
   * </p>
   *
   * <pre>
     client.batch(httpRequestInitializer)
         .queue(...)
         .queue(...)
         .execute();
   * </pre>
   *
   * @param httpRequestInitializer The initializer to use when creating the top-level batch HTTP
   *        request or {@code null} for none
   * @return newly created Batch request
   */
  public final BatchRequest batch(HttpRequestInitializer httpRequestInitializer) {
    @SuppressWarnings("deprecated")
    BatchRequest batch =
        new BatchRequest(getRequestFactory().getTransport(), httpRequestInitializer);
    if (Strings.isNullOrEmpty(batchPath)) {
      batch.setBatchUrl(new GenericUrl(getRootUrl() + "batch"));
    } else {
      batch.setBatchUrl(new GenericUrl(getRootUrl() + batchPath));
    }
    return batch;
  }

  /** Returns whether discovery pattern checks should be suppressed on required parameters. */
  public final boolean getSuppressPatternChecks() {
    return suppressPatternChecks;
  }

  /**
   * Returns whether discovery required parameter checks should be suppressed.
   *
   * @since 1.14
   */
  public final boolean getSuppressRequiredParameterChecks() {
    return suppressRequiredParameterChecks;
  }

  /** If the specified root URL does not end with a "/" then a "/" is added to the end. */
  static String normalizeRootUrl(String rootUrl) {
    Preconditions.checkNotNull(rootUrl, "root URL cannot be null.");
    if (!rootUrl.endsWith("/")) {
      rootUrl += "/";
    }
    return rootUrl;
  }

  /**
   * If the specified service path does not end with a "/" then a "/" is added to the end. If the
   * specified service path begins with a "/" then the "/" is removed.
   */
  static String normalizeServicePath(String servicePath) {
    Preconditions.checkNotNull(servicePath, "service path cannot be null");
    if (servicePath.length() == 1) {
      Preconditions.checkArgument(
          "/".equals(servicePath), "service path must equal \"/\" if it is of length 1.");
      servicePath = "";
    } else if (servicePath.length() > 0) {
      if (!servicePath.endsWith("/")) {
        servicePath += "/";
      }
      if (servicePath.startsWith("/")) {
        servicePath = servicePath.substring(1);
      }
    }
    return servicePath;
  }

  /**
   * Builder for {@link AbstractGoogleClient}.
   *
   * <p>
   * Implementation is not thread-safe.
   * </p>
   */
  public abstract static class Builder {

    /** HTTP transport. */
    final HttpTransport transport;

    /**
     * Initializer to use when creating an {@link AbstractGoogleClientRequest} or {@code null} for
     * none.
     */
    GoogleClientRequestInitializer googleClientRequestInitializer;

    /** HTTP request initializer or {@code null} for none. */
    HttpRequestInitializer httpRequestInitializer;

    /** Object parser to use for parsing responses. */
    final ObjectParser objectParser;

    /** The root URL of the service, for example {@code "https://www.googleapis.com/"}. */
    String rootUrl;

    /** The service path of the service, for example {@code "tasks/v1/"}. */
    String servicePath;

    /** The batch path of the service, for example {@code "batch/tasks"}. */
    String batchPath;

    /**
     * Application name to be sent in the User-Agent header of each request or {@code null} for
     * none.
     */
    String applicationName;

    /** Whether discovery pattern checks should be suppressed on required parameters. */
    boolean suppressPatternChecks;

    /** Whether discovery required parameter checks should be suppressed. */
    boolean suppressRequiredParameterChecks;

    /**
     * Returns an instance of a new builder.
     *
     * @param transport The transport to use for requests
     * @param rootUrl root URL of the service. Must end with a "/"
     * @param servicePath service path
     * @param objectParser object parser or {@code null} for none
     * @param httpRequestInitializer HTTP request initializer or {@code null} for none
     */
    protected Builder(HttpTransport transport, String rootUrl, String servicePath,
        ObjectParser objectParser, HttpRequestInitializer httpRequestInitializer) {
      this.transport = Preconditions.checkNotNull(transport);
      this.objectParser = objectParser;
      setRootUrl(rootUrl);
      setServicePath(servicePath);
      this.httpRequestInitializer = httpRequestInitializer;
    }

    /** Builds a new instance of {@link AbstractGoogleClient}. */
    public abstract AbstractGoogleClient build();

    /** Returns the HTTP transport. */
    public final HttpTransport getTransport() {
      return transport;
    }

    /**
     * Returns the object parser or {@code null} for none.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public ObjectParser getObjectParser() {
      return objectParser;
    }

    /**
     * Returns the URL-encoded root URL of the service, for example
     * {@code https://www.googleapis.com/}.
     *
     * <p>
     * Must be URL-encoded and must end with a "/".
     * </p>
     */
    public final String getRootUrl() {
      return rootUrl;
    }

    /**
     * Sets the URL-encoded root URL of the service, for example {@code https://www.googleapis.com/}
     * .
     * <p>
     * If the specified root URL does not end with a "/" then a "/" is added to the end.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setRootUrl(String rootUrl) {
      this.rootUrl = normalizeRootUrl(rootUrl);
      return this;
    }

    /**
     * Returns the URL-encoded service path of the service, for example {@code "tasks/v1/"}.
     *
     * <p>
     * Must be URL-encoded and must end with a "/" and not begin with a "/". It is allowed to be an
     * empty string {@code ""}.
     * </p>
     */
    public final String getServicePath() {
      return servicePath;
    }

    /**
     * Sets the URL-encoded service path of the service, for example {@code "tasks/v1/"}.
     *
     * <p>
     * It is allowed to be an empty string {@code ""} or a forward slash {@code "/"}, if it is a
     * forward slash then it is treated as an empty string. This is determined when the library is
     * generated and normally should not be changed.
     * </p>
     *
     * <p>
     * If the specified service path does not end with a "/" then a "/" is added to the end. If the
     * specified service path begins with a "/" then the "/" is removed.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setServicePath(String servicePath) {
      this.servicePath = normalizeServicePath(servicePath);
      return this;
    }

    /**
     * Sets the URL-encoded batch path of the service, for example {@code "batch/tasks"}.
     */
    public Builder setBatchPath(String batchPath) {
      this.batchPath = batchPath;
      return this;
    }

    /** Returns the Google client request initializer or {@code null} for none. */
    public final GoogleClientRequestInitializer getGoogleClientRequestInitializer() {
      return googleClientRequestInitializer;
    }

    /**
     * Sets the Google client request initializer or {@code null} for none.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setGoogleClientRequestInitializer(
        GoogleClientRequestInitializer googleClientRequestInitializer) {
      this.googleClientRequestInitializer = googleClientRequestInitializer;
      return this;
    }

    /** Returns the HTTP request initializer or {@code null} for none. */
    public final HttpRequestInitializer getHttpRequestInitializer() {
      return httpRequestInitializer;
    }

    /**
     * Sets the HTTP request initializer or {@code null} for none.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setHttpRequestInitializer(HttpRequestInitializer httpRequestInitializer) {
      this.httpRequestInitializer = httpRequestInitializer;
      return this;
    }

    /**
     * Returns the application name to be used in the UserAgent header of each request or
     * {@code null} for none.
     */
    public final String getApplicationName() {
      return applicationName;
    }

    /**
     * Sets the application name to be used in the UserAgent header of each request or {@code null}
     * for none.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setApplicationName(String applicationName) {
      this.applicationName = applicationName;
      return this;
    }

    /** Returns whether discovery pattern checks should be suppressed on required parameters. */
    public final boolean getSuppressPatternChecks() {
      return suppressPatternChecks;
    }

    /**
     * Sets whether discovery pattern checks should be suppressed on required parameters.
     *
     * <p>
     * Default value is {@code false}.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     */
    public Builder setSuppressPatternChecks(boolean suppressPatternChecks) {
      this.suppressPatternChecks = suppressPatternChecks;
      return this;
    }

    /**
     * Returns whether discovery required parameter checks should be suppressed.
     *
     * @since 1.14
     */
    public final boolean getSuppressRequiredParameterChecks() {
      return suppressRequiredParameterChecks;
    }

    /**
     * Sets whether discovery required parameter checks should be suppressed.
     *
     * <p>
     * Default value is {@code false}.
     * </p>
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     *
     * @since 1.14
     */
    public Builder setSuppressRequiredParameterChecks(boolean suppressRequiredParameterChecks) {
      this.suppressRequiredParameterChecks = suppressRequiredParameterChecks;
      return this;
    }

    /**
     * Suppresses all discovery pattern and required parameter checks.
     *
     * <p>
     * Overriding is only supported for the purpose of calling the super implementation and changing
     * the return type, but nothing else.
     * </p>
     *
     * @since 1.14
     */
    public Builder setSuppressAllChecks(boolean suppressAllChecks) {
      return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true);
    }
  }
}
