/*
 * Copyright (C) 2014 Jan Pokorsky
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package cz.cas.lib.proarc.webapp.server;

import cz.cas.lib.proarc.authentication.Authenticators;
import cz.cas.lib.proarc.common.config.AppConfiguration;
import cz.cas.lib.proarc.common.config.AppConfigurationException;
import cz.cas.lib.proarc.common.config.AppConfigurationFactory;
import cz.cas.lib.proarc.common.dao.DaoFactory;
import cz.cas.lib.proarc.common.dao.empiredb.EmpireConfiguration;
import cz.cas.lib.proarc.common.dao.empiredb.EmpireDaoFactory;
import cz.cas.lib.proarc.common.fedora.FedoraStorageInitializer;
import cz.cas.lib.proarc.common.fedora.RemoteStorage;
import cz.cas.lib.proarc.common.imports.ImportBatchManager;
import cz.cas.lib.proarc.common.imports.ImportDispatcher;
import cz.cas.lib.proarc.common.imports.ImportProcess;
import cz.cas.lib.proarc.common.jobs.JobHandler;
import cz.cas.lib.proarc.common.object.DigitalObjectManager;
import cz.cas.lib.proarc.common.object.model.MetaModelRepository;
import cz.cas.lib.proarc.common.sql.DbUtils;
import cz.cas.lib.proarc.common.user.UserManager;
import cz.cas.lib.proarc.common.user.UserUtil;
import cz.cas.lib.proarc.common.workflow.WorkflowManager;
import cz.cas.lib.proarc.common.workflow.profile.WorkflowProfiles;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.quartz.SchedulerException;

/**
 * Initializes the application.
 *
 * @author Jan Pokorsky
 */
public final class ProarcInitializer {

    private static final Logger LOG = Logger.getLogger(ProarcInitializer.class.getName());
    private static final ProarcInitializer INSTANCE = new ProarcInitializer();

    private final ExecutorService executor;
    private Future<Void> asyncTask;
    private DaoFactory daoFactory;

    public static ProarcInitializer getInstance() {
        return INSTANCE;
    }

    private ProarcInitializer() {
        executor = Executors.newSingleThreadExecutor();
    }

    /**
     * Passes when the initialization is done.
     * @throws init failure
     */
    public void isReady() throws Exception {
        asyncTask.get(2, TimeUnit.MINUTES);
    }

    /**
     * Starts the initialization.
     *
     * @param env config params
     */
    public void start(Map<String, String> env) {
//        LOG.info("Starting " + AppConfiguration.FULL_VERSION);
        AppConfiguration config = initConfig(env);
        initProarcModel(config);
        DataSource proarcSource = initProarcDb();
        initUsers(config, proarcSource, daoFactory);
        initImport(config, daoFactory);
        DigitalObjectManager.setDefault(new DigitalObjectManager(
                config, ImportBatchManager.getInstance(), null,
                MetaModelRepository.getInstance(), UserUtil.getDefaultManger()));
        Authenticators.setInstance(new Authenticators(config.getAuthenticators()));
        initWorkflow(config, daoFactory, UserUtil.getDefaultManger());
        initSchedulers(config);
        asyncTask = executor.submit(new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                asyncInitialization();
                return null;
            }
        });
        executor.shutdown();
    }

    /**
     * Releases referenced resources.
     */
    public void destroy() {
//        LOG.info("Destroing " + AppConfiguration.FULL_VERSION);
        ImportDispatcher importDispatcher = ImportDispatcher.getDefault();
        importDispatcher.stop();
        daoFactory = null;
    }

    /**
     * The asynchronous initialization. It helps to access the Fedora without
     * blocking the servlet context initialization in case
     * it runs in the same container.
     */
    private void asyncInitialization() {
        FedoraStorageInitializer rsi = new FedoraStorageInitializer(RemoteStorage.getInstance());
        rsi.init();
        UserUtil.initDefaultAdmin();
    }

    /**
     * Creates configuration of the application. The lookup of default properties
     * searches servlet init parameters, system properties and system environment.
     *
     * @param env servlet params
     * @return the configuration
     */
    private AppConfiguration initConfig(Map<String, String> env) {
        try {
            AppConfigurationFactory configFactory = AppConfigurationFactory.getInstance();
            AppConfiguration config = configFactory.create(env);
            configFactory.setDefaultInstance(config);

            config.copyConfigTemplate(config.getConfigHome());

            return configFactory.defaultInstance();
        } catch (AppConfigurationException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private void initProarcModel(AppConfiguration config) {
        MetaModelRepository.setInstance(config.getPlugins());
    }

    private DataSource initProarcDb() {
        try {
            DataSource proarcSource = DbUtils.getProarcSource();
            daoFactory = new EmpireDaoFactory(EmpireConfiguration.postgres(proarcSource));
            daoFactory.init();
            return proarcSource;
        } catch (NamingException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private void initUsers(AppConfiguration config, DataSource source, DaoFactory daoFactory) {
        try {
            UserManager manager = UserUtil.createUserManagerPostgressImpl(config, source, daoFactory);
            UserUtil.setDefaultManger(manager);
        } catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private void initImport(AppConfiguration config, DaoFactory daoFactory) {
        ImportBatchManager.setInstance(config, daoFactory);
        ImportBatchManager ibm = ImportBatchManager.getInstance();
        ImportDispatcher importDispatcher = new ImportDispatcher();
        ImportDispatcher.setDefault(importDispatcher);
        importDispatcher.init();
        ImportProcess.resumeAll(ibm, importDispatcher, config);
    }

    private void initWorkflow(AppConfiguration config, DaoFactory daoFactory, UserManager users) {
        try {
            File workflowFile = config.getWorkflowConfiguration();
            WorkflowProfiles.copyDefaultFile(config.getWorkflowConfiguration());
            WorkflowProfiles.setInstance(new WorkflowProfiles(workflowFile));
            WorkflowManager.setInstance(new WorkflowManager(
                    WorkflowProfiles.getInstance(), daoFactory, users));
        } catch (Exception ex) {
            throw new IllegalStateException("The workflow initialization failed!", ex);
        }
    }

    private void initSchedulers(AppConfiguration config) {
        try {
            JobHandler.getInstance().init(config);
        } catch (SchedulerException e) {
            LOG.log(Level.SEVERE,"Scheduler initializing failed");
            e.printStackTrace();
        }
    }

}