/**
 * 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.hadoop.fs.azure;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;

import org.apache.hadoop.classification.InterfaceAudience;

import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.RetryPolicyFactory;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.blob.BlobListingDetails;
import com.microsoft.azure.storage.blob.BlobProperties;
import com.microsoft.azure.storage.blob.BlobRequestOptions;
import com.microsoft.azure.storage.blob.CloudBlob;
import com.microsoft.azure.storage.blob.CopyState;
import com.microsoft.azure.storage.blob.ListBlobItem;
import com.microsoft.azure.storage.blob.PageRange;

/**
 * This is a very thin layer over the methods exposed by the Windows Azure
 * Storage SDK that we need for WASB implementation. This base class has a real
 * implementation that just simply redirects to the SDK, and a memory-backed one
 * that's used for unit tests.
 *
 * IMPORTANT: all the methods here must remain very simple redirects since code
 * written here can't be properly unit tested.
 */
@InterfaceAudience.Private
abstract class StorageInterface {

  /**
   * Sets the timeout to use when making requests to the storage service.
   * <p>
   * The server timeout interval begins at the time that the complete request
   * has been received by the service, and the server begins processing the
   * response. If the timeout interval elapses before the response is returned
   * to the client, the operation times out. The timeout interval resets with
   * each retry, if the request is retried.
   * 
   * The default timeout interval for a request made via the service client is
   * 90 seconds. You can change this value on the service client by setting this
   * property, so that all subsequent requests made via the service client will
   * use the new timeout interval. You can also change this value for an
   * individual request, by setting the
   * {@link RequestOptions#timeoutIntervalInMs} property.
   * 
   * If you are downloading a large blob, you should increase the value of the
   * timeout beyond the default value.
   * 
   * @param timeoutInMs
   *          The timeout, in milliseconds, to use when making requests to the
   *          storage service.
   */
  public abstract void setTimeoutInMs(int timeoutInMs);

  /**
   * Sets the RetryPolicyFactory object to use when making service requests.
   * 
   * @param retryPolicyFactory
   *          the RetryPolicyFactory object to use when making service requests.
   */
  public abstract void setRetryPolicyFactory(
      final RetryPolicyFactory retryPolicyFactory);

  /**
   * Creates a new Blob service client.
   * 
   */
  public abstract void createBlobClient(CloudStorageAccount account);

  /**
   * Creates an instance of the <code>CloudBlobClient</code> class using the
   * specified Blob service endpoint.
   * 
   * @param baseUri
   *          A <code>java.net.URI</code> object that represents the Blob
   *          service endpoint used to create the client.
   */
  public abstract void createBlobClient(URI baseUri);

  /**
   * Creates an instance of the <code>CloudBlobClient</code> class using the
   * specified Blob service endpoint and account credentials.
   * 
   * @param baseUri
   *          A <code>java.net.URI</code> object that represents the Blob
   *          service endpoint used to create the client.
   * @param credentials
   *          A {@link StorageCredentials} object that represents the account
   *          credentials.
   */
  public abstract void createBlobClient(URI baseUri,
      StorageCredentials credentials);

  /**
   * Returns the credentials for the Blob service, as configured for the storage
   * account.
   * 
   * @return A {@link StorageCredentials} object that represents the credentials
   *         for this storage account.
   */
  public abstract StorageCredentials getCredentials();

  /**
   * Returns a reference to a {@link CloudBlobContainerWrapper} object that
   * represents the cloud blob container for the specified address.
   * 
   * @param name
   *          A <code>String</code> that represents the name of the container.
   * @return A {@link CloudBlobContainerWrapper} object that represents a
   *         reference to the cloud blob container.
   * 
   * @throws URISyntaxException
   *           If the resource URI is invalid.
   * @throws StorageException
   *           If a storage service error occurred.
   */
  public abstract CloudBlobContainerWrapper getContainerReference(String name)
      throws URISyntaxException, StorageException;

  /**
   * A thin wrapper over the {@link CloudBlobDirectory} class that simply
   * redirects calls to the real object except in unit tests.
   */
  @InterfaceAudience.Private
  public abstract static class CloudBlobDirectoryWrapper implements
      ListBlobItem {
    /**
     * Returns the URI for this directory.
     * 
     * @return A <code>java.net.URI</code> object that represents the URI for
     *         this directory.
     */
    public abstract URI getUri();

    /**
     * Returns an enumerable collection of blob items whose names begin with the
     * specified prefix, using the specified flat or hierarchical option,
     * listing details options, request options, and operation context.
     * 
     * @param prefix
     *          A <code>String</code> that represents the prefix of the blob
     *          name.
     * @param useFlatBlobListing
     *          <code>true</code> to indicate that the returned list will be
     *          flat; <code>false</code> to indicate that the returned list will
     *          be hierarchical.
     * @param listingDetails
     *          A <code>java.util.EnumSet</code> object that contains
     *          {@link BlobListingDetails} values that indicate whether
     *          snapshots, metadata, and/or uncommitted blocks are returned.
     *          Committed blocks are always returned.
     * @param options
     *          A {@link BlobRequestOptions} object that specifies any
     *          additional options for the request. Specifying <code>null</code>
     *          will use the default request options from the associated service
     *          client ( {@link CloudBlobClient}).
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @return An enumerable collection of {@link ListBlobItem} objects that
     *         represent the block items whose names begin with the specified
     *         prefix in this directory.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     * @throws URISyntaxException
     *           If the resource URI is invalid.
     */
    public abstract Iterable<ListBlobItem> listBlobs(String prefix,
        boolean useFlatBlobListing, EnumSet<BlobListingDetails> listingDetails,
        BlobRequestOptions options, OperationContext opContext)
        throws URISyntaxException, StorageException;
  }

  /**
   * A thin wrapper over the {@link CloudBlobContainer} class that simply
   * redirects calls to the real object except in unit tests.
   */
  @InterfaceAudience.Private
  public abstract static class CloudBlobContainerWrapper {
    /**
     * Returns the name of the container.
     * 
     * @return A <code>String</code> that represents the name of the container.
     */
    public abstract String getName();

    /**
     * Returns a value that indicates whether the container exists, using the
     * specified operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @return <code>true</code> if the container exists, otherwise
     *         <code>false</code>.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    public abstract boolean exists(OperationContext opContext)
        throws StorageException;

    /**
     * Returns the metadata for the container.
     * 
     * @return A <code>java.util.HashMap</code> object that represents the
     *         metadata for the container.
     */
    public abstract HashMap<String, String> getMetadata();

    /**
     * Sets the metadata for the container.
     * 
     * @param metadata
     *          A <code>java.util.HashMap</code> object that represents the
     *          metadata being assigned to the container.
     */
    public abstract void setMetadata(HashMap<String, String> metadata);

    /**
     * Downloads the container's attributes, which consist of metadata and
     * properties, using the specified operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    public abstract void downloadAttributes(OperationContext opContext)
        throws StorageException;

    /**
     * Uploads the container's metadata using the specified operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    public abstract void uploadMetadata(OperationContext opContext)
        throws StorageException;

    /**
     * Creates the container using the specified operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    public abstract void create(OperationContext opContext)
        throws StorageException;

    /**
     * Returns a wrapper for a CloudBlobDirectory.
     * 
     * @param relativePath
     *          A <code>String</code> that represents the name of the directory,
     *          relative to the container
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     * 
     * @throws URISyntaxException
     *           If URI syntax exception occurred.
     */
    public abstract CloudBlobDirectoryWrapper getDirectoryReference(
        String relativePath) throws URISyntaxException, StorageException;

    /**
     * Returns a wrapper for a CloudBlockBlob.
     * 
     * @param relativePath
     *          A <code>String</code> that represents the name of the blob,
     *          relative to the container
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     * 
     * @throws URISyntaxException
     *           If URI syntax exception occurred.
     */
    public abstract CloudBlobWrapper getBlockBlobReference(
        String relativePath) throws URISyntaxException, StorageException;
  
    /**
     * Returns a wrapper for a CloudPageBlob.
     *
     * @param relativePath
     *            A <code>String</code> that represents the name of the blob, relative to the container 
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * 
     * @throws URISyntaxException
     *             If URI syntax exception occurred.            
     */
    public abstract CloudBlobWrapper getPageBlobReference(String relativePath)
        throws URISyntaxException, StorageException;
  }
  
  
  /**
   * A thin wrapper over the {@link CloudBlob} class that simply redirects calls
   * to the real object except in unit tests.
   */
  @InterfaceAudience.Private
  public interface CloudBlobWrapper extends ListBlobItem {
    /**
     * Returns the URI for this blob.
     * 
     * @return A <code>java.net.URI</code> object that represents the URI for
     *         the blob.
     */
    URI getUri();

    /**
     * Returns the metadata for the blob.
     * 
     * @return A <code>java.util.HashMap</code> object that represents the
     *         metadata for the blob.
     */
    HashMap<String, String> getMetadata();

    /**
     * Sets the metadata for the blob.
     * 
     * @param metadata
     *          A <code>java.util.HashMap</code> object that contains the
     *          metadata being assigned to the blob.
     */
    void setMetadata(HashMap<String, String> metadata);

    /**
     * Copies an existing blob's contents, properties, and metadata to this instance of the <code>CloudBlob</code>
     * class, using the specified operation context.
     *
     * @param sourceBlob
     *            A <code>CloudBlob</code> object that represents the source blob to copy.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *
     */
    public abstract void startCopyFromBlob(CloudBlobWrapper sourceBlob,
        BlobRequestOptions options, OperationContext opContext)
        throws StorageException, URISyntaxException;
    
    /**
     * Returns the blob's copy state.
     * 
     * @return A {@link CopyState} object that represents the copy state of the
     *         blob.
     */
    CopyState getCopyState();

    /**
     * Downloads a range of bytes from the blob to the given byte buffer, using the specified request options and
     * operation context.
     *
     * @param offset
     *            The byte offset to use as the starting point for the source.
     * @param length
     *            The number of bytes to read.
     * @param buffer
     *            The byte buffer, as an array of bytes, to which the blob bytes are downloaded.
     * @param bufferOffset
     *            The byte offset to use as the starting point for the target.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    void downloadRange(final long offset, final long length,
        final OutputStream outStream, final BlobRequestOptions options,
        final OperationContext opContext)
            throws StorageException, IOException;

    /**
     * Deletes the blob using the specified operation context.
     * <p>
     * A blob that has snapshots cannot be deleted unless the snapshots are also
     * deleted. If a blob has snapshots, use the
     * {@link DeleteSnapshotsOption#DELETE_SNAPSHOTS_ONLY} or
     * {@link DeleteSnapshotsOption#INCLUDE_SNAPSHOTS} value in the
     * <code>deleteSnapshotsOption</code> parameter to specify how the snapshots
     * should be handled when the blob is deleted.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    void delete(OperationContext opContext, SelfRenewingLease lease)
        throws StorageException;

    /**
     * Checks to see if the blob exists, using the specified operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @return <code>true</code> if the blob exists, otherwise
     *         <code>false</code>.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    boolean exists(OperationContext opContext)
        throws StorageException;

    /**
     * Populates a blob's properties and metadata using the specified operation
     * context.
     * <p>
     * This method populates the blob's system properties and user-defined
     * metadata. Before reading a blob's properties or metadata, call this
     * method or its overload to retrieve the latest values for the blob's
     * properties and metadata from the Windows Azure storage service.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    void downloadAttributes(OperationContext opContext)
        throws StorageException;

    /**
     * Returns the blob's properties.
     * 
     * @return A {@link BlobProperties} object that represents the properties of
     *         the blob.
     */
    BlobProperties getProperties();

    /**
     * Opens a blob input stream to download the blob using the specified
     * operation context.
     * <p>
     * Use {@link CloudBlobClient#setStreamMinimumReadSizeInBytes} to configure
     * the read size.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @return An <code>InputStream</code> object that represents the stream to
     *         use for reading from the blob.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    InputStream openInputStream(BlobRequestOptions options,
        OperationContext opContext) throws StorageException;

    /**
     * Uploads the blob's metadata to the storage service using the specified
     * lease ID, request options, and operation context.
     * 
     * @param opContext
     *          An {@link OperationContext} object that represents the context
     *          for the current operation. This object is used to track requests
     *          to the storage service, and to provide additional runtime
     *          information about the operation.
     * 
     * @throws StorageException
     *           If a storage service error occurred.
     */
    void uploadMetadata(OperationContext opContext)
        throws StorageException;

    void uploadProperties(OperationContext opContext,
        SelfRenewingLease lease)
        throws StorageException;

    SelfRenewingLease acquireLease() throws StorageException;
    
    /**
     * Sets the minimum read block size to use with this Blob.
     * 
     * @param minimumReadSizeBytes
     *          The maximum block size, in bytes, for reading from a block blob
     *          while using a {@link BlobInputStream} object, ranging from 512
     *          bytes to 64 MB, inclusive.
     */
    void setStreamMinimumReadSizeInBytes(
        int minimumReadSizeBytes);

    /**
     * Sets the write block size to use with this Blob.
     * 
     * @param writeBlockSizeBytes
     *          The maximum block size, in bytes, for writing to a block blob
     *          while using a {@link BlobOutputStream} object, ranging from 1 MB
     *          to 4 MB, inclusive.
     * 
     * @throws IllegalArgumentException
     *           If <code>writeBlockSizeInBytes</code> is less than 1 MB or
     *           greater than 4 MB.
     */
    void setWriteBlockSizeInBytes(int writeBlockSizeBytes);

    CloudBlob getBlob();
  }

  /**
   * A thin wrapper over the {@link CloudBlockBlob} class that simply redirects calls
   * to the real object except in unit tests.
   */
  public abstract interface CloudBlockBlobWrapper
      extends CloudBlobWrapper {
    /**
     * Creates and opens an output stream to write data to the block blob using the specified 
     * operation context.
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    OutputStream openOutputStream(
        BlobRequestOptions options,
        OperationContext opContext) throws StorageException;
  }

  /**
   * A thin wrapper over the {@link CloudPageBlob} class that simply redirects calls
   * to the real object except in unit tests.
   */
  public abstract interface CloudPageBlobWrapper
      extends CloudBlobWrapper {
    /**
     * Creates a page blob using the specified request options and operation context.
     *
     * @param length
     *            The size, in bytes, of the page blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @throws IllegalArgumentException
     *             If the length is not a multiple of 512.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    void create(final long length, BlobRequestOptions options,
            OperationContext opContext) throws StorageException;
    

    /**
     * Uploads a range of contiguous pages, up to 4 MB in size, at the specified offset in the page blob, using the
     * specified lease ID, request options, and operation context.
     * 
     * @param sourceStream
     *            An <code>InputStream</code> object that represents the input stream to write to the page blob.
     * @param offset
     *            The offset, in number of bytes, at which to begin writing the data. This value must be a multiple of
     *            512.
     * @param length
     *            The length, in bytes, of the data to write. This value must be a multiple of 512.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @throws IllegalArgumentException
     *             If the offset or length are not multiples of 512, or if the length is greater than 4 MB.
     * @throws IOException
     *             If an I/O exception occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    void uploadPages(final InputStream sourceStream, final long offset,
        final long length, BlobRequestOptions options,
        OperationContext opContext) throws StorageException, IOException;

    /**
     * Returns a collection of page ranges and their starting and ending byte offsets using the specified request
     * options and operation context.
     *
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     *
     * @return An <code>ArrayList</code> object that represents the set of page ranges and their starting and ending
     *         byte offsets.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */

    ArrayList<PageRange> downloadPageRanges(BlobRequestOptions options,
            OperationContext opContext) throws StorageException;
    
    void uploadMetadata(OperationContext opContext)
        throws StorageException; 
  }
}