/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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 org.jboss.shrinkwrap.api;

import java.io.OutputStream;
import java.util.Collection;
import java.util.Map;

import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.NamedAsset;
import org.jboss.shrinkwrap.api.exporter.StreamExporter;
import org.jboss.shrinkwrap.api.formatter.Formatter;
import org.jboss.shrinkwrap.api.formatter.Formatters;

/**
 * Represents a collection of resources which may be constructed programmatically. In effect this represents a virtual
 * filesystem.
 *
 * <br />
 * <br />
 * All {@link Archive} types support the addition of {@link Node}s under a designated {@link ArchivePath} (context). The
 * contents of a {@link Node} are either a directory or {@link Asset}.
 *
 * <br />
 * <br />
 * {@link Archive}s are generally created via an {@link ArchiveFactory} or via the default configuration shortcut
 * {@link ShrinkWrap} utility class.
 *
 * <br />
 * <br />
 * Because {@link Archive}s are {@link Assignable}, they may be wrapped in another user "view" used to perform
 * operations like adding JavaEE Spec-specific resources or exporting in ZIP format.
 *
 * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
 * @version $Revision: $
 */
public interface Archive<T extends Archive<T>> extends Assignable {
    // -------------------------------------------------------------------------------------||
    // Contracts --------------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Obtains the name of this archive (ie. myLibrary.jar)
     */
    String getName();

    /**
     * Obtains a globally-unique identifier for this {@link Archive}
     *
     * @return
     */
    String getId();

    /**
     * Adds the specified asset under the specified path into the target context
     *
     * @param target
     *            The context under which to add the assets
     * @param asset
     * @return
     * @throws IllegalArgumentException
     *             If no target or assets were specified
     * @throws IllegalArchivePathException
     *             If the target is invalid.
     */
    T add(Asset asset, ArchivePath target) throws IllegalArgumentException;

    /**
     * Adds the specified asset under the specified target (directory) using the specified name. The resultant path will
     * be treating the specified path as a prefix namespace, then appending the name.
     *
     * @param target
     *            The context directory under which to add the asset
     * @param name
     *            The name to assign the assent under the target namespace
     * @param asset
     * @return
     * @throws IllegalArgumentException
     *             If the target, name, or asset was not specified
     * @throws IllegalArchivePathException
     *             If the target is invalid.
     */
    T add(Asset asset, ArchivePath target, String name) throws IllegalArgumentException;

    /**
     * Adds the specified asset under the specified target (directory) using the specified name. The resultant path will
     * be treating the specified path as a prefix namespace, then appending the name.
     *
     * @param target
     *            The context directory under which to add the asset
     * @param name
     *            The name to assign the assent under the target namespace
     * @param asset
     * @return
     * @throws IllegalArgumentException
     *             If the target, name, or asset was not specified
     * @throws IllegalArchivePathException
     *             If the target is invalid.
     */
    T add(Asset asset, String target, String name) throws IllegalArgumentException;

    /**
     * Adds the asset encapsulated within the specified {@link NamedAsset} under the encapsulated name and target
     * (directory)
     *
     * @param namedAsset
     * @return
     * @throws IllegalArgumentException
     *             If either the target or asset is not specified
     * @throws IllegalArchivePathException
     *             If the target is invalid.
     */
    T add(NamedAsset namedAsset) throws IllegalArgumentException;

    /**
     * Adds the specified resource under the context denoted by the specified target
     *
     * @param target
     * @param asset
     * @return
     * @throws IllegalArgumentException
     *             If either the target or asset is not specified
     * @throws IllegalArchivePathException
     *             If the target is invalid.
     */
    T add(Asset asset, String target) throws IllegalArgumentException;

    /**
     * Adds the specified directory.
     *
     * @param path
     *            The path to add
     * @return This archive
     * @throws IllegalArgumentException
     *             If no path was specified
     * @throws IllegalArchivePathException
     *             If the path is invalid.
     */
    T addAsDirectory(String path) throws IllegalArgumentException;

    /**
     * Adds the specified directories.
     *
     * @param paths
     *            The paths to add
     * @return This archive
     * @throws IllegalArgumentException
     *             If no paths were specified
     * @throws IllegalArchivePathException
     *             If at least one path is invalid.
     */
    T addAsDirectories(String... paths) throws IllegalArgumentException;

    /**
     * Adds the specified directory.
     *
     * @param path
     *            The path to add
     * @return This archive
     * @throws IllegalArgumentException
     *             If no path was specified
     * @throws IllegalArchivePathException
     *             If the path is invalid.
     */
    T addAsDirectory(ArchivePath path) throws IllegalArgumentException;

    /**
     * Adds the specified directories.
     *
     * @param paths
     *            The paths to add
     * @return This archive
     * @throws IllegalArgumentException
     *             If no paths were specified
     * @throws IllegalArchivePathException
     *             If at least one path is invalid.
     */
    T addAsDirectories(ArchivePath... paths) throws IllegalArgumentException;

    /**
     * Add an array of listeners for call back based.
     *
     * @param listener
     *            CallBack on add
     * @return This archive
     */
    T addHandlers(ArchiveEventHandler... handlers);

    /**
     * Obtains the {@link Node} located at the specified path
     *
     * @param path
     * @return The {@link Node}, or null if nothing is found at the specified path
     * @throws IllegalArgumentException
     *             If the path is not specified
     */
    Node get(ArchivePath path) throws IllegalArgumentException;

    /**
     * Obtains the {@link Node} located at the specified path
     *
     * @param path
     * @return The {@link Node}, or null if nothing is found at the Path
     * @throws IllegalArgumentException
     *             If the path is not specified
     */
    Node get(String path) throws IllegalArgumentException;

    /**
     * Get a nested {@link Archive} as a specific type.<br/>
     * <br/>
     *
     * The found Archives must have been added as a Archive, no import is performed.
     *
     * @param <X>
     * @param type
     *            The Type to return the Archive as
     * @param path
     *            The location of the Archive
     * @return The found Archive as given type or null if none found at given path
     * @throws IllegalArgumentException
     *             if found {@link Asset} is not pointing to a {@link Archive}
     * @see Archive#getAsType(Class, ArchivePath)
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> X getAsType(Class<X> type, String path);

    /**
     * Get a nested {@link Archive} as a specific type.<br/>
     * <br/>
     *
     * The found Archives must have been added as a Archive, no import is performed.
     *
     * @param <X>
     * @param type
     *            The Type to return the Archive as
     * @param path
     *            The location of the Archive
     * @return The found Archive as given type or null if none found at given {@link ArchivePath}
     * @throws IllegalArgumentException
     *             if found {@link Asset} is not pointing to a {@link Archive}
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> X getAsType(Class<X> type, ArchivePath path);

    /**
     * Get all nested {@link Archive} matching the filter as a specific type.<br/>
     * <br/>
     *
     * The found Archives must have been added as a Archive, no import is performed.
     *
     * @param <X>
     * @param type
     *            The Type to return the Archive as
     * @param filter
     *            Filter to match result
     * @return A {@link Collection} of found Archives matching given filter or empty {@link Collection} if non found.
     * @throws IllegalArgumentException
     *             if found {@link Asset} is not pointing to a {@link Archive}
     * @see Archive#getAsType(Class, ArchivePath)
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> Collection<X> getAsType(Class<X> type, Filter<ArchivePath> filter);

    /**
     * Get a nested {@link Archive} as a specific type using the specify {@code ArchiveFormat}
     *
     * @param type
     *            The Type to return the Archive as
     * @param path
     *            The location of the Archive
     * @param archiveFormat
     *            The archive format
     * @return The found Archive as given type or null if none found at the given path
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> X getAsType(Class<X> type, String path, ArchiveFormat archiveFormat);

    /**
     * Get a nested {@link Archive} located in a {@code ArchivePath} as a specific type using the specify
     * {@code ArchiveFormat}
     *
     * @param type
     *            The Type to return the Archive as
     * @param path
     *            The location of the Archive
     * @param archiveFormat
     *            The archive format
     * @return The found Archive as given type or null if none found at given {@link ArchivePath}
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> X getAsType(Class<X> type, ArchivePath path, ArchiveFormat archiveFormat);

    /**
     * Get all nested {@link Archive} matching the filter as a specific type using the specify {@code ArchiveFormat}.
     *
     * @param <X>
     * @param type
     *            The Type to return the Archive as
     * @param filter
     *            Filter to match result
     * @param archiveFormat
     *            The archive format
     * @return A {@link Collection} of found Archives matching given filter or empty {@link Collection} if non found.
     * @see Archive#getAsType(Class, ArchivePath, ArchiveFormat)
     * @see Archive#add(Archive, ArchivePath, Class)
     * @see Archive#add(Archive, String, Class)
     */
    <X extends Archive<X>> Collection<X> getAsType(Class<X> type, Filter<ArchivePath> filter,
        ArchiveFormat archiveFormat);

    /**
     * Denotes whether this archive contains a resource at the specified path
     *
     * @param path
     * @return
     * @throws IllegalArgumentException
     *             If the path is not specified
     */
    boolean contains(ArchivePath path) throws IllegalArgumentException;

    /**
     * Denotes whether this archive contains a resource at the specified path
     *
     * @param path
     * @return
     * @throws IllegalArgumentException
     *             If the path is not specified
     */
    boolean contains(String path) throws IllegalArgumentException;

    /**
     * Removes the {@link Node} in the {@link Archive} at the specified {@link ArchivePath}. If the path is a directory,
     * recursively removes all contents. If the path does not exist, return null.
     *
     * @param path
     * @return The Node removed
     */
    Node delete(ArchivePath path) throws IllegalArgumentException;

    /**
     * Removes the {@link Node} in the {@link Archive} at the {@link ArchivePath} indicated by the specified String
     * archivePath. If the path is a directory, recursively removes all contents. If the path does not exist, return
     * null.
     *
     * @param archivePath
     * @return The Node removed
     * @see #delete(ArchivePath)
     */
    Node delete(String archivePath) throws IllegalArgumentException;

    /**
     * Obtains all assets in this archive, along with their respective paths. The returned Map will be an immutable
     * view.
     *
     * @return
     */
    Map<ArchivePath, Node> getContent();

    /**
     * Obtains all assets matching given filter in this archive, along with its respective Path. The returned Map will
     * be an immutable view.
     *
     * @return
     */
    Map<ArchivePath, Node> getContent(Filter<ArchivePath> filter);

    /**
     * Obtains all assets matching given filter in this archive as a new Archive.<br/>
     * <br/>
     * This is an alias for shallowCopy(Filter).
     *
     * @see org.jboss.shrinkwrap.api.Archive#shallowCopy(Filter)
     * @param filter
     * @return
     */
    T filter(Filter<ArchivePath> filter);

    /**
     * Add an archive under a specific context and maintain the archive name as context path.
     *
     * @param path
     *            to use
     * @param archive
     *            to add
     * @param exporter
     *            Exporter type to use in fulfilling the {@link Asset#openStream()} contract for the added (nested)
     *            archive.
     * @return
     * @throws IllegalArgumentException
     *             If any argument is not specified
     */
    T add(Archive<?> archive, ArchivePath path, Class<? extends StreamExporter> exporter)
        throws IllegalArgumentException;

    /**
     * Add an archive under a specific context and maintain the archive name as context path.
     *
     * @param path
     *            to use
     * @param archive
     *            to add
     * @param exporter
     *            Exporter type to use in fulfilling the {@link Asset#openStream()} contract for the added (nested)
     *            archive.
     * @return
     * @throws IllegalArgumentException
     *             If the path or archive are not specified
     */
    T add(Archive<?> archive, String path, Class<? extends StreamExporter> exporter) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive without maintaining the archive name in the context path.
     *
     * @param source
     *            Archive to add contents from
     * @return
     * @throws IllegalArgumentException
     *             If the existing archive is not specified
     */
    T merge(Archive<?> source) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive without maintaining the archive name in the context path.
     *
     * The filter control which {@link ArchivePath}s to include form the source {@link Archive}.
     *
     * @param source
     *            Archive to add contents from
     * @return
     * @throws IllegalArgumentException
     *             If the existing archive is not specified
     */
    T merge(Archive<?> source, Filter<ArchivePath> filter) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive in a specific path without maintaining the archive name in the
     * context path.
     *
     * @param source
     *            Archive to add contents from
     * @param path
     *            Path to add contents to
     * @return
     * @throws IllegalArgumentException
     *             If the path or existing archive is not specified
     */
    T merge(Archive<?> source, ArchivePath path) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive in a specific path without maintaining the archive name in the
     * context path.
     *
     * @param source
     *            Archive to add contents from
     * @param path
     *            Path to add contents to
     * @return
     * @throws IllegalArgumentException
     *             If the path or existing archive is not specified
     */
    T merge(Archive<?> source, String path) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive in a specific path without maintaining the archive name in the
     * context path. The filter control which {@link ArchivePath}s to include form the source {@link Archive}.
     *
     * @param source
     *            Archive to add contents from
     * @param path
     *            Path to add contents to
     * @param filter
     *            Filter to use for including {@link Asset}s in the merge.
     * @return
     * @throws IllegalArgumentException
     *             If the path or existing archive is not specified
     */
    T merge(Archive<?> source, ArchivePath path, Filter<ArchivePath> filter) throws IllegalArgumentException;

    /**
     * Merge the contents from an existing archive in a specific path without maintaining the archive name in the
     * context path. The filter control which {@link ArchivePath}s to include form the source {@link Archive}.
     *
     * @param source
     *            Archive to add contents from
     * @param path
     *            Path to add contents to
     * @param filter
     *            Filter to use for including {@link Asset}s in the merge.
     * @return
     * @throws IllegalArgumentException
     *             If the path or existing archive is not specified
     */
    T merge(Archive<?> source, String path, Filter<ArchivePath> filter) throws IllegalArgumentException;

    /**
     * Moves the asset under the source path to the target path.
     *
     * @param source
     *            The context under which to remove the assets
     * @param target
     *            The context under which to add the moved assets
     * @return the resulting archive with the moved assets
     * @throws IllegalArgumentException
     *             If any of the paths is not specified
     * @throws IllegalArchivePathException
     *             If the source path is invalid.
     */
    T move(ArchivePath source, ArchivePath target) throws IllegalArgumentException, IllegalArchivePathException;

    /**
     * Moves the asset under the source path to the target path.
     *
     * @param source
     *            The context under which to remove the assets
     * @param target
     *            The context under which to add the moved assets
     * @return the resulting archive with the moved assets
     * @throws IllegalArgumentException
     *             If any of the paths is not specified
     * @throws IllegalArchivePathException
     *             If the source path is invalid.
     */
    T move(String source, String target) throws IllegalArgumentException, IllegalArchivePathException;

    /**
     * Acts as a shorthand for {@link Archive#toString(Formatter)} where the {@link Formatters#SIMPLE} is leveraged.
     *
     * @return
     */
    @Override
    String toString();

    /**
     * If "true" is specified, acts as a shorthand for {@link Archive#toString(Formatter)} where the
     * {@link Formatters#VERBOSE} is leveraged. Otherwise the {@link Formatters#SIMPLE} will be used (equivalent to
     * {@link Archive#toString()}).
     *
     * @return
     */
    String toString(boolean verbose);

    /**
     * Returns a view of this {@link Archive} as returned from the specified {@link Formatter}. Common options may be to
     * use the predefined formatters located in {@link Formatters}
     *
     * @param formatter
     * @return
     * @throws IllegalArgumentException
     *             If the formatter is not specified
     */
    String toString(Formatter formatter) throws IllegalArgumentException;

    /**
     * Prints the content of this {@link Archive} to the specified {@link OutputStream} on the format defined by the
     * specified {@link Formatter}. The caller is responsible for opening, flushing and eventually closing the stream.
     *
     * @param outputStream
     *            the stream to print the archive contents to
     * @param formatter
     *            the output format
     * @throws IllegalArgumentException
     *             if an exceptions occur when writing the archive contents.
     */
    void writeTo(OutputStream outputStream, Formatter formatter) throws IllegalArgumentException;

    /**
     * Creates a shallow copy of this {@link Archive}. Assets from this archive are made available under the same paths.
     * However, removing old assets or adding new assets on this archive affects does not affect the new archive.
     *
     * @return a new archive with a copy of the pointers to the assets
     */
    Archive<T> shallowCopy();

    /**
     * Creates a shallow copy of this {@link Archive} based on given filter.Assets from this archive are made available
     * under the same paths. However, removing old assets or adding new assets on this archive affects does not affect
     * the new archive.
     *
     * @return a new archive with a copy of the pointers to the assets
     */
    Archive<T> shallowCopy(Filter<ArchivePath> filter);
}
