/**
 * <p>Copyright:Copyright(c) 2016</p>
 * <p>Company:上海中信信息发展股份有限公司</p>
 * <p>类更新历史信息</p>
 * @author 张二伟
 * @date 2017-03-19 14:58
 */

package com.cesgroup.cesmonitor.service.map;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MessageProcessingModel;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.PDUFactory;
import org.snmp4j.util.TableEvent;
import org.snmp4j.util.TableUtils;

import com.cesgroup.cesmonitor.common.JSON;

/**
 * 自动发现设备节点连接关系
 *
 *
 * @author zhang.erwei
 * @version 1.0.0 2017年5月22日
 */
public class MapAutoCreatePrepareRalation {

    protected Logger logger = LoggerFactory.getLogger(MapAutoCreatePrepareRalation.class);

    private static int version = SnmpConstants.version2c;

    private static String protocol = "udp";

    private static int port = 161;

    private static String oidIp = "1.3.6.1.4.1.9.9.23.1.2.1.1.4";//SNMPv2-SMI::enterprises.9.23.1.2.1.1.4

    //    private static String oidIfName = "1.3.6.1.2.1.31.1.1.1.1";//IF-MIB::ifName
    private static String oidIfDesc = "1.3.6.1.2.1.2.2.1.2";//IF-MIB::ifDescr

    private Map<String, String> phisicalAdrressIpMapping = new HashMap<String, String>();

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        //        String ip = "192.168.50.53";
        String ip = "10.250.200.254";
        //        String ip = "10.1.3.254";
        //        String ip = "10.1.3.254";
        //        String ip = "10.250.200.5";
        //        String ip = "192.168.50.53";

        String community = "public";
        //        String community = "huawei123";

        MapAutoCreatePrepareRalation tester = new MapAutoCreatePrepareRalation();
        //        tester.snmpWalk(ip, community, "1.3.6.1.4.1.9.9.23.1.2.1.1.4");
        //        tester.snmpWalk(ip, community, "1.3.6.1.2.1.31.1.1.1.1");
        //        tester.ipToHexString("10.250.200.5");
        //        tester.ipToHexString("10.250.190.233");
        //        tester.ipToHexString("192.168.20.86");
        Device device = new Device();
        device.setIp(ip);
        device.setCommunity(community);
        device.setSnmpPort(port);
        //        test1(tester, device);
        tester.getLink(device);
        System.out.println(JSON.toJson(device));
    }

    //    public void test1(MapAutoCreate tester, Device device) {
    //        logger.debug("0===============" + device.getIp());
    //        tester.getCoreLink(device);
    //        for (Device d : device.getLink().values()) {
    //            logger.debug("1===============" + d.getIp());
    //            tester.getCoreLink(d);
    //            for (Device d2 : d.getLink().values()) {
    //                logger.debug("2===============" + d2.getIp());
    //                tester.getCoreLink(d2);
    //                //                for (Device d3 : d2.getLink().values()) {
    //                //                    logger.debug("3===============" + d3.getIp());
    //                //                    tester.walk(d3);
    //                //                }
    //            }
    //        }
    //    }

    public void getLink(Device device) {
        this.phisicalAdrressIpMapping.putAll(this.collectPhisicalAdrress(device));
        logger.debug("00===============" + device.getIp());
        this.getDeviceLink(device);
        logger.debug("0===============" + device.getIp());
        this.getCoreLink(device);
        for (Device d : device.getLink().values()) {
            logger.debug("1===============" + d.getIp());
            this.getDeviceLink(d);
            for (Device d2 : d.getLink().values()) {
                logger.debug("2===============" + d2.getIp());
            }

        }
    }

    public Map<String, String> collectPhisicalAdrress(Device device) {
        Map<String, String> data = new HashMap<String, String>();
        TransportMapping transport = null;
        Snmp snmp = null;
        CommunityTarget target;
        String[] IP_OIDS = { "1.3.6.1.2.1.4.22.1.1", // ipNetToMediaIfIndex
            "1.3.6.1.2.1.4.22.1.2", // ipNetToMediaPhysAddress
            "1.3.6.1.2.1.4.22.1.3" };// ipNetToMediaNetAddress
        try {
            String address = protocol + ":" + device.getIp() + "/" + device.getSnmpPort();
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            snmp.listen();
            target = new CommunityTarget();
            target.setCommunity(new OctetString(device.getCommunity()));
            target.setRetries(2);
            target.setAddress(GenericAddress.parse(address));
            target.setTimeout(8000);
            target.setVersion(SnmpConstants.version2c);
            TableUtils tableUtils = new TableUtils(snmp, new PDUFactory() {

                @Override
                public PDU createPDU(Target arg0) {
                    PDU request = new PDU();
                    request.setType(PDU.GET);
                    return request;
                }

                @Override
                public PDU createPDU(MessageProcessingModel messageProcessingModel) {
                    // TODO Auto-generated method stub
                    return null;
                }
            });
            // 获取ip
            OID[] ipcolumns = new OID[IP_OIDS.length];
            for (int i = 0; i < IP_OIDS.length; i++) {
                ipcolumns[i] = new OID(IP_OIDS[i]);
            }
            @SuppressWarnings("unchecked")
            List<TableEvent> iplist = tableUtils.getTable(target, ipcolumns, null, null);
            if (iplist.size() == 1 && iplist.get(0).getColumns() == null) {
                logger.debug(" null");
            } else {
                for (TableEvent event : iplist) {
                    VariableBinding[] values = event.getColumns();
                    if (values == null) {
                        continue;
                    }

                    //                    logger.debug(" ipNetToMediaIfIndex:" + values[0].getVariable().toString()
                    //                        + "   ipNetToMediaPhysAddress:" + values[1].getVariable().toString()
                    //                        + "   ipNetToMediaNetAddress:" + values[2].getVariable().toString());
                    data.put(values[1].getVariable().toString(), values[2].getVariable().toString());

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (transport != null) {
                    transport.close();
                }
                if (snmp != null) {
                    snmp.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    private void getDeviceLink(Device device) {
        if (device.getIp() == null || device.getSnmpPort() == null) {
            return;
        }
        //this.phisicalAdrressIpMapping.putAll(this.collectPhisicalAdrress(device));
        String address = protocol + ":" + device.getIp() + "/" + device.getSnmpPort();

        //SNMPv2-SMI::enterprises.9.9.656.1.4.1.1.2
        String targetOIDString = "1.3.6.1.4.1.9.9.656.1.4.1.1.2";//1.3.6.1.4.1.9.9.656.1.4.1.1.2.10117.48.48.48.48.48.48.48.48.48.48.48.48.49.55.49.50.53.70.69.70.70.49.68.65 = 00:57:d2:d1:a4:0b
        OID targetOID = new OID(targetOIDString);

        PDU requestPDU = new PDU();
        requestPDU.setType(PDU.GETNEXT);
        requestPDU.add(new VariableBinding(targetOID));

        CommunityTarget target = SnmpUtil.createCommunityTarget(address, device.getCommunity(), version, 1 * 1000L, 1);
        TransportMapping transport = null;
        Snmp snmp = null;
        try {
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            transport.listen();

            boolean finished = false;
            while (!finished) {
                VariableBinding vb = null;
                ResponseEvent response = snmp.send(requestPDU, target);
                PDU responsePDU = response.getResponse();

                if (null == responsePDU) {
                    logger.debug("responsePDU == null");
                    finished = true;
                    break;
                } else {
                    vb = responsePDU.get(0);
                }
                // check finish
                finished = checkWalkFinished(targetOID, responsePDU, vb);
                if (!finished) {
                    // Dump response.
                    //                    logger.debug("----" + vb.toString());
                    String oid = vb.getOid().toString().substring(targetOIDString.length() + 1);
                    String key = oid.substring(0, oid.indexOf('.'));
                    String phisicalAddress = vb.getVariable().toString();
                    String ip = phisicalAdrressIpMapping.get(phisicalAddress);
                    String ifName = snmpGetIfName(snmp, target, key);
                    logger.debug(ip + "=" + ifName + "=" + phisicalAddress);
                    Device d = new Device();
                    d.setIp(ip);
                    d.setCommunity(device.getCommunity());
                    device.getLink().put(ifName, d);
                    // Set up the variable binding for the next entry.
                    requestPDU.setRequestID(new Integer32(0));
                    requestPDU.set(0, vb);
                }
            }
            //            logger.debug("success finish snmp walk!");
        } catch (Exception e) {
            //            logger.debug("SNMP walk Exception: " + e);
            logger.info("无法连接SNMP：IP={},port={}", device.getIp(), device.getSnmpPort());
        } finally {
            if (snmp != null) {
                try {
                    snmp.close();
                } catch (IOException ex1) {
                    snmp = null;
                }
            }
            if (transport != null) {
                try {
                    transport.close();
                } catch (IOException ex2) {
                    transport = null;
                }
            }
        }
    }

    private void getCoreLink(Device device) {

        String address = protocol + ":" + device.getIp() + "/" + device.getSnmpPort();

        OID targetOID = new OID(oidIp);

        PDU requestPDU = new PDU();
        requestPDU.setType(PDU.GETNEXT);
        requestPDU.add(new VariableBinding(targetOID));

        CommunityTarget target = SnmpUtil.createCommunityTarget(address, device.getCommunity(), version, 1 * 1000L, 1);
        TransportMapping transport = null;
        Snmp snmp = null;
        try {
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            transport.listen();

            boolean finished = false;
            while (!finished) {
                VariableBinding vb = null;
                ResponseEvent response = snmp.send(requestPDU, target);
                PDU responsePDU = response.getResponse();

                if (null == responsePDU) {
                    logger.debug("responsePDU == null");
                    finished = true;
                    break;
                } else {
                    vb = responsePDU.get(0);
                }
                // check finish
                finished = checkWalkFinished(targetOID, responsePDU, vb);
                if (!finished) {
                    // Dump response.
                    //                    logger.debug("----" + vb.toString());//1.3.6.1.4.1.9.9.23.1.2.1.1.4.10102.1 = 0a:fa:c8:05
                    String oid = vb.getOid().toString().substring(oidIp.length() + 1);
                    String key = oid.substring(0, oid.lastIndexOf('.'));

                    String ifName = snmpGetIfName(snmp, target, key);
                    //                    logger.debug("----" + key);
                    String vs[] = vb.getVariable().toString().split(":");
                    String ipString = "";
                    for (String v : vs) {
                        ipString += "." + Integer.parseInt(v, 16);
                    }
                    ipString = ipString.substring(1);
                    logger.debug(ipString + "=" + ifName);
                    Device d = new Device();
                    d.setIp(ipString);
                    d.setCommunity(device.getCommunity());
                    device.getLink().put(ifName, d);

                    // Set up the variable binding for the next entry.
                    requestPDU.setRequestID(new Integer32(0));
                    requestPDU.set(0, vb);
                }
            }
            //            logger.debug("success finish snmp walk!");
        } catch (Exception e) {
            //            logger.debug("SNMP walk Exception: " + e);
            e.printStackTrace();
        } finally {
            if (snmp != null) {
                try {
                    snmp.close();
                } catch (IOException ex1) {
                    snmp = null;
                }
            }
            if (transport != null) {
                try {
                    transport.close();
                } catch (IOException ex2) {
                    transport = null;
                }
            }
        }

    }

    @SuppressWarnings("unchecked")
    private String snmpGetIfName(Snmp snmp, CommunityTarget target, String key) {
        try {
            PDU pdu = new PDU();
            // pdu.add(new VariableBinding(new OID(new int[]
            // {1,3,6,1,2,1,1,2})));
            String oid = oidIfDesc + "." + key;
            pdu.add(new VariableBinding(new OID(oid)));
            pdu.setType(PDU.GET);
            // 发送同步消息
            ResponseEvent response = snmp.send(pdu, target);
            //            logger.debug("PeerAddress:" + response.getPeerAddress());
            PDU responsePdu = response.getResponse();

            if (responsePdu == null) {
                //                logger.debug("Request time out");
                return null;
            } else {
                Vector vbVect = responsePdu.getVariableBindings();
                //                logger.debug("vb size:" + vbVect.size());
                if (vbVect.size() == 0) {
                    //logger.debug(" pdu vb size is 0 ");
                    return null;
                } else {
                    Object obj = vbVect.firstElement();
                    VariableBinding vb = (VariableBinding) obj;
                    //                    logger.debug(vb.getOid() + " = " + vb.getVariable());
                    return vb.getVariable().toString();
                }

            }
            //            logger.debug("success finish snmp get the oid!");
        } catch (Exception e) {
            logger.debug("SNMP Get Exception:" + e);
        }
        return null;
    }

    private void snmpWalk(String ip, String community, String targetOid) {
        String address = protocol + ":" + ip + "/" + port;

        OID targetOID = new OID(targetOid);

        PDU requestPDU = new PDU();
        requestPDU.setType(PDU.GETNEXT);
        requestPDU.add(new VariableBinding(targetOID));

        CommunityTarget target = SnmpUtil.createCommunityTarget(address, community, version, 2 * 1000L, 3);
        TransportMapping transport = null;
        Snmp snmp = null;
        try {
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            transport.listen();

            boolean finished = false;
            while (!finished) {
                VariableBinding vb = null;
                ResponseEvent response = snmp.send(requestPDU, target);
                PDU responsePDU = response.getResponse();

                if (null == responsePDU) {
                    logger.debug("responsePDU == null");
                    finished = true;
                    break;
                } else {
                    vb = responsePDU.get(0);
                }
                // check finish
                finished = checkWalkFinished(targetOID, responsePDU, vb);
                if (!finished) {
                    // Dump response.
                    logger.debug("----" + vb.toString());
                    logger.debug("----" + vb.getOid().toString());
                    logger.debug("----" + vb.getVariable().toString());

                    // Set up the variable binding for the next entry.
                    requestPDU.setRequestID(new Integer32(0));
                    requestPDU.set(0, vb);
                }
            }
            logger.debug("success finish snmp walk!");
        } catch (Exception e) {
            logger.debug("SNMP walk Exception: " + e);
        } finally {
            if (snmp != null) {
                try {
                    snmp.close();
                } catch (IOException ex1) {
                    snmp = null;
                }
            }
            if (transport != null) {
                try {
                    transport.close();
                } catch (IOException ex2) {
                    transport = null;
                }
            }
        }

    }

    private String ipToHexString(String ip) {
        String[] vs = ip.split("\\.");
        String s = "";
        for (String v : vs) {
            s += " " + Integer.toHexString(Integer.valueOf(v));

        }
        s = s.substring(1);
        logger.debug(s);
        return s;
    }

    /**
     * check snmp walk finish
     *
     * @param resquestPDU
     * @param targetOID
     * @param responsePDU
     * @param vb
     * @return
     */
    private boolean checkWalkFinished(OID targetOID, PDU responsePDU, VariableBinding vb) {
        boolean finished = false;
        if (responsePDU.getErrorStatus() != 0) {
            //            logger.debug("responsePDU.getErrorStatus() != 0 ");
            //            logger.debug(responsePDU.getErrorStatusText());
            finished = true;
        } else if (vb.getOid() == null) {
            //            logger.debug("vb.getOid() == null");
            finished = true;
        } else if (vb.getOid().size() < targetOID.size()) {
            //            logger.debug("vb.getOid().size() <targetOID.size()");
            finished = true;
        } else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
            //            logger.debug("targetOID.leftMostCompare() != 0");
            finished = true;
        } else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
            //            logger.debug("Null.isExceptionSyntax(vb.getVariable().getSyntax())");
            finished = true;
        } else if (vb.getOid().compareTo(targetOID) <= 0) {
            //            logger.debug("Variable received is not " + "lexicographic successor of requested " + "one:");
            //            logger.debug(vb.toString() + " <= " + targetOID);
            finished = true;
        }
        return finished;
    }

}
