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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

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.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc2.SvnRemoteOperationRunner;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.LocationsInfo;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RepositoryInfo;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.SvnGetInfo;
import org.tmatesoft.svn.core.wc2.SvnInfo;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnRemoteGetInfo extends SvnRemoteOperationRunner<SvnInfo, SvnGetInfo> {

    public boolean isApplicable(SvnGetInfo operation, SvnWcGeneration wcGeneration) throws SVNException {
        if (super.isApplicable(operation, wcGeneration)) {
            return true;
        }
        SVNRevision revision = operation.getRevision();
        if (!revision.isLocal()) {
            return true;
        }
        return false;
    }

    protected SvnInfo run() throws SVNException {
        SvnTarget infoTarget = getOperation().getFirstTarget();
        Structure<RepositoryInfo> repositoryInfo = 
            getRepositoryAccess().createRepositoryFor(infoTarget, getOperation().getRevision(), infoTarget.getResolvedPegRevision(), null);
        
        SVNRepository repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository);
        SVNURL url = repositoryInfo.<SVNURL>get(RepositoryInfo.url);
        long revNum = repositoryInfo.lng(RepositoryInfo.revision);
        SVNURL repositoryRootUrl = repository.getRepositoryRoot(true);
        String repositoryUUID = repository.getRepositoryUUID(true);
        
        repositoryInfo.release();
        
        SVNURL parentURL = url.removePathTail();
        String baseName = SVNPathUtil.tail(url.getPath());
        SVNDirEntry rootEntry = null;
        
        SVNDepth depth = getOperation().getDepth();
        SVNRevision pegRevision = infoTarget.getResolvedPegRevision();
        
        try {
            rootEntry = repository.info("", revNum);
        } catch (SVNException e) {
            if (e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                if (url.equals(repositoryRootUrl)) {
                    if (depth.compareTo(SVNDepth.EMPTY) > 0) {
                        SVNLock[] locks = null;
                        if (pegRevision == SVNRevision.HEAD) {
                            try {
                                locks = repository.getLocks("");
                            } catch (SVNException svne) {
                                SVNErrorCode code = svne.getErrorMessage().getErrorCode();
                                if (code == SVNErrorCode.RA_NOT_IMPLEMENTED || code == SVNErrorCode.UNSUPPORTED_FEATURE) {
                                    locks = new SVNLock[0];
                                } else {
                                    throw svne;
                                }
                            }
                        } else {
                            locks = new SVNLock[0];
                        }
                        locks = locks == null ? new SVNLock[0] : locks;
                        Map<String, SVNLock> locksMap = new HashMap<String, SVNLock>();
                        for (int i = 0; i < locks.length; i++) {
                            SVNLock lock = locks[i];
                            locksMap.put(lock.getPath(), lock);
                        }
                        pushDirInfo(repository, SVNRevision.create(revNum), "", repository.getRepositoryRoot(true), repositoryUUID, url, locksMap, depth);
                        return getOperation().first();
                    }
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Server does not support retrieving information about the repository root");
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                SVNNodeKind urlKind = repository.checkPath("", revNum);
                if (urlKind == SVNNodeKind.NONE) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision {1}", new Object[] { url, new Long(revNum) });
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                
                SVNRepository parentRepository = getRepositoryAccess().createRepository(parentURL, null, false);
                
                Collection<SVNDirEntry> dirEntries = parentRepository.getDir("", revNum, null, SVNDirEntry.DIRENT_KIND | SVNDirEntry.DIRENT_CREATED_REVISION | SVNDirEntry.DIRENT_TIME
                        | SVNDirEntry.DIRENT_LAST_AUTHOR, (Collection<SVNDirEntry>) null);
                
                for (SVNDirEntry dirEntry : dirEntries) {
                    if (baseName.equals(dirEntry.getName())) {
                        rootEntry = dirEntry;
                        break;
                    }
                }
                if (rootEntry == null) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision {1}", new Object[] { url, new Long(revNum)});
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
            } else {
                throw e;
            }
        }
        if (rootEntry == null || rootEntry.getKind() == SVNNodeKind.NONE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' non-existent in revision {1}", new Object[] { url, new Long(revNum) });
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        
        SVNLock lock = null;
        if (rootEntry.getKind() == SVNNodeKind.FILE) {
            try {
                Structure<LocationsInfo> locations = getRepositoryAccess().getLocations(null, SvnTarget.fromURL(url), SVNRevision.create(revNum), SVNRevision.HEAD, SVNRevision.UNDEFINED);
                if (locations != null && locations.hasValue(LocationsInfo.startUrl)) {
                    SVNURL headURL = locations.<SVNURL>get(LocationsInfo.startUrl);
                    locations.release();
                    if (headURL.equals(url)) {
                        try {
                            lock = repository.getLock("");
                        } catch (SVNException e) {
                            if (!(e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED)) {
                                throw e;
                            }
                        }
                    }
                }
            } catch (SVNException e) {
                SVNErrorCode code = e.getErrorMessage().getErrorCode();
                if (code != SVNErrorCode.FS_NOT_FOUND && code != SVNErrorCode.CLIENT_UNRELATED_RESOURCES) {
                    throw e;
                }
            }
        }
        SvnInfo info = creatSvnInfoForEntry(repositoryRootUrl, repositoryUUID, rootEntry, url, revNum, lock);
        getOperation().receive(SvnTarget.fromURL(url), info);
        
        if (depth.compareTo(SVNDepth.EMPTY) > 0 && rootEntry.getKind() == SVNNodeKind.DIR) {
            SVNLock[] locks = null;
            if (pegRevision == SVNRevision.HEAD) {
                try {
                    locks = repository.getLocks("");
                } catch (SVNException svne) {
                    SVNErrorCode code = svne.getErrorMessage().getErrorCode();
                    if (code == SVNErrorCode.RA_NOT_IMPLEMENTED || code == SVNErrorCode.UNSUPPORTED_FEATURE) {
                        locks = new SVNLock[0];
                    } else {
                        throw svne;
                    }
                }
            } else {
                locks = new SVNLock[0];
            }
            locks = locks == null ? new SVNLock[0] : locks;
            Map<String, SVNLock> locksMap = new HashMap<String, SVNLock>();
            for (int i = 0; i < locks.length; i++) {
                lock = locks[i];
                locksMap.put(lock.getPath(), lock);
            }
            
            pushDirInfo(repository, SVNRevision.create(revNum), "", repository.getRepositoryRoot(true), repositoryUUID, url, locksMap, depth);
        }
        return getOperation().first();        
    }
    
    private void pushDirInfo(SVNRepository repos, SVNRevision rev, String dir, SVNURL root, String uuid, SVNURL url, Map<String, SVNLock> locks, SVNDepth depth) throws SVNException {
        Collection<SVNDirEntry> children =  repos.getDir(dir, rev.getNumber(), null, SVNDirEntry.DIRENT_SIZE | SVNDirEntry.DIRENT_KIND | SVNDirEntry.DIRENT_CREATED_REVISION | SVNDirEntry.DIRENT_TIME | SVNDirEntry.DIRENT_LAST_AUTHOR,  new ArrayList<SVNDirEntry>());
        
        for (SVNDirEntry child : children) {
            SVNURL childURL = url.appendPath(child.getName(), false);
            String path = SVNPathUtil.append(dir, child.getName());
            String fsPath = "/" + SVNPathUtil.getRelativePath(root.toDecodedString(), childURL.toDecodedString());

            SVNLock lock = locks.get(fsPath);
            
            if (depth.compareTo(SVNDepth.IMMEDIATES) >= 0 || (depth == SVNDepth.FILES && child.getKind() == SVNNodeKind.FILE)) {
                SvnInfo info = creatSvnInfoForEntry(root, uuid, child, childURL, rev.getNumber(), lock);
                
                getOperation().receive(SvnTarget.fromURL(childURL), info);
            }
            
            if (depth == SVNDepth.INFINITY && child.getKind() == SVNNodeKind.DIR) {
                pushDirInfo(repos, rev, path, root, uuid, childURL, locks, depth);
            }
        }
    }

    private SvnInfo creatSvnInfoForEntry(SVNURL root, String uuid, SVNDirEntry entry, SVNURL entryURL, long revision, SVNLock lock) {
        SvnInfo info = new SvnInfo();
        info.setKind(entry.getKind());
        info.setLastChangedAuthor(entry.getAuthor());
        info.setLastChangedDate(SVNDate.fromDate(entry.getDate()));
        info.setLastChangedRevision(entry.getRevision());
        info.setLock(lock);
        info.setRepositoryRootURL(root);
        info.setRepositoryUuid(uuid);
        info.setSize(entry.getSize());
        info.setUrl(entryURL);
        info.setRevision(revision);
        return info;
    }

}
