/*
 * ====================================================================
 * 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.internal.wc.admin;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.util.SVNLogType;


/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public abstract class SVNLog {
    public static final String DELETE_CHANGELIST = "delete-changelist";

    public static final String DELETE_ENTRY = "delete-entry";

    public static final String MODIFY_ENTRY = "modify-entry";

    public static final String MODIFY_WC_PROPERTY = "modify-wcprop";

    public static final String DELETE_LOCK = "delete-lock";

    public static final String MOVE = "mv";

    public static final String APPEND = "append";

    public static final String DELETE = "rm";

    public static final String READONLY = "readonly";

    public static final String COPY_AND_TRANSLATE = "cp-and-translate";

    public static final String COPY_AND_DETRANSLATE = "cp-and-detranslate";

    public static final String COPY = "cp";

    public static final String MERGE = "merge";

    public static final String MAYBE_READONLY = "maybe-readonly";

    public static final String MAYBE_EXECUTABLE = "maybe-executable";
    
    public static final String SET_TIMESTAMP = "set-timestamp";

    public static final String COMMIT = "committed";

    public static final String UPGRADE_FORMAT = "upgrade-format";

    public static final String ADD_TREE_CONFLICT = "add-tree-conflict";    

    public static final String NAME_ATTR = "name";

    public static final String DATA_ATTR = "data";

    public static final String PROPERTY_NAME_ATTR = "propname";

    public static final String PROPERTY_VALUE_ATTR = "propval";

    public static final String DEST_ATTR = "dest";

    public static final String TIMESTAMP_ATTR = "timestamp";

    public static final String REVISION_ATTR = "revision";

    public static final String FORMAT_ATTR = "format";

    public static final String FORCE_ATTR = "force";

    public static final String ATTR1 = "arg1";
    public static final String ATTR2 = "arg2";
    public static final String ATTR3 = "arg3";
    public static final String ATTR4 = "arg4";
    public static final String ATTR5 = "arg5";
    public static final String ATTR6 = "arg6";

    public static final String WC_TIMESTAMP = "working";
    public static final String WC_WORKING_SIZE = "working";

    protected Collection myCache;
    protected SVNAdminArea myAdminArea;

    public abstract void save() throws SVNException;

    public abstract String toString();

    public abstract void delete() throws SVNException;

    public abstract boolean exists();
    
    protected SVNLog(SVNAdminArea adminArea) {
        myAdminArea = adminArea;
    }
    
    public void addCommand(String name, SVNProperties attributes, boolean save) throws SVNException {
        if (myCache == null) {
            myCache = new ArrayList();
        }
        attributes = new SVNProperties(attributes);
        attributes.put("", name);
        myCache.add(attributes);
        if (save) {
            save();
        }
    }

    public void deleteCommandsByName(String name, boolean save) throws SVNException {
        if (myCache == null) {
            return;
        }

        for (Iterator iterator = myCache.iterator(); iterator.hasNext(); ) {
            Object command = iterator.next();
            if (!(command instanceof SVNProperties)) {
                continue;
            }

            SVNProperties attributes = (SVNProperties) command;
            String commandName = attributes.getStringValue("");

            if (!name.equals(commandName)) {
                continue;
            }

            iterator.remove();
        }

        if (save) {
            save();
        }
    }

    public void deleteLastCommand(boolean save) throws SVNException {
        if (myCache == null) {
            return;
        }

        int size = myCache.size();
        int commandIdex = 0;
        for (Iterator iterator = myCache.iterator(); iterator.hasNext(); ) {
            if (commandIdex == size - 1) {
                iterator.remove();
            }
            commandIdex++;
        }

        if (save) {
            save();
        }
    }

    public void deleteCommandsByNameAndAttributes(String name, SVNProperties attributes, boolean save) throws SVNException {
        if (myCache == null) {
            return;
        }

        for (Iterator iterator = myCache.iterator(); iterator.hasNext(); ) {
            Object command = iterator.next();
            if (!(command instanceof SVNProperties)) {
                continue;
            }

            SVNProperties commandAttributes = (SVNProperties) command;
            String commandName = commandAttributes.getStringValue("");

            if (!name.equals(commandName)) {
                continue;
            }

            SVNProperties commandAttributesWithoutName = new SVNProperties(commandAttributes);
            commandAttributesWithoutName.remove("");

            if (!commandAttributesWithoutName.equals(attributes)) {
                continue;
            }

            iterator.remove();
        }

        if (save) {
            save();
        }
    }

    public SVNStatusType logChangedEntryProperties(String name, SVNProperties modifiedEntryProps) throws SVNException {
        SVNStatusType status = SVNStatusType.LOCK_UNCHANGED;
        if (modifiedEntryProps != null) {
            SVNProperties command = new SVNProperties();
            command.put(SVNLog.NAME_ATTR, name);
            for (Iterator names = modifiedEntryProps.nameSet().iterator(); names.hasNext();) {
                String propName = (String) names.next();
                SVNPropertyValue propValue = modifiedEntryProps.getSVNPropertyValue(propName);
                String longPropName = !propName.startsWith(SVNProperty.SVN_ENTRY_PREFIX) ? SVNProperty.SVN_ENTRY_PREFIX + propName : propName;

                if (SVNProperty.LOCK_TOKEN.equals(longPropName)) {
                    SVNProperties deleteLockCommand = new SVNProperties();
                    deleteLockCommand.put(SVNLog.NAME_ATTR, name);
                    addCommand(SVNLog.DELETE_LOCK, deleteLockCommand, false);
                    status = SVNStatusType.LOCK_UNLOCKED;
                } else if (propValue != null) {
                    command.put(propName, propValue);
                }
            }
            addCommand(SVNLog.MODIFY_ENTRY, command, false);
            command.clear();
        }
        return status;
    }

    public void logChangedWCProperties(String name, SVNProperties modifiedWCProps) throws SVNException {
        if (modifiedWCProps != null) {
            SVNProperties command = new SVNProperties();
            command.put(SVNLog.NAME_ATTR, name);
            for (Iterator names = modifiedWCProps.nameSet().iterator(); names.hasNext();) {
                String propName = (String) names.next();
                SVNPropertyValue propValue = modifiedWCProps.getSVNPropertyValue(propName);
                command.put(SVNLog.PROPERTY_NAME_ATTR, propName);
                if (propValue != null) {
                    command.put(SVNLog.PROPERTY_VALUE_ATTR, propValue);
                } else {
                    command.remove(SVNLog.PROPERTY_VALUE_ATTR);
                }
                addCommand(SVNLog.MODIFY_WC_PROPERTY, command, false);
            }
        }
    }

    public void logTweakEntry(String name, String newURL, long newRevision) throws SVNException {
        SVNProperties attributes = new SVNProperties();
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.KIND), SVNProperty.KIND_FILE);
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.REVISION), Long.toString(newRevision));
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.DELETED), Boolean.FALSE.toString());
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.ABSENT), Boolean.FALSE.toString());
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.WORKING_SIZE), Long.toString(SVNProperty.WORKING_SIZE_UNKNOWN));
        attributes.put(SVNProperty.shortPropertyName(SVNProperty.TEXT_TIME), SVNDate.NULL.format());
        if (newURL != null) {
            attributes.put(SVNProperty.shortPropertyName(SVNProperty.URL), newURL);
        }
        
        logChangedEntryProperties(name, attributes);
    }

    public void logRemoveRevertFile(String name, SVNAdminArea adminArea, boolean isProp) throws SVNException {
        String revertPath = null;
        if (isProp) {
            revertPath = SVNAdminUtil.getPropRevertPath(name, SVNNodeKind.FILE, false);
        } else {
            revertPath = SVNAdminUtil.getTextRevertPath(name, false);
        }
        File revertFile = adminArea.getFile(revertPath);
        if (revertFile.isFile()) {
            SVNProperties command = new SVNProperties();
            command.put(SVNLog.NAME_ATTR, revertPath);
            addCommand(SVNLog.DELETE, command, false);
            command.clear();
        }
    }

    public void run(SVNLogRunner runner) throws SVNException {
        Collection commands = readCommands();
        if (commands == null || commands.isEmpty()) {
            return;
        }
        
        try {
            int count = 0;
            for (Iterator cmds = commands.iterator(); cmds.hasNext();) {
                SVNProperties command = (SVNProperties) cmds.next();
                String name = command.getStringValue("");
                String attrName = command.getStringValue(SVNLog.NAME_ATTR);
                if (attrName == null && !SVNLog.UPGRADE_FORMAT.equals(name)) {
                    SVNErrorCode code = count <= 1 ? SVNErrorCode.WC_BAD_ADM_LOG_START : SVNErrorCode.WC_BAD_ADM_LOG;
                    SVNErrorMessage err = SVNErrorMessage.create(code, 
                            "Log entry missing ''name'' attribute (entry ''{0}'' for directory ''{1}'')", 
                            new Object[] { name, myAdminArea.getRoot() });
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                if (runner != null) {
                    runner.runCommand(myAdminArea, name, command, ++count);
                }
                cmds.remove();
            }
        } catch (SVNException e) {
            // save failed command and unexecuted commands back to the log file.
            myCache = null;
            for (Iterator cmds = commands.iterator(); cmds.hasNext();) {
                SVNProperties command = (SVNProperties) cmds.next();
                SVNPropertyValue name = command.remove("");
                addCommand(name.getString(), command, false);
            }
            save();
            throw e;
        }
    }
    
    protected abstract Collection readCommands() throws SVNException;

}
