#!/usr/bin/env python
# coding=utf-8

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from __future__ import unicode_literals

import os, sys, signal

from blueman.bluez.obex.Base import ObexdNotFoundError

#support running uninstalled
_dirname = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if os.path.exists(os.path.join(_dirname, "CHANGELOG.md")):
    sys.path.insert(0, _dirname)

from dbus.mainloop.glib import DBusGMainLoop
from optparse import OptionParser
import gettext
import urllib
import time

from blueman.Constants import *
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

from blueman.bluez.Adapter import Adapter
from blueman.bluez.obex.ObjectPush import ObjectPush
from blueman.main.Device import Device
from blueman.main.FakeDevice import FakeDevice
from blueman.bluez.Manager import Manager
from blueman.Functions import *
from blueman.Constants import *
from blueman.gui.DeviceSelectorDialog import DeviceSelectorDialog
from blueman.main.SpeedCalc import SpeedCalc
from blueman.main.AppletService import AppletService

from blueman.bluez import obex

DBusGMainLoop(set_as_default=True)

# Workaround introspection bug, gnome bug 622084
signal.signal(signal.SIGINT, signal.SIG_DFL)

enable_rgba_colormap()


class Sender(GObject.GObject):
    __gsignals__ = {
        str('result'): (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_BOOLEAN,)),
    }

    def __init__(self, device, adapter, files):
        GObject.GObject.__init__(self)
        self.Builder = Gtk.Builder()
        self.Builder.set_translation_domain("blueman")
        self.Builder.add_from_file(UI_PATH + "/send-dialog.ui")
        self.window = self.Builder.get_object("window")

        self.l_dest = self.Builder.get_object("l_dest")
        self.l_file = self.Builder.get_object("l_file")

        self.pb = self.Builder.get_object("pb")

        self.b_cancel = self.Builder.get_object("b_cancel")
        self.b_cancel.connect("clicked", self.on_cancel)

        self.pb.props.text = _("Connecting")

        self.device = device
        self.adapter = Adapter(adapter)
        self.files = files
        self.object_push = None
        self.transfer = None

        self.total_bytes = 0
        self.total_transferred = 0

        self._last_bytes = 0
        self._last_update = 0

        self.error_dialog = None
        self.cancelling = False

        #bytes transferred on a current transfer
        self.transferred = 0

        self.speed = SpeedCalc(6)

        for i in range(len(self.files) - 1, -1, -1):
            f = self.files[i]
            match = re.match("file://(.*)", f)
            if match:
                f = self.files[i] = urllib.unquote(match.groups(1)[0])

            if os.path.exists(f) and not os.path.isdir(f):
                f = os.path.abspath(f)
                self.total_bytes += os.path.getsize(f)
            else:
                self.files.remove(f)

        self.num_files = len(self.files)
        try:
            self.client = obex.Client()
        except ObexdNotFoundError:
            d = Gtk.MessageDialog(self.window,
                                  type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK)
            d.props.text = _("obexd not available")
            d.props.secondary_text = _("obexd is probably not installed")
            d.run()
            d.destroy()
            exit(1)

        if self.num_files == 0:
            exit(1)

        self.l_file.props.label = os.path.basename(self.files[-1])

        self.client.connect('session-created', self.on_session_created)
        self.client.connect('session-failed', self.on_session_failed)
        self.client.connect('session-removed', self.on_session_removed)

        print("Sending to", device.Address)
        self.l_dest.props.label = device.Alias

        self.create_session()

        self.window.show()

    def create_session(self):
        dprint("Creating session")
        props = self.adapter.get_properties()
        self.client.create_session(self.device.Address, props["Address"])

    def on_cancel(self, button):
        self.pb.props.text = _("Cancelling")
        if button:
            button.props.sensitive = False

        if self.object_push:
            self.client.remove_session(self.object_push.get_session_path())
        else:
            self.emit("result", False)

    def on_transfer_started(self, _object_push, transfer_path, filename):
        if self.total_transferred == 0:
            self.pb.props.text = _("Sending File") + (" %(0)s/%(1)s (%(2).2f %(3)s/s) " + _("ETA:") + " %(4)s") % {
                "1": self.num_files,
                "0": (self.num_files - len(self.files) + 1),
                "2": 0.0,
                "3": "B/s",
                "4": "∞"}

        self.l_file.props.label = filename
        self._last_bytes = 0
        self.transferred = 0

        self.transfer = obex.Transfer(transfer_path)
        self.transfer.connect("error", self.on_transfer_error)
        self.transfer.connect("progress", self.on_transfer_progress)
        self.transfer.connect("completed", self.on_transfer_completed)

    def on_transfer_failed(self, _object_push, error):
        self.on_transfer_error(None, None, str(error))

    def on_transfer_progress(self, _transfer, progress):
        self.transferred = progress
        if self._last_bytes == 0:
            self.total_transferred += progress
        else:
            self.total_transferred += (progress - self._last_bytes)

        self._last_bytes = progress

        tm = time.time()
        if tm - self._last_update > 0.5:
            spd = self.speed.calc(self.total_transferred)
            (size, units) = format_bytes(spd)
            try:
                x = ((self.total_bytes - self.total_transferred) / spd) + 1
                if x > 60:
                    x /= 60
                    eta = ngettext("%.0f Minute", "%.0f Minutes", round(x)) % x
                else:
                    eta = ngettext("%.0f Second", "%.0f Seconds", round(x)) % x
            except ZeroDivisionError:
                eta = "∞"

            self.pb.props.text = _("Sending File") + (" %(0)s/%(1)s (%(2).2f %(3)s/s) " + _("ETA:") + " %(4)s") % {
                "1": self.num_files,
                "0": (self.num_files - len(self.files) + 1),
                "2": size,
                "3": units,
                "4": eta}
            self._last_update = tm

        self.pb.props.fraction = float(self.total_transferred) / self.total_bytes

    def on_transfer_completed(self, _transfer):
        del self.files[-1]
        self.transfer = None

        self.process_queue()

    def process_queue(self):
        if len(self.files) > 0:
            self.send_file(self.files[-1])
        else:
            self.emit("result", True)

    def send_file(self, file_path):
        dprint(file_path)
        if self.object_push:
            self.object_push.send_file(file_path)

    def on_transfer_error(self, _transfer, msg):
        if not self.error_dialog:
            self.speed.reset()
            d = Gtk.MessageDialog(self.window,
                                  type=Gtk.MessageType.ERROR)
            d.props.text = msg
            d.props.modal = True
            d.props.secondary_text = _("Error occurred while sending file %s") % os.path.basename(self.files[-1])
            d.props.icon_name = "blueman"

            if len(self.files) > 1:
                d.add_button(_("Skip"), Gtk.ResponseType.NO)
            d.add_button(_("Retry"), Gtk.ResponseType.YES)
            d.add_button("_Cancel", Gtk.ResponseType.CANCEL)


            def on_response(dialog, resp):
                dialog.destroy()
                self.error_dialog = None

                if resp == "_Cancel":
                    self.on_cancel(None)
                elif resp == Gtk.ResponseType.NO:
                    self.total_bytes -= os.path.getsize(self.files[-1])
                    self.total_transferred -= self.transferred
                    self.transferred = 0
                    del self.files[-1]
                    if not self.object_push:
                        self.create_session()
                    self.process_queue()
                elif resp == Gtk.ResponseType.YES:
                    self.total_transferred -= self.transferred
                    self.transferred = 0
                    if not self.object_push:
                        self.create_session()

                    self.process_queue()
                else:
                    self.on_cancel(None)

            d.connect("response", on_response)
            d.show()
            self.error_dialog = d

    def on_session_created(self, _client, session_path):
        self.object_push = ObjectPush(session_path)
        self.object_push.connect("transfer-started", self.on_transfer_started)
        self.object_push.connect("transfer-failed", self.on_transfer_failed)
        self.process_queue()

    def on_session_failed(self, _client, msg):
        d = Gtk.MessageDialog(self.window,
                              type=Gtk.MessageType.ERROR, buttons=(Gtk.ButtonsType.CLOSE))
        d.props.text = _("Error occurred")
        d.props.icon_name = "blueman"
        d.props.secondary_text = str(msg).split(":")[1].strip(" ")

        d.run()
        d.destroy()
        exit(1)

    def on_session_removed(self, _client):
        self.emit("result", False)


class SendTo:
    def __init__(self):
        setup_icon_path()

        usage = "Usage: %prog [options] file1 file2 ... fileN"
        parser = OptionParser(usage)
        parser.add_option("-d", "--device", dest="device",
                          action="store", help=_("Send files to this device"), metavar="ADDRESS")

        parser.add_option("", "--dest", dest="device",
                          action="store", help="Same as --device", metavar="ADDRESS")

        parser.add_option("-s", "--source", dest="source",
                          action="store", help=_("Source adapter. Takes address or adapter's name eg. hci0"),
                          metavar="PATTERN")

        (options, args) = parser.parse_args()

        check_bluetooth_status(_("Bluetooth needs to be turned on for file sending to work"), lambda: exit())

        self.options = options

        self.files = []
        if not args:
            self.files = self.select_files()
        else:
            self.files = [os.path.abspath(f) for f in args]

        self.device = None
        self.adapter = None

        if options.device is None:
            if not self.select_device():
                exit()

            self.do_send()

        else:
            m = Manager()
            try:
                if options.source is not None:
                    try:
                        adapter = m.get_adapter(options.source)
                    except:
                        adapter = m.get_adapter()
                else:
                    adapter = m.get_adapter()
            except:
                print("Error: No Adapters present")
                exit()
            try:
                d = adapter.find_device(options.device)
            except:
                info = {}
                info["Address"] = options.device
                info["Name"] = info["Address"].replace(":", "-")
                info["Alias"] = info["Name"]
                info["Fake"] = True

                d = FakeDevice(info)

            self.device = Device(d)
            self.adapter = adapter.get_object_path()
            self.do_send()

        Gtk.main()

    def do_send(self):
        if not self.files:
            dprint("No files to send")
            exit()

        sender = Sender(self.device, self.adapter, self.files)

        def on_result(sender, res):
            Gtk.main_quit()

        sender.connect("result", on_result)

    def select_files(self):
        d = Gtk.FileChooserDialog(_("Select files to send"), buttons=("_Cancel", Gtk.ResponseType.REJECT,
                                                                      "_OK", Gtk.ResponseType.ACCEPT))
        d.props.icon_name = "blueman-send-file"
        d.set_select_multiple(True)
        resp = d.run()

        if resp == Gtk.ResponseType.ACCEPT:
            files = d.get_filenames()
            d.destroy()
            return files
        else:
            d.destroy()
            return []

    def select_device(self):
        d = DeviceSelectorDialog()
        resp = d.run()
        if resp == Gtk.ResponseType.ACCEPT:
            sel = d.GetSelection()
            if sel:
                self.device = sel[1]
                self.adapter = sel[0]
                return True
            else:
                return False
        else:
            return False


set_proc_title()
SendTo()
