package com.yfd.monitor.gdw2019.utils;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.yfd.monitor.gdw2019.bean.Device;
import com.yfd.monitor.gdw2019.bean.DeviceChannel;
import com.yfd.monitor.gdw2019.event.subscribe.catalog.CatalogEvent;
import com.yfd.monitor.utils.DateUtil;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import javax.sip.RequestEvent;
import javax.sip.message.Request;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 基于dom4j的工具包
 *
 *
 */
public class XmlUtil {
    /**
     * 日志服务
     */
    private static Logger logger = LoggerFactory.getLogger(XmlUtil.class);

    /**
     * 解析XML为Document对象
     *
     * @param xml 被解析的XMl
     *
     * @return Document
     */
    public static Element parseXml(String xml) {
        Document document = null;
        //
        StringReader sr = new StringReader(xml);
        SAXReader saxReader = new SAXReader();
        try {
            document = saxReader.read(sr);
        } catch (DocumentException e) {
            logger.error("解析失败", e);
        }
        return null == document ? null : document.getRootElement();
    }

    /**
     * 获取element对象的text的值
     *
     * @param em  节点的对象
     * @param tag 节点的tag
     * @return 节点
     */
    public static String getText(Element em, String tag) {
        if (null == em) {
            return null;
        }
        Element e = em.element(tag);
        //
        return null == e ? null : e.getText().trim();
    }

    /**
     * 递归解析xml节点，适用于 多节点数据
     *
     * @param node     node
     * @param nodeName nodeName
     * @return List<Map<String, Object>>
     */
    public static List<Map<String, Object>> listNodes(Element node, String nodeName) {
        if (null == node) {
            return null;
        }
        // 初始化返回
        List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
        // 首先获取当前节点的所有属性节点
        List<Attribute> list = node.attributes();

        Map<String, Object> map = null;
        // 遍历属性节点
        for (Attribute attribute : list) {
            if (nodeName.equals(node.getName())) {
                if (null == map) {
                    map = new HashMap<String, Object>();
                    listMap.add(map);
                }
                // 取到的节点属性放到map中
                map.put(attribute.getName(), attribute.getValue());
            }

        }
        // 遍历当前节点下的所有节点 ，nodeName 要解析的节点名称
        // 使用递归
        Iterator<Element> iterator = node.elementIterator();
        while (iterator.hasNext()) {
            Element e = iterator.next();
            listMap.addAll(listNodes(e, nodeName));
        }
        return listMap;
    }

    /**
     * xml转json
     *
     * @param element
     * @param json
     */
    public static void node2Json(Element element, JSONObject json) {
        // 如果是属性
        for (Object o : element.attributes()) {
            Attribute attr = (Attribute) o;
            if (!ObjectUtils.isEmpty(attr.getValue())) {
                json.put("@" + attr.getName(), attr.getValue());
            }
        }
        List<Element> chdEl = element.elements();
        if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值
            json.put(element.getName(), element.getText());
        }

        for (Element e : chdEl) {   // 有子元素
            if (!e.elements().isEmpty()) {  // 子元素也有子元素
                JSONObject chdjson = new JSONObject();
                node2Json(e, chdjson);
                Object o = json.get(e.getName());
                if (o != null) {
                    JSONArray jsona = null;
                    if (o instanceof JSONObject) {  // 如果此元素已存在,则转为jsonArray
                        JSONObject jsono = (JSONObject) o;
                        json.remove(e.getName());
                        jsona = new JSONArray();
                        jsona.add(jsono);
                        jsona.add(chdjson);
                    }
                    if (o instanceof JSONArray) {
                        jsona = (JSONArray) o;
                        jsona.add(chdjson);
                    }
                    json.put(e.getName(), jsona);
                } else {
                    if (!chdjson.isEmpty()) {
                        json.put(e.getName(), chdjson);
                    }
                }
            } else { // 子元素没有子元素
                for (Object o : element.attributes()) {
                    Attribute attr = (Attribute) o;
                    if (!ObjectUtils.isEmpty(attr.getValue())) {
                        json.put("@" + attr.getName(), attr.getValue());
                    }
                }
                if (!e.getText().isEmpty()) {
                    json.put(e.getName(), e.getText());
                }
            }
        }
    }
    public static  Element getRootElement(RequestEvent evt) throws DocumentException {

        return getRootElement(evt, "gb2312");
    }

    public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
        Request request = evt.getRequest();
        return getRootElement(request.getRawContent(), charset);
    }

    public static Element getRootElement(byte[] content, String charset) throws DocumentException {
        if (charset == null) {
            charset = "gb2312";
        }
        SAXReader reader = new SAXReader();
        reader.setEncoding(charset);
        Document xml = reader.read(new ByteArrayInputStream(content));
        return xml.getRootElement();
    }

    private enum ChannelType{
        CivilCode, BusinessGroup,VirtualOrganization,Other
    }

    public static DeviceChannel channelContentHander(Element itemDevice, Device device, String event){
        DeviceChannel deviceChannel = new DeviceChannel();
        deviceChannel.setDeviceId(device.getDeviceId());
        Element channdelIdElement = itemDevice.element("DeviceID");
        if (channdelIdElement == null) {
            logger.warn("解析Catalog消息时发现缺少 DeviceID");
            return null;
        }
        String channelId = channdelIdElement.getTextTrim();
        if (ObjectUtils.isEmpty(channelId)) {
            logger.warn("解析Catalog消息时发现缺少 DeviceID");
            return null;
        }
        deviceChannel.setChannelId(channelId);
        if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) {
            // 除了ADD和update情况下需要识别全部内容，
            return deviceChannel;
        }

        ChannelType channelType = ChannelType.Other;
        if (channelId.length() <= 8) {
            channelType = ChannelType.CivilCode;
            deviceChannel.setHasAudio(false);
        }else {
            if (channelId.length() == 20) {
                int code = Integer.parseInt(channelId.substring(10, 13));
                switch (code){
                    case 215:
                        channelType = ChannelType.BusinessGroup;
                        deviceChannel.setHasAudio(false);
                        break;
                    case 216:
                        channelType = ChannelType.VirtualOrganization;
                        deviceChannel.setHasAudio(false);
                        break;
                    case 136:
                    case 137:
                    case 138:
                        deviceChannel.setHasAudio(true);
                        break;
                    default:
                        deviceChannel.setHasAudio(false);
                        break;

                }
            }
        }

        Element channdelNameElement = itemDevice.element("Name");
        String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim() : "";
        deviceChannel.setName(channelName);

        String civilCode = XmlUtil.getText(itemDevice, "CivilCode");
        deviceChannel.setCivilCode(civilCode);
        if (channelType == ChannelType.CivilCode && civilCode == null) {
            deviceChannel.setParental(1);
            // 行政区划如果没有传递具体值，则推测一个
            if (channelId.length() > 2) {
                deviceChannel.setCivilCode(channelId.substring(0, channelId.length() - 2));
            }
        }
        if (channelType.equals(ChannelType.CivilCode)) {
            // 行政区划其他字段没必要识别了，默认在线即可
            deviceChannel.setStatus(1);
            deviceChannel.setParental(1);
            deviceChannel.setCreateTime(DateUtil.getNow());
            deviceChannel.setUpdateTime(DateUtil.getNow());
            return deviceChannel;
        }
        /**
         * 行政区划展示设备树与业务分组展示设备树是两种不同的模式
         * 行政区划展示设备树 各个目录之间主要靠deviceId做关联,摄像头通过CivilCode指定其属于那个行政区划;都是不超过十位的编号; 结构如下:
         * 河北省
         *    --> 石家庄市
         *          --> 摄像头
         *String parentId = XmlUtil.getText(itemDevice, "ParentID");
         if (parentId != null) {
         if (parentId.contains("/")) {
         String lastParentId = parentId.substring(parentId.lastIndexOf("/") + 1);
         String businessGroup = parentId.substring(0, parentId.indexOf("/"));
         deviceChannel.setParentId(lastParentId);
         }else {
         deviceChannel.setParentId(parentId);
         }
         }
         deviceCh          --> 正定县
         *                  --> 摄像头
         *                  --> 摄像头
         *
         * 业务分组展示设备树是顶级是业务分组,其下的虚拟组织靠BusinessGroupID指定其所属的业务分组;摄像头通过ParentId来指定其所属于的虚拟组织:
         * 业务分组
         *    --> 虚拟组织
         *         --> 摄像头
         *         --> 虚拟组织
         *             --> 摄像头
         *             --> 摄像头
         */
        String parentId = XmlUtil.getText(itemDevice, "ParentID");
        String businessGroupID = XmlUtil.getText(itemDevice, "BusinessGroupID");
        if (parentId != null) {
            if (parentId.contains("/")) {
                String lastParentId = parentId.substring(parentId.lastIndexOf("/") + 1);
                if (businessGroupID == null) {
                    businessGroupID = parentId.substring(0, parentId.indexOf("/"));
                }
                deviceChannel.setParentId(lastParentId);
            }else {
                deviceChannel.setParentId(parentId);
            }
            // 兼容设备通道信息中自己为自己父节点的情况
            if (deviceChannel.getParentId().equals(deviceChannel.getChannelId())) {
                deviceChannel.setParentId(null);
            }
        }
        deviceChannel.setBusinessGroupId(businessGroupID);
        if (channelType.equals(ChannelType.BusinessGroup) || channelType.equals(ChannelType.VirtualOrganization)) {
            // 业务分组和虚拟组织 其他字段没必要识别了，默认在线即可
            deviceChannel.setStatus(1);
            deviceChannel.setParental(1);
            deviceChannel.setCreateTime(DateUtil.getNow());
            deviceChannel.setUpdateTime(DateUtil.getNow());
            return deviceChannel;
        }

        Element statusElement = itemDevice.element("Status");

        if (statusElement != null) {
            String status = statusElement.getTextTrim().trim();
            // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
            if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) {
                deviceChannel.setStatus(1);
            }
            if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
                deviceChannel.setStatus(0);
            }
        }else {
            deviceChannel.setStatus(1);
        }
        // 识别自带的目录标识
        String parental = XmlUtil.getText(itemDevice, "Parental");
        // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
        if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) {
            deviceChannel.setParental(0);
        }else {
            deviceChannel.setParental(1);
        }


        deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer"));
        deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model"));
        deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner"));
        deviceChannel.setCertNum(XmlUtil.getText(itemDevice, "CertNum"));
        deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
        deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
        deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password"));

        String safetyWay = XmlUtil.getText(itemDevice, "SafetyWay");
        if (ObjectUtils.isEmpty(safetyWay)) {
            deviceChannel.setSafetyWay(0);
        } else {
            deviceChannel.setSafetyWay(Integer.parseInt(safetyWay));
        }

        String registerWay = XmlUtil.getText(itemDevice, "RegisterWay");
        if (ObjectUtils.isEmpty(registerWay)) {
            deviceChannel.setRegisterWay(1);
        } else {
            deviceChannel.setRegisterWay(Integer.parseInt(registerWay));
        }

        if (XmlUtil.getText(itemDevice, "Certifiable") == null
                || XmlUtil.getText(itemDevice, "Certifiable") == "") {
            deviceChannel.setCertifiable(0);
        } else {
            deviceChannel.setCertifiable(Integer.parseInt(XmlUtil.getText(itemDevice, "Certifiable")));
        }

        if (XmlUtil.getText(itemDevice, "ErrCode") == null
                || XmlUtil.getText(itemDevice, "ErrCode") == "") {
            deviceChannel.setErrCode(0);
        } else {
            deviceChannel.setErrCode(Integer.parseInt(XmlUtil.getText(itemDevice, "ErrCode")));
        }

        deviceChannel.setEndTime(XmlUtil.getText(itemDevice, "EndTime"));
        deviceChannel.setSecrecy(XmlUtil.getText(itemDevice, "Secrecy"));
        deviceChannel.setIpAddress(XmlUtil.getText(itemDevice, "IPAddress"));
        if (XmlUtil.getText(itemDevice, "Port") == null || XmlUtil.getText(itemDevice, "Port") == "") {
            deviceChannel.setPort(0);
        } else {
            deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port")));
        }


        String longitude = XmlUtil.getText(itemDevice, "Longitude");
        if (NumericUtil.isDouble(longitude)) {
            deviceChannel.setLongitude(Double.parseDouble(longitude));
        } else {
            deviceChannel.setLongitude(0.00);
        }
        String latitude = XmlUtil.getText(itemDevice, "Latitude");
        if (NumericUtil.isDouble(latitude)) {
            deviceChannel.setLatitude(Double.parseDouble(latitude));
        } else {
            deviceChannel.setLatitude(0.00);
        }

        deviceChannel.setGpsTime(DateUtil.getNow());


        if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) {
            //兼容INFO中的信息
            Element info = itemDevice.element("Info");
            if(XmlUtil.getText(info, "PTZType") == null || "".equals(XmlUtil.getText(info, "PTZType"))){
                deviceChannel.setPTZType(0);
            }else{
                deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(info, "PTZType")));
            }
        } else {
            deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
        }

        return deviceChannel;
    }

    /**
     * 新增方法支持内部嵌套
     *
     * @param element xmlElement
     * @param clazz 结果类
     * @param <T> 泛型
     * @return 结果对象
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        T t = clazz.getDeclaredConstructor().newInstance();
        for (Field field : fields) {
            ReflectionUtils.makeAccessible(field);
            MessageElement annotation = field.getAnnotation(MessageElement.class);
            if (annotation == null) {
                continue;
            }
            String value = annotation.value();
            String subVal = annotation.subVal();
            Element element1 = element.element(value);
            if (element1 == null) {
                continue;
            }
            if ("".equals(subVal)) {
                // 无下级数据
                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
                Object o = simpleTypeDeal(field.getType(), fieldVal);
                ReflectionUtils.setField(field, t,  o);
            } else {
                // 存在下级数据
                ArrayList<Object> list = new ArrayList<>();
                Type genericType = field.getGenericType();
                if (!(genericType instanceof ParameterizedType)) {
                    continue;
                }
                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
                for (Element element2 : element1.elements(subVal)) {
                    list.add(loadElement(element2, aClass));
                }
                ReflectionUtils.setField(field, t, list);
            }
        }
        return t;
    }

    /**
     * 简单类型处理
     *
     * @param tClass
     * @param val
     * @return
     */
    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
        if (tClass.equals(String.class)) {
            return val.toString();
        }
        if (tClass.equals(Integer.class)) {
            return Integer.valueOf(val.toString());
        }
        if (tClass.equals(Double.class)) {
            return Double.valueOf(val.toString());
        }
        if (tClass.equals(Long.class)) {
            return Long.valueOf(val.toString());
        }
        return val;
    }
}