/*
 * @(#)PathSystem.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.inet.vfs;

import com.jscape.util.CollectionTools;
import com.jscape.util.StringTools;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * Unix path system.
 *
 * @author Alex
 */
public final class PathSystem {

    private static final String CURRENT_DIRECTORY = ".";
    private static final String PARENT_DIRECTORY = "..";
    private static final String SEPARATOR = "/";
    private static final char SEPARATOR_CHAR = '/';
    private static final char BAD_SEPARATOR_CHAR = '\\';
    private static final String BAD_SEPARATOR = "\\";
    private static final String ROOT = "";

    public static String root() {
        return ROOT;
    }

    public static boolean isRoot(String path) {
        return ROOT.equals(path) || (ROOT + SEPARATOR).equals(path);
    }

    public static String currentDirectory() {
        return CURRENT_DIRECTORY;
    }

    public static String parentDirectory() {
        return PARENT_DIRECTORY;
    }

    public static String separator() {
        return SEPARATOR;
    }

    public static char separatorChar() {
        return SEPARATOR_CHAR;
    }

    public static String parentOf(String pathname) {
        if (pathname.equals(ROOT)) {
            return ROOT;
        }

        List<String> elements = new ArrayList<>(Arrays.asList(elementsOf(resolve(root(), pathname))));

        if (elements.size() > 0) {
            elements.remove(elements.size() - 1);
        }

        return asString(elements);
    }

    public static String getName(String pathname) {
        if (pathname.equals(ROOT) || pathname.equals(separator())) {
            return ROOT;
        }

        pathname = resolve(ROOT, pathname);
        String[] elements = elementsOf(pathname);

        return elements[elements.length - 1];
    }

    /**
     * Checks if the specified element is allowed.
     *
     * @param name element to check
     *
     * @return <code>true</code> if element is allowed;
     *         <code>false</code> otherwise
     */
    public static boolean nameAllowed(String name) {
        return StringTools.isValidString(name) && !name.contains(separator()) && !name.contains(BAD_SEPARATOR);
    }

    public static String combine(String firstPath, String secondPath) {
        return firstPath.endsWith(separator())
               ? firstPath + secondPath
               : firstPath + separator() + secondPath;
    }

    public static String resolve(String original, String pathname) {
        if (original.equals(ROOT) && pathname.equals(ROOT)) {
            return ROOT;
        }

        original = resolve(ROOT, original);

        if (pathname.length() == 0) {
            return ROOT;
        }

        // replace possible backslashes
        pathname = pathname.replace(BAD_SEPARATOR_CHAR, separatorChar());

        List<String> elements = isAbsolute(pathname)
                        ? new LinkedList<>()
                        : CollectionTools.list(elementsOf(original));

        List<String> ret = resolve(elements, pathname);

        return ret != null ? asString(ret) : null;
    }

    /**
     * Checks if the pathname denotes absolute path.
     *
     * @param pathname pathname
     *
     * @return <code>true</code> if pathname denotes absolute path;
     *         <code>false</code> otherwise
     */
    private static boolean isAbsolute(String pathname) {
        return pathname.startsWith(separator());
    }

    /**
     * Returns path elements.
     *
     * @param pathname target pathname
     *
     * @return path elements
     */
    public static String[] elementsOf(String pathname) {
        return StringTools.split(pathname, separator(), false);
    }

    /**
     * Creates a new path from the specified parts.
     *
     * @param elements elements
     * @param pathname target pathname
     *
     * @return pathname elements list
     */
    private static List<String> resolve(List<String> elements, String pathname) {
        String[] parts = elementsOf(pathname);

        for (int i = 0; i < parts.length; ++i) {
            String part = parts[i];

            if (!nameAllowed(part)) {
                return null;
            }

            if (parentDirectory().equals(part)) {
                if (elements.size() > 0) {
                    elements.remove(elements.size() - 1);
                }
            } else if (!currentDirectory().equals(part)) {
                elements.add(part);
            }
        }

        return elements;
    }

    /**
     * Returns path string representation.
     *
     * @param elements path elements
     *
     * @return path string representation
     */
    private static String asString(List<String> elements) {
        StringBuilder buffer = new StringBuilder(ROOT);

        for (int i = 0, size = elements.size(); i < size; ++i) {
            buffer.append(separator());
            buffer.append(elements.get(i));
        }

        return buffer.toString();
    }

    private PathSystem() {
        // empty
    }

    public interface Provider {

        String getRoot();

        boolean isRoot(String path);

        String currentDirectory();

        boolean isCurrentDirectory(String path);

        String parentDirectory();

        boolean isParentDirectory(String path);

        String separator();

        boolean allowsName(String name);

        boolean allowsPath(String path);

        String resolve(String original, String path)
                throws UnsupportedPathException;

        String resolve(String path)
                throws UnsupportedPathException;

        String nameOf(String path)
                throws UnsupportedPathException;

        String parentOf(String path)
                throws UnsupportedPathException;

        boolean isAncestor(String candidate, String path)
                throws UnsupportedPathException;
    }

    public static class UnsupportedPathException
            extends Exception {

        public UnsupportedPathException(String path) {
            super(path);
        }

    }

    public static class UnixProvider
            implements Provider {

        private static final String ROOT = "";
        private static final String CURRENT_DIRECTORY = ".";
        private static final String PARENT_DIRECTORY = "..";
        private static final String SEPARATOR = "/";
        private static final String BAD_SEPARATOR = "\\";

        public String getRoot() {
            return ROOT;
        }

        public boolean isRoot(String path) {
            return ROOT.equals(path) || SEPARATOR.equals(path);
        }

        public String currentDirectory() {
            return CURRENT_DIRECTORY;
        }

        public boolean isCurrentDirectory(String path) {
            return CURRENT_DIRECTORY.equals(path);
        }

        public String parentDirectory() {
            return PARENT_DIRECTORY;
        }

        public boolean isParentDirectory(String path) {
            return PARENT_DIRECTORY.equals(path);
        }

        public String separator() {
            return SEPARATOR;
        }

        public boolean allowsName(String name) {
            return StringTools.isValidString(name) && !normalize(name).contains(SEPARATOR);
        }

        public boolean allowsPath(String path) {
            try {
                resolve(path);
                return true;
            } catch (UnsupportedPathException e) {
                return false;
            }
        }

        public String resolve(String original, String path)
                throws UnsupportedPathException {
            original = normalize(original);
            path = normalize(path);

            if (isRoot(original) && isRoot(path)) {
                return ROOT + SEPARATOR;
            }

            return combine(split(isAbsolute(path) ? path : combine(resolve(original), path)));
        }

        public String resolve(String path)
                throws UnsupportedPathException {
            return resolve(ROOT, path);
        }

        public String nameOf(String path)
                throws UnsupportedPathException {
            path = normalize(path);
            List<String> elements = split(path);

            return elements.size() == 0 ? ROOT : elements.get(elements.size() - 1);
        }

        public String parentOf(String path)
                throws UnsupportedPathException {
            return resolve(path, PARENT_DIRECTORY);
        }

        public boolean isAncestor(String candidate, String path)
                throws UnsupportedPathException {
            return resolve(path).startsWith(combine(resolve(candidate), ""));
        }

        private boolean isAbsolute(String path) {
            return path.startsWith(SEPARATOR);
        }

        private String normalize(String path) {
            return path.replace(BAD_SEPARATOR, SEPARATOR);
        }

        private List<String> split(String path)
                throws UnsupportedPathException {
            String[] parts = StringTools.split(path, SEPARATOR, false);
            List<String> ret = new LinkedList<>();

            for (String part : parts) {
                if (!allowsName(part)) {
                    throw new UnsupportedPathException(path);
                }

                if (isParentDirectory(part)) {
                    if (ret.size() > 0) {
                        ret.remove(ret.size() - 1);
                    }
                } else if (isCurrentDirectory(part)) {
                    continue;
                }

                ret.add(part);
            }

            return ret;
        }

        private String combine(String first, String second) {
            return first.endsWith(SEPARATOR) ? first + second : first + SEPARATOR + second;
        }

        private String combine(List<String> elements) {
            StringBuilder buf = new StringBuilder(ROOT);

            for (String element : elements) {
                buf.append(SEPARATOR);
                buf.append(element);
            }

            return buf.toString();
        }
    }
}
