/*
 * Copyright 2016 the original author or authors.
 *
 * 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.gradle.api.internal.file;

import org.apache.commons.lang.StringUtils;
import org.gradle.api.UncheckedIOException;
import org.gradle.internal.nativeintegration.filesystem.FileSystem;
import org.gradle.internal.os.OperatingSystem;
import org.gradle.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

class FileNormaliser {
    private static final String FILE_PATH_SEPARATORS = File.separatorChar != '/' ? ("/" + File.separator) : File.separator;
    private static final String PATH_SEPARATOR_PATTERN = "[" + Pattern.quote(FILE_PATH_SEPARATORS) + "]";
    private static final String DOTS_SEGMENT_PATTERN = "(?:^|" + PATH_SEPARATOR_PATTERN + ")\\.\\.?(?:$|" + PATH_SEPARATOR_PATTERN + ")";
    private static final String EMPTY_SEGMENT_PATTERN = PATH_SEPARATOR_PATTERN + PATH_SEPARATOR_PATTERN;
    private static final String ENDS_WITH_PATH_SEPARATOR_PATTERN = PATH_SEPARATOR_PATTERN + "$";
    private static final Pattern NORMALISING_REQUIRED_PATTERN_DOTS_OR_EMPTY_SEGMENT = Pattern.compile("(?:"
        + DOTS_SEGMENT_PATTERN
        + "|"
        + EMPTY_SEGMENT_PATTERN
        + "|"
        + ENDS_WITH_PATH_SEPARATOR_PATTERN
        + ")");

    private final FileSystem fileSystem;

    FileNormaliser(FileSystem fileSystem) {
        this.fileSystem = fileSystem;
    }

    // normalizes a path in similar ways as File.getCanonicalFile(), except that it
    // does NOT resolve symlinks (by design)
    public File normalise(File file) {
        try {
            assert file.isAbsolute() : String.format("Cannot normalize a relative file: '%s'", file);

            if (OperatingSystem.current().isWindows()) {
                // on Windows, File.getCanonicalFile() doesn't resolve symlinks
                return file.getCanonicalFile();
            }

            File candidate;
            String filePath = file.getPath();
            List<String> path = null;
            if (isNormalisingRequired(filePath)) {
                path = splitAndNormalisePath(filePath);
                String resolvedPath = CollectionUtils.join(File.separator, path);
                boolean needLeadingSeparator = File.listRoots()[0].getPath().startsWith(File.separator);
                if (needLeadingSeparator) {
                    resolvedPath = File.separator + resolvedPath;
                }
                candidate = new File(resolvedPath);
            } else {
                candidate = file;
            }

            if (fileSystem.isCaseSensitive()) {
                return candidate;
            }

            // Short-circuit the slower lookup method by using the canonical file
            File canonical = candidate.getCanonicalFile();
            if (candidate.getPath().equalsIgnoreCase(canonical.getPath())) {
                return canonical;
            }

            // Canonical path is different to what we expected (eg there is a link somewhere in there). Normalise a segment at a time
            // TODO - start resolving only from where the expected and canonical paths are different
            if (path == null) {
                path = splitAndNormalisePath(filePath);
            }
            File current = File.listRoots()[0];
            for (int pos = 0; pos < path.size(); pos++) {
                File child = findChild(current, path.get(pos));
                if (child == null) {
                    current = new File(current, CollectionUtils.join(File.separator, path.subList(pos, path.size())));
                    break;
                }
                current = child;
            }
            return current;
        } catch (IOException e) {
            throw new UncheckedIOException(String.format("Could not normalize path for file '%s'.", file), e);
        }
    }

    boolean isNormalisingRequired(String filePath) {
        return NORMALISING_REQUIRED_PATTERN_DOTS_OR_EMPTY_SEGMENT.matcher(filePath).find();
    }

    private List<String> splitAndNormalisePath(String filePath) {
        String[] segments = splitPath(filePath);
        List<String> path = new ArrayList<String>(segments.length);
        for (String segment : segments) {
            if (segment.equals("..")) {
                if (!path.isEmpty()) {
                    path.remove(path.size() - 1);
                }
            } else if (!segment.equals(".") && segment.length() > 0) {
                path.add(segment);
            }
        }
        return path;
    }

    private String[] splitPath(String filePath) {
        return StringUtils.split(filePath, FILE_PATH_SEPARATORS);
    }

    private File findChild(File current, String segment) throws IOException {
        String[] children = current.list();
        if (children == null) {
            return null;
        }
        // TODO - find some native methods for doing this
        for (String child : children) {
            if (child.equalsIgnoreCase(segment)) {
                return new File(current, child);
            }
        }
        return new File(current, segment);
    }
}

@@ -3142,32 +3142,123 @@
 %0A            %7D%0A%0A
+            // If the file system is case sensitive, we don't have to normalise it further%0A
             if (
@@ -3285,24 +3285,24 @@
 sitive()) %7B%0A
-
             
@@ -3958,440 +3958,50 @@
     
-File current = File.listRoots()%5B0%5D;%0A            for (int pos = 0; pos %3C path.size(); pos++) %7B%0A                File child = findChild(current, path.get(pos));%0A                if (child == null) %7B%0A                    current = new File(current, CollectionUtils.join(File.separator, path.subList(pos, path.size())));%0A                    break;%0A                %7D%0A                current = child;%0A            %7D%0A            return current
+return normaliseUnixPathIgnoringCase(path)
 ;%0A  
@@ -4986,17 +4986,541 @@
 ile 
-findChild
+normaliseUnixPathIgnoringCase(List%3CString%3E path) throws IOException %7B%0A        File current = File.listRoots()%5B0%5D;%0A        for (int pos = 0; pos %3C path.size(); pos++) %7B%0A            File child = findChildIgnoringCase(current, path.get(pos));%0A            if (child == null) %7B%0A                current = new File(current, CollectionUtils.join(File.separator, path.subList(pos, path.size())));%0A                break;%0A            %7D%0A            current = child;%0A        %7D%0A        return current;%0A    %7D%0A%0A    private File findChildIgnoringCase
 (Fil

Refactor to improve clarity