/*
 * ====================================================================
 * 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.cli.svn;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import org.tmatesoft.svn.cli.SVNCommandUtil;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNXMLUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNPath;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.ConflictInfo;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusClient;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class SVNStatusCommand extends SVNXMLCommand implements ISVNStatusHandler {

    private SVNStatusPrinter myStatusPrinter;
    private Map myStatusCache;
    
    private int textConflicts;
    private int propConflicts;
    private int treeConflicts;

    public SVNStatusCommand() {
        super("status", new String[] {"stat", "st"});
    }

    protected Collection createSupportedOptions() {
        Collection options = new ArrayList();

        options.add(SVNOption.UPDATE);
        options.add(SVNOption.VERBOSE);
        options.add(SVNOption.NON_RECURSIVE);
        options.add(SVNOption.DEPTH);
        options.add(SVNOption.QUIET);
        options.add(SVNOption.NO_IGNORE);
        options.add(SVNOption.INCREMENTAL);
        options.add(SVNOption.XML);
        options.add(SVNOption.IGNORE_EXTERNALS);
        options.add(SVNOption.CHANGELIST);
        return options;
    }

    public void run() throws SVNException {
        Collection targets = new ArrayList();
        targets = getSVNEnvironment().combineTargets(targets, true);
        if (targets.isEmpty()) {
            targets.add("");
        }
        myStatusPrinter = new SVNStatusPrinter(getSVNEnvironment());
        SVNStatusClient client = getSVNEnvironment().getClientManager().getStatusClient();
        if (!getSVNEnvironment().isXML()) {
            client.setEventHandler(new SVNNotifyPrinter(getSVNEnvironment()));
        }
        if (getSVNEnvironment().isXML()) {
            if (!getSVNEnvironment().isIncremental()) {
                printXMLHeader("status");
            }
        } else if (getSVNEnvironment().isIncremental()) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_ARG_PARSING_ERROR, "'incremental' option only valid in XML mode");
            SVNErrorManager.error(err, SVNLogType.CLIENT);
        }

        Collection changeLists = getSVNEnvironment().getChangelistsCollection();
        for (Iterator ts = targets.iterator(); ts.hasNext();) {
            String target = (String) ts.next();
            SVNPath commandTarget = new SVNPath(target);

            if (getSVNEnvironment().isXML()) {
                StringBuffer xmlBuffer = openXMLTag("target", SVNXMLUtil.XML_STYLE_NORMAL, "path", SVNCommandUtil.getLocalPath(target), null);
                getSVNEnvironment().getOut().print(xmlBuffer);
            }

            try {
                long rev = client.doStatus(commandTarget.getFile(), SVNRevision.HEAD,
                        getSVNEnvironment().getDepth(), getSVNEnvironment().isUpdate(),
                        getSVNEnvironment().isVerbose(), getSVNEnvironment().isNoIgnore(),
                        false, this, changeLists);

                if (getSVNEnvironment().isXML()) {
                    StringBuffer xmlBuffer = new StringBuffer();
                    if (rev >= 0) {
                        xmlBuffer = openXMLTag("against", SVNXMLUtil.XML_STYLE_SELF_CLOSING, "revision", Long.toString(rev), xmlBuffer);
                    }
                    xmlBuffer = closeXMLTag("target", xmlBuffer);
                    getSVNEnvironment().getOut().print(xmlBuffer);
                }
            } catch (SVNException e) {
                getSVNEnvironment().handleWarning(e.getErrorMessage(), new SVNErrorCode[] {SVNErrorCode.WC_NOT_DIRECTORY, SVNErrorCode.WC_PATH_NOT_FOUND},
                        getSVNEnvironment().isQuiet());
            }
        }
        if (myStatusCache != null) {
            for (Iterator changelists = myStatusCache.keySet().iterator(); changelists.hasNext();) {
                String changelist = (String) changelists.next();
                Map statuses = (Map) myStatusCache.get(changelist);
                getSVNEnvironment().getOut().println("\n--- Changelist '" + changelist + "':");
                for (Iterator paths = statuses.keySet().iterator(); paths.hasNext();) {
                    String path = (String) paths.next();
                    SVNStatus status = (SVNStatus) statuses.get(path);
                    myStatusPrinter.printStatus(path, status,
                            getSVNEnvironment().isVerbose() || getSVNEnvironment().isUpdate(),
                            getSVNEnvironment().isVerbose(), getSVNEnvironment().isQuiet(), getSVNEnvironment().isUpdate());
                }
            }
        }
        if (getSVNEnvironment().isXML() && !getSVNEnvironment().isIncremental()) {
            printXMLFooter("status");
        }
        if (! getSVNEnvironment().isQuiet() && ! getSVNEnvironment().isXML()) {
            printConflictStats();
        }

    }

    public void handleStatus(SVNStatus status) throws SVNException {
        countConflicts(status);
        
        String path = getSVNEnvironment().getRelativePath(status.getFile());
        path = SVNCommandUtil.getLocalPath(path);
        if (status != null && status.getChangelistName() != null) {
            if (myStatusCache == null) {
                myStatusCache = new TreeMap();
            }
            if (!myStatusCache.containsKey(status.getChangelistName())) {
                myStatusCache.put(status.getChangelistName(), new LinkedHashMap());
            }
            ((Map) myStatusCache.get(status.getChangelistName())).put(path, status);
            return;
        }
        if (getSVNEnvironment().isXML()) {
            if (SVNStatusPrinter.combineStatus(status) == SVNStatusType.STATUS_NONE && status.getRemoteContentsStatus() == SVNStatusType.STATUS_NONE) {
                return;
            }
            StringBuffer xmlBuffer = printXMLStatus(status, path);
            getSVNEnvironment().getOut().print(xmlBuffer);
        } else {
            myStatusPrinter.printStatus(path, status,
                getSVNEnvironment().isVerbose() || getSVNEnvironment().isUpdate(),
                getSVNEnvironment().isVerbose(), getSVNEnvironment().isQuiet(), getSVNEnvironment().isUpdate());
        }
    }

    private void countConflicts(SVNStatus status) throws SVNException {
        if (status.isConflicted()) {
            if (status.getPropRejectFile() != null) {
                propConflicts++;
            } 
            if (status.getConflictWrkFile() != null || status.getConflictOldFile() != null || status.getConflictNewFile() != null) {
                textConflicts++;
            }
            if (status.getTreeConflict() != null) {
                treeConflicts++;
            }
        }
    }

    protected StringBuffer printXMLStatus(SVNStatus status, String path) {
        StringBuffer xmlBuffer = openXMLTag("entry", SVNXMLUtil.XML_STYLE_NORMAL, "path", path, null);
        Map xmlMap = new LinkedHashMap();
        xmlMap.put("props", status.getNodeStatus() != SVNStatusType.STATUS_DELETED ?
                status.getPropertiesStatus().toString() : SVNStatusType.STATUS_NONE.toString());
        xmlMap.put("item", status.getCombinedNodeAndContentsStatus().toString());
        if (status.isLocked()) {
            xmlMap.put("wc-locked", "true");
        }
        if (status.isCopied()) {
            xmlMap.put("copied", "true");
        }
        if (status.isSwitched()) {
            xmlMap.put("switched", "true");
        }
        if (status.isFileExternal()) {
            xmlMap.put("file-external", "true");
        }
        if (status.isVersioned() && !status.isCopied()) {
            xmlMap.put("revision", status.getRevision().toString());
        }
        if (status.getTreeConflict() != null) {
            xmlMap.put("tree-conflicted", "true");
        }
        xmlBuffer = openXMLTag("wc-status", SVNXMLUtil.XML_STYLE_NORMAL, xmlMap, xmlBuffer);
        if (status.isVersioned() && status.getCommittedRevision().isValid()) {
            xmlBuffer = openXMLTag("commit", SVNXMLUtil.XML_STYLE_NORMAL, "revision", status.getCommittedRevision().toString(), xmlBuffer);
            xmlBuffer = openCDataTag("author", status.getAuthor(), xmlBuffer);
            if (status.getCommittedDate() != null) {
                xmlBuffer = openCDataTag("date", SVNDate.formatDate(status.getCommittedDate()), xmlBuffer);
            }
            xmlBuffer = closeXMLTag("commit", xmlBuffer);
        }
        if (status.isVersioned() && status.getLocalLock() != null) {
            xmlBuffer = openXMLTag("lock", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer);
            xmlBuffer = openCDataTag("token", status.getLocalLock().getID(), xmlBuffer);
            xmlBuffer = openCDataTag("owner", status.getLocalLock().getOwner(), xmlBuffer);
            xmlBuffer = openCDataTag("comment", status.getLocalLock().getComment(), xmlBuffer);
            xmlBuffer = openCDataTag("created", SVNDate.formatDate(status.getLocalLock().getCreationDate()), xmlBuffer);
            xmlBuffer = closeXMLTag("lock", xmlBuffer);
        }
        xmlBuffer = closeXMLTag("wc-status", xmlBuffer);
        if (status.getRemoteNodeStatus() != SVNStatusType.STATUS_NONE || status.getRemotePropertiesStatus() != SVNStatusType.STATUS_NONE ||
                status.getRemoteLock() != null) {
            xmlMap.put("props", status.getRemotePropertiesStatus().toString());
            xmlMap.put("item", status.getCombinedRemoteNodeAndContentsStatus().toString());
            xmlBuffer = openXMLTag("repos-status", SVNXMLUtil.XML_STYLE_NORMAL, xmlMap, xmlBuffer);
            if (status.getRemoteLock() != null) {
                xmlBuffer = openXMLTag("lock", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer);
                xmlBuffer = openCDataTag("token", status.getRemoteLock().getID(), xmlBuffer);
                xmlBuffer = openCDataTag("owner", status.getRemoteLock().getOwner(), xmlBuffer);
                xmlBuffer = openCDataTag("comment", status.getRemoteLock().getComment(), xmlBuffer);
                xmlBuffer = openCDataTag("created", SVNDate.formatDate(status.getRemoteLock().getCreationDate()), xmlBuffer);
                if (status.getRemoteLock().getExpirationDate() != null) {
                    xmlBuffer = openCDataTag("expires", SVNDate.formatDate(status.getRemoteLock().getExpirationDate()), xmlBuffer);
                }
                xmlBuffer = closeXMLTag("lock", xmlBuffer);
            }
            xmlBuffer = closeXMLTag("repos-status", xmlBuffer);
        }
        xmlBuffer = closeXMLTag("entry", xmlBuffer);
        return xmlBuffer;
    }
    
    private void printConflictStats()
    {
      if (textConflicts > 0 || propConflicts > 0 ||
          treeConflicts > 0)
      getEnvironment().getOut().println("Summary of conflicts:");

      if (textConflicts > 0)
          getEnvironment().getOut().println(
          "  Text conflicts: " + textConflicts);

      if (propConflicts > 0)
          getEnvironment().getOut().println(
          "  Property conflicts: " + propConflicts);

      if (treeConflicts > 0)
          getEnvironment().getOut().println(
          "  Tree conflicts: " + treeConflicts);

    }

    
}