/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.j2ee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;

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

/**
 * Provides very basic installation capabilities.
 */
public class Installer {

    /**
     * the default logger
     */
    private static final Logger log = LoggerFactory.getLogger(Installer.class);

    /**
     * Return code for installation succeeded
     */
    public static final int C_INSTALL_OK = 0;

    /**
     * Return code for invalid input parameter
     */
    public static final int C_INVALID_INPUT = 1;

    /**
     * Return code for repository home already exists
     */
    public static final int C_HOME_EXISTS = 2;

    /**
     * Return code for repository home is missing
     */
    public static final int C_HOME_MISSING = 3;

    /**
     * Return code for repository config already exists
     */
    public static final int C_CONFIG_EXISTS = 4;

    /**
     * Return code for repository config is missing
     */
    public static final int C_CONFIG_MISSING = 5;

    /**
     * Return code for bootstrap config already exists
     */
    public static final int C_BOOTSTRAP_EXISTS = 6;

    /**
     * Return code for a general install error
     */
    public static final int C_INSTALL_ERROR = 7;

    /**
     * place to store the config file
     */
    private final File bootstrapConfigFile;

    /**
     * the servlet context
     */
    private final ServletContext context;

    /**
     * the place for the repository config template
     * todo: to be configured
     */
    private final String configTemplate =
        "/org/apache/jackrabbit/core/repository.xml";

    /**
     * the place for the bootstrap properties template
     * todo: to be configured
     */
    private final String bootstrapTemplate = "/WEB-INF/templates/bootstrap.properties";

    /**
     * Creates a new installer
     * @param bootstrapConfigFile the location for the config file
     * @param context the servlet context for accessing resources
     */
    public Installer(File bootstrapConfigFile, ServletContext context) {
        this.bootstrapConfigFile = bootstrapConfigFile;
        this.context = context;
    }

    /**
     * Handles the installation.
     *
     * @param req the servlet request with the input parameters
     * @return the installation return code
     *
     * @throws ServletException if a servlet error occurs.
     * @throws IOException if an I/O error occurs.
     */
    public int installRepository(HttpServletRequest req)
            throws ServletException, IOException {
        String repHome = req.getParameter("repository_home");
        String repType = req.getParameter("repository_type");
        String repXml = req.getParameter("repository_xml");
        String mode = req.getParameter("mode");

        if (repHome == null || mode == null) {
            return C_INVALID_INPUT;
        }
        File home = new File(repHome);

        File config;
        if ("oak".equals(repType)) {
            config = null;
            repXml = null;
        } else if (repXml == null || repXml.length() == 0) {
            config = new File(home, "repository.xml");
            repXml = config.getPath();
        } else {
            config = new File(repXml);
        }

        if ("new".equals(mode)) {
            // Test internal folder repository existence and not home because home is already created
            // by org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet
            if (new File(home, "repository").exists()) {
                log.error("Trying to install new repository home '{}' but it already contain a repository", repHome);
                return C_HOME_EXISTS;
            }
            if (config != null && config.exists()) {
                log.error("Trying to install new repository config '{}' but already exists", repXml);
                return C_CONFIG_EXISTS;
            }
            log.info("Creating new repository home '{}'", repHome);
            home.mkdirs();

            if (config != null) {
                // install repository xml for Jackrabbit Classic
                try {
                    installRepositoryConfig(config);
                } catch (IOException e) {
                    log.error("Error while installing new repository config '{}': {}", repXml, e.toString());
                    return C_BOOTSTRAP_EXISTS;
                }
            }
        } else {
            if (!home.exists()) {
                log.error("Trying to use existing repository home '{}' but does not exists", repHome);
                return C_HOME_MISSING;
            }
            if (config != null && !config.exists()) {
                log.error("Trying to use existing repository config '{}' but does not exists", repXml);
                return C_CONFIG_MISSING;
            }
        }
        // install bootstrap.properties
        try {
            installBootstrap(bootstrapConfigFile, repHome, repXml);
        } catch (IOException e) {
            log.error("Error while installing '{}': {}", bootstrapConfigFile.getPath(), e.toString());
            return C_INSTALL_ERROR;
        }
        return C_INSTALL_OK;
    }

    /**
     * Installs the repository config file from the template
     * @param dest the destination location
     * @throws IOException if an I/O error occurs.
     */
    private void installRepositoryConfig(File dest) throws IOException {
        log.info("Creating new repository config: {}", dest.getPath());
        InputStream in = context.getResourceAsStream(configTemplate);
        if (in == null) {
            in = getClass().getResourceAsStream(configTemplate);
        }
        OutputStream out = new FileOutputStream(dest);
        byte[] buffer = new byte[8192];
        int read;
        while ((read = in.read(buffer)) >= 0) {
            out.write(buffer, 0, read);
        }
        in.close();
        out.close();
    }

    /**
     * Installs the bootstrap config file from the template
     * @param dest the destination location
     * @param repHome the repository home location
     * @param repXml the repository xml location
     * @throws IOException if an I/O error occurs
     */
    private void installBootstrap(File dest, String repHome, String repXml)
            throws IOException {
        log.info("Creating new bootstrap properties: {}", dest.getPath());
        InputStream in = context.getResourceAsStream(bootstrapTemplate);
        Properties props = new Properties();
        props.load(in);
        props.setProperty("repository.home", repHome);
        if (repXml != null) {
            props.setProperty("repository.config", repXml);
        }
        in.close();
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        OutputStream out = new FileOutputStream(dest);
        props.store(out, "bootstrap properties for the repository startup servlet.");
        out.close();
    }

}
