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

import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.wc.*;
import org.tmatesoft.svn.core.internal.wc17.*;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbExternals;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNCapability;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver;
import org.tmatesoft.svn.core.wc2.SvnGetStatus;
import org.tmatesoft.svn.core.wc2.SvnStatus;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class SvnNgGetStatus extends SvnNgOperationRunner<SvnStatus, SvnGetStatus> implements ISvnObjectReceiver<SvnStatus> {
    
    private boolean targetDeletedInRepository;
    
    @Override
    protected SvnStatus run(SVNWCContext context) throws SVNException {
        File directoryPath;
        String targetName;
        
        SVNNodeKind kind = context.readKind(getFirstTarget(), false);
        SVNDepth depth = getOperation().getDepth();
        if (kind == SVNNodeKind.DIR) {
            directoryPath = getFirstTarget();
            targetName = "";            
        } else {
            directoryPath = SVNFileUtil.getParentFile(getFirstTarget());
            targetName = SVNFileUtil.getFileName(getFirstTarget());
            if (kind == SVNNodeKind.FILE) {
                if (depth == SVNDepth.EMPTY) {
                    depth = SVNDepth.FILES;
                }
            } else {
                boolean notAWc = false;
                try {
                    kind = context.readKind(directoryPath, false);
                    notAWc = kind != SVNNodeKind.DIR;
                } catch (SVNException e) {
                    notAWc = true;
                }
                if (notAWc) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "The node ''{0}'' was not found", getFirstTarget());
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
            }
        }
        
        String[] globalIgnores = context.getOptions().getIgnorePatterns();
        
        if (getOperation().isRemote()) {
            SVNURL url = context.getUrlFromPath(directoryPath);
            if (url == null) {
                SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Entry ''{0}'' has no URL", directoryPath);
                SVNErrorManager.error(error, SVNLogType.WC);
            }
            SVNRepository repository = getRepositoryAccess().createRepository(url, null, true);
            long rev;
            if (getOperation().getRevision() == SVNRevision.HEAD) {
                rev = -1;
            } else {
                rev = context.getRevisionNumber(getOperation().getRevision(), null, repository, getFirstTarget());
            }
            kind = repository.checkPath("", rev);
            checkCancelled();
            SVNStatusEditor17 editor = null;
            SVNReporter17 reporter = null;
            if (kind == SVNNodeKind.NONE) {
                boolean added = context.isNodeAdded(directoryPath);
                if (added) {
                    boolean replaced = context.isNodeReplaced(directoryPath);
                    if (replaced) {
                        added = false;
                    }
                }
                setTargetDeletedInRepository(!added);
                editor = new SVNStatusEditor17(getFirstTarget(), context, 
                        getOperation().getOptions(), getOperation().isReportIgnored(), getOperation().isReportAll(), depth, this);
                editor.setFileListHook(getOperation().getFileListHook());
                checkCancelled();
                editor.closeEdit();
            } else {
                editor = new SVNRemoteStatusEditor17(directoryPath, targetName, context, 
                        getOperation().getOptions(), getOperation().isReportIgnored(), getOperation().isReportAll(),
                        depth, this);
                editor.setFileListHook(getOperation().getFileListHook());
                
                SVNRepository locksRepos = getRepositoryAccess().createRepository(url, null, false);
                checkCancelled();
                boolean serverSupportsDepth = repository.hasCapability(SVNCapability.DEPTH);
                reporter = new SVNReporter17(getFirstTarget(), context, false, !serverSupportsDepth,
                        depth, false, true, true, false, null);
                SVNStatusReporter17 statusReporter = new SVNStatusReporter17(locksRepos, reporter, editor);
                String target = "".equals(targetName) ? null : targetName;
                SVNDepth statusDepth = getOperation().isDepthAsSticky() ? depth : SVNDepth.UNKNOWN;
                repository.status(rev, target, statusDepth, statusReporter, SVNCancellableEditor.newInstance((ISVNEditor) editor, this, null));
            }
            getOperation().setRemoteRevision(editor.getTargetRevision());                
            
            long reportedFiles = reporter != null ? reporter.getReportedFilesCount() : 0;
            long totalFiles = reporter != null ? reporter.getTotalFilesCount() : 0;
            SVNEvent event = SVNEventFactory.createSVNEvent(getFirstTarget(), SVNNodeKind.NONE, null, editor.getTargetRevision(), SVNEventAction.STATUS_COMPLETED, null, null, null,
                    reportedFiles, totalFiles);
            
            handleEvent(event, ISVNEventHandler.UNKNOWN);
        } else {
            SVNStatusEditor17 editor = new SVNStatusEditor17(directoryPath, context, context.getOptions(), 
                    getOperation().isReportIgnored(), 
                    getOperation().isReportAll(),
                    depth,
                    this);
            editor.setFileListHook(getOperation().getFileListHook());
            try {
                editor.walkStatus(getFirstTarget(),
                        depth,
                        getOperation().isReportAll(),
                        getOperation().isReportIgnored(),
                        false,
                        globalIgnores != null ? Arrays.asList(globalIgnores) : null);
            } catch (SVNException e) {
                if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_MISSING) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_WORKING_COPY, "''{0}'' is not a working copy", getFirstTarget());
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                throw e;
            }
        }
        
        if (depth.isRecursive() && getOperation().isReportExternals()) {
            Map<File, File> externals = context.getDb().getExternalsDefinedBelow(getFirstTarget());
            doExternalStatus(externals);
        }
        
        return getOperation().first();
    }

    private void doExternalStatus(Map<File, File> externalsNew) throws SVNException {
        for (Iterator<File> paths = externalsNew.keySet().iterator(); paths.hasNext(); ) {
            File fullPath = paths.next();
            File wcRootAbsPath = externalsNew.get(fullPath);

            Structure<StructureFields.ExternalNodeInfo> externalInfo = SvnWcDbExternals.readExternal(getWcContext(), fullPath, wcRootAbsPath, StructureFields.ExternalNodeInfo.kind);
            ISVNWCDb.SVNWCDbKind externalKind = externalInfo.get(StructureFields.ExternalNodeInfo.kind);

            if (externalKind != ISVNWCDb.SVNWCDbKind.Dir) {
                continue;
            }

            if (SVNFileType.getType(fullPath) != SVNFileType.DIRECTORY) {
                continue;
            }
            handleEvent(SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.STATUS_EXTERNAL, null, null, null), ISVNEventHandler.UNKNOWN);
            try {

                SvnGetStatus getStatus = getOperation().getOperationFactory().createGetStatus();
                getStatus.setSingleTarget(SvnTarget.fromFile(fullPath));
                getStatus.setRevision(SVNRevision.HEAD);
                getStatus.setDepth(getOperation().getDepth());
                getStatus.setRemote(getOperation().isRemote());
                getStatus.setReportAll(getOperation().isReportAll());
                getStatus.setReportIgnored(getOperation().isReportIgnored());
                getStatus.setReportExternals(getOperation().isReportExternals());
                getStatus.setReceiver(getOperation().getReceiver());
                getStatus.setFileListHook(getOperation().getFileListHook());

                getStatus.run();
            } catch (SVNException e) {
                if (e instanceof SVNCancelException) {
                    throw e;
                }
            }
        }
    }

    private void setTargetDeletedInRepository(boolean deleted) {
        this.targetDeletedInRepository = deleted;
    }

    private boolean isTargetDeletedInRepository() {
        return targetDeletedInRepository;
    }

    public void receive(SvnTarget target, SvnStatus object) throws SVNException {
        if (getOperation().getApplicableChangelists() != null && !getOperation().getApplicableChangelists().isEmpty()) {
            if (!getOperation().getApplicableChangelists().contains(object.getChangelist())) {
                return;
            }
        }      
        if (isTargetDeletedInRepository()) {
            object.setRepositoryNodeStatus(SVNStatusType.STATUS_DELETED);
        }
        getOperation().receive(target, object);
    }

    @Override
    public void reset(SvnWcGeneration wcGeneration) {
        super.reset(wcGeneration);
        setTargetDeletedInRepository(false);
    }
}
