/*
 *  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
 *
 *      https://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.IOException;
import java.io.InputStream;
import java.util.ArrayList;
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.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.resolver.VfsResolver;
import org.apache.ivy.util.Message;

/**
 * VFS implementation of the Resource interface
 */
public class VfsResource implements Resource {
    private String vfsURI;

    private FileSystemManager fsManager;

    private transient boolean init = false;

    private transient boolean exists;

    private transient long lastModified;

    private transient long contentLength;

    private transient FileContent content = null;

    private transient FileObject resourceImpl;

    // Constructor
    public VfsResource(String vfsURI, FileSystemManager fsManager) {
        this.vfsURI = vfsURI;
        this.fsManager = fsManager;
        this.init = false;
    }

    private void init() {
        if (!init) {
            try {
                resourceImpl = fsManager.resolveFile(vfsURI);
                content = resourceImpl.getContent();
                exists = resourceImpl.exists();
                lastModified = content.getLastModifiedTime();
                contentLength = content.getSize();
            } catch (FileSystemException e) {
                Message.debug(e);
                Message.verbose(e.getLocalizedMessage());
                exists = false;
                lastModified = 0;
                contentLength = 0;
            }
            init = true;
        }
    }

    /**
     * Get a list of direct descendants of the given resource. Note that attempts to get a list of
     * children does <em>not</em> result in an error. Instead an error message is
     * logged and an empty ArrayList returned.
     *
     * @return A <code>ArrayList</code> of VFSResources
     */
    public List<String> getChildren() {
        init();
        List<String> list = new ArrayList<>();
        try {
            if (resourceImpl != null && resourceImpl.exists()
                    && resourceImpl.getType() == FileType.FOLDER) {
                for (FileObject child : resourceImpl.getChildren()) {
                    list.add(normalize(child.getName().getURI()));
                }
            }
        } catch (IOException e) {
            Message.debug(e);
            Message.verbose(e.getLocalizedMessage());
        }
        return list;
    }

    public FileContent getContent() {
        init();
        return content;
    }

    /**
     * Get the name of the resource.
     *
     * @return a <code>String</code> representing the Resource URL.
     */
    public String getName() {
        return normalize(vfsURI);
    }

    public Resource clone(String cloneName) {
        return new VfsResource(cloneName, fsManager);
    }

    /**
     * The VFS FileName getURI method seems to have a bug in it where file: URIs will have 4 forward
     * slashes instead of 3.
     *
     * @param vfsURI ditto
     * @return a normalized String representing the VFS URI
     */
    public static String normalize(String vfsURI) {
        if (vfsURI == null) {
            return "";
        }

        if (vfsURI.startsWith("file:////")) {
            vfsURI = vfsURI.replaceFirst("////", "///");
        }
        return vfsURI;
    }

    /**
     * Get the last modification time of the resource.
     *
     * @return a <code>long</code> indicating last modified time.
     */
    public long getLastModified() {
        init();
        return lastModified;
    }

    /**
     * Get the size of the resource
     *
     * @return a <code>long</code> representing the size of the resource (in bytes).
     */
    public long getContentLength() {
        init();
        return contentLength;
    }

    /**
     * Flag indicating whether a resource is available for querying
     *
     * @return <code>true</code> if the resource is available for querying, <code>false</code>
     *         otherwise.
     */
    public boolean exists() {
        init();
        return exists;
    }

    /**
     * Return a flag indicating whether a provided VFS resource physically exists
     *
     * @return <code>true</code> if the resource physically exists, <code>false</code> otherwise.
     */
    public boolean physicallyExists() {
        // TODO: there is no need for this method anymore, replace it by calling exists();
        init();

        try {
            return resourceImpl.exists();
            // originally I only checked for a FileSystemException. I expanded it to
            // include all exceptions when I found it would throw a NPE exception when the query was
            // run on ill-formed VFS URI.
        } catch (Exception e) {
            Message.verbose("Fail to check the existence of the resource " + getName(), e);
            return false;
        }
    }

    public String toString() {
        return VfsResolver.prepareForDisplay(getName());
    }

    public boolean isLocal() {
        return getName().startsWith("file:");
    }

    public InputStream openStream() throws IOException {
        return getContent().getInputStream();
    }
}
