/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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.kie.server.services.jbpm.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import org.drools.core.util.StringUtils;
import org.jbpm.kie.services.impl.FormManagerService;
import org.jbpm.services.api.DefinitionService;
import org.jbpm.services.api.RuntimeDataService;
import org.jbpm.services.api.UserTaskService;
import org.jbpm.services.api.model.ProcessDefinition;
import org.jbpm.services.task.commands.GetUserTaskCommand;
import org.kie.api.task.model.Task;
import org.kie.server.services.api.KieServerRegistry;
import org.kie.server.services.impl.locator.ContainerLocatorProvider;
import org.kie.server.services.jbpm.locator.ByTaskIdContainerLocator;
import org.kie.server.services.jbpm.ui.api.UIFormProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FormServiceBase {

    public static final Logger logger = LoggerFactory.getLogger(FormServiceBase.class);

    private static final ServiceLoader<UIFormProvider> formProviders = ServiceLoader.load(UIFormProvider.class);

    private DefinitionService definitionService;
    private RuntimeDataService dataService;
    private UserTaskService userTaskService;

    private KieServerRegistry registry;

    private Set<UIFormProvider> providers = new LinkedHashSet<UIFormProvider>();

    public enum FormType {

        FORM_MODELLER_TYPE("FORM", "xml"),
        FORM_TYPE("FRM", "json"),
        FREE_MARKER_TYPE("FTL", "xml"),
        ANY("ANY", null);

        private String name;
        private String contentType;

        private FormType(String name, String contentType) {
            this.name = name;
            this.contentType = contentType;
        }

        public String getName() {
            return name;
        }

        public String getContentType() {
            return contentType;
        }

        public static FormType fromName(String name) {
            if (FORM_MODELLER_TYPE.getName().equals(name)) {
                return FORM_MODELLER_TYPE;
            } else if (FORM_TYPE.getName().equals(name)) {
                return FORM_TYPE;
            } else if (FREE_MARKER_TYPE.getName().equals(name)) {
                return FREE_MARKER_TYPE;
            } else if (ANY.getName().equals(name)) {
                return ANY;
            } else {
                throw new IllegalArgumentException("No FormType enum value for " + name);
            }
        }
    }

    public FormServiceBase(DefinitionService definitionService, RuntimeDataService dataService, UserTaskService userTaskService, FormManagerService formManagerService, KieServerRegistry registry) {
        this.definitionService = definitionService;
        this.dataService = dataService;
        this.userTaskService = userTaskService;
        this.registry = registry;

        providers.addAll(collectFormProviders(formManagerService));
    }

    public String getFormDisplayProcess(String containerId, String processId, String lang, boolean filterContent, String formType) {
        containerId = registry.getContainerId(containerId, ContainerLocatorProvider.get().getLocator());

        ProcessDefinition processDesc = definitionService.getProcessDefinition(containerId, processId);
        if (processDesc == null) {
            throw new IllegalStateException("Process definition " + containerId + " : " + processId + " not found");
        }
        Map<String, String> processData = definitionService.getProcessVariables(containerId, processId);

        if (processData == null) {
            processData = new HashMap<>();
        }

        Map<String, Object> renderContext = new HashMap<>();
        renderContext.put("process", processDesc);
        renderContext.put("outputs", processData);
        renderContext.put("lang", lang);
        renderContext.put("filterForm", filterContent);

        for (UIFormProvider provider : providers) {

            // if there's no formType or formType is ANY the first provider able to generate the formContent wins
            if (!FormType.ANY.getName().equals(formType) && !provider.getType().equals(formType)) {
                logger.debug("Provider {} does not support {} form type", provider, formType);
                continue;
            }
            String template = provider.render(processDesc.getName(), processDesc, renderContext);
            if (!StringUtils.isEmpty(template)) {

                return template;
            }
        }

        logger.warn("Unable to find form to render for process '{}'", processDesc.getName());
        throw new IllegalStateException("No form for process with id " + processDesc.getName() + " found");
    }

    public String getFormDisplayTask(String containerId,
                                     long taskId,
                                     String lang,
                                     boolean filterContent,
                                     String formType) {

        containerId = registry.getContainerId(containerId, new ByTaskIdContainerLocator(taskId));

        Task task = userTaskService.execute(containerId, new GetUserTaskCommand(registry.getIdentityProvider().getName(), taskId));
        if (task == null) {
            throw new IllegalStateException("No task with id " + taskId + " found");
        }
        String name = task.getName();
        ProcessDefinition processDesc = dataService.getProcessesByDeploymentIdProcessId(containerId, task.getTaskData().getProcessId());
        Map<String, Object> renderContext = new HashMap<>();
        renderContext.put("filterForm", filterContent);

        Map<String, Object> input = userTaskService.getTaskInputContentByTaskId(containerId, taskId);
        renderContext.put("inputs", input);
        for (Map.Entry<String, Object> inputVar : input.entrySet()) {
            renderContext.put(inputVar.getKey(), inputVar.getValue());
        }

        Map<String, String> outputDef = definitionService.getTaskOutputMappings(containerId, task.getTaskData().getProcessId(), task.getName());
        Map<String, Object> output = userTaskService.getTaskOutputContentByTaskId(containerId, taskId);
        renderContext.put("outputs", output);
        for (Map.Entry<String, Object> outputVar : output.entrySet()) {
            renderContext.put(outputVar.getKey(), outputVar.getValue());
        }
        outputDef.forEach((k, v) -> {
            if (!renderContext.containsKey(k)) {
                renderContext.put(k, "");
            }
        });

        renderContext.put("lang", lang);
        renderContext.put("task", task);

        for (UIFormProvider provider : providers) {
            // if there's no formType or formType is ANY the first provider able to generate the formContent wins
            if (!FormType.ANY.getName().equals(formType) && !provider.getType().equals(formType)) {
                logger.debug("Provider {} does not support {} form type", provider, formType);
                continue;
            }

            String template = provider.render(name, task, processDesc, renderContext);
            if (!StringUtils.isEmpty(template)) {
                return template;
            }
        }

        logger.warn("Unable to find form to render for task '{}' on process '{}'", name, processDesc.getName());
        throw new IllegalStateException("No form for task with id " + taskId + " found");
    }

    protected List<UIFormProvider> collectFormProviders(FormManagerService formManagerService) {
        List<UIFormProvider> uiFormProviders = new ArrayList<UIFormProvider>();

        for (UIFormProvider formProvider : formProviders) {
            formProvider.configure(formManagerService);
            uiFormProviders.add(formProvider);
        }

        Collections.sort(uiFormProviders, new Comparator<UIFormProvider>() {
            @Override
            public int compare(UIFormProvider e1, UIFormProvider e2) {
                return Integer.valueOf(e1.getPriority()).compareTo(Integer.valueOf(e2.getPriority()));
            }
        });

        return uiFormProviders;
    }
}
