/*
 * ====================================================================
 * 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.wc;

import java.io.File;
import java.util.Map;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription;

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

	public static SVNRevisionStatus getRevisionStatus(final File wcPath, String trailURL, 
	        final boolean committed, ISVNEventHandler eventHandler) throws SVNException {
		
	    SVNWCAccess wcAccess = null; 
	        
	    try {    
    	    wcAccess = SVNWCAccess.newInstance(eventHandler);
    		SVNAdminAreaInfo anchor = wcAccess.openAnchor(wcPath, false, SVNWCAccess.INFINITE_DEPTH);
    		
    		final long[] minRev = { SVNRepository.INVALID_REVISION };
            final long[] maxRev = { SVNRepository.INVALID_REVISION };
    		
    		final boolean[] isSwitched = { false, false, false }; 
    		final boolean[] isModified = { false };
    		final boolean[] isSparseCheckOut = { false };
    		final SVNURL[] wcURL = { null };
    		
    		SVNStatusEditor editor = new SVNStatusEditor(null, wcAccess, anchor, false, true, 
                    SVNDepth.INFINITY, new ISVNStatusHandler() {
                public void handleStatus(SVNStatus status) throws SVNException {
                    SVNEntry entry = status.getEntry();
                    if (entry == null) {
                        return;
                    }
                    
                    if (status.getContentsStatus() != SVNStatusType.STATUS_ADDED) {
                        long itemRev = committed ? entry.getCommittedRevision() : entry.getRevision();
                        if (!SVNRevision.isValidRevisionNumber(minRev[0]) || itemRev < minRev[0]) {
                            minRev[0] = itemRev;
                        }
                        if (!SVNRevision.isValidRevisionNumber(maxRev[0]) || itemRev > maxRev[0]) {
                            maxRev[0] = itemRev;
                        }
                    }
                    
                    isSwitched[0] |= status.isSwitched();
                    isModified[0] |= status.getContentsStatus() != SVNStatusType.STATUS_NORMAL; 
                    isModified[0] |= status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL &&
                    status.getPropertiesStatus() != SVNStatusType.STATUS_NONE;                           
                    isSparseCheckOut[0] |= entry.getDepth() != SVNDepth.INFINITY;
                    
                    if (wcPath != null && wcURL[0] == null && wcPath.equals(status.getFile())) {
                        wcURL[0] = entry.getSVNURL();
                    }
                }
            });
    
    		editor.closeEdit();
    		if (!isSwitched[0] && trailURL != null) {
    		    if (wcURL[0] == null) {
    		        isSwitched[0] = true;
    		    } else {
    		        String wcURLStr = wcURL[0].toDecodedString();
    		        if (trailURL.length() > wcURLStr.length() || !wcURLStr.endsWith(trailURL)) {
    		            isSwitched[0] = true;
    		        }
    		    }
    		}
            return new SVNRevisionStatus(minRev[0], maxRev[0], isSwitched[0], isModified[0], isSparseCheckOut[0]);
	    } finally {
	        wcAccess.close();
	    }
	}
	
	public static SVNStatus getStatus(File path, SVNWCAccess wcAccess) throws SVNException {
		SVNEntry entry = null;
		SVNEntry parentEntry = null;
		SVNAdminArea adminArea = null;
		if (wcAccess != null) {
			entry = wcAccess.getEntry(path, false);
			adminArea = entry != null ? entry.getAdminArea() : null; 
		}
		
		File parentPath = path.getParentFile();
		if (entry != null && parentPath != null) {
			SVNAdminArea parentArea = wcAccess.retrieve(parentPath);
			if (parentArea != null) {
				parentEntry = wcAccess.getEntry(parentPath, false);
			}
		}
		return assembleStatus(path, adminArea, entry, parentEntry, SVNNodeKind.UNKNOWN, false, true, false, 
				null, null, wcAccess);
	}
	
    public static SVNStatus assembleStatus(File file, SVNAdminArea dir, SVNEntry entry, SVNEntry parentEntry, 
    		SVNNodeKind fileKind, boolean special, boolean reportAll, boolean isIgnored, Map repositoryLocks, 
    		SVNURL reposRoot, SVNWCAccess wcAccess) throws SVNException {
        boolean hasProps = false;
        boolean isTextModified = false;
        boolean isPropsModified = false;
        boolean isLocked = false;
        boolean isSwitched = false;
        boolean isSpecial = false;
        boolean isFileExternal = false;
        
        SVNStatusType textStatus = SVNStatusType.STATUS_NORMAL;
        SVNStatusType propStatus = SVNStatusType.STATUS_NONE;
        
        SVNLock repositoryLock = null;
        
        if (repositoryLocks != null) {
            SVNURL url = null;
            if (entry != null && entry.getSVNURL() != null) {
                url = entry.getSVNURL();
            } else if (parentEntry != null && parentEntry.getSVNURL() != null) {
                url = parentEntry.getSVNURL().appendPath(file.getName(), false);
            }
            if (url != null) {
                repositoryLock = getLock(repositoryLocks, url, reposRoot);
            }
        }
        
        if (fileKind == SVNNodeKind.UNKNOWN || fileKind == null) {
            SVNFileType fileType = SVNFileType.getType(file);
            fileKind = SVNFileType.getNodeKind(fileType);
            special = !SVNFileUtil.symlinksSupported() ? false : fileType == SVNFileType.SYMLINK;
        }

        int wcFormatNumber = dir != null ? dir.getWorkingCopyFormatVersion() : -1;
        
        SVNTreeConflictDescription treeConflict = wcAccess.getTreeConflict(file);
        if (entry == null) {
            SVNStatus status = new SVNStatus(null, file, SVNNodeKind.UNKNOWN,
                    SVNRevision.UNDEFINED, SVNRevision.UNDEFINED, null, null, SVNStatusType.STATUS_NONE, 
                    SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE, false,
                    false, false, false, null, null, null, null, null, SVNRevision.UNDEFINED, repositoryLock, null, 
                    null, null, wcFormatNumber, treeConflict);
            status.setDepth(SVNDepth.UNKNOWN);
            status.setRemoteStatus(SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE, repositoryLock, SVNNodeKind.NONE);
            status.setRepositoryRootURL(reposRoot);
            SVNStatusType text = SVNStatusType.STATUS_NONE;
            SVNFileType fileType = SVNFileType.getType(file);
            if (fileType != SVNFileType.NONE) {
                text = isIgnored ? SVNStatusType.STATUS_IGNORED : SVNStatusType.STATUS_UNVERSIONED;
            }
            if (fileType == SVNFileType.NONE && treeConflict != null) {
                text = SVNStatusType.STATUS_MISSING;
            }
            status.setContentsStatus(text);
            if (status.getURL() != null && status.getRepositoryRelativePath() == null && reposRoot != null) {
                status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(reposRoot, status.getURL(), false));
            }
            if (treeConflict != null) {
                status.setNodeStatus(SVNStatusType.STATUS_CONFLICTED);
                status.setIsConflicted(true);
            }

            return status;
        }
        if (entry.getKind() == SVNNodeKind.DIR) {
            if (fileKind == SVNNodeKind.DIR) {
                if (wcAccess.isMissing(file)) {
                    textStatus = SVNStatusType.STATUS_OBSTRUCTED;
                }
            } else if (fileKind != SVNNodeKind.NONE) {
                textStatus = SVNStatusType.STATUS_OBSTRUCTED;
            }
        }
        
        if (entry.getExternalFilePath() != null) {
            isFileExternal = true;
        } else if (entry.getSVNURL() != null && parentEntry != null && parentEntry.getSVNURL() != null) {
            String urlName = SVNPathUtil.tail(entry.getSVNURL().getURIEncodedPath());
            if (!SVNEncodingUtil.uriEncode(file.getName()).equals(urlName)) {
                isSwitched = true;
            }
            if (!isSwitched && !entry.getSVNURL().removePathTail().equals(parentEntry.getSVNURL())) {
                isSwitched = true;
            }
        }
        if (textStatus != SVNStatusType.STATUS_OBSTRUCTED) {
            String name = entry.getName();
            if (dir != null && dir.hasProperties(name)) {
                propStatus = SVNStatusType.STATUS_NORMAL;
                hasProps = true;
            }
            isPropsModified = dir != null && dir.hasPropModifications(name);
            if (hasProps) {
                isSpecial = !SVNFileUtil.isWindows && dir != null && dir.getProperties(name).getPropertyValue(SVNProperty.SPECIAL) != null;
            }
            if (entry.getKind() == SVNNodeKind.FILE && special == isSpecial) {
                isTextModified = dir != null && dir.hasTextModifications(name, false);
            }
            if (isTextModified) {
                textStatus = SVNStatusType.STATUS_MODIFIED;
            }
            if (isPropsModified) {
                propStatus = SVNStatusType.STATUS_MODIFIED;
            }
            if (entry.getPropRejectFile() != null || 
                    entry.getConflictOld() != null || entry.getConflictNew() != null || entry.getConflictWorking() != null) {
                if (dir != null && dir.hasTextConflict(name)) {
                    textStatus = SVNStatusType.STATUS_CONFLICTED;
                }
                if (dir != null && dir.hasPropConflict(name)) {
                    propStatus = SVNStatusType.STATUS_CONFLICTED;
                }
            }
            if (entry.isScheduledForAddition() && textStatus != SVNStatusType.STATUS_CONFLICTED) {
                textStatus = SVNStatusType.STATUS_ADDED;
                propStatus = SVNStatusType.STATUS_NONE;
            } else if (entry.isScheduledForReplacement() && textStatus != SVNStatusType.STATUS_CONFLICTED) {
                textStatus = SVNStatusType.STATUS_REPLACED;
                propStatus = SVNStatusType.STATUS_NONE;
            } else if (entry.isScheduledForDeletion() && textStatus != SVNStatusType.STATUS_CONFLICTED) {
                textStatus = SVNStatusType.STATUS_DELETED;
                propStatus = SVNStatusType.STATUS_NONE;
            }
            if (entry.isIncomplete() && textStatus != SVNStatusType.STATUS_DELETED && textStatus != SVNStatusType.STATUS_ADDED) { 
                textStatus = SVNStatusType.STATUS_INCOMPLETE;
            } else if (fileKind == SVNNodeKind.NONE) {
                if (textStatus != SVNStatusType.STATUS_DELETED) {
                    textStatus = SVNStatusType.STATUS_MISSING;
                }
            } else if (fileKind != entry.getKind()) {
                textStatus = SVNStatusType.STATUS_OBSTRUCTED;
            } else if ((!isSpecial && special) || (isSpecial && !special)) {
                textStatus = SVNStatusType.STATUS_OBSTRUCTED;
            }
            if (fileKind == SVNNodeKind.DIR && entry.getKind() == SVNNodeKind.DIR) {
                isLocked = wcAccess.isLocked(file);
            }
        }
        if (!reportAll) {
            if ((textStatus == SVNStatusType.STATUS_NONE || textStatus == SVNStatusType.STATUS_NORMAL) &&
                (propStatus == SVNStatusType.STATUS_NONE || propStatus == SVNStatusType.STATUS_NORMAL) &&
                !isLocked && !isSwitched && entry.getLockToken() == null && repositoryLock == null && 
                entry.getChangelistName() == null && !isFileExternal && treeConflict == null) {
                return null;
            }
        }
        SVNLock localLock = null;
        if (entry.getLockToken() != null) {
            localLock = new SVNLock(null, entry.getLockToken(), entry.getLockOwner(), entry.getLockComment(),
                    SVNDate.parseDate(entry.getLockCreationDate()), null);
        }
        File conflictNew = dir != null ? dir.getFile(entry.getConflictNew()) : null;
        File conflictOld = dir != null ? dir.getFile(entry.getConflictOld()) : null;
        File conflictWrk = dir != null ? dir.getFile(entry.getConflictWorking()) : null;
        File conflictProp = dir != null ? dir.getFile(entry.getPropRejectFile()) : null;

        SVNStatus status = new SVNStatus(entry.getSVNURL(), file, entry.getKind(),
                SVNRevision.create(entry.getRevision()), SVNRevision.create(entry.getCommittedRevision()),
                SVNDate.parseDate(entry.getCommittedDate()), entry.getAuthor(),
                textStatus,  propStatus, SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE, 
                isLocked, entry.isCopied(), isSwitched, isFileExternal, conflictNew, conflictOld, conflictWrk, conflictProp, 
                entry.getCopyFromURL(), SVNRevision.create(entry.getCopyFromRevision()),
                repositoryLock, localLock, entry.asMap(), entry.getChangelistName(), wcFormatNumber, treeConflict);
        status.setEntry(entry);
        status.setDepth(entry.isDirectory() ? entry.getDepth() : SVNDepth.UNKNOWN);
        if (reposRoot == null) {
        	reposRoot = entry.getRepositoryRootURL();
        }
        status.setRepositoryRootURL(reposRoot);
        if (reposRoot != null && status.getURL() != null && status.getRepositoryRelativePath() == null) {
            status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(reposRoot, status.getURL(), false));
        }
        if (textStatus == SVNStatusType.STATUS_CONFLICTED || propStatus == SVNStatusType.STATUS_CONFLICTED || treeConflict != null) {
            status.setIsConflicted(true);
        }
        return status;
    }

    public static SVNLock getLock(Map repositoryLocks, SVNURL url, SVNURL reposRoot) {
        // get decoded path
        if (reposRoot == null || repositoryLocks == null || repositoryLocks.isEmpty() || url == null) {
            return null;
        }
        String urlString = url.getPath();
        String root = reposRoot.getPath();
        String path;
        if (urlString.equals(root)) {
            path = "/";
        } else {
            path = urlString.substring(root.length());
        }
        return (SVNLock) repositoryLocks.get(path);
    }

}
