package com.huigou.explorer.controller;


import com.huigou.data.repository.GeneralRepository;
import com.huigou.explorer.application.ModelApplication;
import com.huigou.explorer.converters.WorkflowModeConverter;
import com.huigou.explorer.domain.model.Model;
import com.huigou.explorer.util.GraphUtils;
import com.huigou.uasp.annotation.ControllerMapping;
import com.huigou.uasp.bmp.common.easysearch.EasySearch;
import com.huigou.uasp.bmp.common.easysearch.domain.model.EasySearchParse;
import com.huigou.uasp.bmp.common.easysearch.domain.model.QuerySchemeField;
import com.huigou.uasp.bmp.opm.application.impl.MemEasySearcherImpl;
import com.huigou.uasp.bpm.managment.application.ProcDefinitionApplication;
import com.huigou.uasp.bpm.managment.domain.model.ProcDefinition;
import com.huigou.uasp.client.CommonController;
import com.huigou.uasp.log.annotation.LogInfo;
import com.huigou.uasp.log.domain.model.LogType;
import com.huigou.uasp.log.domain.model.OperationType;
import com.huigou.util.ClassHelper;
import com.huigou.util.SDO;
import com.mxgraph.util.mxUtils;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.impl.util.io.StringStreamSource;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.beanutils.converters.ClassConverter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.xml.sax.SAXException;

import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yonghuan
 */
@ControllerMapping("procDefinition")
@Controller
public class ModelController extends CommonController implements ApplicationContextAware {

    @Autowired
    private ProcDefinitionApplication procDefinitionApplication;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ModelApplication modelApplication;
    @Autowired
    private WorkflowModeConverter workflowModeConverter;
    @Autowired
    private BpmnXMLConverter bpmnXMLConverter;
    @Autowired
    private GeneralRepository generalRepository;
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    protected String getPagePath() {
        return "/system/configtool/editor/";
    }

    @RequiresPermissions("ProcDefinition:query")
    @LogInfo(logType = LogType.SYS, subType = "", operaionType = OperationType.VIEW, description = "跳转到流程模型页面")
    public String forwardModeler() {
        String id = getSDO().getId();
        Assert.hasText(id, "流程id不能为空");
        ProcDefinition pd = procDefinitionApplication.loadProcDefinition(id);
        return forward("modeler", pd);
    }

    /**
     * 根据流程id查询流程模型
     */
    @RequiresPermissions("ProcDefinition:query")
    public String queryModel() throws TransformerException {
        String id = getSDO().getId();
        Model model = modelApplication.findByProcDefinitionId(id);
        if (model == null) {
            ProcDefinition pd = procDefinitionApplication.loadProcDefinition(id);
            if (pd != null) {
                BpmnModel bpmnModel = null;
                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                        .processDefinitionKey(pd.getProcId())
                        .latestVersion()
                        .singleResult();
                if (processDefinition != null) {
                    bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
                }

                if (bpmnModel != null) {
                    byte[] bs = workflowModeConverter.convertToXML(bpmnModel, StandardCharsets.UTF_8);
                    if (ArrayUtils.isNotEmpty(bs)) {
                        model = new Model();
                        model.setProcDefinitionId(pd.getId());
                        model.setXml(new String(bs, StandardCharsets.UTF_8));
                    }
                }
            }
        }
        return toResult(model);
    }

    @RequiresPermissions("ProcDefinition:update")
    public String saveModel() throws UnsupportedEncodingException {
        SDO sdo = getSDO();
        String xml = decodeXml(sdo.getString("xml"));
        String imageXml = decodeXml(sdo.getString("imageXml"));
        String procDefinitionId = sdo.getString("procDefinitionId");
        Model model = modelApplication.findByProcDefinitionId(procDefinitionId);
        if (model == null) {
            model = new Model();
            model.setProcDefinitionId(procDefinitionId);
        }
        model.setXml(xml);
        model.setImageXml(imageXml);
        modelApplication.save(model);
        return success();
    }

    private String decodeXml(String xml) throws UnsupportedEncodingException {
        if (xml != null) {
            byte[] bs = Base64.getDecoder().decode(xml);
            xml = new String(bs, StandardCharsets.UTF_8.name());
            xml = URLDecoder.decode(xml, StandardCharsets.UTF_8.name());
        }
        return xml;
    }

    @RequiresPermissions("ProcDefinition:update")
    public String showListeners() {
        return forward("listeners");
    }

    @RequiresPermissions("ProcDefinition:query")
    public String showListenerDetails() {
        SDO sdo = getSDO();
        String id = sdo.getString("__id");
        putAttribute("__id", id);
        return forward("listenerDetails");
    }

    /**
     * 调整到用户任务列表。
     */
    public String showTaskListeners() {
        SDO sdo = getSDO();
        String id = sdo.getString("__id");
        putAttribute("__id", id);
        return forward("taskListeners");
    }

    public String showTaskListenerDetails() {
        return forward("taskListenerDetails");
    }

    /**
     * 部署流程
     */
    @RequiresPermissions("ProcDefinition:update")
    public String deploy() {
        modelApplication.deploy(getSDO().getId());
        return success();
    }

    public String showCallActivityParameters() {
        String type = getSDO().getString("type");
        putAttribute("type", type);
        return forward("callActivityParameters");
    }

    /**
     * 导出流程图
     */
    public String export() throws IOException, ParserConfigurationException, SAXException {
        SDO sdo = getSDO();
        String format = sdo.getString("format");
        ProcDefinition procDefinition = procDefinitionApplication.loadProcDefinition(sdo.getId());
        Model model = modelApplication.findByProcDefinitionId(sdo.getId());
        HttpServletRequest request = getRequest();
        HttpServletResponse response = getResponse();
        if ("xml".equals(format)) {
            BpmnModel bpmnModel;
            if (model != null) {
                bpmnModel = workflowModeConverter.convertToBpmnModel(model.getXml().getBytes(), StandardCharsets.UTF_8);
            } else {
                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                        .processDefinitionKey(procDefinition.getProcId())
                        .latestVersion()
                        .singleResult();
                bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
            }
            if (bpmnModel != null) {
                byte[] bpmnXml = bpmnXMLConverter.convertToXML(bpmnModel);
                response.setContentType(MimeTypeUtils.APPLICATION_XML_VALUE);
                response.setContentLength(bpmnXml.length);
                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                String fileName = encodeAttachmentName(request, String.join(".", procDefinition.getName(), "xml"));
                response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);
                try (OutputStream os = response.getOutputStream()) {
                    os.write(bpmnXml);
                }
            }
        } else {
            String fname = sdo.getString("filename");
            Integer w = sdo.getInteger("w");
            Integer h = sdo.getInteger("h");
            String tmp = sdo.getString("bg");
            String imageXml = sdo.getString("imageXml");
            if (imageXml != null) {
                byte[] bs = Base64.getDecoder().decode(imageXml);
                imageXml = new String(bs);
                imageXml = URLDecoder.decode(imageXml, StandardCharsets.UTF_8.name());
            }

            Color bg = (tmp != null) ? mxUtils.parseColor(tmp) : null;
            // Checks parameters
            // Allows transparent backgrounds only for PNG
            if (bg == null && !format.equals("png")) {
                bg = Color.WHITE;
            }

            if (fname != null && fname.toLowerCase().endsWith(".xml")) {
                fname = fname.substring(0, fname.length() - 4) + format;
            }
            writeImage(format, fname, w, h, bg, imageXml, request, response);
        }
        return NONE;
    }

    public String bpmnXmlToMxgraphXml() throws UnsupportedEncodingException {
        SDO sdo = getSDO();
        String bpmnXml = sdo.getString("bpmnXml");
        bpmnXml = decodeXml(bpmnXml);
        BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(new StringStreamSource(bpmnXml), false, false);
        byte[] mxGraphXml = this.workflowModeConverter.convertToXML(bpmnModel, StandardCharsets.UTF_8);
        return toResult(new String(mxGraphXml, StandardCharsets.UTF_8));
    }

    private void writeImage(String format, String fname, int w, int h, Color bg, String xml, HttpServletRequest request, HttpServletResponse response)
            throws IOException, SAXException, ParserConfigurationException {
        if (fname != null) {
            fname = encodeAttachmentName(request, fname);
            response.setContentType("application/x-unknown");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fname + "\"; filename*=UTF-8''" + fname);
        } else if (format != null) {
            response.setContentType("image/" + format.toLowerCase());
        }
        try (OutputStream os = response.getOutputStream()) {
            GraphUtils.writeImage(format, w, h, bg, xml, os);
        }
    }

    /**
     * 查询流程模型绑定的表单的字段
     */
    public String queryFormFields() {
        String formNo = getSDO().getString("formNo");
        EntityManager em = generalRepository.getEntityManager();
        List<Map<String, Object>> formFields = ((List<Object>) em.createQuery("select fa from FormAttribute fa inner join  SafFormslist f on fa.formId=f.id where f.formNo=:formNo")
                .setParameter("formNo", formNo)
                .getResultList())
                .stream()
                .map(field -> ClassHelper.beanToMap(field))
                .map((Map<String, Object> field) -> {
                    em.createQuery("select t.name from DbTables t where t.tableName=:tableName")
                            .setParameter("tableName", field.get("tableName"))
                            .getResultList()
                            .stream()
                            .map(String::valueOf)
                            .findAny()
                            .ifPresent(tableName -> field.put("tableLabel", tableName));
                    return field;
                }).collect(Collectors.toList());
        return toResult(formFields);
    }

    /**
     * easy search用户任务监听器。
     */
    @EasySearch(configType = "bpm", queryName = "taskListener")
    public Object searchTaskListener(SDO sdo) {
        return searchListener(sdo, TaskListener.class);
    }


    /**
     * easy search ExecutionListener
     */
    @EasySearch(configType = "bpm", queryName = "executionListener")
    public Object searchExecutionListener(SDO sdo) {
        return searchListener(sdo, ExecutionListener.class);
    }

    private Object searchListener(SDO sdo, Class<?> listenerClass) {
        Integer intPage = sdo.getInteger("intPage", 1);
        Integer pageSize = sdo.getInteger("pageSize");
        PageRequest pageRequest = new PageRequest(intPage - 1, pageSize);
        String paramValue = sdo.getString("paramValue");

        List<QuerySchemeField> fields = Arrays.asList(new QuerySchemeField("监听器名称", "name", "string", 400L));

        EasySearchParse easySearchParse = new EasySearchParse();
        easySearchParse.setFields(fields);
        easySearchParse.setWidth(400L);
        MemEasySearcherImpl<Map<String, Object>> memEasySearcher = new MemEasySearcherImpl<>();
        ApplicationContext rootApplicationContext = applicationContext.getParent();
        List<Map<String, Object>> flowBrokers = rootApplicationContext.getBeansOfType(listenerClass).keySet().stream()
                .map(name -> {
                    Map<String, Object> flowBroker = new HashMap<>(1);
                    flowBroker.put("name", name);
                    return flowBroker;
                }).collect(Collectors.toList());
        return memEasySearcher.search(flowBrokers, easySearchParse, pageRequest, (flowBroker -> StringUtils.isBlank(paramValue) || ((String) flowBroker.get("name")).contains(paramValue)));
    }

}
