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

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

import org.tmatesoft.svn.core.SVNDepth;
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.SVNSkel;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.ExternalNodeInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbExternals;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNConflictDescription;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver;
import org.tmatesoft.svn.core.wc2.SvnGetStatus;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnScheduleForRemoval;
import org.tmatesoft.svn.core.wc2.SvnStatus;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnNgRemove extends SvnNgOperationRunner<Void, SvnScheduleForRemoval> {

    @Override
    protected Void run(SVNWCContext context) throws SVNException {
        for(SvnTarget target : getOperation().getTargets()) {
            final File path = target.getFile();
            checkCancelled();            
            final File lockRoot = getWcContext().acquireWriteLock(path, true, true);
            try {
                if (!getOperation().isForce() && getOperation().isDeleteFiles()) {
                    checkCanDelete(getOperation().getOperationFactory(), context, path);
                }
                if (!getOperation().isDryRun()) {
                    delete(context, path, null, !getOperation().isDeleteFiles(), true, getOperation().getEventHandler());
                }
            } finally {
                getWcContext().releaseWriteLock(lockRoot);
            }
        }
        return null;
    }

    public static void checkCanDelete(SvnOperationFactory opFactory, SVNWCContext context, File path) throws SVNException {
        Structure<ExternalNodeInfo> info = null;
        try {
            info = SvnWcDbExternals.readExternal(context, path, path, ExternalNodeInfo.kind);
            if (info != null && info.get(ExternalNodeInfo.kind) == SVNWCDbKind.File) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CANNOT_DELETE_FILE_EXTERNAL,
                        "Cannot remove the external at ''{0}''; please edit or delete the svn:externals property on ''{1}''",
                        path, SVNFileUtil.getParentFile(path));
                SVNErrorManager.error(err, SVNLogType.WC);
            }
        } catch (SVNException e) {
            if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) {
                throw e;
            }
        } finally {
            if (info != null) {
                info.release();
            }
        }
        SvnGetStatus status = opFactory.createGetStatus();
        status.setSingleTarget(SvnTarget.fromFile(path));
        status.setDepth(SVNDepth.INFINITY);
        status.setReceiver(new ISvnObjectReceiver<SvnStatus>() {
            public void receive(SvnTarget target, SvnStatus status) throws SVNException {
                SVNErrorMessage err = null;
                if (status.getNodeStatus() == SVNStatusType.STATUS_OBSTRUCTED) {
                    err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, 
                            "''{0}'' is in the way of the resource actually under version control", target.getFile());
                } else if (!status.isVersioned()) {
                    err = SVNErrorMessage.create(SVNErrorCode.UNVERSIONED_RESOURCE, 
                        "''{0}'' is not under version control", target.getFile());
                } else if ((status.getNodeStatus() == SVNStatusType.STATUS_ADDED ||
                            status.getNodeStatus() == SVNStatusType.STATUS_REPLACED) &&
                            status.getTextStatus() == SVNStatusType.STATUS_NORMAL &&
                            (status.getPropertiesStatus() == SVNStatusType.STATUS_NONE ||
                            status.getPropertiesStatus() == SVNStatusType.STATUS_NORMAL)) {
                } else if (status.getNodeStatus() != SVNStatusType.STATUS_NORMAL &&
                        status.getNodeStatus() != SVNStatusType.STATUS_DELETED &&
                        status.getNodeStatus() != SVNStatusType.STATUS_MISSING) {
                    err = SVNErrorMessage.create(SVNErrorCode.CLIENT_MODIFIED, 
                    "''{0}'' has local modifications -- commit or revert them first", target.getFile());
                }
                if (err != null) {
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                
            }            
        });
        status.run();
    }

    public static void delete(SVNWCContext context, File path, File movedToAbsPath, boolean keepLocal, boolean deleteUnversioned, ISVNEventHandler handler) throws SVNException {
        Structure<NodeInfo> info = null;
        try {
            info = context.getDb().readInfo(path, NodeInfo.status, NodeInfo.kind, NodeInfo.conflicted);
        } catch (SVNException e) {
            if (deleteUnversioned && e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) {
                if (SVNFileType.getType(path) == SVNFileType.NONE) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_FILENAME, "''{0}'' does not exist", path);
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
                if (!keepLocal) {
                    SVNFileUtil.deleteAll(path, handler);
                }
                return;
            }
            throw e;
        }
        SVNWCDbStatus status = info.get(NodeInfo.status);
        if (status == SVNWCDbStatus.Excluded || status == SVNWCDbStatus.NotPresent) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, 
                    "''{0}'' cannot be deleted", path);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (status == SVNWCDbStatus.Normal && info.get(NodeInfo.kind) == SVNWCDbKind.Dir) {
            if (context.getDb().isWCRoot(path)) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS, 
                        "''{0}'' is the root of a working copy and cannot be deleted", path);
                SVNErrorManager.error(err, SVNLogType.WC);
            }
        }
        context.writeCheck(SVNFileUtil.getParentFile(path));
        List<SVNConflictDescription> conflicts = null;
        if (!keepLocal && info.is(NodeInfo.conflicted)) {
            conflicts = context.getDb().readConflicts(path);
        }
        info.release();

        SVNSkel workItems = null; //TODO

        context.getDb().opDelete(path, movedToAbsPath, movedToAbsPath != null || !keepLocal, null, workItems, handler);
        if (!keepLocal && conflicts != null) {
            for (SVNConflictDescription conflict : conflicts) {
                if (conflict.isTextConflict()) {
                    if (conflict.getMergeFiles() != null) {
                        SVNFileUtil.deleteFile(conflict.getMergeFiles().getBaseFile());
                        SVNFileUtil.deleteFile(conflict.getMergeFiles().getRepositoryFile());
                        SVNFileUtil.deleteFile(conflict.getMergeFiles().getLocalFile());
                    }
                } else if (conflict.isPropertyConflict() && conflict.getMergeFiles() != null) {
                    SVNFileUtil.deleteFile(conflict.getMergeFiles().getRepositoryFile());
                }
            }
        }
        if (!keepLocal) {
            SVNFileUtil.deleteAll(path, handler);
        }
    }

}
