package org.tmatesoft.svn.core.internal.wc2.remote;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.tmatesoft.svn.core.ISVNDirEntryHandler;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeOriginInfo;
import org.tmatesoft.svn.core.internal.wc2.SvnRemoteOperationRunner;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RepositoryInfo;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RevisionsPair;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver;
import org.tmatesoft.svn.core.wc2.SvnGetProperties;
import org.tmatesoft.svn.core.wc2.SvnInheritedProperties;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnRemoteGetProperties extends SvnRemoteOperationRunner<SVNProperties, SvnGetProperties> {

    public boolean isApplicable(SvnGetProperties operation, SvnWcGeneration wcGeneration) throws SVNException {
        if (operation.isRevisionProperties()) {
            return false;
        }        
        if (super.isApplicable(operation, wcGeneration)) {
            return true;
        }
        if (!operation.getRevision().isLocal()) {
            return true;
        }
        return false;
    }

    @Override
    protected SVNProperties run() throws SVNException {
        SvnTarget target = getOperation().getFirstTarget();
        SVNURL url;
        SVNRepository repository;
        long revnum;
        SvnTarget reposTarget = target;
        SVNRevision revision = getOperation().getRevision();
        SVNRevision pegRevision = target.getResolvedPegRevision();

        if (pegRevision.isLocal() || revision.isLocal()) {
            if (!target.isFile()) {
                SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED);
                SVNErrorManager.error(errorMessage, SVNLogType.WC);
            }
            final File localAbsPath = getOperation().getFirstTarget().getFile();
            final Structure<NodeOriginInfo> origin = getWcContext().getNodeOrigin(localAbsPath, false, 
                    NodeOriginInfo.isCopy, NodeOriginInfo.copyRootAbsPath, 
                    NodeOriginInfo.reposRelpath, NodeOriginInfo.reposRootUrl,
                    NodeOriginInfo.reposUuid, NodeOriginInfo.revision);
            final File reposRelPath = origin.get(NodeOriginInfo.reposRelpath);
            if (reposRelPath != null) {
                final SVNURL rootURL = origin.get(NodeOriginInfo.reposRootUrl);
                url = rootURL.appendPath(SVNFileUtil.getFilePath(reposRelPath), false);
                reposTarget = SvnTarget.fromURL(url);
                Structure<RevisionsPair> revisionPair = null;
                revisionPair = getRepositoryAccess().getRevisionNumber(null, target, target.getResolvedPegRevision(), revisionPair);
                final long pegrevnum = revisionPair.lng(RevisionsPair.revNumber);
                pegRevision = SVNRevision.create(pegrevnum);
                
                if (getOperation().getRevision().isLocal()) {
                    revisionPair = getRepositoryAccess().getRevisionNumber(null, target, getOperation().getRevision(), revisionPair);
                    revnum = revisionPair.lng(RevisionsPair.revNumber);
                    revision = SVNRevision.create(revnum);
                } 
            } 
        } 

        Structure<RepositoryInfo> repositoryInfo = 
            getRepositoryAccess().createRepositoryFor(
                    reposTarget, 
                    revision, 
                    pegRevision, 
                    null);
        repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository);
        revnum = repositoryInfo.lng(RepositoryInfo.revision);
        url = repositoryInfo.<SVNURL>get(RepositoryInfo.url);
        repositoryInfo.release();
        
        SVNNodeKind kind = repository.checkPath("", revnum);
        if (kind == SVNNodeKind.UNKNOWN) {
            final SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Unknown node kind for ''{0}''", repository.getLocation());
            SVNErrorManager.error(err, SVNLogType.WC);
        } else if (kind == null || kind == SVNNodeKind.NONE) {
            final SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' does not exist in revision {1}", repository.getLocation(), revnum);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (getOperation().getTargetInheritedPropertiesReceiver() != null) {
            final SVNURL repositoryRoot = repository.getRepositoryRoot(true);
            final Map<String, SVNProperties> inheritedProperties = repository.getInheritedProperties("", revnum, null);
            final List<SvnInheritedProperties> result = new ArrayList<SvnInheritedProperties>();
            for (String path : inheritedProperties.keySet()) {
                final SvnInheritedProperties propItem = new SvnInheritedProperties();
                propItem.setTarget(SvnTarget.fromURL(repositoryRoot.appendPath(path, false)));
                propItem.setProperties(inheritedProperties.get(path));
                result.add(propItem);
            }
            if (!result.isEmpty()) {
                getOperation().getTargetInheritedPropertiesReceiver().receive(target, result);
            }
        }
        remotePropertyGet(url, kind, "", repository, revnum, getOperation().getDepth(), getOperation());
        
        return getOperation().first();
    }
    
    public static void remotePropertyGet(SVNURL url, SVNNodeKind kind, String path, SVNRepository repos, long revNumber, SVNDepth depth, ISvnObjectReceiver<SVNProperties> receiver) throws SVNException {
        SVNURL fullURL = url.appendPath(path, false);
        SVNProperties props = new SVNProperties();
        final Collection<SVNDirEntry> dirEntries = new LinkedList<SVNDirEntry>();
        if (kind == SVNNodeKind.DIR) {
            ISVNDirEntryHandler handler = SVNDepth.FILES.compareTo(depth) <= 0 ? new ISVNDirEntryHandler() {
                public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {
                    dirEntries.add(dirEntry);
                }
            } : null;
            repos.getDir(path, revNumber, props, SVNDirEntry.DIRENT_KIND, handler);
        } else if (kind == SVNNodeKind.FILE) {
            repos.getFile(path, revNumber, props, null);
        } else {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Unknown node kind for ''{0}''", fullURL);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (props != null) {
            for (String name : new HashSet<String>(props.nameSet())) {
                if (!SVNProperty.isRegularProperty(name)) {
                    props.remove(name);
                }                
            }
            if (!props.isEmpty()) {
                receiver.receive(SvnTarget.fromURL(fullURL), props);
            }
        }
        
        if (depth.compareTo(SVNDepth.EMPTY) > 0 && dirEntries != null && kind == SVNNodeKind.DIR) {
            for (SVNDirEntry entry : dirEntries) {
                if (entry.getKind() == SVNNodeKind.FILE || depth.compareTo(SVNDepth.FILES) > 0) {
                    String entryPath = SVNPathUtil.append(path, entry.getName());
                    SVNDepth depthBelow = depth;
                    if (depth == SVNDepth.IMMEDIATES) {
                        depthBelow = SVNDepth.EMPTY;
                    }
                    remotePropertyGet(url, entry.getKind(), entryPath, repos, revNumber, depthBelow, receiver);
                }
            }
        }
    }


}
