/*
 *  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.ivy.plugins.repository.vfs;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.ivy.core.settings.TimeoutConstraint;
import org.apache.ivy.plugins.repository.AbstractRepository;
import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.TransferEvent;
import org.apache.ivy.util.CopyProgressListener;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;

/**
 * Implementation of a VFS repository
 */
public class VfsRepository extends AbstractRepository {
    /**
     * Name of the resource defining the Ivy VFS Repo configuration.
     */
    private static final String IVY_VFS_CONFIG = "ivy_vfs.xml";

    private StandardFileSystemManager manager = null;

    private final CopyProgressListener progress = new RepositoryCopyProgressListener(this);

    /**
     * Create a new Ivy VFS Repository Instance
     */
    public VfsRepository() {
    }

    public VfsRepository(final TimeoutConstraint timeoutConstraint) {
        super(timeoutConstraint);
    }

    private FileSystemManager getVFSManager() throws IOException {
        synchronized (this) {
            if (manager == null) {
                manager = createVFSManager();
            }
        }
        return manager;
    }

    private StandardFileSystemManager createVFSManager() throws IOException {
        StandardFileSystemManager result = null;
        try {
            /*
             * The DefaultFileSystemManager gets its configuration from the jakarta-vfs-common
             * implementation which includes the res and tmp schemes which are of no use to use
             * here. Using StandardFileSystemManager lets us specify which schemes to support as
             * well as providing a mechanism to change this support without recompilation.
             */
            result = new StandardFileSystemManager() {
                protected void configurePlugins() throws FileSystemException {
                    // disable automatic loading potential unsupported extensions
                }
            };
            result.setConfiguration(getClass().getResource(IVY_VFS_CONFIG));
            result.init();

            // Generate and print a list of available schemes
            Message.verbose("Available VFS schemes...");
            String[] schemes = result.getSchemes();
            Arrays.sort(schemes);
            for (String scheme : schemes) {
                Message.verbose("VFS Supported Scheme: " + scheme);
            }
        } catch (FileSystemException e) {
            /*
             * If our attempt to initialize a VFS Repository fails we log the failure but continue
             * on. Given that an Ivy instance may involve numerous different repository types, it
             * seems overly cautious to throw a runtime exception on the initialization failure of
             * just one repository type.
             */
            Message.error("Unable to initialize VFS repository manager!");
            Message.error(e.getLocalizedMessage());
            throw new IOException(e.getLocalizedMessage(), e);
        }

        return result;
    }

    protected void finalize() {
        if (manager != null) {
            manager.close();
            manager = null;
        }
    }

    /**
     * Get a VfsResource
     *
     * @param vfsURI
     *            a <code>String</code> identifying a VFS Resource
     * @return Resource
     * @throws IOException on failure
     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
     */
    public Resource getResource(String vfsURI) throws IOException {
        return new VfsResource(vfsURI, getVFSManager());
    }

    /**
     * Transfer a VFS Resource from the repository to the local file system.
     *
     * @param srcVfsURI
     *            a <code>String</code> identifying the VFS resource to be fetched
     * @param destination
     *            a <code>File</code> identifying the destination file
     * @throws IOException on failure
     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
     */
    public void get(String srcVfsURI, File destination) throws IOException {
        VfsResource src = new VfsResource(srcVfsURI, getVFSManager());
        fireTransferInitiated(src, TransferEvent.REQUEST_GET);
        try {
            FileContent content = src.getContent();
            if (content == null) {
                throw new IllegalArgumentException("invalid vfs uri " + srcVfsURI
                        + ": no content found");
            }
            FileUtil.copy(content.getInputStream(), destination, progress);
        } catch (IOException | RuntimeException ex) {
            fireTransferError(ex);
            throw ex;
        }
    }

    /**
     * Return a listing of the contents of a parent directory. Listing is a set of strings
     * representing VFS URIs.
     *
     * @param vfsURI
     *            providing identifying a VFS provided resource
     * @return List
     * @throws IOException
     *             on failure.
     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
     */
    public List<String> list(String vfsURI) throws IOException {
        List<String> list = new ArrayList<>();
        Message.debug("list called for URI" + vfsURI);
        FileObject resourceImpl = getVFSManager().resolveFile(vfsURI);
        Message.debug("resourceImpl=" + resourceImpl.toString());
        Message.debug("resourceImpl.exists()" + resourceImpl.exists());
        Message.debug("resourceImpl.getType()" + resourceImpl.getType());
        Message.debug("FileType.FOLDER" + FileType.FOLDER);
        if (resourceImpl.exists() && resourceImpl.getType() == FileType.FOLDER) {
            List<FileObject> children = Arrays.asList(resourceImpl.getChildren());
            for (FileObject child : children) {
                Message.debug("child " + children.indexOf(child) + child.getName().getURI());
                list.add(VfsResource.normalize(child.getName().getURI()));
            }
        }
        return list;
    }

    /**
     * Transfer an Ivy resource to a VFS repository
     *
     * @param source
     *            a <code>File</code> identifying the local file to transfer to the repository
     * @param vfsURI
     *            a <code>String</code> identifying the destination VFS Resource.
     * @param overwrite
     *            whether to overwrite an existing resource.
     * @throws IOException on failure.
     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
     */
    public void put(File source, String vfsURI, boolean overwrite) throws IOException {
        VfsResource dest = new VfsResource(vfsURI, getVFSManager());
        fireTransferInitiated(dest, TransferEvent.REQUEST_PUT);
        if (dest.physicallyExists() && !overwrite) {
            throw new IOException("Cannot copy. Destination file: " + dest.getName()
                    + " exists and overwrite not set.");
        }
        if (dest.getContent() == null) {
            throw new IllegalArgumentException("invalid vfs uri " + vfsURI
                    + " to put data to: resource has no content");
        }

        FileUtil.copy(new FileInputStream(source), dest.getContent().getOutputStream(), progress);
    }

}
