package com.unis.flowable.server.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


import com.unis.flowable.server.util.DefaultProcessDiagramCanvas;
import com.unis.flowable.server.util.DefaultProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.*;

import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;

import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;

import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.service.BpmnDisplayJsonConverter;
import org.flowable.ui.modeler.service.CmmnDisplayJsonConverter;
import org.flowable.ui.modeler.service.mapper.InfoMapper;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.unis.flowable.common.vo.DeploymentList;
import com.unis.flowable.server.config.PageResponse;
import sun.misc.BASE64Encoder;


import javax.servlet.http.HttpServletResponse;

@Service
public class FlowProcessService {

	@Autowired
	private RepositoryService repositoryService;
	protected ObjectMapper objectMapper = new ObjectMapper();

	protected Map<String, InfoMapper> propertyMappers = new HashMap<>();
	protected List<String> eventElementTypes = new ArrayList<>();

	@Autowired
	private HistoryService historyService;

	@Autowired
	protected BpmnDisplayJsonConverter bpmnDisplayJsonConverter;

	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private ModelService modelService;
	@Autowired
	private TaskService taskService;

	@Autowired
	private CmmnDisplayJsonConverter cmmnDisplayJsonConverter;

	// 获取
	public List<DeploymentList> processList(String orgId, Integer pageNum, Integer pageSize) {
		new PageResponse().enablePage(pageNum, pageSize);
		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion()
				.orderByProcessDefinitionKey().asc();

		List<ProcessDefinition> processDefinitions = processDefinitionQuery.list();

		List<DeploymentList> deploymentlist = new ArrayList<DeploymentList>();
		if (processDefinitions != null && processDefinitions.size() > 0) {
			for (ProcessDefinition m : processDefinitions) {
				DeploymentList deploymentLists = new DeploymentList();
				deploymentLists.setId(m.getId());
				deploymentLists.setName(m.getName());
				deploymentLists.setKey(m.getKey());
				deploymentLists.setVersion(m.getVersion());
				deploymentLists.setResourceName(m.getResourceName());
				deploymentLists.setDiagramResourceName(m.getDiagramResourceName());
				deploymentLists.setDeploymentId(m.getDeploymentId());
				deploymentlist.add(deploymentLists);
			}

		}

		return deploymentlist;
	}

	public int delete(String id) {
		repositoryService.deleteDeployment(id);
		return 1;
	}

	public boolean isFinished(String processInstanceId) {
		return historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId)
				.count() > 0;
	}

	public Map<String, Object> processDiagram(String processId) {
		Map<String, Object> map = new HashMap<String, Object>();
		ObjectNode displayNode = objectMapper.createObjectNode();
		try {

			GraphicInfo diagramInfo = new GraphicInfo();
			String processDefinitionId = "";
			if (this.isFinished(processId)) {// 如果流程已经结束，则得到结束节点
				HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery()
						.processInstanceId(processId).singleResult();

				processDefinitionId = pi.getProcessDefinitionId();
			} else {// 如果流程没有结束，则取当前活动节点
				// 根据流程实例ID获得当前处于活动状态的ActivityId合集
				ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId)
						.singleResult();
				processDefinitionId = pi.getProcessDefinitionId();
			}
			BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

			if (bpmnModel == null || bpmnModel.getLocationMap().isEmpty()) {

				map.put("message", "获取失败，不存在该流程！");
				map.put("code", 400);
				return map;
			}

			ArrayNode elementArray = objectMapper.createArrayNode();
			ArrayNode flowArray = objectMapper.createArrayNode();

			if (CollectionUtils.isNotEmpty(bpmnModel.getPools())) {
				ArrayNode poolArray = objectMapper.createArrayNode();
				boolean firstElement = true;
				for (Pool pool : bpmnModel.getPools()) {
					ObjectNode poolNode = objectMapper.createObjectNode();
					poolNode.put("id", pool.getId());
					poolNode.put("name", pool.getName());
					GraphicInfo poolInfo = bpmnModel.getGraphicInfo(pool.getId());
					fillGraphicInfo(poolNode, poolInfo, true);
					org.flowable.bpmn.model.Process process = bpmnModel.getProcess(pool.getId());
					if (process != null && CollectionUtils.isNotEmpty(process.getLanes())) {
						ArrayNode laneArray = objectMapper.createArrayNode();
						for (Lane lane : process.getLanes()) {
							ObjectNode laneNode = objectMapper.createObjectNode();
							laneNode.put("id", lane.getId());
							laneNode.put("name", lane.getName());
							fillGraphicInfo(laneNode, bpmnModel.getGraphicInfo(lane.getId()), true);
							laneArray.add(laneNode);
						}
						poolNode.set("lanes", laneArray);
					}
					poolArray.add(poolNode);

					double rightX = poolInfo.getX() + poolInfo.getWidth();
					double bottomY = poolInfo.getY() + poolInfo.getHeight();
					double middleX = poolInfo.getX() + (poolInfo.getWidth() / 2);
					if (firstElement || middleX < diagramInfo.getX()) {
						diagramInfo.setX(middleX);
					}
					if (firstElement || poolInfo.getY() < diagramInfo.getY()) {
						diagramInfo.setY(poolInfo.getY());
					}
					if (rightX > diagramInfo.getWidth()) {
						diagramInfo.setWidth(rightX);
					}
					if (bottomY > diagramInfo.getHeight()) {
						diagramInfo.setHeight(bottomY);
					}
					firstElement = false;
				}
				displayNode.set("pools", poolArray);

			} else {
				// in initialize with fake x and y to make sure the minimal values are set
				diagramInfo.setX(9999);
				diagramInfo.setY(1000);
			}

			for (org.flowable.bpmn.model.Process process : bpmnModel.getProcesses()) {
				processElements(process.getFlowElements(), bpmnModel, elementArray, flowArray, diagramInfo);
				processArtifacts(process.getArtifacts(), bpmnModel, elementArray, flowArray, diagramInfo);
			}

			displayNode.set("elements", elementArray);
			displayNode.set("flows", flowArray);

			displayNode.put("diagramBeginX", diagramInfo.getX());
			displayNode.put("diagramBeginY", diagramInfo.getY());
			displayNode.put("diagramWidth", diagramInfo.getWidth());
			displayNode.put("diagramHeight", diagramInfo.getHeight());

			List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
					.processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc()
					.orderByHistoricActivityInstanceEndTime().asc().list();

			Set<String> linkedList = new HashSet<String>();
			for (HistoricActivityInstance historicActivityInstance : list) {
				linkedList.add(historicActivityInstance.getActivityId());
			}
			
			map.put("message", "获取失败，不存在该流程！");
			map.put("code", 200);
			map.put("data", displayNode);
			map.put("flow", linkedList);
			return map;
		} catch (Exception e) {
			return null;
		}
	}

	public JsonNode genProcessDiagramccc(String modelId) {

		ObjectNode displayNode = objectMapper.createObjectNode();
		Model model = modelService.getModel(modelId);
		if (model.getModelType() != null && AbstractModel.MODEL_TYPE_CMMN == model.getModelType()) {
			cmmnDisplayJsonConverter.processCaseElements(model, displayNode, new org.flowable.cmmn.model.GraphicInfo());
		} else {
			bpmnDisplayJsonConverter.processProcessElements(model, displayNode, new GraphicInfo());
		}
		return displayNode;
	}

	protected void processElements(Collection<FlowElement> elementList, BpmnModel model, ArrayNode elementArray,
			ArrayNode flowArray, GraphicInfo diagramInfo) {

		for (FlowElement element : elementList) {
			// ignore data objects in visual representation
			if (DataObject.class.isInstance(element)) {
				continue;

			} else if (element instanceof SequenceFlow) {
				ObjectNode elementNode = objectMapper.createObjectNode();
				SequenceFlow flow = (SequenceFlow) element;
				elementNode.put("id", flow.getId());
				elementNode.put("type", "sequenceFlow");
				elementNode.put("sourceRef", flow.getSourceRef());
				elementNode.put("targetRef", flow.getTargetRef());
				elementNode.put("name", flow.getName());
				List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(flow.getId());
				if (CollectionUtils.isNotEmpty(flowInfo)) {
					ArrayNode waypointArray = objectMapper.createArrayNode();
					for (GraphicInfo graphicInfo : flowInfo) {
						ObjectNode pointNode = objectMapper.createObjectNode();
						fillGraphicInfo(pointNode, graphicInfo, false);
						waypointArray.add(pointNode);
						fillDiagramInfo(graphicInfo, diagramInfo);
					}
					elementNode.set("waypoints", waypointArray);

					String className = element.getClass().getSimpleName();
					if (propertyMappers.containsKey(className)) {
						elementNode.set("properties", propertyMappers.get(className).map(element));
					}

					flowArray.add(elementNode);
				}

			} else {

				ObjectNode elementNode = objectMapper.createObjectNode();
				elementNode.put("id", element.getId());
				elementNode.put("name", element.getName());

				GraphicInfo graphicInfo = model.getGraphicInfo(element.getId());
				if (graphicInfo != null) {
					fillGraphicInfo(elementNode, graphicInfo, true);
					fillDiagramInfo(graphicInfo, diagramInfo);
				}

				String className = element.getClass().getSimpleName();
				elementNode.put("type", className);
				fillEventTypes(className, element, elementNode);

				if (element instanceof ServiceTask) {
					ServiceTask serviceTask = (ServiceTask) element;
					if (ServiceTask.MAIL_TASK.equals(serviceTask.getType())) {
						elementNode.put("taskType", "mail");

					} else if ("camel".equals(serviceTask.getType())) {
						elementNode.put("taskType", "camel");

					} else if ("mule".equals(serviceTask.getType())) {
						elementNode.put("taskType", "mule");

					} else if (ServiceTask.HTTP_TASK.equals(serviceTask.getType())) {
						elementNode.put("taskType", "http");
					} else if (ServiceTask.SHELL_TASK.equals(serviceTask.getType())) {
						elementNode.put("taskType", "shell");
					}

				} else if (element instanceof BoundaryEvent) {
					BoundaryEvent boundaryEvent = (BoundaryEvent) element;
					elementNode.put("cancelActivity", boundaryEvent.isCancelActivity());

				} else if (element instanceof StartEvent) {
					StartEvent startEvent = (StartEvent) element;
					if (startEvent.getSubProcess() instanceof EventSubProcess && !startEvent.isInterrupting()) {
						elementNode.put("interrupting", false);
					} else {
						elementNode.put("interrupting", true);
					}
				}

				if (propertyMappers.containsKey(className)) {
					elementNode.set("properties", propertyMappers.get(className).map(element));
				}

				elementArray.add(elementNode);

				if (element instanceof SubProcess) {
					SubProcess subProcess = (SubProcess) element;

					// skip collapsed sub processes
					if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
						continue;
					}

					processElements(subProcess.getFlowElements(), model, elementArray, flowArray, diagramInfo);
					processArtifacts(subProcess.getArtifacts(), model, elementArray, flowArray, diagramInfo);
				}
			}
		}
	}

	protected void processArtifacts(Collection<Artifact> artifactList, BpmnModel model, ArrayNode elementArray,
			ArrayNode flowArray, GraphicInfo diagramInfo) {

		for (Artifact artifact : artifactList) {

			if (artifact instanceof Association) {
				ObjectNode elementNode = objectMapper.createObjectNode();
				Association flow = (Association) artifact;
				elementNode.put("id", flow.getId());
				elementNode.put("type", "association");
				elementNode.put("sourceRef", flow.getSourceRef());
				elementNode.put("targetRef", flow.getTargetRef());
				fillWaypoints(flow.getId(), model, elementNode, diagramInfo);
				flowArray.add(elementNode);

			} else {

				ObjectNode elementNode = objectMapper.createObjectNode();
				elementNode.put("id", artifact.getId());

				if (artifact instanceof TextAnnotation) {
					TextAnnotation annotation = (TextAnnotation) artifact;
					elementNode.put("text", annotation.getText());
				}

				GraphicInfo graphicInfo = model.getGraphicInfo(artifact.getId());
				if (graphicInfo != null) {
					fillGraphicInfo(elementNode, graphicInfo, true);
					fillDiagramInfo(graphicInfo, diagramInfo);
				}

				String className = artifact.getClass().getSimpleName();
				elementNode.put("type", className);

				elementArray.add(elementNode);
			}
		}
	}

	protected void fillWaypoints(String id, BpmnModel model, ObjectNode elementNode, GraphicInfo diagramInfo) {
		List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(id);
		ArrayNode waypointArray = objectMapper.createArrayNode();
		for (GraphicInfo graphicInfo : flowInfo) {
			ObjectNode pointNode = objectMapper.createObjectNode();
			fillGraphicInfo(pointNode, graphicInfo, false);
			waypointArray.add(pointNode);
			fillDiagramInfo(graphicInfo, diagramInfo);
		}
		elementNode.set("waypoints", waypointArray);
	}

	protected void fillEventTypes(String className, FlowElement element, ObjectNode elementNode) {
		if (eventElementTypes.contains(className)) {
			Event event = (Event) element;
			if (CollectionUtils.isNotEmpty(event.getEventDefinitions())) {
				EventDefinition eventDef = event.getEventDefinitions().get(0);
				ObjectNode eventNode = objectMapper.createObjectNode();
				if (eventDef instanceof TimerEventDefinition) {
					TimerEventDefinition timerDef = (TimerEventDefinition) eventDef;
					eventNode.put("type", "timer");
					if (StringUtils.isNotEmpty(timerDef.getTimeCycle())) {
						eventNode.put("timeCycle", timerDef.getTimeCycle());
					}
					if (StringUtils.isNotEmpty(timerDef.getTimeDate())) {
						eventNode.put("timeDate", timerDef.getTimeDate());
					}
					if (StringUtils.isNotEmpty(timerDef.getTimeDuration())) {
						eventNode.put("timeDuration", timerDef.getTimeDuration());
					}

				} else if (eventDef instanceof ErrorEventDefinition) {
					ErrorEventDefinition errorDef = (ErrorEventDefinition) eventDef;
					eventNode.put("type", "error");
					if (StringUtils.isNotEmpty(errorDef.getErrorCode())) {
						eventNode.put("errorCode", errorDef.getErrorCode());
					}

				} else if (eventDef instanceof SignalEventDefinition) {
					SignalEventDefinition signalDef = (SignalEventDefinition) eventDef;
					eventNode.put("type", "signal");
					if (StringUtils.isNotEmpty(signalDef.getSignalRef())) {
						eventNode.put("signalRef", signalDef.getSignalRef());
					}

				} else if (eventDef instanceof MessageEventDefinition) {
					MessageEventDefinition messageDef = (MessageEventDefinition) eventDef;
					eventNode.put("type", "message");
					if (StringUtils.isNotEmpty(messageDef.getMessageRef())) {
						eventNode.put("messageRef", messageDef.getMessageRef());
					}
				}
				elementNode.set("eventDefinition", eventNode);
			}
		}
	}

	protected void fillGraphicInfo(ObjectNode elementNode, GraphicInfo graphicInfo, boolean includeWidthAndHeight) {
		commonFillGraphicInfo(elementNode, graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(),
				graphicInfo.getHeight(), includeWidthAndHeight);
	}

	protected void commonFillGraphicInfo(ObjectNode elementNode, double x, double y, double width, double height,
			boolean includeWidthAndHeight) {

		elementNode.put("x", x);
		elementNode.put("y", y);
		if (includeWidthAndHeight) {
			elementNode.put("width", width);
			elementNode.put("height", height);
		}
	}

	protected void fillDiagramInfo(GraphicInfo graphicInfo, GraphicInfo diagramInfo) {
		double rightX = graphicInfo.getX() + graphicInfo.getWidth();
		double bottomY = graphicInfo.getY() + graphicInfo.getHeight();
		double middleX = graphicInfo.getX() + (graphicInfo.getWidth() / 2);
		if (middleX < diagramInfo.getX()) {
			diagramInfo.setX(middleX);
		}
		if (graphicInfo.getY() < diagramInfo.getY()) {
			diagramInfo.setY(graphicInfo.getY());
		}
		if (rightX > diagramInfo.getWidth()) {
			diagramInfo.setWidth(rightX);
		}
		if (bottomY > diagramInfo.getHeight()) {
			diagramInfo.setHeight(bottomY);
		}
	}

	public String genProcessDiagram(String processId) {
		try{
			List<String> highLightedFinishes=new ArrayList();
			String processDefinitionId = "";
			if (this.isFinished(processId)) {// 如果流程已经结束，则得到结束节点
				HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processId).singleResult();
				processDefinitionId=pi.getProcessDefinitionId();
			} else {// 如果流程没有结束，则取当前活动节点
				// 根据流程实例ID获得当前处于活动状态的ActivityId合集
				ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
				processDefinitionId=pi.getProcessDefinitionId();
				Task singleResult = taskService.createTaskQuery().processInstanceId(processId).singleResult();
				highLightedFinishes.add(singleResult.getTaskDefinitionKey());
			}

			List<HistoricActivityInstance> historyProcess =  historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();
			//所有流程节点
			List<String> activityIds = new ArrayList<>();
			//所有流程线
			List<String> flows = new ArrayList<>();
			//获取流程图
			BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
			for (HistoricActivityInstance hi : historyProcess) {
				String activityType = hi.getActivityType();
				//线
				if (activityType.equals("sequenceFlow")) {
					flows.add(hi.getActivityId());
				} else
					//节点
					if (activityType.equals("userTask") || activityType.equals("startEvent") || activityType.equals("endEvent") || activityType.equals("exclusiveGateway")) {
						activityIds.add(hi.getActivityId());
					}
			}

			ProcessEngineConfiguration engConf = processEngine().getProcessEngineConfiguration();
			InputStream in = new DefaultProcessDiagramGenerator().generateDiagram(
					bpmnModel,
					"png",
					highLightedFinishes,
					activityIds,
					flows,
					engConf.getActivityFontName(),
					engConf.getLabelFontName(),
					engConf.getAnnotationFontName(),
					engConf.getClassLoader(),
					1.0, true
			);
			return getBase64FromInputStream(in).replaceAll("\n", "").replaceAll("\r", "");
		}catch (Exception e){
			e.printStackTrace();
		}
		return null;
	}


	public ProcessEngine processEngine() {
		return ProcessEngines.getDefaultProcessEngine();
	}

	//2-InputStream转化为 base64
	public static String getBase64FromInputStream(InputStream in)
	{
		StringBuffer sb=new StringBuffer();
		sb.append("data:image/jpeg;base64,");
		String result = "";
		// 将图片文件转化为字节数组字符串，并对其进行Base64编码处理
		byte[] data = null;
		// 读取图片字节数组
		try {
			ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
			byte[] buff = new byte[100];
			int rc = 0;
			while ((rc = in.read(buff, 0, 100)) > 0) {
				swapStream.write(buff, 0, rc);
			}
			data = swapStream.toByteArray();
			result =new BASE64Encoder().encode(data) ;
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return sb.append(result).toString();
	}


	public void getImage(HttpServletResponse response, String processId) throws IOException {
		List<String> highLightedFinishes=new ArrayList();
		String processDefinitionId = "";
		if (this.isFinished(processId)) {// 如果流程已经结束，则得到结束节点
			HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processId).singleResult();
			processDefinitionId=pi.getProcessDefinitionId();
		} else {// 如果流程没有结束，则取当前活动节点
			// 根据流程实例ID获得当前处于活动状态的ActivityId合集
			ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
			processDefinitionId=pi.getProcessDefinitionId();
			Task singleResult = taskService.createTaskQuery().processInstanceId(processId).singleResult();
			highLightedFinishes.add(singleResult.getTaskDefinitionKey());
		}

		List<HistoricActivityInstance> historyProcess =  historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();
		//所有流程节点
		List<String> activityIds = new ArrayList<>();
		//所有流程线
		List<String> flows = new ArrayList<>();
		//获取流程图
		BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
		for (HistoricActivityInstance hi : historyProcess) {
			String activityType = hi.getActivityType();
			//线
			if (activityType.equals("sequenceFlow")) {
				flows.add(hi.getActivityId());
			} else
			//节点
			if (activityType.equals("userTask") || activityType.equals("startEvent") || activityType.equals("endEvent") || activityType.equals("exclusiveGateway")) {
				activityIds.add(hi.getActivityId());
			}
		}


		ProcessEngineConfiguration engConf = processEngine().getProcessEngineConfiguration();
		InputStream in = new DefaultProcessDiagramGenerator().generateDiagram(
				bpmnModel,
				"png",
				highLightedFinishes,
				activityIds,
				flows,
				engConf.getActivityFontName(),
				engConf.getLabelFontName(),
				engConf.getAnnotationFontName(),
				engConf.getClassLoader(),
				1.0, true
		);


		OutputStream out = null;
		byte[] buf = new byte[1024];
		int legth = 0;
		try {
			out = response.getOutputStream();
			while ((legth = in.read(buf)) != -1) {
				out.write(buf, 0, legth);
			}
		} finally {
			if (in != null) {
				in.close();
			}
			if (out != null) {
				out.close();
			}
		}

	}




}
