package com.persagy.adm.diagram.core.build;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.adm.diagram.core.ContentParser;
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.DataFilter;
import com.persagy.adm.diagram.core.model.logic.DynGroup;
import com.persagy.adm.diagram.core.model.logic.LogicFilter;
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.template.MainPipePoint;
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 final Object DATA_NULL = new Object();

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

	private CalcContext context;

	private Diagram diagram;

	private DiagramTemplate template;

	/**
	 * template.mainPipes引用，避免空指针
	 */
	private List<MainPipe> mainPipes;

	private LegendLoader legendLoader;

	private ContentParser contentParser;

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

	public DiagramBuilder(CalcContext context, LegendLoader legendLoader) {
		this.context = context;
		this.legendLoader = legendLoader;

		this.diagram = context.getDiagram();
		this.template = context.getTemplate();
		this.mainPipes = template.getMainPipes() != null ? template.getMainPipes() : new ArrayList<>();

		this.context.setDiagramBuilder(this);

		this.contentParser = SpringUtil.getBean("diagramContentParser");
	}

	/**
	 * 加载设备数据，构建系统图结构
	 */
	public void buildStructure(){
		calcContainerAndMainPipes(template.getFrame());

		buildMainPipeAndNodes();

		handleNodes();
		handleContainers();
	}

	/**
	 * 对容器和相关的干管进行计算和数据加载
	 */
	private void calcContainerAndMainPipes(Container container){
		List<Container> subEquipBoxes = new ArrayList<>();
		List<Container> subDynCons = new ArrayList<>();
		container.readSubContainers(subEquipBoxes, subDynCons);

		List<MainPipe> relMainPipes = getRelMainPipes(container, subEquipBoxes);

		DynGroup dynGroup = container.getDynGroup();
		if (dynGroup != null) {
			List<ObjectNode> dynData;
			CalcGroup group;
			int pos = container.getParent().getChildren().indexOf(container);

			boolean isCompSrc = dynGroup.isCompSrc();
			if(isCompSrc) {
				IEquipHolder srcComp = context.getComp(dynGroup.getDynSource(), dynGroup.getDynSourceType());
				dynData = findMatchedData(srcComp);
			} else {
				//TODO 楼层，建筑等分组方式
				dynData = new ArrayList<>();
			}

			if (CollUtil.isEmpty(dynData)) {
				return;
			}

			group = new CalcGroup();
			context.setCurrentGroup(group);

			String containerJson = null;
			String mainPipesJson = null;

			for (int i = 0; i < dynData.size(); i++) {
				group.setCurrentIdx(i);

				if(i == 0){
					setGroupContainer(container, group);
					setGroupMainPipes(relMainPipes, group);

					if(isCompSrc) {
						group.setDynSrcComp(context.getComp(dynGroup.getDynSource(), dynGroup.getDynSourceType()), dynData.get(i));
					}

					loadEquipsData(subEquipBoxes, subDynCons, relMainPipes);
				} else {
					if(containerJson == null) {
						containerJson = contentParser.toJson(container);
					}
					if(mainPipesJson == null) {
						mainPipesJson = contentParser.toJson(relMainPipes);
					}

					String idLabel = StrUtil.join("_", group.getIdx());

					Container newCon = copyContainer(containerJson, idLabel);
					container.getParent().addComp(newCon, pos + i);
					setGroupContainer(newCon, group);

					List<MainPipe> newMps = copyRelMainPipes(mainPipesJson, idLabel);
					mainPipes.addAll(newMps);
					setGroupMainPipes(newMps, group);

					List<Container> newSubEquipBoxes = new ArrayList<>();
					List<Container> newSubDynCons = new ArrayList<>();
					newCon.readSubContainers(newSubEquipBoxes, newSubDynCons);

					if(isCompSrc) {
						group.setDynSrcComp(context.getComp(dynGroup.getDynSource(), dynGroup.getDynSourceType()), dynData.get(i));
					}

					loadEquipsData(newSubEquipBoxes, newSubDynCons, newMps);
				}

			}

			//清除当前计算组
			context.clearCurrentGroup();
		} else {
			loadEquipsData(subEquipBoxes, subDynCons, relMainPipes);
		}
	}

	private void setGroupContainer(Container con, CalcGroup currentGroup) {
		for (Container item : con.listContainers()) {
			currentGroup.addComp(getSrcId(item.getId()), item);
		}
	}

	private void setGroupMainPipes(List<MainPipe> mps,  CalcGroup currentGroup){
		for(MainPipe mp : mps){
			currentGroup.addComp(getSrcId(mp.getId()), mp);
		}
	}

	private Container copyContainer(String containerJson, String idLabel) {
		Container container = contentParser.parseContent(containerJson, Container.class);
		for (Container con : container.listContainers()) {
			con.setId(getSrcId(con.getId()) + ":" + idLabel);
		}
		container.initParent();

		return container;
	}

	private List<MainPipe> copyRelMainPipes(String mainPipesJson, String idLabel){
		List<MainPipe> mainPipes = new ArrayList<>();
		Collections.addAll(mainPipes, contentParser.parseContent(mainPipesJson, MainPipe[].class));
		for(MainPipe mp : mainPipes){
			mp.setId(getSrcId(mp.getId()) + ":" + idLabel);
			changeMainPipeRefIds(mp);

			mp.init(template);
		}
		return mainPipes;
	}

	private void changeMainPipeRefIds(MainPipe mp){
		List<String> relConIds = mp.getRelatedContainers();
		if(relConIds != null){
			for (int i = 0; i < relConIds.size(); i++) {
				Container con = context.getComp(relConIds.get(i), Container.TYPE);
				if(con != null) {
					relConIds.set(i, con.getId());
				}
			}
		}

		for(List<MainPipePoint> line : mp.getPath()){
			for (MainPipePoint p : line) {
				Container con = context.getComp(p.getContainerId(), Container.TYPE);
				if(con != null) {
					p.setContainerId(con.getId());
				}
			}
		}
	}

	private String getSrcId(String id){
		int pos = id.indexOf(':');
		if(pos > 0){
			return id.substring(0, pos);
		}
		return id;
	}

	private List<MainPipe> getRelMainPipes(Container container, List<Container> subEquipBoxes){
		List<MainPipe> relMainPipes = new ArrayList<>();
		boolean isFrame = template.isFrameContainer(container);
		for(MainPipe mainPipe : mainPipes) {
			List<Container> relCons = mainPipe.getRelatedContainerList();
			boolean relWithMe;
			if(isFrame){
				relWithMe = CollUtil.isEmpty(relCons);
			} else {
				relWithMe = relCons != null && relCons.contains(container);
			}
			if(relWithMe){
				relMainPipes.add(mainPipe);
			} else {
				for(Container subEq : subEquipBoxes) {
					if(relCons != null && relCons.contains(subEq)){
						relMainPipes.add(mainPipe);
						break;
					}
				}
			}
		}
		return relMainPipes;
	}

	private void loadEquipsData(List<Container> subEquipBoxes, List<Container> subDynCons, List<MainPipe> relMainPipes){
		for(MainPipe mainPipe : relMainPipes) {
			if (mainPipe.isBindEquipment()){
				loadMatchedData(mainPipe);
			}
		}
		for(Container subCon : subEquipBoxes) {
			loadMatchedData(subCon);
		}

		if (subDynCons.size() > 0) {
			for(Container subDyn : subDynCons) {
				calcContainerAndMainPipes(subDyn);
			}
		}
	}

	public List<ObjectNode> loadMatchedData(IEquipHolder equipHolder){
		List<ObjectNode> matchedData = equipHolder.getMatchedData();
		if(matchedData == null){
			matchedData = findMatchedData(equipHolder);
			if(matchedData == null) {
				matchedData = Collections.EMPTY_LIST;
			}
			equipHolder.setMatchedData(matchedData);
		}
		return matchedData;
	}

	private List<ObjectNode> findMatchedData(IEquipHolder equipHolder){
		ArrayList<ObjectNode> data = null;
		for(ObjectNode obj : context.getOptionalObjs()) {
			if (match(obj, equipHolder)){
				if(data == null){
					data = new ArrayList<>();
				}
				data.add(obj);
			}
		}
		return data;
	}

	private void buildMainPipeAndNodes(){
		for(MainPipe mainPipe : mainPipes) {
			if (mainPipe.isBindEquipment()) {
				List<ObjectNode> matchedData = mainPipe.getMatchedData();
				if(CollUtil.isNotEmpty(matchedData)) {
					orderDataList(matchedData);

					ObjectNode obj = (ObjectNode)mainPipe.getMatchedData().get(0);
					mainPipe.setDataObject(obj);
					mainPipe.setDataObjectId(obj.get("id").asText());

					context.getEquipMap().put(mainPipe.getDataObjectId(), mainPipe);
				}
			}
		}
		for(Container con : template.getContainers()) {
			if(con.isEquipmentBox()) {
				List<ObjectNode> matchedData = con.getMatchedData();
				if(CollUtil.isNotEmpty(matchedData)) {
					orderDataList(matchedData);

					for(ObjectNode obj : matchedData) {
						if(con.getEquipPack() != null) {
							addPackData(con, obj);
						} else {
							addEquipNode(con, obj);
						}
					}
				}
			}
		}
	}

	private void orderDataList(List<ObjectNode> dataList){
		//TODO 对设备数据进行排序操作
	}

	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 = legendLoader.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 = legendLoader.findLegendById(con.getEquipPack().getLegendId(), classCode.substring(0, 4));
			}
			if(legend == null) {
				legend = legendLoader.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() {
		for(Container con : template.getContainers()) {
			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;
	}

	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 static boolean hasRelationFilter(IEquipHolder equipHolder) {
		DataFilter filter = equipHolder.getDataFilter();
		if(filter != null) {
			if (filter.getType() == DataFilter.TYPE_RELATION) {
				return true;
			}
			if (filter instanceof LogicFilter) {
				LinkedList<DataFilter> list = new LinkedList<>();
				list.addAll(((LogicFilter) filter).getItems());

				while (list.size() > 0) {
					DataFilter item = list.removeFirst();
					if(item.getType() == DataFilter.TYPE_RELATION){
						return true;
					} else if(item instanceof LogicFilter) {
						list.addAll(((LogicFilter) item).getItems());
					}
				}
			}
		}
		return false;
	}

	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();
		}
		return name;
	}

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

}
