package com.huigou.explorer.controller;


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.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.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.impl.util.io.StringStreamSource;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.xml.sax.SAXException;

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.Base64;

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

    @Autowired
    private ProcDefinitionApplication procDefinitionApplication;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ModelApplication modelApplication;
    @Autowired
    private WorkflowModeConverter workflowModeConverter;
    @Autowired
    private BpmnXMLConverter bpmnXMLConverter;

    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);
        }
    }

}
