/*
 * ====================================================================
 * 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.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNMergeInfo;
import org.tmatesoft.svn.core.SVNMergeInfoInheritance;
import org.tmatesoft.svn.core.SVNMergeRangeList;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.internal.io.fs.FSEntry;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSParentPath;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.util.SVNLogType;


/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class SVNMergeInfoManager {
    
    public Map getMergeInfo(String[] paths, FSRevisionRoot root, SVNMergeInfoInheritance inherit, 
            boolean includeDescendants) throws SVNException {

        if (!root.getOwner().supportsMergeInfo()) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, 
                    "Querying mergeinfo requires version {0} of the FSFS filesystem schema;" +
                    " filesystem ''{1}'' uses only version {2}", 
                    new Object[] {FSFS.MIN_MERGE_INFO_FORMAT, root.getOwner().getDBRoot(),
                            root.getOwner().getDBFormat()});
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        Map mergeInfoAsHashes = getMergeInfoForPaths(root, paths, inherit, includeDescendants);
        Map mergeInfo = new TreeMap();
        for (Iterator mergeInfoIter = mergeInfoAsHashes.keySet().iterator(); mergeInfoIter.hasNext();) {
            String path = (String) mergeInfoIter.next();
            Map pathMergeInfo = (Map) mergeInfoAsHashes.get(path);
            mergeInfo.put(path, new SVNMergeInfo(path, pathMergeInfo));
        }
        return mergeInfo;
    }
    
    private Map getMergeInfoForPaths(FSRevisionRoot root, String[] paths, 
            SVNMergeInfoInheritance inherit, boolean includeDescendants) throws SVNException {
        Map result = new TreeMap();
        for (int i = 0; i < paths.length; i++) {
            String path = paths[i];
            Map pathMergeInfo = getMergeInfoForPath(root, path, inherit);
            if (pathMergeInfo != null) {
                result.put(path, pathMergeInfo);
            }
            if (includeDescendants) {
                addDescendantMergeInfo(result, root, path);
            }
        }    
        return result;
    }

    private void addDescendantMergeInfo(Map result, FSRevisionRoot root, String path) throws SVNException {
        FSRevisionNode node = root.getRevisionNode(path);
        if (node.hasDescendantsWithMergeInfo()) {
            crawlDirectoryForMergeInfo(root, path, node, result);
        }
    }
    
    private Map crawlDirectoryForMergeInfo(FSRevisionRoot root, String path, FSRevisionNode node, 
            Map result) throws SVNException {
        FSFS fsfs = root.getOwner();
        Map entries = node.getDirEntries(fsfs);
        for (Iterator entriesIter = entries.values().iterator(); entriesIter.hasNext();) {
            FSEntry entry = (FSEntry) entriesIter.next();
            String kidPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, entry.getName()));
            FSRevisionNode kidNode = root.getRevisionNode(kidPath);
            if (kidNode.hasMergeInfo()) {
                SVNProperties propList = kidNode.getProperties(fsfs);
                String mergeInfoString = propList.getStringValue(SVNProperty.MERGE_INFO);
                if (mergeInfoString == null) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, 
                            "Node-revision #''{0}'' claims to have mergeinfo but doesn''t", entry.getId());
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }
                Map kidMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null);
                result.put(kidPath, kidMergeInfo);
            }
            if (kidNode.hasDescendantsWithMergeInfo()) {
                crawlDirectoryForMergeInfo(root, kidPath, kidNode, result);
            }
        }
        return result;
    }
    
    private Map getMergeInfoForPath(FSRevisionRoot revRoot, String path, SVNMergeInfoInheritance inherit) throws SVNException {
        Map mergeInfo = null;
        path = SVNPathUtil.canonicalizeAbsolutePath(path);
        FSParentPath parentPath = revRoot.openPath(path, true, true);
        if (inherit == SVNMergeInfoInheritance.NEAREST_ANCESTOR && parentPath.getParent() == null) {
            return mergeInfo;
        }
        
        FSParentPath nearestAncestor = null;
        if (inherit == SVNMergeInfoInheritance.NEAREST_ANCESTOR) {
            nearestAncestor = parentPath.getParent();
        } else {
            nearestAncestor = parentPath;
        }
        
        FSFS fsfs = revRoot.getOwner();
        while (true) {
            boolean hasMergeInfo = nearestAncestor.getRevNode().hasMergeInfo();
            if (hasMergeInfo) {
                break;
            }
            
            if (inherit == SVNMergeInfoInheritance.EXPLICIT) {
                return mergeInfo;
            }
            nearestAncestor = nearestAncestor.getParent();
            if (nearestAncestor == null) {
                return mergeInfo;
            }
        }
        
        SVNProperties propList = nearestAncestor.getRevNode().getProperties(fsfs);
        String mergeInfoString = propList.getStringValue(SVNProperty.MERGE_INFO);
        if (mergeInfoString == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, 
                    "Node-revision ''{0}@{1}'' claims to have mergeinfo but doesn''t", 
                    new Object[] { nearestAncestor.getAbsPath(), revRoot.getRevision()});
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        
        if (nearestAncestor == parentPath) {
            return SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null);
        } 
        
        Map tmpMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null); 
        tmpMergeInfo = SVNMergeInfoUtil.getInheritableMergeInfo(tmpMergeInfo, null, 
                SVNRepository.INVALID_REVISION, SVNRepository.INVALID_REVISION);
        mergeInfo = appendToMergedFroms(tmpMergeInfo, parentPath.getRelativePath(nearestAncestor));
        return mergeInfo;
    }
    
    private Map appendToMergedFroms(Map mergeInfo, String pathComponent) {
        Map result = new TreeMap(); 
        for (Iterator pathsIter = mergeInfo.keySet().iterator(); pathsIter.hasNext();) {
            String path = (String) pathsIter.next();
            SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(path);
            result.put(SVNPathUtil.append(path, pathComponent), rangeList.dup());
        }
        return result;
    }
    
}
