/*
 * ====================================================================
 * 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.util.Iterator;
import java.util.LinkedList;

import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.util.ISVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

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


/**
 * This log handler implementation writes xml formatted information 
 * about the log entries it's passed to a specified <b>ContentHandler</b>. 
 * 
 * @version 1.3
 * @author  TMate Software Ltd.
 * @since   1.2
 */
public class SVNXMLLogHandler extends AbstractXMLHandler implements ISVNLogEntryHandler {
    /**
     * <code>'copyfrom-rev'</code> attribute.
     */
    public static final String COPYFROM_REV_ATTR = "copyfrom-rev";

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

    /**
     * <code>'action'</code> attribute.
     */
    public static final String ACTION_ATTR = "action";

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

    /**
     * <code>'msg'</code> tag.
     */
    public static final String MSG_TAG = "msg";

    /**
     * <code>'path'</code> tag.
     */
    public static final String PATH_TAG = "path";

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

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

    /**
     * <code>'logentry'</code> tag.
     */
    public static final String LOGENTRY_TAG = "logentry";

    /**
     * <code>'log'</code> tag.
     */
    public static final String LOG_TAG = "log";
    
    private boolean myIsOmitLogMessage;
    private LinkedList myMergeStack;

    /**
     * Creates a new log handler.
     * 
     * @param contentHandler a <b>ContentHandler</b> to form 
     *                       an XML tree
     */
    public SVNXMLLogHandler(ContentHandler contentHandler) {
        this(contentHandler, null);
    }

    /**
     * Creates a new log handler.
     * 
     * @param contentHandler a <b>ContentHandler</b> to form 
     *                       an XML tree
     * @param log            a debug logger
     */
    public SVNXMLLogHandler(ContentHandler contentHandler, ISVNDebugLog log) {
        super(contentHandler, log);
    }
    /**
     * Returns the header name specific for a log handler.
     * 
     * @return {@link #LOG_TAG} string
     */
    public String getHeaderName() {
        return LOG_TAG;
    }

    /**
     * Handles a next log entry producing corresponding xml.
     * 
     * @param  logEntry       log entry 
     * @throws SVNException 
     */
    public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
        try {
            sendToHandler(logEntry);
        } catch (SAXException e) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.XML_MALFORMED, e.getLocalizedMessage());
            SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
        }
    }
    
    /**
     * Sets whether log messages must be omitted or not.
     * 
     * @param omitLogMessage  <span class="javakeyword">true</span> to omit; 
     *                        otherwise <span class="javakeyword">false</span> 
     */
    public void setOmitLogMessage(boolean omitLogMessage) {
        myIsOmitLogMessage = omitLogMessage;
    }
    
    private void sendToHandler(SVNLogEntry logEntry) throws SAXException {
        if (logEntry.getRevision() == 0 && logEntry.getMessage() == null) {
            return;
        }
        addAttribute(REVISION_ATTR, logEntry.getRevision() + "");
        openTag(LOGENTRY_TAG);
        if (logEntry.getAuthor() != null) {
            addTag(AUTHOR_TAG, logEntry.getAuthor());
        }
        if (logEntry.getDate() != null && logEntry.getDate().getTime() != 0) {
            addTag(DATE_TAG, SVNDate.formatDate(logEntry.getDate()));
        }
        if (logEntry.getChangedPaths() != null && !logEntry.getChangedPaths().isEmpty()) {
            openTag(PATHS_TAG);
            for (Iterator paths = logEntry.getChangedPaths().keySet().iterator(); paths.hasNext();) {
                String key = (String) paths.next();
                SVNLogEntryPath path = (SVNLogEntryPath) logEntry.getChangedPaths().get(key);
                addAttribute(ACTION_ATTR, path.getType() + "");
                if (path.getCopyPath() != null) {
                    addAttribute(COPYFROM_PATH_ATTR, path.getCopyPath());
                    addAttribute(COPYFROM_REV_ATTR, path.getCopyRevision() + "");
                } 
                addTag(PATH_TAG, path.getPath());
            }
            closeTag(PATHS_TAG);
        }
        
        if (!myIsOmitLogMessage) {
            String message = logEntry.getMessage();
            message = message == null ? "" : message;
            addTag(MSG_TAG, message);
        }
        
        if (myMergeStack != null && !myMergeStack.isEmpty()) {
            MergeFrame frame = (MergeFrame) myMergeStack.getLast();
            frame.myNumberOfChildrenRemaining--;
        }
        
        //TODO: FIXME
        if (logEntry.hasChildren()) {
            MergeFrame frame = new MergeFrame();
            //frame.myNumberOfChildrenRemaining = logEntry.getNumberOfChildren();
            if (myMergeStack == null) {
                myMergeStack = new LinkedList();
            }
            myMergeStack.addLast(frame);
        } else {
            while(myMergeStack != null && !myMergeStack.isEmpty()) {
                MergeFrame frame = (MergeFrame) myMergeStack.getLast();
                if (frame.myNumberOfChildrenRemaining == 0) {
                    closeTag(LOGENTRY_TAG);
                    myMergeStack.removeLast();
                } else {
                    break;
                }
            }
            closeTag(LOGENTRY_TAG);
        }
    }
    
    private class MergeFrame {
        private long myNumberOfChildrenRemaining;
    }

}
