# LiMEaide
# Copyright (c) 2011-2018 Daryl Bennett

# Author:
# Daryl Bennett - kd8bny@gmail.com

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

import logging
import selectors
import socket
import struct
import sys
import time
import threading

from termcolor import colored
from queue import Queue


class TCP_CLIENT(threading.Thread):
    """Non-blocking Client to transfer files from server"""

    def __init__(self, qresult, ip, port, output):
        super(TCP_CLIENT, self).__init__()
        self.logger = logging.getLogger(__name__)
        self.qresult = qresult
        self.ip = ip
        self.port = port
        self.output = output

        self.result = {'success': True, 'terminal': False}
        self.byte_count = 0

    def __transfer_status__(self, bytes_len):
        """Provide status of the files being transfered.

        :param Length of bytes received """

        self.byte_count += bytes_len
        mbytes_so_far = int(self.byte_count / float(1 << 20))
        print(colored(
            "Transfer of {0} is at {1:d} MiB".format(
                self.output, mbytes_so_far), 'cyan'), end='\r', flush=True)

    def __write_out__(self, data):
        """Write data to file by appending

        :param data to append"""

        try:
            with open(self.output, 'ab') as f:
                f.write(data)

        except Exception as e:
            self.logger.error("Unable to save output: {}".format(e))
            self.result['success'] = False
            self.result['terminal'] = True
            sys.exit(colored("Unable to save output", 'red'))

    def __handle_client__(self, key, mask, sel):
        """Connect to client using a selector

        :return Should client retry to connect
        """

        retry = True
        sock = key.fileobj

        if mask & selectors.EVENT_READ:
            try:
                recv_data = sock.recv(1024)

                if recv_data:
                    self.__transfer_status__(len(recv_data))
                    self.__write_out__(recv_data)
                else:
                    self.logger.info("File saved")
                    print("\n")
                    retry = False

            except socket.error as e:
                self.logger.error(e)
                self.result['success'] = False
                retry = False

        return retry

    def run(self):
        retry = True

        self.logger.info("Connecting to Socket")
        sel = selectors.DefaultSelector()
        conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        conn.setsockopt(
            socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
        conn.setblocking(False)

        conn.connect_ex((self.ip, self.port))
        sel.register(conn, selectors.EVENT_READ, data=None)

        while retry:
            events = sel.select()
            for key, mask in events:
                retry = self.__handle_client__(key, mask, sel)

        sel.unregister(conn)
        if self.result['success']:
            conn.shutdown(socket.SHUT_RDWR)

        self.qresult.put(self.result)
        self.logger.info("Socket Closed")


class CONNECTION_MANAGER(threading.Thread):
    """Handle all client connections and attempts"""

    def __init__(self, q, e):
        super(CONNECTION_MANAGER, self).__init__()
        self.logger = logging.getLogger(__name__)
        self.event_kill = e
        self.queue = q

        self.qstatus = Queue()
        self.retry_count = 0

    def __start_client__(self, conn):
        """Start a new client thread"""

        client = TCP_CLIENT(self.qstatus, conn[0], conn[1], conn[2])
        client.start()
        client.join()

    def run(self):
        while not self.event_kill.is_set():
            time.sleep(3)
            if self.queue.empty():
                continue
            else:
                connection = self.queue.get()
                self.__start_client__(connection)
                status = self.qstatus.get()

                if not status['success']:
                    if status['terminal']:
                        self.logger.warning(
                            "Failed connection, closing down")
                        self.event_kill.set()

                    elif self.retry_count > 4:
                        self.logger.warning(
                            "Retry count exceeded, closing down")
                        self.event_kill.set()

                    else:
                        self.retry_count += 1
                        self.logger.warning(
                            "connection failed, adding back to queue")
                        self.queue.put(connection)

        self.logger.info("Connection manager is finished")