package com.xydl.cac.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.xydl.cac.entity.IcdConfigType;
import com.xydl.cac.entity.IcdFile;
import com.xydl.cac.entity.IcdIed;
import com.xydl.cac.exception.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.*;

public class IcdXmlUtil {

    public static IcdFile loadIcdType(String xml) throws Exception {
        XmlMapper xmlMapper = XmlMapper.builder()
                .build();
        JsonNode root;
        try {
            root = xmlMapper.readTree(xml);
        } catch (Exception ex) {
            throw new BusinessException("ICD文件解析失败，请上传正确的xml类型文件");
        }
        IcdFile icdFile = IcdFile.builder()
                .xml(xml)
                .build();
        processTypeRoot(root, icdFile);
        return icdFile;
    }

    private static void processTypeRoot(JsonNode root, IcdFile icdFile) throws BusinessException {
        LinkedHashMap<String, IcdConfigType> configMap = new LinkedHashMap<>();

        Map<String, JsonNode> mapLNodeType = buildLNodeTypeMap(root);
        Map<String, JsonNode> mapDOType = buildDOTypeMap(root);
        Map<String, JsonNode> mapDAType = buildDATypeMap(root);

        List<JsonNode> iedNodeList = findNodes(root, "IED");
        for (JsonNode iedNode : iedNodeList) {
            processIEDNode(configMap, iedNode,
                    mapLNodeType, mapDOType, mapDAType);
        }
        icdFile.setConfigList(new ArrayList<>(configMap.values()));

        List<IcdIed> iedList = new ArrayList<>();
        HashMap<String, String> iedMap = new HashMap<>();
        List<JsonNode> apList = findNodes(root, "ConnectedAP");
        for (JsonNode apNode : apList) {
            IcdIed ied = processAPNode(apNode);
            if (!iedMap.containsKey(ied.getName())) {
                iedMap.put(ied.getName(), "1");
                iedList.add(ied);
            }
        }
        icdFile.setIedList(iedList);
    }

    private static IcdIed processAPNode(JsonNode apNode) {
        IcdIed ied = new IcdIed();
        ied.setPort(102);
        String iedName = apNode.get("iedName").asText();
        ied.setName(iedName);
        List<JsonNode> pList = findNodes(apNode, "P");
        for (JsonNode pNode : pList) {
            String type = pNode.get("type").asText();
            if ("IP".equalsIgnoreCase(type)) {
                ied.setIp(pNode.get("").asText());
            } else if ("OSI-AP-Title".equalsIgnoreCase(type)) {
                String apTitle = pNode.get("").asText();
                ied.setApTitle(apTitle.replaceAll("\"", ""));
            }
        }
        return ied;
    }

    private static void processIEDNode(LinkedHashMap<String, IcdConfigType> configMap, JsonNode iedNode,
                                       Map<String, JsonNode> mapLNodeType, Map<String, JsonNode> mapDOType, Map<String, JsonNode> mapDAType) throws BusinessException {
        String iedName = iedNode.get("name").asText();

        List<JsonNode> devList = findNodes(iedNode, "LDevice");
        for (JsonNode dev : devList) {
            processTypeDeviceNode(configMap, iedName, dev,
                    mapLNodeType, mapDOType, mapDAType);
        }
    }

    private static void processTypeDeviceNode(LinkedHashMap<String, IcdConfigType> configMap, String iedName, JsonNode deviceNode,
                                              Map<String, JsonNode> mapLNodeType, Map<String, JsonNode> mapDOType, Map<String, JsonNode> mapDAType) throws BusinessException {
        String ldeviceInst = deviceNode.get("inst").asText();
        Map<String, JsonNode> mapLN = buildLNMap(deviceNode);

        List<JsonNode> fcdaList = findNodes(deviceNode, "FCDA");
        for (JsonNode fcdaNode : fcdaList) {
            String lnClass = fcdaNode.get("lnClass").asText();
            String lnInst = fcdaNode.get("lnInst").asText();
            String doName = fcdaNode.get("doName").asText();
            String fc = fcdaNode.get("fc").asText();
            JsonNode preNode = fcdaNode.get("prefix");
            if (preNode != null) {
                String prefix = preNode.asText();
                if (StringUtils.isNotBlank(prefix)) {
                    lnClass = prefix + lnClass;
                }
            }

            JsonNode lnNode = mapLN.get(lnClass + lnInst);
            String lnType = lnNode.get("lnType").asText();
            JsonNode nodeLnDesc = lnNode.get("desc");
            String lnDesc = null;
            if (nodeLnDesc != null) {
                lnDesc = nodeLnDesc.asText();
            }
            JsonNode nodeLNodeType = mapLNodeType.get(lnType);
            JsonNode nodeDO = findLNodeType_DO_Node(nodeLNodeType, doName);
            if (nodeDO == null) {
                throw new BusinessException(lnType + "该LNodeType节点下缺少名字为" + doName + "的DO节点");
            }
            String doType = nodeDO.get("type").asText();
            JsonNode nodeDoDesc = nodeDO.get("desc");
            String doDesc = null;
            if (nodeDoDesc != null) {
                doDesc = nodeDoDesc.asText();
            }
            JsonNode nodeDOType = mapDOType.get(doType);
            String lastname = findLastname(nodeDOType, fc, mapDAType);

            String key = iedName + ldeviceInst + "/" + lnClass;
            if ("MX".equals(fc)) {
                IcdConfigType config = configMap.get(key);
                if (config == null) {
                    config = IcdConfigType.builder()
                            .iedName(iedName)
                            .ldeviceInst(ldeviceInst)
                            .lnClass(lnClass)
                            .lnDesc(lnDesc)
                            .build();
                    configMap.put(key, config);
                }
                config.addInst(lnInst);
                config.addAtt(doName, fc, lastname, doDesc, lnInst);
            } else if ("ST".equals(fc)) {
                IcdConfigType config = configMap.get(key);
                if (config == null) {
                    config = IcdConfigType.builder()
                            .iedName(iedName)
                            .ldeviceInst(ldeviceInst)
                            .lnClass(lnClass)
                            .build();
                    configMap.put(key, config);
                }
                String param = fc + "$" + doName + "$" + lastname;
                config.addInst(lnInst);
                config.addAtt(doName, fc, lastname, doDesc, lnInst);
            }
        }
    }

    private static Map<String, JsonNode> buildLNMap(JsonNode deviceNode) {
        Map<String, JsonNode> map = new LinkedHashMap<>();
        List<JsonNode> list = findNodes(deviceNode, "LN");
        for (JsonNode node : list) {
            String lnClass = node.get("lnClass").asText();
            String inst = node.get("inst").asText();
            JsonNode preNode = node.get("prefix");
            if (preNode != null) {
                String prefix = preNode.asText();
                if (StringUtils.isNotBlank(prefix)) {
                    lnClass = prefix + lnClass;
                }
            }
            map.put(lnClass + inst, node);
        }
        return map;
    }

    private static Map<String, JsonNode> buildDOTypeMap(JsonNode root) {
        Map<String, JsonNode> map = new LinkedHashMap<>();
        List<JsonNode> list = findNodes(root, "DOType");
        for (JsonNode node : list) {
            String id = node.get("id").asText();
            map.put(id, node);
        }
        return map;
    }

    private static Map<String, JsonNode> buildDATypeMap(JsonNode root) {
        Map<String, JsonNode> map = new LinkedHashMap<>();
        List<JsonNode> list = findNodes(root, "DAType");
        for (JsonNode node : list) {
            String id = node.get("id").asText();
            map.put(id, node);
        }
        return map;
    }

    private static Map<String, JsonNode> buildLNodeTypeMap(JsonNode root) {
        Map<String, JsonNode> map = new LinkedHashMap<>();
        List<JsonNode> listLNodeType = findNodes(root, "LNodeType");
        for (JsonNode node : listLNodeType) {
            String id = node.get("id").asText();
            map.put(id, node);
        }
        return map;
    }

    private static JsonNode findLNodeType_DO_Node(JsonNode lnNode, String doName) {
        JsonNode result = null;
        List<JsonNode> doiList = findNodes(lnNode, "DO");
        for (JsonNode doiNode : doiList) {
            String doiName = doiNode.get("name").asText();
            if (doiName.equals(doName)) {
                result = doiNode;
                break;
            }
        }
        return result;
    }

    private static String findLastname(JsonNode node, String fcname, Map<String, JsonNode> mapDAType) {
        String result = "";
        List<JsonNode> daList = findNodes(node, "DA");
        for (JsonNode daNode : daList) {
            String fc = daNode.get("fc").asText();
            String bType = daNode.get("bType").asText();
            JsonNode dchgNode = daNode.get("dchg");
            if (bType.equals("Struct") && fc.equals(fcname)) {
                result = daNode.get("name").asText();
                JsonNode typeNode = daNode.get("type");
                if (typeNode != null && StringUtils.isNotBlank(typeNode.asText())) {
                    JsonNode nodeDAType = mapDAType.get(typeNode.asText());
                    if (nodeDAType != null) {
                        List<JsonNode> bdaList = findNodes(nodeDAType, "BDA");
                        if (!CollectionUtils.isEmpty(bdaList)) {
                            String name = bdaList.get(0).get("name").asText();
                            result = result + "$" + name;
                        }
                    }
                }
                break;
            } else if (dchgNode != null && dchgNode.asText().equals("true") && fc.equals(fcname)) {
                result = daNode.get("name").asText();
                JsonNode typeNode = daNode.get("type");
                if (typeNode != null && StringUtils.isNotBlank(typeNode.asText())) {
                    JsonNode nodeDAType = mapDAType.get(typeNode.asText());
                    if (nodeDAType != null) {
                        List<JsonNode> bdaList = findNodes(nodeDAType, "BDA");
                        if (!CollectionUtils.isEmpty(bdaList)) {
                            String name = bdaList.get(0).get("name").asText();
                            result = result + "$" + name;
                        }
                    }
                }
            }
        }
        return result;
    }

    private static String findLN_DOI_DAI(JsonNode lnNode, String doName) {
        String result = "";
        List<JsonNode> doiList = findNodes(lnNode, "DOI");
        for (JsonNode doiNode : doiList) {
            String doiName = doiNode.get("name").asText();
            if (doiName.equals(doName)) {
                JsonNode pnode = doiNode;
                List<JsonNode> sdiList = findNodes(doiNode, "SDI");
                if (sdiList.size() > 0) {
                    JsonNode sdiNode = sdiList.get(0);
                    result = sdiNode.get("name").asText();
                    pnode = sdiNode;
                }
                List<JsonNode> daiList = findNodes(pnode, "DAI");
                if (daiList.size() > 0) {
                    JsonNode daiNode = daiList.get(0);
                    String daiName = daiNode.get("name").asText();
                    if (StringUtils.isNotBlank(result)) {
                        result = result + "$" + daiName;
                    } else {
                        result = daiName;
                    }
                }
                break;
            }
        }
        return result;
    }

    private static List<JsonNode> findNodes(JsonNode root, String fieldName) {
        List<JsonNode> result = new ArrayList<>();
        List<JsonNode> list = root.findValues(fieldName);
        for (JsonNode node : list) {
            if (node instanceof ObjectNode) {
                result.add(node);
            }
            if (node instanceof ArrayNode) {
                ArrayNode array = (ArrayNode) node;
                Iterator<JsonNode> elements = array.elements();
                while (elements.hasNext()) {
                    JsonNode item = elements.next();
                    result.add(item);
                }
            }
        }
        return result;
    }
}
