/*
 * ====================================================================
 * Copyright (c) 2004-2012 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core.internal.util;

import java.io.File;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.util.SVNLogType;


/**
 * @author TMate Software Ltd.
 * @version 1.3
 */
public class SVNPathUtil {

    public static final Comparator<String> PATH_COMPARATOR = new Comparator<String>() {

        public int compare(String o1, String o2) {
            if (o1 == o2) {
                return 0;
            } else if (o1 == null) {
                return -1;
            } else if (o2 == null) {
                return 1;
            } 
            return o1.replace('/', '\0').compareTo(o2.replace('/', '\0'));
        }
    };

    public static boolean isCanonical(String path) {
        return (path != null && path.equals(canonicalizePath(path)));
    }
    
    public static void checkPathIsValid(String path) throws SVNException {
        for (int i = 0; i < path.length(); i++) {
            char ch = path.charAt(i);
            if (SVNEncodingUtil.isASCIIControlChar(ch)) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_PATH_SYNTAX, "Invalid control character ''{0}'' in path ''{1}''", new String[]{"0x" + SVNFormatUtil.getHexNumberFromByte((byte) ch), path});
                SVNErrorManager.error(err, SVNLogType.DEFAULT);
            }
        }
    }

    public static String getAbsolutePath(String path){
        if (path == null){
            return null;
        }        
        if (path.length() == 0 || "/".equals(path)){
            return "/";           
        }
        if (isURL(path)){
            return path;            
        }
        path = path.endsWith("/") ? path.substring(0, path.length() - "/".length()) : path;
        return path.startsWith("/") ? path : "/" + path;       
    }

    public static String validateFilePath(String path) {
        path = path.replace(File.separatorChar, '/');
        StringBuffer result = new StringBuffer();
        List segments = new LinkedList();
        for (StringTokenizer tokens = new StringTokenizer(path, "/", false); tokens.hasMoreTokens();) {
            String segment = tokens.nextToken();
            if ("..".equals(segment)) {
                if (!segments.isEmpty()) {
                    segments.remove(segments.size() - 1);
                } else {
                    File root = new File(System.getProperty("user.dir"));
                    while (root.getParentFile() != null) {
                        segments.add(0, root.getParentFile().getName());
                        root = root.getParentFile();
                    }
                }
                continue;
            } else if (".".equals(segment) || segment.length() == 0) {
                continue;
            }
            segments.add(segment);
        }
        if (path.length() > 0 && path.charAt(0) == '/') {
            result.append("/");
        }
        if (path.length() > 1 && path.charAt(1) == '/') {
            result.append("/");
        }
        for (Iterator tokens = segments.iterator(); tokens.hasNext();) {
            String token = (String) tokens.next();
            result.append(token);
            if (tokens.hasNext()) {
                result.append('/');
            }
        }
        if (result.length() == 2 && result.charAt(result.length() - 1) == ':') {
            result.append('/'); 
        }
        return result.toString();
    }

    public static String canonicalizePath(String path) {
        if (path == null){
            return null;           
        }
        
        boolean isUrl = isURL(path);
        
        StringBuffer result = new StringBuffer();
        int i = 0;
        for (; i < path.length(); i++) {
            if (path.charAt(i) == '/' || path.charAt(i) == ':') {
                break;
            }
        }
        String scheme = null;
        int index = 0;
        if (i > 0 && i + 2 < path.length() && path.charAt(i) == ':' && path.charAt(i + 1) == '/' && path.charAt(i + 2) == '/') {
            scheme = path.substring(0, i + 3);
            result.append(scheme);
            index = i + 3;
        }
        if (index < path.length() && path.charAt(index) == '/') {
            result.append('/');
            index++;
            if (SVNFileUtil.isWindows && scheme == null && index < path.length() && path.charAt(index) == '/') {
                result.append('/');
                index++;
            }
        }
        int segmentCount = 0;
        while (index < path.length()) {
            int nextIndex = index;
            while (nextIndex < path.length() && path.charAt(nextIndex) != '/' 
            		 && !(isUrl && (nextIndex + 2) < path.length() && path.charAt(nextIndex) == '%' && path.charAt(nextIndex + 1) == '2' &&
    				Character.toUpperCase(path.charAt(nextIndex + 2)) == 'F')) {
            	nextIndex++;
            }
            int slashLength = 0;
            if (nextIndex < path.length()) {
            	if (path.charAt(nextIndex) == '/')
            		slashLength = 1;
            	else if (path.charAt(nextIndex) == '%')
            		slashLength = 3;
            }
            int segmentLength = nextIndex - index;
            if (segmentLength == 0 || (segmentLength == 1 && path.charAt(index) == '.')
            		|| (isUrl && segmentLength == 3 && path.charAt(index) == '%' && path.charAt(index + 1) == '2' && Character.toUpperCase(path.charAt(index + 2))  == 'E')) {
            } else {
                result.append(path.substring(index, index + segmentLength));
            	if (slashLength > 0)
            		result.append('/');
            	segmentCount++;
            }
            index = nextIndex;
            if (index < path.length()) {
                index += slashLength;
            }
        }
        if ((segmentCount > 0 || scheme != null) && result.charAt(result.length() - 1) == '/') {
            result = result.delete(result.length() - 1, result.length());
        }
        if (SVNFileUtil.isWindows && segmentCount < 2 && result.length() >= 2 && result.charAt(0) == '/' && result.charAt(1) == '/') {
            result = result.delete(0, 1);
        }
        return result.toString();
    }

    public static String canonicalizeAbsolutePath(String path) {
        if (path == null) {
            return null;
        }
     
        if ("".equals(path)) {
            return "/";
        }
        
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        
        StringBuffer canonicalizedPath = new StringBuffer();
        boolean skipSlashes = false;
        for (int i = 0; i < path.length(); i++) {
            char ch = path.charAt(i);
            if (ch == '/') {
                if (skipSlashes) {
                    continue;
                }
                skipSlashes = true;
            } else {
                if (skipSlashes) {
                    skipSlashes = false;
                }
            }
            canonicalizedPath.append(ch);
        }
        
        if (canonicalizedPath.length() > 1 && canonicalizedPath.charAt(canonicalizedPath.length() - 1) == '/') {
            canonicalizedPath.deleteCharAt(canonicalizedPath.length() - 1);
        }
        return canonicalizedPath.toString();
    }
    
    public static String append(String f, String s) {
        f = f == null ? "" : f;
        s = s == null ? "" : s;
        int l1 = f.length();
        int l2 = s.length();
        char[] r = new char[l1 + l2 + 2];
        int index = 0;
        for (int i = 0; i < l1; i++) {
            char ch = f.charAt(i);
            if (i + 1 == l1 && ch == '/') {
                break;
            }
            r[index++] = ch;
        }
        for (int i = 0; i < l2; i++) {
            char ch = s.charAt(i);
            if (i == 0 && ch != '/' && index > 0) {
                r[index++] = '/';
            }
            if (i + 1 == l2 && ch == '/') {
                break;
            }
            r[index++] = ch;
        }
        return new String(r, 0, index);
    }

    public static boolean isSinglePathComponent(String name) {
        /* Can't be empty or `..'  */
        if (name == null || "".equals(name) || "..".equals(name)) {
            return true;
        }
        /* Slashes are bad */
        if (name.indexOf('/') != -1) {
            return false;
        }
        /* It is valid.  */
        return true;
    }

    public static String head(String path) {
        for (int i = 0; i < path.length(); i++) {
            if (path.charAt(i) == '/') {
                return path.substring(0, i);
            }
        }
        return path;
    }

    public static String removeHead(String path) {
        for (int i = 0; i < path.length(); i++) {
            if (path.charAt(i) == '/') {
                int ind = i;
                for (; ind < path.length(); ind++) {
                    if (path.charAt(ind) == '/') {
                        continue;
                    }
                    break;
                }
                return path.substring(ind);
            }
        }
        return "";
    }

    public static String tail(String path) {
        int index = path.length() - 1;
        if (index >= 0 && index < path.length() && path.charAt(index) == '/') {
            index--;
        }
        for (int i = index; i >= 0; i--) {
            if (path.charAt(i) == '/') {
                return path.substring(i + 1, index + 1);
            }
        }
        return path;
    }

    public static String removeTail(String path) {
        int index = path.length() - 1;
        while (index >= 0) {
            if (path.charAt(index) == '/') {
                return path.substring(0, index);
            }
            index--;
        }
        return "";
    }

    public static String getCommonPathAncestor(String path1, String path2) {
        if (path1 == null || path2 == null) {
            return null;
        }
        path1 = path1.replace(File.separatorChar, '/');
        path2 = path2.replace(File.separatorChar, '/');

        int index = 0;
        int separatorIndex = 0;
        while (index < path1.length() && index < path2.length()) {
            if (path1.charAt(index) != path2.charAt(index)) {
                break;
            }
            if (path1.charAt(index) == '/') {
                separatorIndex = index;
            }
            index++;
        }
        if (index == path1.length() && index == path2.length()) {
            return path1;
        } else if (index == path1.length() && path2.charAt(index) == '/') {
            return path1;
        } else if (index == path2.length() && path1.charAt(index) == '/') {
            return path2;
        }
        return path1.substring(0, separatorIndex);
    }

    public static String condencePaths(String[] paths, Collection condencedPaths, boolean removeRedundantPaths) {
        if (paths == null || paths.length == 0) {
            return null;
        }
        if (paths.length == 1) {
            return paths[0];
        }
        String rootPath = paths[0];
        for (int i = 0; i < paths.length; i++) {
            String url = paths[i];
            rootPath = getCommonPathAncestor(rootPath, url);
        }
        if (condencedPaths != null && removeRedundantPaths) {
            for (int i = 0; i < paths.length; i++) {
                String path1 = paths[i];
                if (path1 == null) {
                    continue;
                }
                for (int j = 0; j < paths.length; j++) {
                    if (i == j) {
                        continue;
                    }
                    String path2 = paths[j];
                    if (path2 == null) {
                        continue;
                    }
                    String common = getCommonPathAncestor(path1, path2);

                    if ("".equals(common) || common == null) {
                        continue;
                    }
                    // remove logner path here
                    if (common.equals(path1)) {
                        paths[j] = null;
                    } else if (common.equals(path2)) {
                        paths[i] = null;
                    }
                }
            }
            for (int j = 0; j < paths.length; j++) {
                String path = paths[j];
                if (path != null && path.equals(rootPath)) {
                    paths[j] = null;
                }
            }
        }

        if (condencedPaths != null) {
            for (int i = 0; i < paths.length; i++) {
                String path = paths[i];
                if (path == null) {
                    continue;
                }
                if (rootPath != null && !"".equals(rootPath)) {
                    path = path.substring(rootPath.length());
                    if (path.startsWith("/")) {
                        path = path.substring(1);
                    }
                }
                condencedPaths.add(path);
            }
        }
        return rootPath;
    }

    public static int getSegmentsCount(String path) {
        int count = path.length() > 0 ? 1 : 0;
        // skip first char, then count number of '/'
        for (int i = 1; i < path.length(); i++) {
            if (path.charAt(i) == '/') {
                count++;
            }
        }
        return count;
    }

    public static boolean isAncestor(String parentPath, String childPath) {
        parentPath = parentPath == null ? "" : parentPath;
        childPath = childPath == null ? "" : childPath;

        if (parentPath.length() == 0) {
            return !childPath.startsWith("/");
        }

        if (childPath.startsWith(parentPath)) {
            if (parentPath.length() != childPath.length() && !parentPath.endsWith("/") &&
                    childPath.charAt(parentPath.length()) != '/') {
                if (parentPath.startsWith("file://") && childPath.startsWith("file://")) {
                    //HACK: maybe encoded back slashes (UNC path)?
                    String encodedSlash = SVNEncodingUtil.uriEncode("\\");
                    return parentPath.endsWith(encodedSlash) ||
                            childPath.substring(parentPath.length()).startsWith(encodedSlash);
                }
                return false;
            }
            return true;
        }

        return false;
    }

    /**
     * Former pathIsChild.
     *
     * @param path
     * @param pathChild
     * @return
     */
    public static String getPathAsChild(String path, String pathChild) {
        if (path == null || pathChild == null) {
            return null;
        }
        if (pathChild.compareTo(path) == 0) {
            return null;
        }
        if (path.length() == 0 && !pathChild.startsWith("/")) {
            return pathChild;
        }
	    if (!path.endsWith("/")) { // We don't want to have /foobar being a child of /foo
		    path = path + "/";
	    }
	    if (pathChild.startsWith(path)) {
		    return pathChild.substring(path.length());
	    }
        return null;
    }

    public static String getRelativePath(String parent, String child) {
        parent = parent.replace(File.separatorChar, '/');
        child = child.replace(File.separatorChar, '/');
        String relativePath = getPathAsChild(parent, child);
        return relativePath == null ? "" : relativePath;
    }
    
    public static boolean isURL(String pathOrUrl) {
        pathOrUrl = pathOrUrl != null ? pathOrUrl.toLowerCase() : null;
        return pathOrUrl != null
                && (pathOrUrl.startsWith("http://")
                        || pathOrUrl.startsWith("https://")
                        || pathOrUrl.startsWith("svn://")
                        || (pathOrUrl.startsWith("svn+") && pathOrUrl.indexOf("://") > 4)
                        || pathOrUrl.startsWith("file://"));
    }    
    
    public static boolean isWithinBasePath(String basePath, String path) {
        return "".equals(basePath) || (path.startsWith(basePath) && (path.length() == basePath.length() || 
                path.charAt(basePath.length()) == '/'));
    }

    public static boolean isAbsolute(String path) {
        if (path == null || path.length() == 0) {
            return false;
        }
        if (path.charAt(0) == '/') {
            return true;
        }
        if (SVNFileUtil.isWindows && path.length() > 1) {
            char ch0 = path.charAt(0);
            char ch1 = path.charAt(1);
            if (((ch0 >= 'A' && ch0 <= 'Z') || (ch0 >= 'a' && ch0 <= 'z')) && ch1 == ':') {
                return true;
            }
        }
        return false;
    }

}
