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

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

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

	private Diagram diagram;

	private DiagramTemplate template;

	private DataStrategy dataStrategy;

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

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

	/**
	 * 记录已经使用的数据id和对应的组件
	 */
	private HashMap<String, Object> equipMap = new HashMap<>();

	public DiagramBuilder(Diagram diagram, DataStrategy dataStrategy) {
		this.diagram = diagram;
		this.template = diagram.getTemplate();
		this.dataStrategy = dataStrategy;

		init();
	}

	private void init(){
		//记录节点中已使用的数据id
		diagram.getNodes().forEach(node -> {
			if (EquipmentNode.TYPE.equals(node.getCompType())) {
				equipMap.put(((EquipmentNode) node).getObjId(), node);
			}
		});
		//记录干管中已使用的数据id
		if(template.getMainPipes() != null) {
			template.getMainPipes().forEach(mainPipe -> {
				if(StrUtil.isNotBlank(mainPipe.getDataObjectId())) {
					equipMap.put(mainPipe.getDataObjectId(), mainPipe);
				}
			});
		}
	}

	/**
	 * 加载设备数据，并进行计算处理
	 * @param containers 容器列表
	 * @param optionalObjs 节点列表
	 */
	public void buildEquipNodeAndContainer(List<Container> containers, List<ObjectNode> optionalObjs){
		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();
					}
				}
			}
		}
		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());

							equipMap.put(mainPipe.getDataObjectId(), mainPipe);

							iter.remove();
							break;
						}
					}
				}
			}
		}

		handleNodes();

		buildContainers(containers);
	}

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

		EquipmentNode node = new EquipmentNode();
		node.setId(IdUtil.simpleUUID());
		node.setObjId(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);

		equipMap.put(node.getObjId(), node);
	}

	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.setContent(name);
		node.setLabel(label);

		diagram.getNodes().add(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);
			}
			if(con.getEquipPack().getLegendId() != null) {
				legend = dataStrategy.getLegend(con.getEquipPack().getLegendId(), classCode.substring(0, 4));
			}
		} 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.simpleUUID());
		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);
			}
			return true;
		}
		return false;
	}

	private void buildContainers(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 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 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);
				} 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){
		for(ObjectNode rel : optionalRels) {
			String from = rel.get(ObjectRelation.OBJ_FROM_HUM).asText();
			String to = rel.get(ObjectRelation.OBJ_TO_HUM).asText();

			Object fromObj = equipMap.get(from);
			Object toObj = equipMap.get(to);

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

						addLine(line);
					}
				}
			}
		}

		//test code TODO
		EquipmentNode n1 = null;
		EquipmentNode n2 = null;
		for(Object o : equipMap.values()) {
			if(o instanceof EquipmentNode){
				String txt = ((EquipmentNode) o).getLabel().getContent();
				if("1#进线柜".equals(txt)) {
					n1 = (EquipmentNode) o;
				} else if("1#计量柜".equals(txt)) {
					n2 = (EquipmentNode) o;
				}
			}
		}
		if(n1 != null && n2 != null) {
			ConnectPoint p1 = new ConnectPoint();
			p1.setHostType(EquipmentNode.TYPE);
			p1.setHostId(n1.getId());
			p1.setAnchorCode("T3");
			p1.setHostObj(n1);
			ConnectPoint p2 = new ConnectPoint();
			p2.setHostType(EquipmentNode.TYPE);
			p2.setHostId(n2.getId());
			p2.setAnchorCode("B3");
			p2.setHostObj(n2);
			Line line = new Line();
			line.setFrom(p1);
			line.setTo(p2);
			addLine(line);
		}
	}

	private ConnectPoint getConnectPoint(Object obj, String  relType, Object theOtherEnd){
		ObjectNode theOtherData = null;
		if(theOtherEnd instanceof EquipmentNode) {
			theOtherData = (ObjectNode) ((EquipmentNode) theOtherEnd).getDataObject();
		} else if(theOtherEnd instanceof MainPipe) {
			theOtherData = (ObjectNode) ((MainPipe) 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);
						}
					}

					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) {
		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 HashMap<String, Object> getEquipMap() {
		return equipMap;
	}

	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)) {
			name = obj.get("name").asText();
		}
		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;
	}

}
