/*
 * ====================================================================
 * 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.core.wc.xml;

import java.io.File;

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.wc.SVNErrorManager;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.util.ISVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;


/**
 * This is an implementation of the <b>ISVNStatusHandler</b> interface 
 * that writes XML formatted status information to a specified 
 * <b>ContentHandler</b>. 
 *  
 * @version 1.3
 * @author  TMate Software Ltd.
 * @since   1.2
 */
public class SVNXMLStatusHandler extends AbstractXMLHandler implements ISVNStatusHandler {
    
    private static final String AGAINST_TAG = "against";
    private static final String TARGET_TAG = "target";

    /**
     * <code>'expires'</code> tag.
     */
    public static final String EXPIRES_TAG = "expires";

    /**
     * <code>'created'</code> tag.
     */
    public static final String CREATED_TAG = "created";
    
    /**
     * <code>'comment'</code> tag.
     */
    public static final String COMMENT_TAG = "comment";

    /**
     * <code>'owner'</code> tag.
     */
    public static final String OWNER_TAG = "owner";

    /**
     * <code>'token'</code> tag.
     */
    public static final String TOKEN_TAG = "token";

    /**
     * <code>'date'</code> tag.
     */
    public static final String DATE_TAG = "date";

    /**
     * <code>'author'</code> tag.
     */
    public static final String AUTHOR_TAG = "author";

    /**
     * <code>'repos-status'</code> tag.
     */
    public static final String REMOTE_STATUS_TAG = "repos-status";

    /**
     * <code>'lock'</code> tag.
     */
    public static final String LOCK_TAG = "lock";

    /**
     * <code>'commit'</code> tag.
     */
    public static final String COMMIT_TAG = "commit";

    /**
     * <code>'wc-status'</code> tag.
     */
    public static final String WC_STATUS_TAG = "wc-status";

    /**
     * <code>'entry'</code> tag.
     */
    public static final String ENTRY_TAG = "entry";

    /**
     * <code>'status'</code> tag.
     */
    public static final String STATUS_TAG = "status";

    /**
     * <code>'revision'</code> attribute.
     */
    public static final String REVISION_ATTR = "revision";

    /**
     * <code>'switched'</code> attribute.
     */
    public static final String SWITCHED_ATTR = "switched";

    /**
     * <code>'copied'</code> attribute.
     */
    public static final String COPIED_ATTR = "copied";

    /**
     * <code>'wc-locked'</code> attribute.
     */
    public static final String WC_LOCKED_ATTR = "wc-locked";

    /**
     * <code>'props'</code> attribute.
     */
    public static final String PROPS_ATTR = "props";

    /**
     * <code>'item'</code> attribute.
     */
    public static final String ITEM_ATTR = "item";

    /**
     * <code>'path'</code> attribute.
     */
    public static final String PATH_ATTR = "path";

    private static final String TRUE = "true";

    private File myTargetPath;
    
    /**
     * Creates a new status handler.
     * 
     * @param saxHandler a <b>ContentHandler</b> to form 
     *                   an XML tree
     */
    public SVNXMLStatusHandler(ContentHandler saxHandler) {
        this(saxHandler, null);
    }    

    /**
     * Creates a new status handler.
     * 
     * @param saxHandler a <b>ContentHandler</b> to form 
     *                   an XML tree
     * @param log        a debug logger
     */
    public SVNXMLStatusHandler(ContentHandler saxHandler, ISVNDebugLog log) {
        super(saxHandler, log);
    }    
    
    /**
     * Begins an XML tree with the target path for which the 
     * status is run. 
     * 
     * @param path a WC target path
     */
    public void startTarget(File path) {
        try {
            myTargetPath = path;
            addAttribute(PATH_ATTR, path.getPath());
            openTag(TARGET_TAG);
        } catch (SAXException e) {
            getDebugLog().logSevere(SVNLogType.DEFAULT, e);
        }
    }

    /**
     * Handles a next <code>status</code> object producing corresponding xml.
     * 
     * @param  status 
     * @throws SVNException 
     */
    public void handleStatus(SVNStatus status) throws SVNException {
        try {
            sendToHandler(status);
        } catch (SAXException th) {
            getDebugLog().logSevere(SVNLogType.DEFAULT, th);
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.XML_MALFORMED, th.getLocalizedMessage());
            SVNErrorManager.error(err, th, SVNLogType.DEFAULT);
        }
    }
    
    /**
     * Closes the formatted XML with the revision against which 
     * the status is run. 
     * 
     * @param revision a revision against which the status is run
     */
    public void endTarget(long revision) {
        try {
            myTargetPath = null;
            if (revision >= 0) {
                addAttribute(REVISION_ATTR, revision + "");
                openTag(AGAINST_TAG);
                closeTag(AGAINST_TAG);
            }
            closeTag(TARGET_TAG);
        } catch (SAXException e) {
            getDebugLog().logSevere(SVNLogType.DEFAULT, e);
        }
    }
    
    private void sendToHandler(SVNStatus status) throws SAXException {
        addAttribute(PATH_ATTR, getRelativePath(status.getFile()));
        openTag(ENTRY_TAG);
        addAttribute(PROPS_ATTR, status.getPropertiesStatus().toString());
        addAttribute(ITEM_ATTR, status.getContentsStatus().toString());
        if (status.isLocked()) {
            addAttribute(WC_LOCKED_ATTR, TRUE);
        }
        if (status.isCopied()) {
            addAttribute(COPIED_ATTR, TRUE);
        }
        if (status.isSwitched()) {
            addAttribute(SWITCHED_ATTR, TRUE);
        }
        if (!status.isCopied() && status.getRevision() != null && status.getRevision().getNumber() >= 0) {
            addAttribute(REVISION_ATTR, status.getRevision().toString());
        }
        openTag(WC_STATUS_TAG);
        if (status.getCommittedRevision() != null && status.getCommittedRevision().getNumber() >= 0) {
            addAttribute(REVISION_ATTR, status.getCommittedRevision().toString());
            openTag(COMMIT_TAG);
            addTag(AUTHOR_TAG, status.getAuthor());
            if (status.getCommittedDate() != null) {
                addTag(DATE_TAG, SVNDate.formatDate(status.getCommittedDate()));
            }
            closeTag(COMMIT_TAG);
        }
        if (status.getLocalLock() != null) {
            openTag(LOCK_TAG);
            addTag(TOKEN_TAG, status.getLocalLock().getID());
            addTag(OWNER_TAG, status.getLocalLock().getOwner());
            addTag(COMMENT_TAG, status.getLocalLock().getComment());
            addTag(CREATED_TAG, SVNDate.formatDate(status.getLocalLock().getCreationDate()));
            closeTag(LOCK_TAG);
        }
        closeTag(WC_STATUS_TAG);
        
        if (status.getRemoteContentsStatus() != SVNStatusType.STATUS_NONE || status.getRemotePropertiesStatus() != SVNStatusType.STATUS_NONE ||
                status.getRemoteLock() != null) {
            addAttribute(PROPS_ATTR, status.getRemotePropertiesStatus().toString());
            addAttribute(ITEM_ATTR, status.getRemoteContentsStatus().toString());
            openTag(REMOTE_STATUS_TAG);
            if (status.getRemoteLock() != null) {
                openTag(LOCK_TAG);
                addTag(TOKEN_TAG, status.getRemoteLock().getID());
                addTag(OWNER_TAG, status.getRemoteLock().getOwner());
                addTag(COMMENT_TAG, status.getRemoteLock().getComment());
                addTag(CREATED_TAG, SVNDate.formatDate(status.getRemoteLock().getCreationDate()));
                if (status.getRemoteLock().getExpirationDate() != null) {
                    addTag(EXPIRES_TAG, SVNDate.formatDate(status.getRemoteLock().getExpirationDate()));
                }
                closeTag(LOCK_TAG);
            }
            closeTag(REMOTE_STATUS_TAG);
        }
        closeTag(ENTRY_TAG);
    }
    
    protected String getRelativePath(File path) {
        String fullPath = path.getAbsoluteFile().getAbsolutePath(); 
        if (myTargetPath == null) {
            return fullPath; 
        }
        StringBuffer relativePath = new StringBuffer();
        // collect path till target is met, then prepend target.
        char pathSeparator = File.separatorChar;
        boolean targetMeet = false;
        if (!path.getAbsoluteFile().equals(myTargetPath.getAbsoluteFile())) {
            do {
                if (relativePath.length() > 0) {
                    relativePath.insert(0, pathSeparator);
                }
                relativePath = relativePath.insert(0, path.getName());
                path = path.getParentFile();
                if (path != null) {
                    targetMeet = path.getAbsoluteFile().equals(myTargetPath.getAbsoluteFile());
                }
            } while(path !=null && !targetMeet);
        } else {
            return myTargetPath.getPath();
        }

        if (path != null) {
            if (relativePath.length() > 0) {
                relativePath.insert(0, pathSeparator);
            }
            relativePath = relativePath.insert(0, myTargetPath.getPath());
        } else {
            return fullPath;
        }
        return relativePath.toString();
    }
    
    protected String getTargetPath() {
        return getRelativePath(myTargetPath);        
    }
    
    protected String getHeaderName() {
        return STATUS_TAG;
    }
}
