/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * 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.uberfire.java.nio.file;

import org.uberfire.java.nio.IOException;
import org.uberfire.java.nio.file.attribute.BasicFileAttributes;

import static org.kie.soup.commons.validation.Preconditions.checkNotNull;

/**
 * Simple file tree walker that works in a similar manner to nftw(3C).
 *
 * @see Files#walkFileTree
 */

class FileTreeWalker {

    private final FileVisitor<? super Path> visitor;
    private final int maxDepth;

    FileTreeWalker(final FileVisitor<? super Path> visitor,
                   final int maxDepth) {
        this.visitor = checkNotNull("visitor",
                                    visitor);
        this.maxDepth = maxDepth;
    }

    /**
     * Walk file tree starting at the given file
     */
    void walk(final Path start) throws IOException {
        checkNotNull("start",
                     start);
        walk(start,
             0);
    }

    private FileVisitResult walk(final Path file,
                                 final int depth)
            throws IOException {
        IOException exc = null;
        BasicFileAttributes attrs = null;
        try {
            attrs = Files.readAttributes(file,
                                         BasicFileAttributes.class);
        } catch (IOException ex) {
            exc = ex;
        }

        if (exc != null) {
            return visitor.visitFileFailed(file,
                                           exc);
        }

        // at maximum depth or file is not a directory
        if (depth >= maxDepth || !attrs.isDirectory()) {
            return visitor.visitFile(file,
                                     attrs);
        }

        DirectoryStream<? extends Path> stream = null;
        FileVisitResult result = null;

        try {
            stream = Files.newDirectoryStream(file);
        } catch (IOException ex) {
            return visitor.visitFileFailed(file,
                                           ex);
        }

        IOException postException = null;

        try {
            result = visitor.preVisitDirectory(file,
                                               attrs);
            if (result != FileVisitResult.CONTINUE) {
                return result;
            }

            try {
                for (final Path entry : stream) {
                    result = walk(entry,
                                  depth + 1);
                    if (result == null || result == FileVisitResult.TERMINATE) {
                        return result;
                    }
                    if (result == FileVisitResult.SKIP_SIBLINGS) {
                        break;
                    }
                }
            } catch (IOException ex) {
                postException = ex;
            }
        } finally {
            try {
                stream.close();
            } catch (IOException ex) {
                if (postException == null) {
                    postException = ex;
                }
            }
        }

        return visitor.postVisitDirectory(file,
                                          postException);
    }
}
