package com.activiti6.controller;

import com.activiti6.controller.editor.ModelSaveRestResource;
import com.activiti6.entity.AjaxEntityVo;
import com.activiti6.service.ModelService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 流程控制器
 * liuzhize 2019年3月7日下午3:28:14
 */
@Controller
public class ModelerController extends BaseController {

    private static final Logger logger = LoggerFactory.getLogger(ModelerController.class);

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ObjectMapper objectMapper;

    @RequestMapping("index")
    public ModelAndView index(ModelAndView modelAndView) {
        modelAndView.setViewName("index");
        modelAndView.addObject("modelList", repositoryService.createModelQuery().list());
        return modelAndView;
    }

    @RequestMapping("list")
    @ResponseBody
    public AjaxEntityVo list(ModelAndView modelAndView) {
        List<Model> list = repositoryService.createModelQuery().list();
        return success(list.size(), list);
    }


    /**
     * 跳转编辑器页面
     *
     * @return
     */
    @GetMapping("editor")
    public String editor() {
        return "modeler";
    }


    /**
     * 创建模型
     *
     * @param response
     * @param name     模型名称
     * @param key      模型key
     */
    @RequestMapping("/create")
    public void create(HttpServletResponse response, String name, String key, String description) throws IOException {
        logger.info("创建模型入参name：{},key:{}", name, key);
        Model model = repositoryService.newModel();
        ObjectNode modelNode = objectMapper.createObjectNode();
        //模型名称
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        //模型备注
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        repositoryService.saveModel(model);
        createObjectNode(model.getId());
        response.sendRedirect("/editor?modelId=" + model.getId());
        logger.info("创建模型结束，返回模型ID：{}", model.getId());
    }

    /**
     * 创建模型时完善ModelEditorSource
     *
     * @param modelId
     */
    @SuppressWarnings("deprecation")
    private void createObjectNode(String modelId) {
        logger.info("创建模型完善ModelEditorSource入参模型ID：{}", modelId);
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset", stencilSetNode);
        try {
            repositoryService.addModelEditorSource(modelId, editorNode.toString().getBytes("utf-8"));
        } catch (Exception e) {
            logger.info("创建模型时完善ModelEditorSource服务异常：{}", e);
        }
        logger.info("创建模型完善ModelEditorSource结束");
    }

    /**
     * 发布流程
     *
     * @param modelId 模型ID
     * @return
     */
    @ResponseBody
    @RequestMapping("/publish")
    public Object publish(String modelId) {
        logger.info("流程部署入参modelId：{}", modelId);
        try {
            Model modelData = repositoryService.getModel(modelId);
            ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            byte[] bpmnBytes = null;
            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            bpmnBytes = new BpmnXMLConverter().convertToXML(model);
            String processName = modelData.getName() + ".bpmn20.xml";
            Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes)).deploy();
        } catch (Exception e) {
            logger.info("部署modelId:{}模型服务异常：{}", modelId, e);
            return error(e.getMessage());
        }
        return success();
    }

    /**
     * 根据模型id导出模型图
     *
     * @param response
     * @param modelId
     */
    @GetMapping("export/{modelId}")
    @ResponseBody
    public void exportModel(HttpServletResponse response, @PathVariable String modelId) {
        BufferedOutputStream bos = null;
        try {
            Model modelData = repositoryService.getModel(modelId);
            byte[] modelEditorSource = repositoryService
                    .getModelEditorSource(modelData.getId());
            JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            BpmnModel model = jsonConverter.convertToBpmnModel(editorNode);
            byte[] bpmnBytes;
            try {
                bpmnBytes = new BpmnXMLConverter().convertToXML(model);
            } catch (Exception e) {
                bpmnBytes = new byte[10];
            }

            // 封装输出流
            bos = new BufferedOutputStream(response.getOutputStream());
            bos.write(bpmnBytes);// 写入流
            String filename = modelData.getName();
            if (StringUtils.isNoneBlank(filename) && filename.length() > 20) {
                filename = filename.substring(0, 20);
            }
            filename = modelData.getKey() + ".bpmn";
            response.setContentType("application/x-msdownload;");
            response.setHeader("Content-Disposition",
                    "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
            response.flushBuffer();
        } catch (Exception e) {
            logger.error("下载模板失败：" + modelId + "erro:" + e);
        } finally {
            try {
                bos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 撤销流程定义
     *
     * @param modelId 模型ID
     * @param
     * @return
     */
    @ResponseBody
    @RequestMapping("/revokePublish")
    public Object revokePublish(String modelId) {
        logger.info("撤销发布流程入参modelId：{}", modelId);
        Map<String, String> map = new HashMap<String, String>();
        Model modelData = repositoryService.getModel(modelId);
        if (null != modelData) {
            try {
                /**
                 * 参数不加true:为普通删除，如果当前规则下有正在执行的流程，则抛异常
                 * 参数加true:为级联删除,会删除和当前规则相关的所有信息，包括历史
                 */
                repositoryService.deleteDeployment(modelData.getDeploymentId(), true);
                map.put("code", "SUCCESS");
            } catch (Exception e) {
                logger.error("撤销已部署流程服务异常：{}", e);
                map.put("code", "FAILURE");
            }
        }
        logger.info("撤销发布流程出参map：{}", map);
        return map;
    }


    @Autowired
    private ModelService modelService;

    /**
     * 删除流程实例
     *
     * @param modelId 模型ID
     * @param
     * @return
     */
    @ResponseBody
    @RequestMapping("/delete")
    public Object deleteProcessInstance(String modelId) {
        modelService.deleteProcessInstance(modelId);
        return success();
    }


    @Autowired
    private ModelSaveRestResource modelSaveRestResource;

    protected BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter();

    protected BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

    @ResponseBody
    @PostMapping("upload")
    public AjaxEntityVo importProcessModel(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
        try {
            convertInputStreamToModel(file.getInputStream());
        } catch (Exception e) {
            return error(e.getMessage());
        }
        return success();
    }

    public void convertInputStreamToModel(InputStream is) throws Exception {
        String modelId = null;

        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader isr = null;
        XMLStreamReader xtr = null;
        try {
            isr = new InputStreamReader(is, "utf-8");
            xtr = xif.createXMLStreamReader(isr);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            // 处理异常
            if (bpmnModel.getMainProcess() == null
                    || bpmnModel.getMainProcess().getId() == null) {
                throw new Exception("模板文件可能存在问题，请检查后重试！");
            }

            ObjectNode modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);
            Model modelData = repositoryService.newModel();
            org.activiti.bpmn.model.Process process = bpmnModel.getMainProcess();
            String key = process.getId();
            checkName(process);
            String name = process.getName();
            String description = process.getDocumentation();
            modelData.setKey(key);
            modelData.setName(name);

            ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
            modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
            modelData.setMetaInfo(modelObjectNode.toString());
            repositoryService.saveModel(modelData);
            repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));

            modelId = modelData.getId();
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e.getMessage());
        } finally {
            try {
                xtr.close();
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
            try {
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void checkName(Process process) {
        String key = process.getId();
        List<Model> list = repositoryService.createModelQuery().modelKey(key).list();
        if (list.size() > 0) {
            throw new RuntimeException("该流程文件已存在无法上传");
        }
    }

    private Model uploadModel(String name, String key, String description) {
        logger.info("创建模型入参name：{},key:{}", name, key);
        Model model = repositoryService.newModel();
        ObjectNode modelNode = objectMapper.createObjectNode();
        //模型名称
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        //模型备注
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        repositoryService.saveModel(model);
        createObjectNode(model.getId());
        return model;
    }


}
