#!/usr/bin/env python
""" UDP sender class """

import socket
import logging
import urlparse

import packet
from logger import define_logger
from execute import execute, ExecuteError


class SenderException(Exception):
    """ Exceptions in Sender class """
    pass


class Timeout(Exception):
    """ Exceptions in Sender class """
    pass


class Sender(object):
    """ UDP sender class """

    def __init__(self, url=None, port=None, host="127.0.0.1", size=256):
        """ Create connection object from input udp string or params"""

        logger = logging.getLogger(__name__)
        # test input
        if url is None and port is None:
            raise SenderException("Bad initialization")
        if url is not None:
            data = urlparse.urlparse(url)
            # check schema
            if data.scheme != "udp":
                mes = "Bad protocol type: %s instead of UDP" % data.scheme
                logger.error(mes)
                raise SenderException("Bad protocol type")
            # try to get port
            try:
                int_port = int(data.port)
            except ValueError:
                logger.error("Bad UDP port")
                raise SenderException("Bad UDP port")

            # save paths
            self.sendto = (data.hostname, int_port)
            self.bindto = (data.hostname, int_port)
            # try to get size
            try:
                self.size = int(data.path.strip("/"))
            except ValueError:
                logger.error("Bad packet part size")
                raise SenderException("Bad packet part size")
        else:
            # url is None - use size and port
            self.sendto = (host, port)
            self.bindto = ("0.0.0.0", port)
            self.size = size

        # create access
        final_port = self.sendto[1]
        answer_port = final_port+1
        access = "iptables -I INPUT -p udp -m multiport --dports {0},{1} -j ACCEPT".format(final_port, answer_port)
        try:
            execute(access)
        except Exception:
            logger.warning("May be, port will be unavailable - can't create rule")
        self.clear_access = "iptables -D INPUT -p udp -m multiport --dports {0},{1} -j ACCEPT".format(final_port, answer_port)

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.binded = False
        self.all_data = {}
        self.send_packer = None

    def __del__(self):
        execute(self.clear_access)

    def bind(self):
        """ Prepare for listening """
        self.sock.bind(self.bindto)
        self.sock.settimeout(0.5)
        self.binded = True


    def send(self, data):
        """ Send data to udp socket"""
        if self.sock.sendto(data, self.sendto) != len(data):
            logger = logging.getLogger(__name__)
            mes = "Cannot send data to %s:%s" % self.sendto
            logger.error(mes)
            raise SenderException("Cannot send data")


    def send_by_protocol(self, data):
        """ Send data by Packet protocol
            data = dict"""
        if self.send_packer is None:
            self.send_packer = packet.Packet()
        parts = self.send_packer.create_packet_v2(data, self.size)
        for part in parts:
            self.send(part)


    def recv(self):
        """ Receive data from udp socket"""
        # check for binding
        if not self.binded:
            self.bind()
        # try to recv
        try:
            data, (remote_ip, remote_port) = self.sock.recvfrom(self.size)
            return data, remote_ip
        except socket.timeout:
            raise Timeout()


    def recv_by_protocol(self):
        """ Receive data from udp socket by Packet protocol"""
        data, remote_ip = self.recv()

        if remote_ip not in self.all_data:
            self.all_data[remote_ip] = packet.Packet()

        return self.all_data[remote_ip].new_packet(data)


    def recv_with_answer(self, stop_event=None):
        """ Receive data from udp socket and send 'ok' back
            Command port = local port + 1
            Answer port = local port
            Waiting for command is blocking """
        # create command socket
        command_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        command_port = self.bindto[1]+1
        command_sock.bind(("0.0.0.0", command_port))
        command_sock.settimeout(1)
        # try to recv
        while True:
            try:
                data, (remote_ip, remote_port) = command_sock.recvfrom(self.size)
                self.send("ok")
                return data, remote_ip
            except socket.timeout:
                if stop_event is not None and stop_event.is_set():
                    # return None if we are interrupted
                    return None


    def verified_send(self, send_host, message, max_repeat=20):
        """ Send and verify it by answer not more then max_repeat
            Send port = local port + 1
            Answer port = local port
            Return True if send is verified """
        # create send socket
        send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        send_port = self.sendto[1]+1
        logger = logging.getLogger(__name__)
        for repeat in range(0, max_repeat):
            send_sock.sendto(message, (send_host, send_port))
            try:
                data, remote_ip = self.recv()
                if remote_ip == send_host and data == "ok":
                    return True
                else:
                    logger.warning("No answer from %s, try %i", send_host, repeat)
            except Timeout:
                logger.warning("No answer from %s, try %i", send_host, repeat)

        return False


define_logger(__name__)
