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

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathRevision;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeOriginInfo;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.ISvnOperationOptionsProvider;
import org.tmatesoft.svn.core.wc2.SvnCopySource;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

import java.io.File;

public class SvnNgRepositoryAccess extends SvnRepositoryAccess {

    public SvnNgRepositoryAccess(ISvnOperationOptionsProvider operationOptionsProvider, SVNWCContext context) throws SVNException {
        super(operationOptionsProvider, context);
    }

    @Override
    public Structure<RepositoryInfo> createRepositoryFor(SvnTarget target, SVNRevision revision, SVNRevision pegRevision, File baseDirectory) throws SVNException {
        SVNURL url = getTargetURL(target);
        if (url == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", target.getFile());
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        SVNRevision[] resolvedRevisions = resolveRevisions(pegRevision, revision, target.isURL(), true);
        SVNRevision pegRev = resolvedRevisions[0];
        SVNRevision startRev = resolvedRevisions[1];
        SVNRepository repository = createRepository(url, baseDirectory);
        Structure<LocationsInfo> locationsInfo = getLocations(repository, target, pegRev, startRev, SVNRevision.UNDEFINED);
        if (target.isURL() && !url.equals(repository.getLocation())) {
            url = repository.getLocation();
        }
        long rev = locationsInfo.lng(LocationsInfo.startRevision);
        url = locationsInfo.<SVNURL>get(LocationsInfo.startUrl);
        locationsInfo.release();
        
        repository.setLocation(url, false);
        
        if (rev < 0) {
            Structure<RevisionsPair> revs = getRevisionNumber(repository, target, SVNRevision.HEAD, null);
            rev = revs.lng(RevisionsPair.revNumber);
            revs.release();            
        }
        
        Structure<RepositoryInfo> result = Structure.obtain(RepositoryInfo.class);
        
        result.set(RepositoryInfo.revision, rev);
        result.set(RepositoryInfo.repository, repository);
        result.set(RepositoryInfo.url, url);
        
        return result;
    }
    
    public Structure<UrlInfo> getURLFromPath(SvnTarget path, SVNRevision revision, SVNRepository repository) throws SVNException {
        Structure<UrlInfo> urlInfo = Structure.obtain(UrlInfo.class);
        SVNURL url = null;
        if (revision == SVNRevision.WORKING) {
            Structure<NodeOriginInfo> nodeOrigin = getWCContext().getNodeOrigin(path.getFile(), false);
            urlInfo.set(UrlInfo.pegRevision, nodeOrigin.lng(NodeOriginInfo.revision));            
            if (nodeOrigin.hasValue(NodeOriginInfo.reposRelpath)) {
              url = SVNWCUtils.join(nodeOrigin.<SVNURL>get(NodeOriginInfo.reposRootUrl), nodeOrigin.<File>get(NodeOriginInfo.reposRelpath));  
            }
            if (url != null && nodeOrigin.hasValue(NodeOriginInfo.isCopy) && nodeOrigin.is(NodeOriginInfo.isCopy) && repository != null) {
                if (!url.equals(repository.getLocation())) {
                    urlInfo.set(UrlInfo.dropRepsitory, true);
                    repository = null;
                    
                }
            }
        }
        if (url == null) {
            url = getWCContext().getNodeUrl(path.getFile());
        }
        if (url == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", path.getFile());
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        urlInfo.set(UrlInfo.url, url);
        return urlInfo;
    }
    
    @Override
    public Structure<RevisionsPair> getRevisionNumber(SVNRepository repository, SvnTarget path, SVNRevision revision, Structure<RevisionsPair> youngestRevision) throws SVNException {
        Structure<RevisionsPair> result = youngestRevision == null ? Structure.obtain(RevisionsPair.class) : youngestRevision;
        if (youngestRevision == null) {
            // initial value for the latest rev num
            result.set(RevisionsPair.youngestRevision, -1);
        }
        if (repository == null && (revision == SVNRevision.HEAD || revision.getDate() != null)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_RA_ACCESS_REQUIRED);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (revision == SVNRevision.UNDEFINED) {
            result.set(RevisionsPair.revNumber, -1);
        } else if (revision.getNumber() >= 0) {
            result.set(RevisionsPair.revNumber, revision.getNumber());            
        } else if (revision == SVNRevision.HEAD) {
            if (youngestRevision != null && youngestRevision.lng(RevisionsPair.youngestRevision) >= 0) {
                result.set(RevisionsPair.revNumber, youngestRevision.lng(RevisionsPair.youngestRevision));            
            } else {
                if (repository == null) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_RA_ACCESS_REQUIRED);
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                long youngest = repository.getLatestRevision();
                result.set(RevisionsPair.revNumber, youngest);            
                result.set(RevisionsPair.youngestRevision, youngest);            
            }
        } else if (revision == SVNRevision.WORKING || revision == SVNRevision.BASE) {
            if (path == null) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED);
                SVNErrorManager.error(err, SVNLogType.WC);
            } else if (!path.isFile()) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "PREV, BASE or COMMITTTED revision keywords are invalid for URL");
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            long revnum = -1;
            try {
                revnum = getWCContext().getNodeCommitBaseRev(path.getFile());
            } catch (SVNException e) {
                if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", path.getFile());
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                throw e;
            }
            if (revnum < 0) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Path ''{0}'' has no committed revision", path.getFile());
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            result.set(RevisionsPair.revNumber, revnum);
        } else if (revision == SVNRevision.COMMITTED || revision == SVNRevision.PREVIOUS) {
            if (path == null) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED);
                SVNErrorManager.error(err, SVNLogType.WC);
            } else if (!path.isFile()) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "PREV, BASE or COMMITTTED revision keywords are invalid for URL");
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            long revnum = -1;
            try {
                Structure<NodeInfo> info = getWCContext().getDb().readInfo(path.getFile(), NodeInfo.changedRev);
                revnum = info.lng(NodeInfo.changedRev);
                info.release();
            } catch (SVNException e) {
                if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", path.getFile());
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                throw e;
            }
            if (revnum < 0) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Path ''{0}'' has no committed revision", path.getFile());
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            if (revision == SVNRevision.PREVIOUS) {
                revnum = revnum - 1;
            }
            result.set(RevisionsPair.revNumber, revnum);
        } else if (revision.getDate() != null) {
            if (repository == null) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_RA_ACCESS_REQUIRED);
                SVNErrorManager.error(err, SVNLogType.WC);
            }
            long revnum = repository.getDatedRevision(revision.getDate());
            result.set(RevisionsPair.revNumber, revnum);
        } else {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Unrecognized revision type requested for ''{0}''", path.getFile());
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (youngestRevision != null && (revision == SVNRevision.HEAD || revision.getDate() != null) &&
                youngestRevision.lng(RevisionsPair.youngestRevision) >= 0 &&
                result.lng(RevisionsPair.revNumber) >= 0 &&
                result.lng(RevisionsPair.revNumber) > youngestRevision.lng(RevisionsPair.youngestRevision)) {
            result.set(RevisionsPair.revNumber, youngestRevision.lng(RevisionsPair.youngestRevision));
        }
        
        return result;
    }

    public SVNRepository createRepository(SVNURL url, File baseDirectory) throws SVNException {
        String expectedUuid = null;
        if (baseDirectory != null) {
            SVNWCNodeReposInfo nodeReposInfo = null;
            try {
                nodeReposInfo = getWCContext().getNodeReposInfo(baseDirectory);
                expectedUuid = nodeReposInfo.reposUuid;
            } catch (SVNException e) {
                SVNErrorCode errorCode = e.getErrorMessage().getErrorCode();
                if (!(errorCode == SVNErrorCode.WC_NOT_WORKING_COPY ||
                      errorCode == SVNErrorCode.WC_PATH_NOT_FOUND ||
                      errorCode == SVNErrorCode.WC_UPGRADE_REQUIRED)) {
                    throw e;
                }
            }            
        }
        return createRepository(url, expectedUuid, true);
    }

    public SvnCopySource createRemoteCopySource(SVNWCContext context, SvnCopySource localCopySource) throws SVNException {
        Structure<NodeOriginInfo> origin = context.getNodeOrigin(localCopySource.getSource().getFile(), true, NodeOriginInfo.reposRelpath, NodeOriginInfo.reposRootUrl, NodeOriginInfo.revision);
        SVNURL url = origin.get(NodeOriginInfo.reposRootUrl);
        url = url.appendPath(SVNFileUtil.getFilePath(origin.<File>get(NodeOriginInfo.reposRelpath)), false);
        SVNRevision pegRevision = localCopySource.getSource().getResolvedPegRevision();
        SVNRevision revision = localCopySource.getRevision();
        if (pegRevision == SVNRevision.UNDEFINED || pegRevision == SVNRevision.WORKING || pegRevision == SVNRevision.BASE) {
            pegRevision = SVNRevision.create(origin.lng(NodeOriginInfo.revision));
        }
        if (revision == SVNRevision.BASE) {
            revision = SVNRevision.create(origin.lng(NodeOriginInfo.revision));
        }
        origin.release();
        localCopySource = SvnCopySource.create(SvnTarget.fromURL(url, pegRevision), revision);
        return localCopySource;
    }

    public SVNPathRevision resolveRevisionAndUrl(SVNRepository svnRepository, SvnTarget svnTarget, SVNRevision pegRevision, SVNRevision revision) throws SVNException {
        SVNRevision startRevision = revision;

        SVNRevision[] svnRevisions = resolveRevisions(pegRevision, revision, svnTarget.isURL(), true);
        pegRevision = svnRevisions[0];
        startRevision = svnRevisions[1];

        Structure<LocationsInfo> locationsInfoStructure = getLocations(svnRepository, svnTarget, pegRevision, startRevision, SVNRevision.UNDEFINED);
        SVNURL url = locationsInfoStructure.get(LocationsInfo.startUrl);
        long rev = locationsInfoStructure.lng(LocationsInfo.startRevision);

        return SVNPathRevision.createWithRepository(svnRepository, url, rev);
    }
    
    protected SVNURL getTargetURL(SvnTarget target) throws SVNException {
        if (target.isURL()) {
            return target.getURL();
        }
        return getWCContext().getNodeUrl(target.getFile());
    }

}
