import logging
import serial_connection
import socket
try:
    import telnetlib
except:
    telnetlib = None
import threading
import time

import config


class TelnetConnection(serial_connection.SerialConnection):
    CONNECTION_TYPE = 'telnet'
    DEFAULT_TIMEOUT = 1
    FAIL_WAIT = 1
    RETRIES = 3
    ENDOFLINE = b'\n'

    def __init__(self, ip, port, timeout=None, logger=None):
        if logger:
            self.logger = logger.getChild(self.__class__.__name__)
        else:
            self.logger = logging.getLogger(self.__class__.__name__)
        self.ip = ip
        self.port = port
        if timeout is None:
            self.timeout = self.DEFAULT_TIMEOUT
        else:
            self.timeout = timeout
        self.recv_lock = threading.RLock()
        self.send_lock = threading.RLock()
        self.verbose = config.get_settings()['verbose']
        self.connection = None
        self.connect()

    def connect(self):
        if not telnetlib:
            raise ImportError('Telnet connection is no longer supported')
        try:
            self.connection = telnetlib.Telnet(self.ip, self.port, timeout=self.timeout)
        except NameError:
            raise ImportError('Telnet connection is no longer supported')
        except OSError as e:
            self.logger.error("Error connecting to %s:%s - %s" % (self.ip, self.port, e))

    def recv(self, timeout=None):
        if not timeout:
            timeout = self.timeout
        fails = 0
        while fails < self.RETRIES:
            with self.recv_lock:
                if not self.connection:
                    self.logger.warning("Can't send - no connection to host")
                    self.connect()
                data = None
                try:
                    if self.connection:
                        data = self._read_from_connection(timeout=timeout)
                    else:
                        raise OSError('No connection')
                    if self.verbose:
                        self.logger.info("RECV: %s" % data)
                except socket.timeout:
                    self.logger.warning("Timeout while reading %s from %s:%s" % (self.CONNECTION_TYPE, self.ip, self.port))
                    return data
                except (OSError, EOFError) as e:
                    self.logger.warning("Can't read %s from %s:%s. Error:%s" %
                                        (self.CONNECTION_TYPE, self.ip, self.port, e))
                except Exception as e:
                    self.logger.warning("Unexpected error while reading from %s %s:%s" %
                                        (self.CONNECTION_TYPE, self.ip, self.port))
                else:
                    return data
                fails += 1
                time.sleep(self.FAIL_WAIT)
                self.close()

    def send(self, data, raw=False):
        if not raw:
            data = self.prepare_data(data)
        try:
            with self.send_lock:
                if self.connection:
                    self._write_to_connection(data)
                    if self.verbose:
                        self.logger.info("SEND: %s" % data.strip())
        except OSError as e:
            self.logger.warning("Can't write to %s on %s:%s. Error:%s" %
                                (self.CONNECTION_TYPE, self.ip, self.port, str(e)))
        except Exception as e:
            self.logger.warning("Unexpected error while writing to %s %s:%s" %
                                (self.CONNECTION_TYPE, self.ip, self.port))
        else:
            return True

    def reset(self):
        with self.send_lock:
            with self.recv_lock:
                self.connection.close()

    def close(self):
        self.logger.info("Closing %s connection %s" % (self.CONNECTION_TYPE, self.ip))
        with self.recv_lock:
            if self.connection:
                self.connection.close()
                self.connection = None

    def _read_from_connection(self, timeout):
        return self.connection.read_until(self.ENDOFLINE, timeout=timeout)

    def _write_to_connection(self, data):
        self.connection.write(data)

