/*
 * Copyright (c) 2010-2013 Evolveum
 *
 * Licensed 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 com.evolveum.midpoint.wf.impl.activiti;

import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.impl.WfConfiguration;
import com.evolveum.midpoint.wf.impl.activiti.users.MidPointUserManagerFactory;
import org.activiti.engine.*;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.repository.Deployment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * Manages Activiti instance (starts, stops, maintains reference to it).
 *
 * @author mederly
 */
@Component
public class ActivitiEngine {

    private static final Trace LOGGER = TraceManager.getTrace(ActivitiEngine.class);

    private ProcessEngine processEngine = null;

    @Autowired
    private WfConfiguration wfConfiguration;

    @PostConstruct
    public void init() {

        LOGGER.trace("Attempting to create Activiti engine.");

        if (!wfConfiguration.isEnabled()) {
            LOGGER.trace("Workflows are disabled, exiting.");
            return;
        }

        String schemaUpdate;
        if (wfConfiguration.isDropDatabase()) {
        	schemaUpdate = ProcessEngineConfiguration.DB_SCHEMA_UPDATE_CREATE_DROP;
		} else {
        	schemaUpdate = wfConfiguration.isActivitiSchemaUpdate() ?
					ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE :
					ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE;
		}

        ProcessEngineConfiguration pec =
                new MidPointStandaloneProcessEngineConfiguration()         // ugly hack (to provide our own mybatis mapping for Task)
                .setDatabaseSchemaUpdate(schemaUpdate);

        pec = ((ProcessEngineConfigurationImpl) pec)
				// ugly hack - in 5.13 they removed setCustomSessionFactories from ProcessEngineConfiguration abstract class
                .setCustomSessionFactories(Collections.singletonList(new MidPointUserManagerFactory()))
                .setJobExecutorActivate(false)
                .setHistory(HistoryLevel.FULL.getKey());

        if (wfConfiguration.getDataSource() != null) {
            pec = pec.setDataSourceJndiName(wfConfiguration.getDataSource());
        } else {
            pec = pec.setJdbcUrl(wfConfiguration.getJdbcUrl())
                    .setJdbcDriver(wfConfiguration.getJdbcDriver())
                    .setJdbcUsername(wfConfiguration.getJdbcUser())
                    .setJdbcPassword(wfConfiguration.getJdbcPassword());
        }

        pec = pec.setJobExecutorActivate(true);

        processEngine = pec.buildProcessEngine();

        LOGGER.info("Activiti engine successfully created.");

        autoDeploy();
    }

    private void autoDeploy() {

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        String[] autoDeploymentFrom = wfConfiguration.getAutoDeploymentFrom();

        for (String adf : autoDeploymentFrom) {
            Resource[] resources;
            try {
                resources = resolver.getResources(adf);
            } catch (IOException e) {
                LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get resources to be automatically deployed from " + adf, e);
                continue;
            }

            LOGGER.info("Auto deployment from " + adf + " yields " + resources.length + " resource(s)");
            for (Resource resource : resources) {
                try {
                    autoDeployResource(resource);
                } catch (IOException | XPathExpressionException | RuntimeException e) {
                    LoggingUtils.logUnexpectedException(LOGGER, "Couldn't deploy the resource " + resource, e);
                }
			}
        }
    }

    private void autoDeployResource(Resource resource) throws IOException, XPathExpressionException {

        RepositoryService repositoryService = processEngine.getRepositoryService();

        URL url = resource.getURL();
        String name = url.toString();
        long resourceLastModified = resource.lastModified();
        LOGGER.debug("Checking resource " + name + " (last modified = " + new Date(resourceLastModified) + ")");

        boolean tooOld = false;

        List<Deployment> existingList = repositoryService.createDeploymentQuery().deploymentName(name).orderByDeploymenTime().desc().listPage(1, 1);
        Deployment existing = existingList != null && !existingList.isEmpty() ? existingList.get(0) : null;
        if (existing != null) {
            if (resourceLastModified >= existing.getDeploymentTime().getTime()) {
                tooOld = true;
            }
            LOGGER.debug("Found deployment " + existing.getName() + ", last modified " + existing.getDeploymentTime() +
                    (tooOld ? " (too old)" : " (current)"));
        } else {
            LOGGER.debug("Deployment with name " + name + " was not found.");
        }

        if (existing == null || tooOld) {
            repositoryService.createDeployment().name(name).addInputStream(name, resource.getInputStream()).deploy();
            LOGGER.info("Successfully deployed Activiti resource " + name); // + " as deployment with id = " + deployment.getId() + ", name = " + deployment.getName());
        }
    }

    @PreDestroy
    public void shutdown() {

        if (processEngine != null) {
            LOGGER.info("Shutting Activiti ProcessEngine down.");
            processEngine.close();
        }
    }

    public ProcessEngine getProcessEngine() {
        return processEngine;
    }

    public HistoryService getHistoryService() {
        return processEngine.getHistoryService();
    }

    public TaskService getTaskService() {
        return processEngine.getTaskService();
    }

    public RuntimeService getRuntimeService() {
        return processEngine.getRuntimeService();
    }

    public IdentityService getIdentityService() {
        return processEngine.getIdentityService();
    }

    public FormService getFormService() {
        return processEngine.getFormService();
    }
}
