/*
 * ====================================================================
 * 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.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.util.SVNHashSet;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
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.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import org.tmatesoft.svn.util.SVNLogType;


/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class SVNDiffEditor implements ISVNEditor {

    private SVNWCAccess myWCAccess;
    private boolean myUseAncestry;
    private boolean myIsReverseDiff;
    private boolean myIsCompareToBase;
    private boolean myIsRootOpen;
    private long myTargetRevision;
    private SVNDirectoryInfo myCurrentDirectory;
    private SVNFileInfo myCurrentFile;
    private SVNDeltaProcessor myDeltaProcessor;
    private SVNAdminAreaInfo myAdminInfo;
    private SVNDepth myDepth;
    private File myTempDirectory;
    private AbstractDiffCallback myDiffCallback;
    private Collection myChangeLists;
    private String myWCRootPath;
    
    public SVNDiffEditor(SVNWCAccess wcAccess, SVNAdminAreaInfo info, AbstractDiffCallback callback,
            boolean useAncestry, boolean reverseDiff, boolean compareToBase, SVNDepth depth,
            Collection changeLists) {
        myWCAccess = wcAccess;
        myAdminInfo = info;
        myUseAncestry = useAncestry;
        myIsReverseDiff = reverseDiff;
        myDepth = depth;
        myIsCompareToBase = compareToBase;
        myDiffCallback = callback;
        myChangeLists = changeLists != null ? changeLists : Collections.EMPTY_LIST;
        myDeltaProcessor = new SVNDeltaProcessor();
    }

    public void targetRevision(long revision) throws SVNException {
        myTargetRevision = revision;
    }

    public void openRoot(long revision) throws SVNException {
        myIsRootOpen = true;
        myCurrentDirectory = createDirInfo(null, "", false, myDepth);
    }

    public void deleteEntry(String path, long revision) throws SVNException {
        File fullPath = new File(myAdminInfo.getAnchor().getRoot(), path);
        SVNAdminArea dir = myWCAccess.probeRetrieve(fullPath);
        SVNEntry entry = myWCAccess.getEntry(fullPath, false);
        if (entry == null) {
            return;
        }
        String name = SVNPathUtil.tail(path);
        myCurrentDirectory.myComparedEntries.add(name);
        if (!myIsCompareToBase && entry.isScheduledForDeletion()) {
            return;
        }
        if (entry.isFile()) {
            if (myIsReverseDiff) {
                File baseFile = dir.getBaseFile(name, false);
                SVNProperties baseProps = dir.getBaseProperties(name).asMap();
                getDiffCallback().fileDeleted(path, baseFile, null, null, null, baseProps, null);
            } else {
                reportAddedFile(myCurrentDirectory, path, entry);
            }
        } else if (entry.isDirectory()) {
            SVNDirectoryInfo info = createDirInfo(myCurrentDirectory, path, false, SVNDepth.INFINITY);
            reportAddedDir(info);
        }
    }
    
    private void reportAddedDir(SVNDirectoryInfo info) throws SVNException {
        SVNAdminArea dir = retrieve(info.myPath);
        SVNEntry thisDirEntry = dir.getEntry(dir.getThisDirName(), false);
        if (SVNWCAccess.matchesChangeList(myChangeLists, thisDirEntry)) {
            SVNProperties wcProps;
            if (myIsCompareToBase) {
                wcProps = dir.getBaseProperties(dir.getThisDirName()).asMap();
            } else {
                wcProps = dir.getProperties(dir.getThisDirName()).asMap();
            }
            SVNProperties propDiff = computePropsDiff(new SVNProperties(), wcProps);
            if (!propDiff.isEmpty()) {
                getDiffCallback().propertiesChanged(info.myPath, null, propDiff, null);
            }
        }
        for(Iterator entries = dir.entries(false); entries.hasNext();) {
            SVNEntry entry = (SVNEntry) entries.next();
            if (dir.getThisDirName().equals(entry.getName())) {
                continue;
            }
            if (!myIsCompareToBase && entry.isScheduledForDeletion()) {
                continue;
            }
            if (entry.isFile()) {
                reportAddedFile(info, SVNPathUtil.append(info.myPath, entry.getName()), entry);
            } else if (entry.isDirectory()) {
                if (info.myDepth.compareTo(SVNDepth.FILES) > 0 || 
                    info.myDepth == SVNDepth.UNKNOWN) {
                    SVNDepth depthBelowHere = info.myDepth;
                    if (depthBelowHere == SVNDepth.IMMEDIATES) {
                        depthBelowHere = SVNDepth.EMPTY;
                    }
                    SVNDirectoryInfo childInfo = createDirInfo(info, 
                                                               SVNPathUtil.append(info.myPath, entry.getName()), 
                                                               false,
                                                               depthBelowHere);
                    reportAddedDir(childInfo);
                }
            }
        }
    }

    private void reportAddedFile(SVNDirectoryInfo info, String path, SVNEntry entry) throws SVNException {
        if (!SVNWCAccess.matchesChangeList(myChangeLists, entry)) {
            return;
        }
        
        if (entry.isCopied()) {
            if (myIsCompareToBase) {
                return;
            }
            reportModifiedFile(info, entry);
            return;
        }
        SVNAdminArea dir = retrieve(info.myPath);
        String name = SVNPathUtil.tail(path);
        SVNProperties wcProps = null;
        if (myIsCompareToBase) {
            wcProps = dir.getBaseProperties(name).asMap();
        } else {
            wcProps = dir.getProperties(name).asMap();
        }
        String mimeType = wcProps.getStringValue(SVNProperty.MIME_TYPE);
        SVNProperties propDiff = computePropsDiff(new SVNProperties(), wcProps);
        
        File sourceFile;
        if (myIsCompareToBase) {
            sourceFile = dir.getBaseFile(name, false);
        } else {
            sourceFile = detranslateFile(dir, name);
        }
        getDiffCallback().fileAdded(path, null, sourceFile, 0, entry.getRevision(), null, mimeType, null, propDiff, null);
    }
    
    private void reportModifiedFile(SVNDirectoryInfo dirInfo, SVNEntry entry) throws SVNException {
        SVNAdminArea dir = retrieve(dirInfo.myPath);
        if (!SVNWCAccess.matchesChangeList(myChangeLists, entry)) {
            return;
        }
        String schedule = entry.getSchedule();
        String fileName = entry.getName();
        if (!getDiffCallback().isDiffCopiedAsAdded() && entry.isCopied()) {
            schedule = null;
        }
        if (!myUseAncestry && entry.isScheduledForReplacement()) {
            schedule = null;
        }
        SVNProperties propDiff = null;
        SVNProperties baseProps = null;
        File baseFile = dir.getBaseFile(fileName, false);
        if (SVNFileType.getType(baseFile) == SVNFileType.NONE) {
            baseFile = dir.getFile(SVNAdminUtil.getTextRevertPath(fileName, false));
        }
        if (!entry.isScheduledForDeletion()) {
            if (getDiffCallback().isDiffCopiedAsAdded() && entry.isCopied()) {
                baseProps = new SVNProperties();
                propDiff = dir.getProperties(fileName).asMap();
            } else {
                baseProps = dir.getBaseProperties(fileName).asMap();
                boolean modified = dir.hasPropModifications(fileName);
                if (modified) {
                    propDiff = computePropsDiff(baseProps, dir.getProperties(fileName).asMap());
                } else {
                    propDiff = new SVNProperties();
                }
            }
        } else {
            baseProps = dir.getBaseProperties(fileName).asMap();
        }
        boolean isAdded = schedule != null && entry.isScheduledForAddition();
        String filePath = SVNPathUtil.append(dirInfo.myPath, fileName);
        if (schedule != null && (entry.isScheduledForDeletion() || entry.isScheduledForReplacement())) {
            String mimeType = dir.getBaseProperties(fileName).getStringPropertyValue(SVNProperty.MIME_TYPE);
            getDiffCallback().fileDeleted(filePath, baseFile, null, mimeType, null, dir.getBaseProperties(fileName).asMap(), null);
            isAdded = entry.isScheduledForReplacement();
        }
        if (isAdded) {
            String mimeType = dir.getProperties(fileName).getStringPropertyValue(SVNProperty.MIME_TYPE);
            
            File tmpFile = detranslateFile(dir, fileName);
            SVNProperties originalProperties = null; 
            long revision = entry.getRevision();
            if (entry.isCopied() && getDiffCallback().isDiffCopiedAsAdded()) {
                originalProperties = new SVNProperties();
                revision = 0;
            } else {
                originalProperties = dir.getBaseProperties(fileName).asMap();
            }
            getDiffCallback().fileAdded(filePath, null, tmpFile, 0, revision, mimeType, null, originalProperties, propDiff, null);
        } else if (schedule == null) {
            boolean modified = dir.hasTextModifications(fileName, false);
            File tmpFile = null;
            if (modified) {
                tmpFile = detranslateFile(dir, fileName);
            }
            if (modified || (propDiff != null && !propDiff.isEmpty())) {
                String baseMimeType = dir.getBaseProperties(fileName).getStringPropertyValue(SVNProperty.MIME_TYPE);
                String mimeType = dir.getProperties(fileName).getStringPropertyValue(SVNProperty.MIME_TYPE);

                getDiffCallback().fileChanged(filePath, modified ? baseFile : null, tmpFile, entry.getRevision(), -1, 
                        baseMimeType, mimeType, baseProps, propDiff, null);
            }
        }
        
    }

    public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
        SVNDepth subDirDepth = myCurrentDirectory.myDepth;
        if (subDirDepth == SVNDepth.IMMEDIATES) {
            subDirDepth = SVNDepth.EMPTY;    
        }
        myCurrentDirectory = createDirInfo(myCurrentDirectory, path, true, subDirDepth);
    }

    public void openDir(String path, long revision) throws SVNException {
        SVNDepth subDirDepth = myCurrentDirectory.myDepth;
        if (subDirDepth == SVNDepth.IMMEDIATES) {
            subDirDepth = SVNDepth.EMPTY;    
        }
        myCurrentDirectory = createDirInfo(myCurrentDirectory, path, false, subDirDepth);
    }

    public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException {
        if (myCurrentDirectory.myPropertyDiff == null) {
            myCurrentDirectory.myPropertyDiff = new SVNProperties();
        }
        myCurrentDirectory.myPropertyDiff.put(name, value);
    }

    public void closeDir() throws SVNException {
        // display dir prop changes.
        SVNProperties diff = myCurrentDirectory.myPropertyDiff;
        if (diff != null && !diff.isEmpty()) {
            // reverse changes
            SVNProperties originalProps = null;
            if (myCurrentDirectory.myIsAdded) {
                originalProps = new SVNProperties();
            } else {
                SVNAdminArea dir = retrieve(myCurrentDirectory.myPath);
                if (dir != null && myIsCompareToBase) {
                    originalProps = dir.getBaseProperties(dir.getThisDirName()).asMap();
                } else {
                    originalProps = dir.getProperties(dir.getThisDirName()).asMap();
                    SVNProperties baseProps = dir.getBaseProperties(dir.getThisDirName()).asMap();
                    SVNProperties reposProps = applyPropChanges(baseProps, myCurrentDirectory.myPropertyDiff);
                    diff = computePropsDiff(originalProps, reposProps);
                    
                }
            }
            if (!myIsReverseDiff) {
                reversePropChanges(originalProps, diff);
            }
            getDiffCallback().propertiesChanged(myCurrentDirectory.myPath, originalProps, diff, null);
            myCurrentDirectory.myComparedEntries.add("");
        }
        if (!myCurrentDirectory.myIsAdded) {
            localDirectoryDiff(myCurrentDirectory);
        }
        String name = SVNPathUtil.tail(myCurrentDirectory.myPath);
        myCurrentDirectory = myCurrentDirectory.myParent;
        if (myCurrentDirectory != null) {
            myCurrentDirectory.myComparedEntries.add(name);
        }
    }

    public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
        String name = SVNPathUtil.tail(path);
        myCurrentFile = createFileInfo(myCurrentDirectory, path, true);
        myCurrentDirectory.myComparedEntries.add(name);
    }

    public void openFile(String path, long revision) throws SVNException {
        String name = SVNPathUtil.tail(path);
        myCurrentFile = createFileInfo(myCurrentDirectory, path, false);
        myCurrentDirectory.myComparedEntries.add(name);
    }

    public void changeFileProperty(String path,String name, SVNPropertyValue value) throws SVNException {
        if (myCurrentFile.myPropertyDiff == null) {
            myCurrentFile.myPropertyDiff = new SVNProperties();
        }
        myCurrentFile.myPropertyDiff.put(name, value);
    }

    public void applyTextDelta(String path, String baseChecksum) throws SVNException {
        SVNEntry entry = myWCAccess.getEntry(myAdminInfo.getAnchor().getFile(myCurrentFile.myPath), false);
        if (entry != null && entry.isCopied()) {
            myCurrentFile.myIsAdded = false;
        }
        if (!myCurrentFile.myIsAdded) {
            SVNAdminArea dir = retrieve(myCurrentDirectory.myPath);
            String fileName = SVNPathUtil.tail(myCurrentFile.myPath);
            myCurrentFile.myBaseFile = dir.getBaseFile(fileName, false);
        } 
        myCurrentFile.myFile = createTempFile();
        myDeltaProcessor.applyTextDelta(myCurrentFile.myBaseFile, myCurrentFile.myFile, false);
    }

    public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
        return myDeltaProcessor.textDeltaChunk(diffWindow);
    }

    public void textDeltaEnd(String path) throws SVNException {
        myDeltaProcessor.textDeltaEnd();
    }

    public void closeFile(String commitPath, String textChecksum) throws SVNException {
        String fileName = SVNPathUtil.tail(myCurrentFile.myPath);
        
        File filePath = myAdminInfo.getAnchor().getFile(myCurrentFile.myPath);
        SVNAdminArea dir = myWCAccess.probeRetrieve(filePath);
        SVNEntry entry = myWCAccess.getEntry(filePath, false);
        SVNProperties baseProperties = null;
        if (myCurrentFile.myIsAdded) {
            baseProperties = new SVNProperties();
        } else {
            baseProperties = dir != null ? dir.getBaseProperties(fileName).asMap() : new SVNProperties();
        }
        SVNProperties reposProperties = applyPropChanges(baseProperties, myCurrentFile.myPropertyDiff);
        String reposMimeType = reposProperties.getStringValue(SVNProperty.MIME_TYPE);
        File reposFile = myCurrentFile.myFile;
        File localFile = null;
        if (reposFile == null) {
            reposFile = dir.getBaseFile(fileName, false);
        }
        if (myCurrentFile.myIsAdded || (!myIsCompareToBase && entry.isScheduledForDeletion())) {
            if (myIsReverseDiff) {
                getDiffCallback().fileAdded(commitPath, null, reposFile, 0, myTargetRevision, null, reposMimeType, null, 
                        myCurrentFile.myPropertyDiff, null);
            } else {
                getDiffCallback().fileDeleted(commitPath, reposFile, null, reposMimeType, null, reposProperties, null);
            }
            return;
        }
        boolean modified = myCurrentFile.myFile != null;
        if (!modified && !myIsCompareToBase) {
            modified = dir.hasTextModifications(fileName, false);
        }
        if (modified) {
            if (myIsCompareToBase) {
                localFile = dir.getBaseFile(fileName, false);
            } else {
                localFile = detranslateFile(dir, fileName);
            }
        } else {
            localFile = null;
            reposFile = null;
        }
        
        SVNProperties originalProps = null;
        if (myIsCompareToBase) {
            originalProps = baseProperties;
        } else {
            originalProps = dir.getProperties(fileName).asMap();
            myCurrentFile.myPropertyDiff = computePropsDiff(originalProps, reposProperties);
        }
        
        if (localFile != null || (myCurrentFile.myPropertyDiff != null && !myCurrentFile.myPropertyDiff.isEmpty())) {
            String originalMimeType = originalProps.getStringValue(SVNProperty.MIME_TYPE);
            if (myCurrentFile.myPropertyDiff != null && !myCurrentFile.myPropertyDiff.isEmpty() && !myIsReverseDiff) {
                reversePropChanges(originalProps, myCurrentFile.myPropertyDiff);
            }
            if (localFile != null || reposFile != null || (myCurrentFile.myPropertyDiff != null && !myCurrentFile.myPropertyDiff.isEmpty())) {
                getDiffCallback().fileChanged(commitPath, 
                        myIsReverseDiff ? localFile : reposFile, 
                        myIsReverseDiff ? reposFile : localFile, 
                        myIsReverseDiff ? -1 : myTargetRevision,
                        myIsReverseDiff ? myTargetRevision : -1,
                        myIsReverseDiff ? originalMimeType : reposMimeType, 
                        myIsReverseDiff ? reposMimeType : originalMimeType,
                        originalProps, myCurrentFile.myPropertyDiff, null);
            }
        }
    }

    public SVNCommitInfo closeEdit() throws SVNException {
        if (!myIsRootOpen) {
            localDirectoryDiff(createDirInfo(null, "", false, myDepth));
        }
        return null;
    }
    

    public void abortEdit() throws SVNException {
    }

    public void absentDir(String path) throws SVNException {
    }

    public void absentFile(String path) throws SVNException {
    }

    public void cleanup() {
        if (myTempDirectory != null) {
            SVNFileUtil.deleteAll(myTempDirectory, true);
        }
    }
    
    private SVNProperties applyPropChanges(SVNProperties props, SVNProperties propChanges) {
        SVNProperties result = new SVNProperties(props);
        if (propChanges != null) {
            for(Iterator names = propChanges.nameSet().iterator(); names.hasNext();) {
                String name = (String) names.next();
                SVNPropertyValue value = propChanges.getSVNPropertyValue(name);
                if (value == null) {
                    result.remove(name);
                } else {
                    result.put(name, value);
                }
            }
        }
        return result;
    }

    private void localDirectoryDiff(SVNDirectoryInfo info) throws SVNException {
        if (myIsCompareToBase) {
            return;
        }
        SVNAdminArea dir = retrieve(info.myPath);
        boolean anchor = !"".equals(myAdminInfo.getTargetName()) && dir == myAdminInfo.getAnchor();
        SVNEntry thisDirEntry = dir.getEntry(dir.getThisDirName(), false);
        if (SVNWCAccess.matchesChangeList(myChangeLists, thisDirEntry) && !anchor && 
                !info.myComparedEntries.contains("")) {
            // generate prop diff for dir.
            if (dir.hasPropModifications(dir.getThisDirName())) {
                SVNVersionedProperties baseProps = dir.getBaseProperties(dir.getThisDirName());
                SVNProperties propDiff = baseProps.compareTo(dir.getProperties(dir.getThisDirName())).asMap();
                getDiffCallback().propertiesChanged(info.myPath, baseProps.asMap(), propDiff, null);
            }
        }
        
        if (info.myDepth == SVNDepth.EMPTY && !anchor) {
            return;
        }
        
        Set processedFiles = null;
        if (getDiffCallback().isDiffUnversioned()) {
            processedFiles = new SVNHashSet();
        }
        for (Iterator entries = dir.entries(false); entries.hasNext();) {
            SVNEntry entry = (SVNEntry) entries.next();
            
            if (processedFiles != null && !dir.getThisDirName().equals(entry.getName())) {
                processedFiles.add(entry.getName());
            }
            if (anchor && !myAdminInfo.getTargetName().equals(entry.getName())) {
                continue;
            }
            if (dir.getThisDirName().equals(entry.getName())) {
                continue;
            }
            if (info.myComparedEntries.contains(entry.getName())) {
                continue;
            }
            info.myComparedEntries.add(entry.getName());
            if (entry.isFile()) {
                reportModifiedFile(info, entry);
            } else if (entry.isDirectory()) {
                if (anchor || info.myDepth.compareTo(SVNDepth.FILES) > 0 || 
                    info.myDepth == SVNDepth.UNKNOWN) {
                    SVNDepth depthBelowHere = info.myDepth;
                    if (depthBelowHere == SVNDepth.IMMEDIATES) {
                        depthBelowHere = SVNDepth.EMPTY;
                    }
                    SVNDirectoryInfo childInfo = createDirInfo(info, 
                                                               SVNPathUtil.append(info.myPath, entry.getName()), 
                                                               false,
                                                               depthBelowHere);
                    localDirectoryDiff(childInfo);
                }
            }
        }
        if (getDiffCallback().isDiffUnversioned()) {
            String relativePath = dir.getRelativePath(myAdminInfo.getAnchor());
            diffUnversioned(dir.getRoot(), dir, relativePath, anchor, processedFiles);
        }
    }

    private void diffUnversioned(File root, SVNAdminArea dir, String parentRelativePath, boolean anchor, Set processedFiles) throws SVNException {
        File[] allFiles = SVNFileListUtil.listFiles(root);
        for (int i = 0; allFiles != null && i < allFiles.length; i++) {
            File file = allFiles[i];
            if (SVNFileUtil.getAdminDirectoryName().equals(file.getName())) {
                continue;
            }
            if (processedFiles != null && processedFiles.contains(file.getName())) {
                continue;
            }
            if (anchor && !myAdminInfo.getTargetName().equals(file.getName())) {
                continue;
            } else if (dir != null) {// && SVNStatusEditor.isIgnored(, name)dir.isIgnored(file.getName())) {
                Collection globalIgnores = SVNStatusEditor.getGlobalIgnores(myWCAccess.getOptions());
                Collection ignores = SVNStatusEditor.getIgnorePatterns(dir, globalIgnores);
                
                String rootRelativePath = null;
                boolean needToComputeRelativePath = false;
                for (Iterator patterns = ignores.iterator(); patterns.hasNext();) {
                    String pattern = (String) patterns.next();
                    if (pattern.startsWith("/")) {
                        needToComputeRelativePath = true;
                        break;
                    }
                }
                if (needToComputeRelativePath) {
                    if (myWCRootPath == null) {
                        File wcRoot = SVNWCUtil.getWorkingCopyRoot(dir.getRoot(), true);
                        myWCRootPath = wcRoot.getAbsolutePath().replace(File.separatorChar, '/');
                    }
                    if (myWCRootPath != null) {
                        rootRelativePath = file.getAbsolutePath().replace(File.separatorChar, '/');
                        rootRelativePath = SVNPathUtil.getPathAsChild(myWCRootPath, rootRelativePath);
                        if (rootRelativePath != null && !rootRelativePath.startsWith("/")) {
                            rootRelativePath = "/" + rootRelativePath;
                        }
                    }
                }
                if (SVNStatusEditor.isIgnored(ignores, file, rootRelativePath)) {
                    continue;
                }
            }
            // generate patch as for added file.
            SVNFileType fileType = SVNFileType.getType(file);
            if (fileType == SVNFileType.DIRECTORY) {
                diffUnversioned(file, null, SVNPathUtil.append(parentRelativePath, file.getName()), false, null);
            } else if (fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK) {
                String mimeType1 = null;
                String mimeType2 = SVNFileUtil.detectMimeType(file, null);
                String filePath = SVNPathUtil.append(parentRelativePath, file.getName());
                getDiffCallback().fileAdded(filePath, null, file, 0, 0, mimeType1, mimeType2, null, null, null);
            }
        }
    }

    private SVNDirectoryInfo createDirInfo(SVNDirectoryInfo parent, String path, boolean added, 
                                           SVNDepth depth) {
        SVNDirectoryInfo info = new SVNDirectoryInfo();
        info.myParent = parent;
        info.myPath = path;
        info.myIsAdded = added;
        info.myDepth = depth;
        return info;
    }

    private SVNFileInfo createFileInfo(SVNDirectoryInfo parent, String path, boolean added) {
        SVNFileInfo info = new SVNFileInfo();
        info.myPath = path;
        info.myIsAdded = added;
        if (parent.myIsAdded) {
            while(parent.myIsAdded) {
                parent = parent.myParent;
            }
            info.myPath = SVNPathUtil.append(parent.myPath, "fake");
        }
        return info;
    }
    
    private File detranslateFile(SVNAdminArea dir, String name) throws SVNException {
        SVNVersionedProperties properties = dir.getProperties(name);
        String keywords = properties.getStringPropertyValue(SVNProperty.KEYWORDS);
        String eolStyle = properties.getStringPropertyValue(SVNProperty.EOL_STYLE);
        String charsetProp = properties.getStringPropertyValue(SVNProperty.CHARSET);
        String mimeType = properties.getStringPropertyValue(SVNProperty.MIME_TYPE);
        ISVNOptions options = dir.getWCAccess().getOptions();
        String charset = SVNTranslator.getCharset(charsetProp, mimeType, dir.getFile(name).getPath(), options);
        boolean special = properties.getPropertyValue(SVNProperty.SPECIAL) != null;
        if (charset == null && keywords == null && eolStyle == null && (!special || !SVNFileUtil.symlinksSupported())) {
            return dir.getFile(name);
        }
        byte[] eol = SVNTranslator.getEOL(eolStyle, options);
        File tmpFile = createTempFile();
        Map keywordsMap = SVNTranslator.computeKeywords(keywords, null, null, null, null, null);
        SVNTranslator.translate(dir.getFile(name), tmpFile, charset, eol, keywordsMap, special, false);
        return tmpFile;
    }
    
    private File createTempFile() throws SVNException {
        File tmpFile = null;
        try {
            return File.createTempFile("diff.", ".tmp", getTempDirectory());
        } catch (IOException e) {
            SVNFileUtil.deleteFile(tmpFile);
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        return null;
    }
    
    private File getTempDirectory() throws SVNException {
        if (myTempDirectory == null) {
            myTempDirectory = getDiffCallback().createTempDirectory();
        }
        return myTempDirectory;
    }
    
    private SVNAdminArea retrieve(String path) throws SVNException {
        File dir = myAdminInfo.getAnchor().getFile(path);
        return myWCAccess.retrieve(dir);
    }
    
    private AbstractDiffCallback getDiffCallback() {
        return myDiffCallback;
    }

    private static class SVNDirectoryInfo {

        private boolean myIsAdded;
        private String myPath;
        private SVNProperties myPropertyDiff;
        private SVNDirectoryInfo myParent;
        private Set myComparedEntries = new SVNHashSet();
        private SVNDepth myDepth;
    }

    private static class SVNFileInfo {

        private boolean myIsAdded;
        private String myPath;
        private File myFile;
        private File myBaseFile;
        private SVNProperties myPropertyDiff;
    }

    private static void reversePropChanges(SVNProperties base, SVNProperties diff) {
        Collection namesList = new ArrayList(diff.nameSet());
        for (Iterator names = namesList.iterator(); names.hasNext();) {
            String name = (String) names.next();
            SVNPropertyValue newValue = diff.getSVNPropertyValue(name);
            SVNPropertyValue oldValue = base.getSVNPropertyValue(name);
            if (oldValue == null && newValue != null) {
                base.put(name, newValue);
                diff.put(name, (SVNPropertyValue) null);
            } else if (oldValue != null && newValue == null) {
                base.put(name, (SVNPropertyValue) null);
                diff.put(name, oldValue);
            } else if (oldValue != null && newValue != null) {
                base.put(name, newValue);
                diff.put(name, oldValue);
            }
        }
    }

    private static SVNProperties computePropsDiff(SVNProperties props1, SVNProperties props2) {
        SVNProperties propsDiff = new SVNProperties();
        for (Iterator names = props2.nameSet().iterator(); names.hasNext();) {
            String newPropName = (String) names.next();
            if (props1.containsName(newPropName)) {
                // changed.
                SVNPropertyValue oldValue = props2.getSVNPropertyValue(newPropName);
                SVNPropertyValue value = props1.getSVNPropertyValue(newPropName);
                if (oldValue != null && !oldValue.equals(value)) {
                    propsDiff.put(newPropName, oldValue);
                } else if (oldValue == null && value != null) {
                    propsDiff.put(newPropName, oldValue);
                }
            } else {
                // added.
                propsDiff.put(newPropName, props2.getSVNPropertyValue(newPropName));
            }
        }
        for (Iterator names = props1.nameSet().iterator(); names.hasNext();) {
            String oldPropName = (String) names.next();
            if (!props2.containsName(oldPropName)) {
                // deleted
                propsDiff.put(oldPropName, (String) null);
            }
        }
        return propsDiff;
    }

}
