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

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.tmatesoft.svn.core.SVNCancelException;
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.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
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.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc2.SvnMarkReplaced;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnNgMarkReplaced extends SvnNgOperationRunner<Void, SvnMarkReplaced> {

    @Override
    protected Void run(SVNWCContext context) throws SVNException {
        for (SvnTarget target : getOperation().getTargets()) {
            final File path = target.getFile();
            final File lock = getWcContext().acquireWriteLock(path, true, true);
            try {
                final ISVNWCDb db = context.getDb();

                final SVNWCContext.ScheduleInternalInfo schedule = context.getNodeScheduleInternal(path, true, false);
                final boolean alreadyReplaced = schedule.schedule == SVNWCContext.SVNWCSchedule.replace;

                if (!alreadyReplaced) {
                    doReplace(db, path);
                }
            } finally {
                getWcContext().releaseWriteLock(lock);
            }
        }
        return null;
    }

    private ISVNWCDb.SVNWCDbKind getKind(ISVNWCDb db, File path) throws SVNException {
        final Structure<StructureFields.NodeInfo> info = db.readInfo(path, StructureFields.NodeInfo.kind);
        final ISVNWCDb.SVNWCDbKind kind = info.get(StructureFields.NodeInfo.kind);
        info.release();
        return kind;
    }

    private void doReplace(ISVNWCDb db, File path) throws SVNException {
        if (db.isWCRoot(path)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS,
                    "''{0}'' is the root of a working copy and cannot be replaced", path);
            SVNErrorManager.error(err, SVNLogType.WC);
        }

        final List<File> deletedPaths = markDeletedRecursively(db, path);

        Collections.sort(deletedPaths);

        for (File deletedPath : deletedPaths) {
            markAdded(db, deletedPath); //deleted + added = replaced
        }
    }

    private void markAdded(ISVNWCDb db, File path) throws SVNException {
        final ISVNWCDb.SVNWCDbKind kind = getKind(db, path);
        if (kind == ISVNWCDb.SVNWCDbKind.Dir) {
            db.opAddDirectory(path, null, null);
        } else if (kind == ISVNWCDb.SVNWCDbKind.File) {
            db.opAddFile(path, null, null);
        }
        final SVNEvent event = SVNEventFactory.createSVNEvent(path,
                kind == ISVNWCDb.SVNWCDbKind.Dir ? SVNNodeKind.DIR : SVNNodeKind.FILE, null, -1, SVNEventAction.ADD,
                SVNEventAction.ADD, null, null, 1, 1);
        handleEvent(event);
    }

    private List<File> markDeletedRecursively(ISVNWCDb db, File path) throws SVNException {
        final List<File> deletedPaths = new ArrayList<File>();
        final ISVNEventHandler eventHandler = getOperation().getEventHandler();

        db.opDelete(path, null, true, null, null, new ISVNEventHandler() {
            public void handleEvent(SVNEvent event, double progress) throws SVNException {
                if (event != null && event.getAction() == SVNEventAction.DELETE) {
                    deletedPaths.add(event.getFile());
                }
                if (eventHandler != null) {
                    eventHandler.handleEvent(event, progress);
                }
            }

            public void checkCancelled() throws SVNCancelException {
                if (eventHandler != null) {
                    eventHandler.checkCancelled();
                }
            }
        });
        return deletedPaths;
    }
}
