/*******************************************************************************
 * Copyright 2016 Jalian Systems Pvt. Ltd.
 * 
 * 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 net.sourceforge.marathon.resource.navigator;

import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemLoopException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.EnumSet;
import java.util.Optional;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javafx.scene.control.ButtonType;
import net.sourceforge.marathon.resource.ResourceView.Operation;

/**
 * Sample code that copies files in a similar manner to the cp(1) program.
 */

public class Copy {

    public static final Logger LOGGER = Logger.getLogger(Copy.class.getName());

    /**
     * Copy source file to target location.
     *
     * @return
     */
    static boolean copyFile(Path source, Path target) {
        CopyOption[] options = new CopyOption[] { COPY_ATTRIBUTES, REPLACE_EXISTING };
        target = getUnique(target);
        try {
            Files.copy(source, target, options);
            return true;
        } catch (Exception x) {
            System.err.format("Unable to copy: %s: %s%n", source, x);
            return false;
        }
    }

    private static Pattern pattern1 = Pattern.compile("(.*)_copy");
    private static Pattern pattern2 = Pattern.compile("(.*)_copy_(\\d+)");

    private static Path getUnique(Path target) {
        String fileName = target.getFileName().toString();
        String name = com.google.common.io.Files.getNameWithoutExtension(fileName);
        String ext = com.google.common.io.Files.getFileExtension(fileName);
        if (!ext.equals("")) {
            ext = "." + ext;
        }
        while (!Files.notExists(target)) {
            Matcher matcher = pattern2.matcher(name);
            if (matcher.matches()) {
                name = matcher.group(1) + "_copy_" + (Integer.parseInt(matcher.group(2)) + 1);
            } else {
                matcher = pattern1.matcher(name);
                if (matcher.matches()) {
                    name = matcher.group(1) + "_copy_2";
                } else {
                    name = name + "_copy";
                }

            }
            target = target.resolveSibling(name + ext);
        }
        return target;
    }

    /**
     * A {@code FileVisitor} that copies a file-tree ("cp -r")
     */
    static class TreeCopier implements FileVisitor<Path> {
        private final Path source;
        private final Path target;
        private Operation operation;

        TreeCopier(Path source, Path target, Operation operation) {
            this.source = source;
            this.target = target;
            this.operation = operation;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
            // before visiting entries in a directory we copy the directory
            // (okay if directory already exists).
            CopyOption[] options = new CopyOption[] { COPY_ATTRIBUTES };

            Path newdir = target.resolve(source.relativize(dir));
            try {
                Files.copy(dir, newdir, options);
            } catch (FileAlreadyExistsException x) {
                // ignore
            } catch (IOException x) {
                System.err.format("Unable to create: %s: %s%n", newdir, x);
                return SKIP_SUBTREE;
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            if (copyFile(file, target.resolve(source.relativize(file)))) {
                if (operation == Operation.CUT) {
                    try {
                        Files.delete(file);
                    } catch (IOException e) {
                        System.err.format("Unable to create: %s: %s%n", file, e);
                    }
                }
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
            // fix up modification time of directory when done
            if (exc == null) {
                Path newdir = target.resolve(source.relativize(dir));
                try {
                    FileTime time = Files.getLastModifiedTime(dir);
                    Files.setLastModifiedTime(newdir, time);
                } catch (IOException x) {
                    System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
                }
                try {
                    if (operation == Operation.CUT) {
                        Files.delete(dir);
                    }
                } catch (IOException e) {
                    System.err.format("Unable to delete directory: %s: %s%n", newdir, e);
                }
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            if (exc instanceof FileSystemLoopException) {
                System.err.println("cycle detected: " + file);
            } else {
                System.err.format("Unable to copy: %s: %s%n", file, exc);
            }
            return CONTINUE;
        }
    }

    static class TreeDeleter implements FileVisitor<Path> {

        private Optional<ButtonType> option;

        public TreeDeleter(Optional<ButtonType> option) {
            this.option = option;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }

        public Optional<ButtonType> getOption() {
            return option;
        }
    }

    public static Path copy(Path source, Path target, Operation operation) throws IOException {
        if (source.equals(target)) {
            return null;
        }
        Path dest = target.resolve(source.getFileName());
        if (operation == Operation.CUT && dest.equals(source)) {
            return null;
        }
        dest = getUnique(dest);
        // follow links when copying files
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        TreeCopier tc = new TreeCopier(source, dest, operation);
        Files.walkFileTree(source, opts, Integer.MAX_VALUE, tc);
        return dest;
    }

    public static Optional<ButtonType> delete(Path path, Optional<ButtonType> option) throws IOException {
        TreeDeleter td = new TreeDeleter(option);
        Files.walkFileTree(path, td);
        return td.getOption();
    }
}
