from file_transfers.file_transfers import *
from messenger.messages import *
from history.database import MESSAGE_AUTHOR
from ui.contact_items import *
from PyQt5 import QtWidgets
import utils.util as util


class FileTransfersHandler:

    def __init__(self, tox, settings, contact_provider):
        self._tox = tox
        self._settings = settings
        self._contact_provider = contact_provider
        self._file_transfers = {}
        # key = (friend number, file number), value - transfer instance
        self._paused_file_transfers = dict(settings['paused_file_transfers'])
        # key - file id, value: [path, friend number, is incoming, start position]
        
    def __del__(self):
        self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
        self._settings.save()

    # -----------------------------------------------------------------------------------------------------------------
    # File transfers support
    # -----------------------------------------------------------------------------------------------------------------

    def incoming_file_transfer(self, friend_number, file_number, size, file_name):
        """
        New transfer
        :param friend_number: number of friend who sent file
        :param file_number: file number
        :param size: file size in bytes
        :param file_name: file name without path
        """
        friend = self._get_friend_by_number(friend_number)
        auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
        inline = is_inline(file_name) and self._settings['allow_inline']
        file_id = self._tox.file_get_file_id(friend_number, file_number)
        accepted = True
        if file_id in self._paused_file_transfers:
            data = self._paused_file_transfers[file_id]
            pos = data[-1] if os.path.exists(data[0]) else 0
            if pos >= size:
                self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
                return
            self._tox.file_seek(friend_number, file_number, pos)
            self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos)
            tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'],
                                 time.time(),
                                 TOX_FILE_TRANSFER_STATE['RUNNING'],
                                 size,
                                 file_name,
                                 friend_number,
                                 file_number)
        elif inline and size < 1024 * 1024:
            self.accept_transfer(None, '', friend_number, file_number, size, True)
            tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'],
                                 time.time(),
                                 TOX_FILE_TRANSFER_STATE['RUNNING'],
                                 size,
                                 file_name,
                                 friend_number,
                                 file_number)

        elif auto:
            path = self._settings['auto_accept_path'] or util.curr_directory()
            self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size)
            tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'],
                                 time.time(),
                                 TOX_FILE_TRANSFER_STATE['RUNNING'],
                                 size,
                                 file_name,
                                 friend_number,
                                 file_number)
        else:
            tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'],
                                 time.time(),
                                 TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
                                 size,
                                 file_name,
                                 friend_number,
                                 file_number)
            accepted = False
        if friend_number == self.get_active_number() and self.is_active_a_friend():
            item = self.create_file_transfer_item(tm)
            if accepted:
                self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state)
            self._messages.scrollToBottom()
        else:
            friend.actions = True

        friend.append_message(tm)

    def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
        """
        Stop transfer
        :param friend_number: number of friend
        :param file_number: file number
        :param already_cancelled: was cancelled by friend
        """
        i = self._get_friend_by_number(friend_number).update_transfer_data(file_number,
                                                                           TOX_FILE_TRANSFER_STATE['CANCELLED'])
        if (friend_number, file_number) in self._file_transfers:
            tr = self._file_transfers[(friend_number, file_number)]
            if not already_cancelled:
                tr.cancel()
            else:
                tr.cancelled()
            if (friend_number, file_number) in self._file_transfers:
                del tr
                del self._file_transfers[(friend_number, file_number)]
        else:
            if not already_cancelled:
                self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
            if friend_number == self.get_active_number() and self.is_active_a_friend():
                tmp = self._messages.count() + i
                if tmp >= 0:
                    self._messages.itemWidget(
                        self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'],
                                                                        0, -1)

    def cancel_not_started_transfer(self, cancel_time):
        self.get_curr_friend().delete_one_unsent_file(cancel_time)
        self.update()

    def pause_transfer(self, friend_number, file_number, by_friend=False):
        """
        Pause transfer with specified data
        """
        tr = self._file_transfers[(friend_number, file_number)]
        tr.pause(by_friend)
        t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
        self._get_friend_by_number(friend_number).update_transfer_data(file_number, t)

    def resume_transfer(self, friend_number, file_number, by_friend=False):
        """
        Resume transfer with specified data
        """
        # self.get_friend_by_number(friend_number).update_transfer_data(file_number,
        #                                                               TOX_FILE_TRANSFER_STATE['RUNNING'])
        tr = self._file_transfers[(friend_number, file_number)]
        if by_friend:
            tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
            tr.signal()
        else:
            tr.send_control(TOX_FILE_CONTROL['RESUME'])

    def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0):
        """
        :param item: transfer item.
        :param path: path for saving
        :param friend_number: friend number
        :param file_number: file number
        :param size: file size
        :param inline: is inline image
        :param from_position: position for start
        """
        path, file_name = os.path.split(path)
        new_file_name, i = file_name, 1
        if not from_position:
            while os.path.isfile(path + '/' + new_file_name):  # file with same name already exists
                if '.' in file_name:  # has extension
                    d = file_name.rindex('.')
                else:  # no extension
                    d = len(file_name)
                new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
                i += 1
        path = os.path.join(path, new_file_name)
        if not inline:
            rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
        else:
            rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
        rt.set_transfer_finished_handler(self.transfer_finished)
        self._file_transfers[(friend_number, file_number)] = rt
        self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
        if item is not None:
            rt.set_state_changed_handler(item.update_transfer_state)
        self._get_friend_by_number(friend_number).update_transfer_data(file_number,
                                                                      TOX_FILE_TRANSFER_STATE['RUNNING'])

    def send_screenshot(self, data, friend_number):
        """
        Send screenshot to current active friend
        :param data: raw data - png
        """
        self.send_inline(data, 'toxygen_inline.png', friend_number)

    def send_sticker(self, path, friend_number):
        with open(path, 'rb') as fl:
            data = fl.read()
        self.send_inline(data, 'sticker.png', friend_number)

    def send_inline(self, data, file_name, friend_number, is_resend=False):
        friend = self._get_friend_by_number(friend_number)
        if friend.status is None and not is_resend:
            m = UnsentFile(file_name, data, time.time())
            friend.append_message(m)
            return
        elif friend.status is None and is_resend:
            raise RuntimeError()
        st = SendFromBuffer(self._tox, friend.number, data, file_name)
        st.set_transfer_finished_handler(self.transfer_finished)
        self._file_transfers[(friend.number, st.get_file_number())] = st
        tm = TransferMessage(MESSAGE_AUTHOR['ME'],
                             time.time(),
                             TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
                             len(data),
                             file_name,
                             friend.number,
                             st.get_file_number())
        item = self.create_file_transfer_item(tm)
        friend.append_message(tm)
        st.set_state_changed_handler(item.update_transfer_state)
        self._messages.scrollToBottom()

    def send_file(self, path, number=None, is_resend=False, file_id=None):
        """
        Send file to current active friend
        :param path: file path
        :param number: friend_number
        :param is_resend: is 'offline' message
        :param file_id: file id of transfer
        """
        friend_number = self.get_active_number() if number is None else number
        friend = self._get_friend_by_number(friend_number)
        if friend.status is None and not is_resend:
            m = UnsentFile(path, None, time.time())
            friend.append_message(m)
            self.update()
            return
        elif friend.status is None and is_resend:
            print('Error in sending')
            raise RuntimeError()
        st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
        st.set_transfer_finished_handler(self.transfer_finished)
        self._file_transfers[(friend_number, st.get_file_number())] = st
        tm = TransferMessage(MESSAGE_AUTHOR['ME'],
                             time.time(),
                             TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
                             os.path.getsize(path),
                             os.path.basename(path),
                             friend_number,
                             st.get_file_number())
        if friend_number == self.get_active_number():
            item = self.create_file_transfer_item(tm)
            st.set_state_changed_handler(item.update_transfer_state)
            self._messages.scrollToBottom()
        self._contacts[friend_number].append_message(tm)

    def incoming_chunk(self, friend_number, file_number, position, data):
        """
        Incoming chunk
        """
        self._file_transfers[(friend_number, file_number)].write_chunk(position, data)

    def outgoing_chunk(self, friend_number, file_number, position, size):
        """
        Outgoing chunk
        """
        self._file_transfers[(friend_number, file_number)].send_chunk(position, size)

    def transfer_finished(self, friend_number, file_number):
        transfer = self._file_transfers[(friend_number, file_number)]
        t = type(transfer)
        if t is ReceiveAvatar:
            self._get_friend_by_number(friend_number).load_avatar()
            if friend_number == self.get_active_number() and self.is_active_a_friend():
                self.set_active(None)
        elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']):  # inline image
            print('inline')
            inline = InlineImage(transfer.get_data())
            i = self._get_friend_by_number(friend_number).update_transfer_data(file_number,
                                                                              TOX_FILE_TRANSFER_STATE['FINISHED'],
                                                                              inline)
            if friend_number == self.get_active_number() and self.is_active_a_friend():
                count = self._messages.count()
                if count + i + 1 >= 0:
                    elem = QtWidgets.QListWidgetItem()
                    item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
                    elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
                    self._messages.insertItem(count + i + 1, elem)
                    self._messages.setItemWidget(elem, item)
                    self._messages.scrollToBottom()
        elif t is not SendAvatar:
            self._get_friend_by_number(friend_number).update_transfer_data(file_number,
                                                                          TOX_FILE_TRANSFER_STATE['FINISHED'])
        del self._file_transfers[(friend_number, file_number)]
        del transfer

    def send_files(self, friend_number):
        friend = self._get_friend_by_number(friend_number)
        friend.remove_invalid_unsent_files()
        files = friend.get_unsent_files()
        try:
            for fl in files:
                data = fl.get_data()
                if data[1] is not None:
                    self.send_inline(data[1], data[0], friend_number, True)
                else:
                    self.send_file(data[0], friend_number, True)
            friend.clear_unsent_files()
            for key in list(self._paused_file_transfers.keys()):
                data = self._paused_file_transfers[key]
                if not os.path.exists(data[0]):
                    del self._paused_file_transfers[key]
                elif data[1] == friend_number and not data[2]:
                    self.send_file(data[0], friend_number, True, key)
                    del self._paused_file_transfers[key]
        except Exception as ex:
            print('Exception in file sending: ' + str(ex))

    # -----------------------------------------------------------------------------------------------------------------
    # Avatars support
    # -----------------------------------------------------------------------------------------------------------------

    def send_avatar(self, friend_number, avatar_path=None):
        """
        :param friend_number: number of friend who should get new avatar
        :param avatar_path: path to avatar or None if reset
        """
        sa = SendAvatar(avatar_path, self._tox, friend_number)
        self._file_transfers[(friend_number, sa.get_file_number())] = sa

    def incoming_avatar(self, friend_number, file_number, size):
        """
        Friend changed avatar
        :param friend_number: friend number
        :param file_number: file number
        :param size: size of avatar or 0 (default avatar)
        """
        friend = self._get_friend_by_number(friend_number)
        ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
        if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']:
            self._file_transfers[(friend_number, file_number)] = ra
            ra.set_transfer_finished_handler(self.transfer_finished)
        else:
            friend.load_avatar()

    # -----------------------------------------------------------------------------------------------------------------
    # Private methods
    # -----------------------------------------------------------------------------------------------------------------

    def _get_friend_by_number(self, friend_number):
        return self._contact_provider.get_friend_by_number(friend_number)
