/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 The eXist Project
 *  http://exist-db.org
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  $Id: Move.java 4481 2006-10-04 14:54:50Z wolfgang_m $
 */
package org.exist.http.webdav.methods;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.security.PermissionDeniedException;
import org.exist.security.User;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.txn.TransactionException;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.xmldb.XmldbURI;

/**
 * Implements the WebDAV move method.
 * @author wolf
 */
public class Move extends AbstractWebDAVMethod {

    
    public Move(BrokerPool pool) {
        super(pool);
    }
    
    /* (non-Javadoc)
     * @see org.exist.http.webdav.WebDAVMethod#process(org.exist.security.User, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.exist.collections.Collection, org.exist.dom.DocumentImpl)
     */
    public void process(User user, HttpServletRequest request,
            HttpServletResponse response, XmldbURI path) throws ServletException, IOException {
    	DBBroker broker = null;
		Collection collection = null;
		DocumentImpl resource = null;
		try {
			broker = pool.get(user);
			collection = broker.openCollection(path, Lock.WRITE_LOCK);
			if(collection == null) {
            	XmldbURI docUri = path.lastSegment();
            	XmldbURI collUri = path.removeLastSegment();
				collection = broker.openCollection(collUri, Lock.WRITE_LOCK);
				if(collection == null) {
					LOG.debug("No resource or collection found for path: " + path);
					response.sendError(HttpServletResponse.SC_NOT_FOUND, NOT_FOUND_ERR);
					return;
				}
				resource = collection.getDocumentWithLock(broker, docUri, Lock.WRITE_LOCK);
				if(resource == null) {
					LOG.debug("No resource found for path: " + path);
					response.sendError(HttpServletResponse.SC_NOT_FOUND, NOT_FOUND_ERR);
					return;
				}
			}
	        String destination = request.getHeader("Destination");
	        XmldbURI destPath = null;
	        try {
	            URI uri = new URI(destination);
	            String host = uri.getHost();
	            int port = uri.getPort();
	            if(!(host.equals(request.getServerName()) && port == request.getServerPort())) {
	                response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
	                        "Copying to a different server is not yet implemented");
	                return;
	            }
	            
                //TODO: use XmldbURI for this stuff too
                String tempDestPath = uri.getPath();
                
                if(tempDestPath.startsWith(request.getContextPath()))
                	tempDestPath = tempDestPath.substring(request.getContextPath().length());
                
                if(tempDestPath.startsWith(request.getServletPath()))
                	tempDestPath = tempDestPath.substring(request.getServletPath().length());
                
                destPath = XmldbURI.create(tempDestPath);
	        } catch (URISyntaxException e) {
	            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Malformed URL in destination header");
	        }
	        if(resource != null)
	            moveResource(user, broker, request, response, resource, destPath);
	        else
	            moveCollection(user, broker, request, response, collection, destPath);
		} catch (EXistException e) {
			throw new ServletException("Failed to copy: " + e.getMessage(), e);
		} catch (LockException e) {
			throw new ServletException("Failed to copy: " + e.getMessage(), e);
		} finally {
			if(collection != null)
				collection.release();
			if(resource != null)
				resource.getUpdateLock().release(Lock.WRITE_LOCK);
			pool.release(broker);
		}
    }

    private void moveCollection(User user, DBBroker broker, HttpServletRequest request, HttpServletResponse response, 
            Collection collection, XmldbURI destination) throws ServletException, IOException {
        if(collection.getURI().equals(destination)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN,
                    "Source and destination are the same");
            return;
        }
        if(destination.lastSegment()==null) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                    "Bad destination: " + destination);
            return;
        }
        Collection destCollection = null;
        TransactionManager transact = pool.getTransactionManager();
        try {
            Txn txn = transact.beginTransaction();
            try {
                
                boolean replaced = false;
                destCollection = broker.openCollection(destination, Lock.WRITE_LOCK);
        		if(destCollection != null) {
        			boolean overwrite = overwrite(request);
        			if(!overwrite) {
        				response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED,
        						"Destination collection exists and overwrite is not allowed");
        				return;
        			}
        			broker.removeCollection(txn, destCollection);
        			replaced = true;
        		}
                
            	XmldbURI parentURI = destination.removeLastSegment();
            	XmldbURI newCollURI = destination.lastSegment();
                 LOG.debug("parent = " + parentURI + "; new name = " + newCollURI);
                destCollection = broker.openCollection(parentURI, Lock.WRITE_LOCK);
                if(destCollection == null) {
                    response.sendError(HttpServletResponse.SC_CONFLICT,
                            "No parent collection: " + parentURI);
                    return;
                }
                broker.moveCollection(txn, collection, destCollection, newCollURI);
                if(replaced)
                    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
                else
                    response.setStatus(HttpServletResponse.SC_CREATED);
            } catch (PermissionDeniedException e) {
                response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
            } catch (LockException e) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            } finally {
            	if(destCollection != null)
            		destCollection.release();
                transact.commit(txn);
                pool.release(broker);
            }
        } catch (TransactionException e) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }
    
    private void moveResource(User user, DBBroker broker, HttpServletRequest request, HttpServletResponse response, 
    		DocumentImpl resource, XmldbURI destination)
    throws ServletException, IOException {
    	XmldbURI docUri = destination.lastSegment();
    	XmldbURI collUri = destination.removeLastSegment();
        if(docUri == null) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                    "Bad destination: " + destination);
            return;
        }
        boolean replaced = false;
        Collection destCollection = null;
        TransactionManager transact = broker.getBrokerPool().getTransactionManager();
        Txn transaction = transact.beginTransaction();
        try {
            destCollection = broker.openCollection(collUri, Lock.WRITE_LOCK);
            if(destCollection == null) {
                transact.abort(transaction);
                response.sendError(HttpServletResponse.SC_CONFLICT,
                        "Destination collection not found");
                return;
            }
    		
            DocumentImpl oldDoc = destCollection.getDocument(broker, docUri);
            if(oldDoc != null) {
                boolean overwrite = overwrite(request);
                if(!overwrite) {
                    transact.abort(transaction);
                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED,
                            "Destination resource exists and overwrite is not allowed");
                    return;
                }
                replaced = true;
            }
            broker.moveXMLResource(transaction, resource, destCollection, docUri);
            
            // [1509776] After a webDAV MOVE all locks need to be removed.
            resource.setUserLock(null);
            resource.getMetadata().setLockToken(null);
            
            transact.commit(transaction);
            if(replaced)
                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
            else
                response.setStatus(HttpServletResponse.SC_CREATED);
        } catch (PermissionDeniedException e) {
            transact.abort(transaction);
            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
        } catch (LockException e) {
            transact.abort(transaction);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        } catch (TransactionException e) {
            transact.abort(transaction);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        } finally {
        	if(destCollection != null)
        		destCollection.release();
        }
    }
    
    private boolean overwrite(HttpServletRequest request) {
        String header = request.getHeader("Overwrite");
        if(header == null)
            return false;
        return header.equals("T");
    }
}
