/*
 * 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.nifi.controller.repository;

import org.apache.nifi.controller.repository.claim.ContentClaim;
import org.apache.nifi.controller.repository.claim.ResourceClaim;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Set;

/**
 * Defines the capabilities of a content repository. Append options are not
 * available on the methods but a merge capability is provided which between
 * that and creating new claims a merge is available.
 *
 */
public interface ContentRepository {

    /**
     * Initializes the Content Repository, providing to it the
     * ContentRepositoryContext.
     *
     * @param context to initialize repository
     * @throws java.io.IOException if unable to init
     */
    void initialize(ContentRepositoryContext context) throws IOException;

    /**
     * Shuts down the Content Repository, freeing any resources that may be
     * held. This is called when an administrator shuts down NiFi.
     */
    void shutdown();

    /**
     * @return the names of all Containers that exist for this Content
     * Repository
     */
    Set<String> getContainerNames();

    /**
     * @param containerName name of container to check capacity on
     * @return the maximum number of bytes that can be stored in the storage
     * mechanism that backs the container with the given name
     * @throws java.io.IOException if unable to check capacity
     * @throws IllegalArgumentException if no container exists with the given
     * name
     */
    long getContainerCapacity(String containerName) throws IOException;

    /**
     * @param containerName to check space on
     * @return the number of bytes available to be used used by the storage
     * mechanism that backs the container with the given name
     * @throws java.io.IOException if unable to check space
     * @throws IllegalArgumentException if no container exists with the given
     * name
     */
    long getContainerUsableSpace(String containerName) throws IOException;

    /**
     * Returns the name of the FileStore that the given container is stored on, or <code>null</code>
     * if not applicable or unable to determine the file store name
     *
     * @param containerName the name of the container
     * @return the name of the FileStore
     */
    String getContainerFileStoreName(String containerName);

    /**
     * Creates a new content claim
     *
     * @param lossTolerant indicates whether the content for the new claim is
     * loss tolerant. If true the repository might choose more volatile storage
     * options which could increase performance for a tradeoff with reliability
     * @return newly created claim
     * @throws java.io.IOException if unable to create claim
     */
    ContentClaim create(boolean lossTolerant) throws IOException;

    /**
     * Increments the number of claimants for the given claim
     *
     * @param claim to increment
     * @return the number of claimants after incrementing
     */
    int incrementClaimaintCount(ContentClaim claim);

    /**
     * Obtains the current number of claimants for the given claim
     *
     * @param claim to get count of
     * @return the number of claimants
     */
    int getClaimantCount(ContentClaim claim);

    /**
     * Reduces the number of claimants for the given claim. Even if the given
     * claim is null or content cannot be found or removed no exception will be
     * thrown.
     *
     * @param claim to decrement
     * @return new claimant count for the given claim
     */
    int decrementClaimantCount(ContentClaim claim);

    /**
     * Removes the content indicated by the given claim
     *
     * @param claim to remove
     *
     * @return a boolean indicating whether or not the destruction of the claim
     * was successful
     */
    boolean remove(ContentClaim claim);

    /**
     * Clones the content for the given content claim and returns content claim
     * of the new object
     *
     * @param original to clone
     * @param lossTolerant if can be place in a loss tolerant repository
     * @return new claim
     * @throws IOException if an IO error occurs. Any content written to the new
     * destination prior to the error will be destroyed
     */
    ContentClaim clone(ContentClaim original, boolean lossTolerant) throws IOException;

    /**
     * Imports content from the given path creating a new content object and
     * claim within the repository.
     *
     * @return the size of the claim
     * @param content to import from
     * @param claim the claim to write imported content to
     * @throws IOException if failure to read given content
     */
    long importFrom(Path content, ContentClaim claim) throws IOException;

    /**
     * Imports content from the given stream creating a new content object and
     * claim within the repository.
     *
     * @return the size of the claim
     * @param content to import from
     * @param claim the claim to write imported content to
     * @throws IOException if unable to read content
     */
    long importFrom(InputStream content, ContentClaim claim) throws IOException;

    /**
     * Exports the content of the given claim to the given destination.
     *
     * @return the size of the destination or the claim
     * @param claim to export from
     * @param destination where to export data
     * @param append if true appends to the destination; false overwrites
     * @throws IOException if an IO error occurs. The state of the content for
     * the given destination is unknown and callers should consider whether they
     * should clean up any partially created paths
     */
    long exportTo(ContentClaim claim, Path destination, boolean append) throws IOException;

    /**
     * Exports the content of the given claim to the given destination.
     *
     * @return the size of the destination or the claim
     * @param claim to export from
     * @param destination where to export data
     * @param append if true appends to the destination; false overwrites
     * @param offset the offset at which the claim should start being copied
     * @param length the number of bytes to copy
     * @throws IOException if an IO error occurs. The state of the content for
     * the given destination is unknown and callers should consider whether they
     * should clean up any partially created paths
     */
    long exportTo(ContentClaim claim, Path destination, boolean append, long offset, long length) throws IOException;

    /**
     * Exports the content of the given claim to the given destination.
     *
     * @return the size of the claim
     * @param claim to export from
     * @param destination where to export data
     * @throws IOException if an IO error occurs.
     */
    long exportTo(ContentClaim claim, OutputStream destination) throws IOException;

    /**
     * Exports a subset of the content of the given claim, starting at offset
     * and copying length bytes, to the given destination.
     *
     * @return the number of bytes copied
     * @param claim to export from
     * @param destination where to export data
     * @param offset the offset into the claim at which the copy should begin
     * @param length the number of bytes to copy
     * @throws IOException if an IO error occurs.
     */
    long exportTo(ContentClaim claim, OutputStream destination, long offset, long length) throws IOException;

    /**
     * @param claim to get size of
     * @return size in bytes of content for given claim
     * @throws IOException if size check failed
     */
    long size(ContentClaim claim) throws IOException;

    /**
     * @param claim to get size of
     * @return size in bytes of the file/object backing the given resource claim, or 0 if this operation is not supported by the implementation
     * @throws IOException if size check failed
     */
    long size(ResourceClaim claim) throws IOException;

    /**
     * Provides access to the input stream for the given claim
     *
     * @param claim to read from
     * @return InputStream over the content of the given claim
     * @throws IOException if unable to read
     */
    InputStream read(ContentClaim claim) throws IOException;

    /**
     * Provides access ot the input stream for the entire Resource Claim
     * @param claim the resource claim to read from
     * @return InputStream over the content of the entire Resource Claim
     * @throws IOException if unable to read
     */
    InputStream read(ResourceClaim claim) throws IOException;

    /**
     * Indicates whether or not this Content Repository supports obtaining an InputStream for
     * an entire Resource Claim. If this method returns <code>false</code>, the {@link #read(ResourceClaim)} should not
     * be called and instead {@link #read(ContentClaim)} should always be used
     * @return <code>true</code> if reading an entire Resource Claim is allowed, <code>false</code> otherwise
     */
    default boolean isResourceClaimStreamSupported() {
        return true;
    }

    /**
     * Obtains an OutputStream to the content for the given claim.
     *
     * @param claim to write to
     * @return the stream to write to
     * @throws IOException if unable to obtain stream
     */
    OutputStream write(ContentClaim claim) throws IOException;

    /**
     * Purges the contents of the repository, as if the repository were newly
     * created.
     */
    void purge();

    /**
     * Performs any cleanup actions that may need to be taken upon system
     * restart. For example, if content was partially written to the repository
     * before the restart, the repository is given a chance to handle this data
     */
    void cleanup();

    /**
     * @param contentClaim the Content Claim to check
     * @return Returns a boolean indicating whether or not the content specified
     * by the given claim can be read, regardless of whether the content has
     * been archived or not. If the ContentRepository does not implement
     * archiving capabilities, this method will return <code>false</code>
     *
     * @throws IOException if unable to determine accessibility
     */
    boolean isAccessible(ContentClaim contentClaim) throws IOException;

    /**
     * Optional operation that returns a List of all Resource Claims that exist in the given Container that are considered "active" (i.e., not archived)
     * @param containerName the name of the container
     * @return a List of all Resource Claims that exist in the given Container
     * @throws IOException if unable to obtain a listing due to an IO failure
     * @throws UnsupportedOperationException if this repository does not implement this capability.
     * @see #isActiveResourceClaimsSupported()
     */
    default Set<ResourceClaim> getActiveResourceClaims(String containerName) throws IOException {
        throw new UnsupportedOperationException();
    }

    /**
     * Indicates whether or not the repository supports obtaining a list of active Resource Claims via the {@link #getActiveResourceClaims(String)} method
     * @return <code>true</code> if the operation is supported, <code>false</code> otherwise
     */
    default boolean isActiveResourceClaimsSupported() {
        return false;
    }
}
