package com.persagy.adm.diagram.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.adm.diagram.core.model.*;
import com.persagy.adm.diagram.core.model.base.Container;
import com.persagy.adm.diagram.core.model.base.IComponent;
import com.persagy.adm.diagram.core.model.base.IDataBind;
import com.persagy.adm.diagram.core.model.base.IEquipHolder;
import com.persagy.adm.diagram.core.model.legend.Anchor;
import com.persagy.adm.diagram.core.model.legend.Legend;
import com.persagy.adm.diagram.core.model.logic.CalcContext;
import com.persagy.adm.diagram.core.model.logic.DataFilter;
import com.persagy.adm.diagram.core.model.template.DiagramTemplate;
import com.persagy.adm.diagram.core.model.template.MainPipe;
import com.persagy.adm.diagram.core.model.virtual.PackNode;
import com.persagy.dmp.digital.entity.ObjectRelation;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 处理系统图的计算逻辑
 * @author zhaoyk
 */
public class DiagramBuilder {

	/**
	 * 设备容器限制，避免模板设置不当导致过多节点
	 */
	public static int equipLimit = 50;

	private CalcContext context;

	private Diagram diagram;

	private DiagramTemplate template;

	private DataStrategy dataStrategy;

	private HashMap<String, List<Legend>> legendsCache = new HashMap<>();

	private HashSet<String> refRelTypes = new HashSet<>();

	public DiagramBuilder(CalcContext context, DataStrategy dataStrategy) {
		this.context = context;
		this.diagram = context.getDiagram();
		this.template = context.getTemplate();
		this.dataStrategy = dataStrategy;

		this.context.setDataStrategy(dataStrategy);
	}

	//加载设备数据，并进行计算处理
	public void buildEquipNodeAndContainer(List<Container> containers, List<ObjectNode> optionalObjs){
		//去掉已经使用的数据项
		if(context.getEquipMap().size() > 0) {
			optionalObjs = optionalObjs.stream().filter(obj -> !context.getEquipMap().containsKey(obj.get("id").asText())).collect(Collectors.toList());
		} else {
			optionalObjs = new ArrayList<>(optionalObjs);
		}

		if(template.getMainPipes() != null) {
			for(MainPipe mainPipe : template.getMainPipes()) {
				if (mainPipe.isBindEquipment() && mainPipe.getDataObject() == null){
					Iterator<ObjectNode> iter = optionalObjs.iterator();
					while (iter.hasNext()) {
						ObjectNode obj = iter.next();
						if(match(obj, mainPipe)) {
							mainPipe.setDataObject(obj);
							mainPipe.setDataObjectId(obj.get("id").asText());

							context.getEquipMap().put(mainPipe.getDataObjectId(), mainPipe);

							iter.remove();
							break;
						}
					}
				}
			}
		}
		for(Container con : containers) {
			if(con.isEquipmentBox()) {
				Iterator<ObjectNode> iter = optionalObjs.iterator();
				while (iter.hasNext()) {
					ObjectNode obj = iter.next();
					if (match(obj, con)) {
						if(con.getEquipPack() != null) {
							addPackData(con, obj);
						} else {
							addEquipNode(con, obj);
						}

						iter.remove();
					}
				}
			}
		}

		handleNodes();

		handleContainers(containers);
	}

	private void addEquipNode(Container con, ObjectNode obj){
		if(con.getChildren().size() > equipLimit) {
			return;
		}

		EquipmentNode node = new EquipmentNode();
		node.setId(IdUtil.fastSimpleUUID());
		node.setDataObjectId(obj.get("id").asText());
		node.setObjClassCode(DiagramBuilder.getClassCode(obj));
		node.setDataObject(obj);

		initNode(node, DiagramBuilder.getName(obj), con);

		Legend legend = findLegend(node.getObjClassCode(), obj);
		node.setLegendId(legend.getId());
		node.setLegend(legend);
	}

	private void initNode(EquipmentNode node, String name, Container con){
		con.addComp(node);
		node.setContainerId(con.getId());
		node.setLayoutIndex(con.getChildren().size() - 1);

		Label label = new Label();
		label.setId(IdUtil.fastSimpleUUID());
		label.setContent(name);
		node.setLabel(label);

		diagram.getNodes().add(node);
		context.getEquipMap().put(node.getDataObjectId(), node);
	}

	private void addPackData(Container con, ObjectNode obj){
		PackNode pn = null;
		String classCode = getClassCode(obj);
		Legend legend = null;
		//single
		if(!con.getEquipPack().isPackByType()) {
			String packName = con.getEquipPack().getPackName();
			if(StrUtil.isBlank(packName)) {
				packName = getTypeName(classCode);
			}
			if(con.getChildren().size() == 0) {
				pn = newPackNode(PackNode.SINGLE_PACK, con, packName);
			} else {
				pn = (PackNode) con.getChildren().get(0);
			}
		} else { //group
			for(IComponent comp : con.getChildren()) {
				PackNode item = (PackNode) comp;
				if(item.getObjClassCode().equals(classCode)){
					pn = item;
					break;
				}
			}
			if(pn == null) {
				pn = newPackNode(classCode, con, getTypeName(classCode));
			}
		}

		if (pn.getLegend() == null) {
			if(con.getEquipPack().getLegendId() != null) {
				legend = dataStrategy.getLegend(con.getEquipPack().getLegendId(), classCode.substring(0, 4));
			}
			if(legend == null) {
				legend = findLegend(classCode, null);
			}

			pn.setLegendId(legend.getId());
			pn.setLegend(legend);
		}

		pn.add(classCode);
	}

	private PackNode newPackNode(String objClsCode, Container con, String packName){
		PackNode pn = new PackNode();
		pn.setId(IdUtil.fastSimpleUUID());
		pn.setObjClassCode(objClsCode);

		initNode(pn, packName, con);

		return pn;
	}

	private boolean match(ObjectNode obj, IEquipHolder equipHolder) {
		String classCode = DiagramBuilder.getClassCode(obj);
		if(equipHolder.getEquipmentTypes() != null && equipHolder.getEquipmentTypes().contains(classCode)) {
			DataFilter filter = equipHolder.getDataFilter();
			if(filter != null) {
				return filter.filter(obj, context);
			}
			return true;
		}
		return false;
	}

	/**
	 * 对节点进行处理，并返回图例锚点会使用到的关系类型
	 */
	private void handleNodes() {
		for(DiagramNode node : diagram.getNodes()) {
			if(PackNode.TYPE.equals(node.getCompType())) {
				PackNode pn = (PackNode)node;
				pn.getLabel().setContent(pn.getLabel().getContent() + ":" + pn.totalCount());
				statRelTypes(pn);
			} else if(EquipmentNode.TYPE.equals(node.getCompType())) {
				statRelTypes((EquipmentNode)node);
			}
		}
	}

	private void statRelTypes(EquipmentNode equipmentNode){
		List<Anchor> anchors = equipmentNode.getLegend().getAnchors();
		if(anchors != null) {
			for(Anchor anchor : anchors) {
				if(anchor.getAcceptRelations() != null) {
					anchor.getAcceptRelations().forEach(rel -> refRelTypes.add(rel));
				}
			}
		}
	}

	private void handleContainers(List<Container> containers) {
		for(Container con : containers) {
			if(con.isEquipmentBox()) {
				if(Boolean.TRUE.equals(con.getProp(Container.PROP_AUTO_HIDDEN)) && CollUtil.isEmpty(con.getChildren())) {
					con.setHidden(true);
				}
			}
		}
	}

	private String getTypeName(String classCode){
		//TODO
		return classCode;
	}

	private Legend findLegend(String classCode, ObjectNode obj){
		List<Legend> legends = legendsCache.get(classCode);
		if (legends == null) {
			legends = dataStrategy.getLegendsForEquipment(classCode);
			if (legends == null) {
				legends = new ArrayList<>();
			}
			legendsCache.put(classCode, legends);
		}
		if(legends.size() > 0) {
			Legend l0 = null, l1 = null;
			for(Legend legend : legends) {
				//系统图类型匹配
				boolean typeMatch = CollUtil.isNotEmpty(legend.getDiagramTypes()) && legend.getDiagramTypes().contains(diagram.getType());
				//过滤条件匹配
				boolean filterMatch;
				if(obj != null && legend.getDataFilter() != null) {
					filterMatch = legend.getDataFilter().filter(obj, context);
				} else {
					filterMatch = true;
				}

				if(typeMatch && filterMatch) {
					return legend;
				}

				if(typeMatch && l0 == null) {
					l0 = legend;
				}
				if(filterMatch && l1 == null) {
					l1 = legend;
				}
			}
			if(l0 != null) {
				return l0;
			}
			if(l1 != null) {
				return l1;
			}
			//没有优先匹配的话取第一个
			return legends.get(0);
		}

		//返回一个缺省图例
		return emptyLegend();
	}
	
	public void buildLines(List<ObjectNode> optionalRels){
		//去掉已经使用的关系项
		if(context.getLineMap().size() > 0) {
			optionalRels = optionalRels.stream().filter(obj -> !context.getLineMap().containsKey(obj.get("id").asText())).collect(Collectors.toList());
		}

		for(ObjectNode rel : optionalRels) {
			IDataBind fromObj = context.getEquipMap().get(rel.get(ObjectRelation.OBJ_FROM_HUM).asText());
			IDataBind toObj = context.getEquipMap().get(rel.get(ObjectRelation.OBJ_TO_HUM).asText());

			if(fromObj != null && toObj != null) {
				String relType = rel.get(ObjectRelation.GRAPH_CODE_HUM).asText() + '/' + rel.get(ObjectRelation.REL_CODE_HUM).asText();
				ConnectPoint from = getConnectPoint(fromObj, relType, toObj);
				if(from != null) {
					ConnectPoint to = getConnectPoint(toObj, relType, fromObj);
					if(to != null) {
						Line line = new Line();
						line.setFrom(from);
						line.setTo(to);
						line.setRelType(relType);
						line.setDataObject(rel);
						line.setDataObjectId(rel.get("id").asText());

						addLine(line);
					}
				}
			}
		}

	}

	private ConnectPoint getConnectPoint(IDataBind obj, String  relType, IDataBind theOtherEnd){
		ObjectNode theOtherData = (ObjectNode) theOtherEnd.getDataObject();
		String theOtherType = getClassCode(theOtherData);

		if(obj instanceof EquipmentNode) {
			EquipmentNode en = (EquipmentNode)obj;
			Anchor anchor = null;
			List<Anchor> anchors = en.getLegend().getAnchors();
			if(CollUtil.isNotEmpty(anchors)) {
				Anchor anchor1 = null; //部分匹配
				for (Anchor a : anchors) {
					Boolean relMatch = null; //关系匹配
					Boolean equipMatch = null; //另一端设备匹配

					if(CollUtil.isNotEmpty(a.getAcceptRelations())) {
						relMatch = a.getAcceptRelations().contains(relType);
					}

					if(!Boolean.FALSE.equals(relMatch)) {
						if(CollUtil.isNotEmpty(a.getToEquipmentTypes())) {
							equipMatch = a.getToEquipmentTypes().contains(theOtherType);
						}
						if(!Boolean.FALSE.equals(equipMatch) && a.getToDataFilter() != null) {
							equipMatch = a.getToDataFilter().filter(theOtherData, context);
						}
					}

					if(!Boolean.FALSE.equals(relMatch) && !Boolean.FALSE.equals(equipMatch)) {
						if(relMatch == null || equipMatch == null) {
							//部分匹配
							if(anchor1 == null) {
								anchor1 = a;
							} else if(CollUtil.isNotEmpty(anchor1.getLines()) && CollUtil.isEmpty(a.getLines())) {
								anchor1 = a;
							}
						} else {
							//完全匹配
							if(CollUtil.isEmpty(a.getLines())) { //优先每个锚点只有一条连线
								anchor = a;
								break;
							} else if(anchor == null) {
								anchor = a;
							}
						}
					}
				}
				if(anchor == null && anchor1 != null) {
					anchor = anchor1;
				}
			}

			if(anchor != null) {
				ConnectPoint cp = new ConnectPoint();
				cp.setHostType(EquipmentNode.TYPE);
				cp.setHostId(en.getId());
				cp.setAnchorCode(anchor.getCode());
				cp.setHostObj(en);
				return cp;
			}
		} else if(obj instanceof MainPipe) {
			MainPipe mp = (MainPipe)obj;
			if(CollUtil.isEmpty(mp.getConnectEquips()) || mp.getConnectEquips().contains(theOtherType)) {
				ConnectPoint cp = new ConnectPoint();
				cp.setHostType(MainPipe.TYPE);
				cp.setHostId(mp.getId());
				cp.setHostObj(mp);
				return cp;
			}
		}
		return null;
	}

	private void addLine(Line line) {
		line.setId(IdUtil.fastSimpleUUID());
		markAnchorLine(line.getFrom(), line);
		markAnchorLine(line.getTo(), line);
		diagram.getLines().add(line);
	}

	private void markAnchorLine(ConnectPoint p, Line line){
		EquipmentNode en = p.getEquipmentNode();
		if (en != null) {
			Anchor anchor = en.getLegend().getAnchor(p.getAnchorCode());
			anchor.addLine(line);
		}
	}

	public HashSet<String> getRefRelTypes() {
		return refRelTypes;
	}

	public Diagram getDiagram() {
		return diagram;
	}

	public DataStrategy getDataStrategy() {
		return dataStrategy;
	}

	public static String getName(ObjectNode obj){
		String name = null;
		if(obj.get("localName") != null) {
			name = obj.get("localName").asText();
		}
		if(StrUtil.isBlank(name) && obj.get("name") != null) {
			name = obj.get("name").asText();
		}
		if(name == null)
			System.out.println();
		return name;
	}

	public static String getClassCode(ObjectNode obj){
		if(obj != null && obj.get("classCode") != null) {
			return obj.get("classCode").asText();
		}
		return null;
	}

	public static Legend emptyLegend(){
		Legend l = new Legend();
		l.setWidth(100);
		l.setHeight(100);
		return l;
	}

}
