"""
Collects network metrics.
"""
# stdlib
import re
import subprocess

# project
from checks import AgentCheck
from utils.platform import Platform

BSD_TCP_METRICS = [
    (re.compile("^\s*(\d+) data packets \(\d+ bytes\) retransmitted\s*$"), 'system.net.tcp.retrans_packs'),
    (re.compile("^\s*(\d+) packets sent\s*$"), 'system.net.tcp.sent_packs'),
    (re.compile("^\s*(\d+) packets received\s*$"), 'system.net.tcp.rcv_packs')
]

SOLARIS_TCP_METRICS = [
    (re.compile("\s*tcpRetransSegs\s*=\s*(\d+)\s*"), 'system.net.tcp.retrans_segs'),
    (re.compile("\s*tcpOutDataSegs\s*=\s*(\d+)\s*"), 'system.net.tcp.in_segs'),
    (re.compile("\s*tcpInSegs\s*=\s*(\d+)\s*"), 'system.net.tcp.out_segs')
]


class Network(AgentCheck):

    SOURCE_TYPE_NAME = 'system'

    TCP_STATES = {
        "ESTABLISHED": "established",
        "SYN_SENT": "opening",
        "SYN_RECV": "opening",
        "FIN_WAIT1": "closing",
        "FIN_WAIT2": "closing",
        "TIME_WAIT": "time_wait",
        "CLOSE": "closing",
        "CLOSE_WAIT": "closing",
        "LAST_ACK": "closing",
        "LISTEN": "listening",
        "CLOSING": "closing",
    }

    NETSTAT_GAUGE = {
        ('udp4', 'connections') : 'system.net.udp4.connections',
        ('udp6', 'connections') : 'system.net.udp6.connections',
        ('tcp4', 'established') : 'system.net.tcp4.established',
        ('tcp4', 'opening') : 'system.net.tcp4.opening',
        ('tcp4', 'closing') : 'system.net.tcp4.closing',
        ('tcp4', 'listening') : 'system.net.tcp4.listening',
        ('tcp4', 'time_wait') : 'system.net.tcp4.time_wait',
        ('tcp6', 'established') : 'system.net.tcp6.established',
        ('tcp6', 'opening') : 'system.net.tcp6.opening',
        ('tcp6', 'closing') : 'system.net.tcp6.closing',
        ('tcp6', 'listening') : 'system.net.tcp6.listening',
        ('tcp6', 'time_wait') : 'system.net.tcp6.time_wait',
    }

    def __init__(self, name, init_config, agentConfig, instances=None):
        AgentCheck.__init__(self, name, init_config, agentConfig, instances=instances)
        if instances is not None and len(instances) > 1:
            raise Exception("Network check only supports one configured instance.")

    def check(self, instance):
        if instance is None:
            instance = {}

        self._excluded_ifaces = instance.get('excluded_interfaces', [])
        self._collect_cx_state = instance.get('collect_connection_state', False)

        self._exclude_iface_re = None
        exclude_re = instance.get('excluded_interface_re', None)
        if exclude_re:
            self.log.debug("Excluding network devices matching: %s" % exclude_re)
            self._exclude_iface_re = re.compile(exclude_re)

        if Platform.is_linux():
            self._check_linux(instance)
        elif Platform.is_bsd():
            self._check_bsd(instance)
        elif Platform.is_solaris():
            self._check_solaris(instance)

    def _submit_devicemetrics(self, iface, vals_by_metric):
        if iface in self._excluded_ifaces or (self._exclude_iface_re and self._exclude_iface_re.match(iface)):
            # Skip this network interface.
            return False

        expected_metrics = [
            'bytes_rcvd',
            'bytes_sent',
            'packets_in.count',
            'packets_in.error',
            'packets_out.count',
            'packets_out.error',
        ]
        for m in expected_metrics:
            assert m in vals_by_metric
        assert len(vals_by_metric) == len(expected_metrics)

        count = 0
        for metric, val in vals_by_metric.iteritems():
            self.rate('system.net.%s' % metric, val, device_name=iface)
            count += 1
        self.log.debug("tracked %s network metrics for interface %s" % (count, iface))

    def _parse_value(self, v):
  
      if v == "-":
            return 0
        else:
            try:
                return long(v)
            except ValueError:
                return 0

    def _submit_regexed_values(self, output, regex_list):
        lines = output.split("\n")
  
      for line in lines:
            for regex, metric in regex_list:
                value = re.match(regex, line)
                if value:
                    self.rate(metric, self._parse_value(value.group(1)))

    def _check_linux(self, instance):
        if self._collect_cx_state:
            netstat = subprocess.Popen(["netstat", "-n", "-u", "-t", "-a"],
                                       stdout=subprocess.PIPE,
                                       close_fds=True).communicate()[0]
            # Active Internet connections (w/o servers)
            # Proto Recv-Q Send-Q Local Address           Foreign Address         State
            # tcp        0      0 46.105.75.4:80          79.220.227.193:2032     SYN_RECV
            # tcp        0      0 46.105.75.4:143         90.56.111.177:56867     ESTABLISHED
            # tcp        0      0 46.105.75.4:50468       107.20.207.175:443      TIME_WAIT
            # tcp6       0      0 46.105.75.4:80          93.15.237.188:58038     FIN_WAIT2
            # tcp6       0      0 46.105.75.4:80          79.220.227.193:2029     ESTABLISHED
            # udp        0      0 0.0.0.0:123             0.0.0.0:*
            # udp6       0      0 :::41458                :::*

            lines = netstat.split("\n")

            metrics = dict.fromkeys(self.NETSTAT_GAUGE.values(), 0)
            for l in lines[2:-1]:
                cols = l.split()
                # 0          1      2               3                           4               5
                # tcp        0      0 46.105.75.4:143         90.56.111.177:56867     ESTABLISHED
                if cols[0].startswith("tcp"):
                    protocol = ("tcp4", "tcp6")[cols[0] == "tcp6"]
                    if cols[5] in self.TCP_STATES:
                        metric = self.NETSTAT_GAUGE[protocol, self.TCP_STATES[cols[5]]]
                        metrics[metric] += 1
                elif cols[0].startswith("udp"):
                    protocol = ("udp4", "udp6")[cols[0] == "udp6"]
                    metric = self.NETSTAT_GAUGE[protocol, 'connections']
                    metrics[metric] += 1

            for metric, value in metrics.iteritems():
                self.gauge(metric, value)


        proc = open('/proc/net/dev', 'r')
        try:
            lines = proc.readlines()
        finally:
            proc.close()
        # Inter-|   Receive                                                 |  Transmit
        #  face |bytes     packets errs drop fifo frame compressed multicast|bytes       packets errs drop fifo colls carrier compressed
        #     lo:45890956   112797   0    0    0     0          0         0    45890956   112797    0    0    0     0       0          0
        #   eth0:631947052 1042233   0   19    0   184          0      1206  1208625538  1320529    0    0    0     0       0          0
        #   eth1:       0        0   0    0    0     0          0         0           0        0    0    0    0     0       0          0
        for l in lines[2:]:
            cols = l.split(':', 1)
            x = cols[1].split()
            # Filter inactive interfaces
            if self._parse_value(x[0]) or self._parse_value(x[8]):
                iface = cols[0].strip()
                metrics = {
                    'bytes_rcvd': self._parse_value(x[0]),
                    'bytes_sent': self._parse_value(x[8]),
                    'packets_in.count': self._parse_value(x[1]),
                    'packets_in.error': self._parse_value(x[2]) + self._parse_value(x[3]),
                    'packets_out.count': self._parse_value(x[9]),
                    'packets_out.error':self._parse_value(x[10]) + self._parse_value(x[11]),
                }
                self._submit_devicemetrics(iface, metrics)

        try:
            proc = open('/proc/net/snmp', 'r')

            # IP:      Forwarding   DefaultTTL InReceives     InHdrErrors  ...
            # IP:      2            64         377145470      0            ...
            # Icmp:    InMsgs       InErrors   InDestUnreachs InTimeExcds  ...
            # Icmp: 