package com.artfess.activiti.def;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.springframework.transaction.TransactionSystemException;

import com.artfess.base.util.AppUtil;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.Dom4jUtil;
import com.artfess.base.util.FileUtil;
import com.artfess.base.util.StringUtil;
import com.artfess.bpm.api.cmd.ActionCmd;
import com.artfess.bpm.api.constant.BpmConstants;
import com.artfess.bpm.api.constant.NodeType;
import com.artfess.bpm.api.context.ContextThreadUtil;
import com.artfess.bpm.api.model.process.task.BpmTask;
import com.artfess.bpm.persistence.manager.ActExecutionManager;
import com.artfess.bpm.persistence.model.ActExecution;
import com.artfess.bpm.util.ClassLoadUtil;

public class BpmDefUtil {

	/**
	 * 将通过设计器设计的流程定义xml添加监听器设置。
	 *
	 * @param id
	 *            流程定义ID
	 * @param name
	 *            流程定义名称
	 * @param xml
	 *            流程定义xml。
	 * @return 转化过的xml。
	 * @throws Exception
	 */
	public static String transBpmDef(String id, String name, String xml) {
		try {
			ClassLoader loader = Thread.currentThread().getContextClassLoader();

			InputStream is = loader
					.getResourceAsStream("com/artfess/bpmx/activiti/xml/transformDef.xsl");

			if (is == null) {
				is = BpmDefUtil.class
						.getResourceAsStream("com/artfess/bpmx/activiti/xml/transformDef.xsl");
			}

			Map<String, String> map = new HashMap<String, String>();
			map.put("id", id);
			map.put("name", name);
			String result = Dom4jUtil.transXmlByXslt(xml, is, map);
			result = result.replace("&lt;", "<").replace("&gt;", ">")
					.replace("xmlns=\"\"", "").replace("&amp;", "&");
			return result;
		} catch (Exception ex) {
			throw new TransactionSystemException("转换流程定义出错", ex);
		}
	}

	/**
	 * 将通过设计器设计的流程定义xml添加监听器设置。
	 *
	 * @param id
	 *            流程定义ID
	 * @param name
	 *            流程定义名称
	 * @param xml
	 *            流程定义xml。
	 * @return 转化过的xml。
	 * @throws Exception
	 */
	public static String transFlashBpmDef(String id, String name, String xml)
			throws Exception {
		return ClassLoadUtil.transform(id, name, xml);
	}

	/**
	 * 将节点之后的节点删除然后指向新的节点。
	 *
	 * @param actDefId
	 *            流程定义ID
	 * @param nodeId
	 *            流程节点ID
	 * @param aryDestination
	 *            需要跳转的节点
	 * @return Map<String,Object> 返回节点和需要恢复节点的集合。
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> prepare(String actDefId, String nodeId,
											  String[] aryDestination) {
		Map<String, Object> map = new HashMap<String, Object>();

		RepositoryService repositoryService = AppUtil
				.getBean(RepositoryService.class);

		// 修改流程定义
		ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(actDefId);

		ActivityImpl curAct = processDefinition.findActivity(nodeId);

		List<PvmTransition> outTrans = curAct.getOutgoingTransitions();
		ActivityImpl cloneCurAct = null;
		try {
			cloneCurAct = (ActivityImpl) FileUtil.cloneObject(curAct);
			List<PvmTransition> cloneOutTrans = (List<PvmTransition>) FileUtil
					.cloneObject(outTrans);
			map.put("outTrans", cloneOutTrans);
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		/**
		 * 解决通过选择自由跳转指向同步节点导致的流程终止的问题。 在目标节点中删除指向自己的流转。
		 */
		for (Iterator<PvmTransition> it = outTrans.iterator(); it.hasNext();) {
			PvmTransition transition = it.next();
			PvmActivity activity = transition.getDestination();
			List<PvmTransition> inTrans = activity.getIncomingTransitions();
			for (Iterator<PvmTransition> itIn = inTrans.iterator(); itIn
					.hasNext();) {
				PvmTransition inTransition = itIn.next();
				if (inTransition.getSource().getId().equals(curAct.getId())) {
					itIn.remove();
				}
			}
		}

		curAct.getOutgoingTransitions().clear();

		if (aryDestination != null && aryDestination.length > 0) {
			for (String dest : aryDestination) {
				// 创建一个连接
				ActivityImpl destAct = processDefinition.findActivity(dest);
				// 条件同步 同步 需要先接相应的网关 再指向原来的目标节点
				ActionCmd cmd = ContextThreadUtil.getActionCmd();
				// 只需要处理 按流程图执行的方式
				if (BeanUtils.isNotEmpty(cmd)
						&& BeanUtils.isNotEmpty(cmd
						.getTransitVars(BpmConstants.BPM_TASK))
						&& "normal".equals(cmd
						.getTransitVars(BpmConstants.BACK_HAND_MODE))) {


					Map<String, Object> gateWayMap = new HashMap<String, Object>();
					if (inInclusiveOrParallelGateway(cloneCurAct,
							NodeType.INCLUSIVEGATEWAY.getKey(), dest,
							gateWayMap)) {
						String afterGateWayActId = String.valueOf(gateWayMap
								.get("gateWayNodeId"));
						ActivityImpl gateWayAct = processDefinition
								.findActivity(afterGateWayActId);
						gateWayAct.getOutgoingTransitions().clear();
						TransitionImpl transitionImpl = gateWayAct
								.createOutgoingTransition();
						transitionImpl.setDestination(destAct);
						destAct = gateWayAct;
						updateRejectExecution(cmd,afterGateWayActId);

					}

					if (inInclusiveOrParallelGateway(cloneCurAct,
							NodeType.PARALLELGATEWAY.getKey(), dest, gateWayMap)) {
						String afterGateWayActId = String.valueOf(gateWayMap
								.get("gateWayNodeId"));
						ActivityImpl gateWayAct = processDefinition
								.findActivity(afterGateWayActId);
						gateWayAct.getOutgoingTransitions().clear();
						TransitionImpl transitionImpl = gateWayAct
								.createOutgoingTransition();
						transitionImpl.setDestination(destAct);
						destAct = gateWayAct;
						updateRejectExecution(cmd,afterGateWayActId);
					}
				}
				TransitionImpl transitionImpl = curAct
						.createOutgoingTransition();
				transitionImpl.setDestination(destAct);
			}
		}

		map.put("activity", curAct);

		return map;

	}

	/**
	 * 将临时节点清除掉，加回原来的节点。
	 *
	 * @param map
	 *            void
	 */
	@SuppressWarnings("unchecked")
	public static void restore(Map<String, Object> map) {
		ActivityImpl curAct = (ActivityImpl) map.get("activity");
		List<PvmTransition> outTrans = (List<PvmTransition>) map
				.get("outTrans");
		curAct.getOutgoingTransitions().clear();
		curAct.getOutgoingTransitions().addAll(outTrans);
	}

	/**
	 *
	 * @param curAct
	 *            当前节点
	 * @param nodeType
	 *            NodeType.INCLUSIVEGATEWAY.getKey()(条件同步网关)
	 *            NodeType.PARALLELGATEWAY.getKey()(同步网关)
	 * @param gateWayNodeId
	 *            往后查询最近的结束网关节点id 用于当前节点的后续节点结束同步(条件)网关）
	 * @return true 当前节点 curAct 前后都有网关，并且网关内不包含驳回的目标节点destNodeId 否则返回false
	 */
	public static boolean inInclusiveOrParallelGateway(ActivityImpl curAct,
													   String nodeType, String destNodeId, Map<String, Object> gatewayMap) {
		gatewayMap.put("hasGateWay", false);

		Boolean preHasGateway = false;
		Boolean afterHasGateway = false;
		Set<String> preNodeIds = new HashSet<String>();
		Set<String> afterNodeIds = new HashSet<String>();
		hasGateWay(curAct.getIncomingTransitions(), "pre", nodeType,
				gatewayMap, preNodeIds);
		preHasGateway = (Boolean) gatewayMap.get("hasGateWay");
		gatewayMap.put("hasGateWay", false);
		hasGateWay(curAct.getOutgoingTransitions(), "after", nodeType,
				gatewayMap, afterNodeIds);
		afterHasGateway = (Boolean) gatewayMap.get("hasGateWay");
		// 往前往后查询节点都具有网关 ，则需要满足驳回的节点不能处于网关内
		if (preHasGateway && afterHasGateway
				&& !preNodeIds.contains(destNodeId)
				&& !afterNodeIds.contains(destNodeId)) {
			return true;
		} else {
			return false;
		}

	}

	/**
	 *
	 * @param comingTransitions
	 *            节点集合
	 * @param direction
	 *            pre | after 往前或者往后查询
	 * @param nodeType
	 *            NodeType.INCLUSIVEGATEWAY.getKey()(条件同步网关)
	 *            NodeType.PARALLELGATEWAY.getKey()(同步网关)
	 * @param Map
	 *            <String,Object> gatewayMap
	 *
	 */
	public static void hasGateWay(List<PvmTransition> comingTransitions,
								  String direction, String nodeType, Map<String, Object> gatewayMap,
								  Set<String> nodeIds) {
		for (PvmTransition pvmTransition : comingTransitions) {
			if (!(Boolean) gatewayMap.get("hasGateWay")) {
				PvmActivity pvmActivity = null;
				if ("pre".equals(direction)) {
					pvmActivity = pvmTransition.getSource();
				}
				if ("after".equals(direction)) {
					pvmActivity = pvmTransition.getDestination();
				}
				if(nodeIds.contains(pvmActivity.getId())){
					continue;
				}
				nodeIds.add(pvmActivity.getId());
				if (nodeType.equals(pvmActivity.getProperty("type"))) {
					// 找到相应的节点类型 hasGateWay
					gatewayMap.put("hasGateWay", true);
					gatewayMap.put("gateWayNodeId", pvmActivity.getId());
				} else {
					List<PvmTransition> _comingTransitions = new ArrayList<PvmTransition>();
					if ("pre".equals(direction)) {
						_comingTransitions = pvmActivity
								.getIncomingTransitions();
					}
					if ("after".equals(direction)) {
						_comingTransitions = pvmActivity
								.getOutgoingTransitions();
					}
					hasGateWay(_comingTransitions, direction, nodeType,
							gatewayMap, nodeIds);


				}

			}
		}
	}

	private static void updateRejectExecution(ActionCmd cmd,String afterGateWayActID){
		// 更新act_ru_execution 表的数据
		BpmTask bpmTask = (BpmTask) cmd
				.getTransitVars(BpmConstants.BPM_TASK);
		String execId = bpmTask.getExecId();
		ActExecutionManager actExecutionManager = AppUtil
				.getBean(ActExecutionManager.class);
		ActExecution actExecution = actExecutionManager.get(execId);
		//处理同步网关内包含会签节点（并行）时，同步网关内人员驳回情况
		while(StringUtil.isNotEmpty(actExecution.getParentId()) && !actExecution.getParentId().equals(actExecution.getProcInstId())) {
			actExecution = actExecutionManager.get(actExecution.getParentId());
			actExecutionManager.updateRejectExecution(actExecution.getParentId(), execId,actExecution.getActId());
		}
		
		actExecutionManager.updateRejectExecution(actExecution.getParentId(), execId,afterGateWayActID);
		
	}

}
