/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * ModeShape 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.modeshape.web.jcr.rest.handler;

import java.io.File;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.api.BackupOptions;
import org.modeshape.jcr.api.Problem;
import org.modeshape.jcr.api.Problems;
import org.modeshape.jcr.api.Repository;
import org.modeshape.jcr.api.RepositoryManager;
import org.modeshape.jcr.api.RestoreOptions;
import org.modeshape.web.jcr.rest.RestHelper;
import org.modeshape.web.jcr.rest.model.JSONAble;
import org.modeshape.web.jcr.rest.model.RestException;
import org.modeshape.web.jcr.rest.model.RestWorkspaces;

/**
 * An extension of the {@link RepositoryHandler} which returns POJO-based rest model instances.
 *
 * @author Horia Chiorean (hchiorea@redhat.com)
 */
public final class RestRepositoryHandler extends AbstractHandler {

    private static final String BACKUP_LOCATION_INIT_PARAM = "backupLocation";
    private static final String JBOSS_DOMAIN_DATA_DIR = "jboss.domain.data.dir";
    private static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
    private static final String USER_HOME = "user.home";
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("ddMMyyyy_HHmmss");

    /**
     * Returns the list of workspaces available to this user within the named repository.
     *
     * @param request the servlet request; may not be null
     * @param repositoryName the name of the repository; may not be null
     * @return the list of workspaces available to this user within the named repository, as a {@link RestWorkspaces} object
     *
     * @throws RepositoryException if there is any other error accessing the list of available workspaces for the repository
     */
    public RestWorkspaces getWorkspaces( HttpServletRequest request,
                                         String repositoryName ) throws RepositoryException {
        assert request != null;
        assert repositoryName != null;

        RestWorkspaces workspaces = new RestWorkspaces();
        Session session = getSession(request, repositoryName, null);
        for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
            String repositoryUrl = RestHelper.urlFrom(request);
            workspaces.addWorkspace(workspaceName, repositoryUrl);
        }
        return workspaces;
    }

    /**
     * Performs a repository backup.
     */
    public Response backupRepository( ServletContext context, 
                                      HttpServletRequest request, 
                                      String repositoryName,
                                      BackupOptions options ) throws RepositoryException {
        final File backupLocation = resolveBackupLocation(context);

        Session session = getSession(request, repositoryName, null);
        String repositoryVersion = session.getRepository().getDescriptorValue(Repository.REP_VERSION_DESC).getString().replaceAll("\\.","");
        final String backupName = "modeshape_" + repositoryVersion + "_" + repositoryName + "_backup_" + DATE_FORMAT.format(new Date());
        final File backup = new File(backupLocation, backupName);
        if (!backup.mkdirs()) {
            throw new RuntimeException("Cannot create backup folder: " + backup);
        }
        logger.debug("Backing up repository '{0}' to '{1}', using '{2}'", repositoryName, backup, options);


        RepositoryManager repositoryManager = ((org.modeshape.jcr.api.Workspace)session.getWorkspace()).getRepositoryManager();
        repositoryManager.backupRepository(backup, options);
        final String backupURL;
        try {
            backupURL = backup.toURI().toURL().toString();
        } catch (MalformedURLException e) {
            //should never happen
            throw new RuntimeException(e);
        }
        JSONAble responseContent = new JSONAble() {
            @Override
            public JSONObject toJSON() throws JSONException {
                JSONObject object = new JSONObject();
                object.put("name", backupName);
                object.put("url", backupURL);
                return object;
            }
        }; 
        return Response.status(Response.Status.CREATED).entity(responseContent).build();
    }

    /**
     * Restores a repository using an existing backup.
     */
    public Response restoreRepository( ServletContext context, 
                                       HttpServletRequest request, 
                                       String repositoryName,
                                       String backupName, 
                                       RestoreOptions options ) throws RepositoryException {
        if (StringUtil.isBlank(backupName)) {
            throw new IllegalArgumentException("The name of the backup cannot be null");
        }
        File backup = resolveBackup(context, backupName);
        logger.debug("Restoring repository '{0}' from backup '{1}' using '{2}'", repositoryName, backup, options);
        Session session = getSession(request, repositoryName, null);
        RepositoryManager repositoryManager = ((org.modeshape.jcr.api.Workspace)session.getWorkspace()).getRepositoryManager();
        final Problems problems = repositoryManager.restoreRepository(backup, options);
        if (!problems.hasProblems()) {
            return Response.ok().build();
        }
        List<JSONAble> response = new ArrayList<JSONAble>(problems.size());
        for (Problem problem : problems) {
            RestException exception = problem.getThrowable() != null ? 
                                      new RestException(problem.getMessage(), problem.getThrowable()) : 
                                      new RestException(problem.getMessage());
            response.add(exception);
        }
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(response).build();
    }
    
    private File resolveBackup( ServletContext context, String backupName ) {
        // first look at the servlet init param
        String backupRoot = context.getInitParameter(BACKUP_LOCATION_INIT_PARAM);
        if (!StringUtil.isBlank(backupRoot)) {
            if (isValidDir(backupRoot + "/" + backupName)) {
                return new File(backupRoot + "/" + backupName);
            }

            // try resolving it as a system property
            backupRoot = System.getProperty(backupRoot);
            if (isValidDir(backupRoot + "/" + backupName)) {
                return new File(backupRoot + "/" + backupName);
            }
        }

        // jboss.domain.data.dir
        backupRoot = System.getProperty(JBOSS_DOMAIN_DATA_DIR);
        if (isValidDir(backupRoot + "/" + backupName)) {
            return new File(backupRoot + "/" + backupName);
        }

        // jboss.server.data.dir
        backupRoot = System.getProperty(JBOSS_SERVER_DATA_DIR);
        if (isValidDir(backupRoot + "/" + backupName)) {
            return new File(backupRoot + "/" + backupName);
        }

        // finally user.home
        backupRoot = System.getProperty(USER_HOME);
        if (isValidDir(backupRoot + "/" + backupName)) {
            return new File(backupRoot + "/" + backupName);
        }

        // none of the above are available, so fail
        throw new IllegalArgumentException(
                "Cannot locate backup '" + backupName + "' anywhere on the server in the following locations:" +
                BACKUP_LOCATION_INIT_PARAM + " context param, " + JBOSS_DOMAIN_DATA_DIR + ", " + JBOSS_SERVER_DATA_DIR + ", "
                + USER_HOME);

    }

    private File resolveBackupLocation( ServletContext context ) {
        // first look at the servlet init param 'backupLocation'
        String backupLocation = context.getInitParameter(BACKUP_LOCATION_INIT_PARAM);
        if (!StringUtil.isBlank(backupLocation)) {
            if (isValidDir(backupLocation)) {
                return new File(backupLocation);
            }

            // try resolving it as a system property
            backupLocation = System.getProperty(backupLocation);
            if (isValidDir(backupLocation)) {
                return new File(backupLocation);
            }
        }

        // jboss.domain.data.dir
        backupLocation = System.getProperty(JBOSS_DOMAIN_DATA_DIR);
        if (isValidDir(backupLocation)) {
            return new File(backupLocation);
        }

        // jboss.server.data.dir
        backupLocation = System.getProperty(JBOSS_SERVER_DATA_DIR);
        if (isValidDir(backupLocation)) {
            return new File(backupLocation);
        }

        // finally user.home
        backupLocation = System.getProperty(USER_HOME);
        if (isValidDir(backupLocation)) {
            return new File(backupLocation);
        }

        // none of the above are available, so fail
        throw new IllegalArgumentException(
                "None of the following locations are writable folders on the server: " +
                BACKUP_LOCATION_INIT_PARAM + " context param, " + JBOSS_DOMAIN_DATA_DIR + ", " + JBOSS_SERVER_DATA_DIR + ", "
                + USER_HOME);
    }

    private boolean isValidDir( String dir ) {
        if (StringUtil.isBlank(dir)) {
            return false;
        }
        File dirFile = new File(dir);
        return dirFile.exists() && dirFile.canWrite() && dirFile.isDirectory();
    }
}
