"""Tracks the latency of a host by sending ICMP echo requests (ping)."""
from datetime import timedelta
import logging
import re
import subprocess
import sys

import voluptuous as vol

from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
from homeassistant.const import CONF_HOST, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.util.process import kill_subprocess

from .const import PING_TIMEOUT

_LOGGER = logging.getLogger(__name__)


ATTR_ROUND_TRIP_TIME_AVG = "round_trip_time_avg"
ATTR_ROUND_TRIP_TIME_MAX = "round_trip_time_max"
ATTR_ROUND_TRIP_TIME_MDEV = "round_trip_time_mdev"
ATTR_ROUND_TRIP_TIME_MIN = "round_trip_time_min"

CONF_PING_COUNT = "count"

DEFAULT_NAME = "Ping"
DEFAULT_PING_COUNT = 5
DEFAULT_DEVICE_CLASS = "connectivity"

SCAN_INTERVAL = timedelta(minutes=5)

PARALLEL_UPDATES = 0

PING_MATCHER = re.compile(
    r"(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)\/(?P<mdev>\d+.\d+)"
)

PING_MATCHER_BUSYBOX = re.compile(
    r"(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)"
)

WIN32_PING_MATCHER = re.compile(r"(?P<min>\d+)ms.+(?P<max>\d+)ms.+(?P<avg>\d+)ms")

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_NAME): cv.string,
        vol.Optional(CONF_PING_COUNT, default=DEFAULT_PING_COUNT): vol.Range(
            min=1, max=100
        ),
    }
)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Ping Binary sensor."""
    host = config[CONF_HOST]
    count = config[CONF_PING_COUNT]
    name = config.get(CONF_NAME, f"{DEFAULT_NAME} {host}")

    add_entities([PingBinarySensor(name, PingData(host, count))], True)


class PingBinarySensor(BinarySensorEntity):
    """Representation of a Ping Binary sensor."""

    def __init__(self, name, ping):
        """Initialize the Ping Binary sensor."""
        self._name = name
        self.ping = ping

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def device_class(self):
        """Return the class of this sensor."""
        return DEFAULT_DEVICE_CLASS

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self.ping.available

    @property
    def device_state_attributes(self):
        """Return the state attributes of the ICMP checo request."""
        if self.ping.data is not False:
            return {
                ATTR_ROUND_TRIP_TIME_AVG: self.ping.data["avg"],
                ATTR_ROUND_TRIP_TIME_MAX: self.ping.data["max"],
                ATTR_ROUND_TRIP_TIME_MDEV: self.ping.data["mdev"],
                ATTR_ROUND_TRIP_TIME_MIN: self.ping.data["min"],
            }

    def update(self):
        """Get the latest data."""
        self.ping.update()


class PingData:
    """The Class for handling the data retrieval."""

    def __init__(self, host, count):
        """Initialize the data object."""
        self._ip_address = host
        self._count = count
        self.data = {}
        self.available = False

        if sys.platform == "win32":
            self._ping_cmd = [
                "ping",
                "-n",
                str(self._count),
                "-w",
                "1000",
                self._ip_address,
            ]
        else:
            self._ping_cmd = [
                "ping",
                "-n",
                "-q",
                "-c",
                str(self._count),
                "-W1",
                self._ip_address,
            ]

    def ping(self):
        """Send ICMP echo request and return details if success."""
        pinger = subprocess.Popen(
            self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        try:
            out = pinger.communicate(timeout=self._count + PING_TIMEOUT)
            _LOGGER.debug("Output is %s", str(out))
            if sys.platform == "win32":
                match = WIN32_PING_MATCHER.search(str(out).split("\n")[-1])
                rtt_min, rtt_avg, rtt_max = match.groups()
                return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": ""}
            if "max/" not in str(out):
                match = PING_MATCHER_BUSYBOX.search(str(out).split("\n")[-1])
                rtt_min, rtt_avg, rtt_max = match.groups()
                return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": ""}
            match = PING_MATCHER.search(str(out).split("\n")[-1])
            rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
            return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev}
        except subprocess.TimeoutExpired:
            kill_subprocess(pinger)
            return False
        except (subprocess.CalledProcessError, AttributeError):
            return False

    def update(self):
        """Retrieve the latest details from the host."""
        self.data = self.ping()
        self.available = bool(self.data)
