import socket
import sys
from abc import ABCMeta
from logging import Logger
from typing import Dict, Optional, List, Union

from nperipherals_core.model.custom_exceptions import DeviceNotConnected
from nperipherals_core.model.device import Device


class NetworkDevice(Device):
    __metaclass__ = ABCMeta

    def __init__(self, logger, device_configurations):
        # type: (Logger, Dict[unicode, unicode]) -> None

        self.logger = logger
        self.names = device_configurations["Name"]  # type: Union[List[str], str]
        self.host = device_configurations["Host"]  # type: unicode
        self.port = int(device_configurations["Port"])  # type: int

        self.socket = None  # type: Optional[socket.socket]
        self.SOCKET_BUF_SIZE = 1024

    def get_name(self):
        # type: () -> unicode
        if isinstance(self.names, list):
            return ','.join(self.names)
        return self.names

    def initialize(self):
        # type: () -> None

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._configure_socket()

    def connect(self):
        # type: () -> None

        try:
            self.socket.connect((self.host, self.port))
        except Exception as e:
            self.logger.exception("Error getting socket connection: [{}]".format(repr(e)))
            raise DeviceNotConnected()

    def disconnect(self):
        # type: () -> None

        try:
            self.socket.close()
        except Exception as _:
            self.logger.exception("Error closing socket connection")

    def _configure_socket(self):
        # type: () -> None

        keep_alive_on = 1
        idle_time_before_keep_alive_seconds = 1
        interval_between_keep_alive_packets_seconds = 3
        keep_alive_max_retries = 5

        if sys.platform == "win32":
            self._configure_windows_socket_keepalive(idle_time_before_keep_alive_seconds,
                                                     interval_between_keep_alive_packets_seconds,
                                                     keep_alive_on)
        else:
            self._configure_linux_socket_keepalive(idle_time_before_keep_alive_seconds,
                                                   interval_between_keep_alive_packets_seconds,
                                                   keep_alive_max_retries,
                                                   keep_alive_on)

    def _configure_linux_socket_keepalive(self, idle_time_before_keep_alive_seconds,
                                          interval_between_keep_alive_packets_seconds,
                                          keep_alive_max_retries,
                                          keep_alive_on):
        # type: (int, int, int, int) -> None

        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, keep_alive_on)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, idle_time_before_keep_alive_seconds)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_between_keep_alive_packets_seconds)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, keep_alive_max_retries)

    def _configure_windows_socket_keepalive(self, idle_time_before_keep_alive_seconds,
                                            interval_between_keep_alive_packets_seconds,
                                            keep_alive_on):
        # type: (int, int, int) -> None

        self.socket.ioctl(socket.SIO_KEEPALIVE_VALS, (keep_alive_on,
                                                      1000 * idle_time_before_keep_alive_seconds,
                                                      1000 * interval_between_keep_alive_packets_seconds))
