#!/usr/bin/python3
# Copyright (c) 2019 STMicroelectronics. All rights reserved.
#
# This software component is licensed by ST under BSD 3-Clause license,
# the "License"; You may not use this file except in compliance with the
# License. You may obtain a copy of the License at:
#                        opensource.org/licenses/BSD-3-Clause


import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib

import re
import os
import subprocess
import pexpect
from time import sleep, time

try:
    from application.bluetooth.wrap_blctl import wrapper_blctl as Bluetoothctl
except ModuleNotFoundError:
    from wrap_blctl import wrapper_blctl as Bluetoothctl

# -------------------------------------------------------------------
# -------------------------------------------------------------------
SUBMODULE_PATH = "application/bluetooth"
DEMO_PATH = "/usr/local/demo"
# -------------------------------------------------------------------
# -------------------------------------------------------------------
ICON_SIZE_1080 = 260
ICON_SIZE_720 = 180
ICON_SIZE_480 = 128
ICON_SIZE_272 = 48

TREELIST_HEIGHT_1080 = 500
TREELIST_HEIGHT_720 = 400
TREELIST_HEIGHT_480 = 160
TREELIST_HEIGHT_272 = 68

# return format:
# [ icon_size, font_size, treelist_height, button_height ]
SIZES_ID_ICON_SIZE = 0
SIZES_ID_FONT_SIZE = 1
SIZES_ID_TREELIST_HEIGHT = 2
SIZES_ID_BUTTON_HEIGHT = 3
def get_sizes_from_screen_size(width, height):
    minsize =  min(width, height)
    icon_size = None
    font_size = None
    treelist_height = None
    button_height = None
    if minsize == 720:
        icon_size = ICON_SIZE_720
        font_size = 25
        treelist_height = TREELIST_HEIGHT_720
        button_height = 60
    elif minsize == 480:
        icon_size = ICON_SIZE_480
        font_size = 20
        treelist_height = TREELIST_HEIGHT_480
        button_height = 60
    elif minsize == 272:
        icon_size = ICON_SIZE_272
        font_size = 15
        treelist_height = TREELIST_HEIGHT_272
        button_height = 25
    elif minsize == 600:
        icon_size = ICON_SIZE_720
        font_size = 15
        treelist_height = TREELIST_HEIGHT_720
        button_height = 60
    elif minsize >= 1080:
        icon_size = ICON_SIZE_1080
        font_size = 32
        treelist_height = TREELIST_HEIGHT_1080
        button_height = 80
    return [icon_size, font_size, treelist_height, button_height]

def get_treelist_height_from_screen_size(width, height):
    minsize =  min(width, height)
    if minsize == 720:
        return TREELIST_HEIGHT_720
    elif minsize == 480:
        return TREELIST_HEIGHT_480
    elif minsize == 272:
        return TREELIST_HEIGHT_272
    elif minsize == 600:
        return ICON_SIZE_1080
    elif minsize >= 1080:
        return ICON_SIZE_1080

# -------------------------------------------------------------------
# -------------------------------------------------------------------

SCAN_DURATION_IN_S  = 15

regexps_audio = [
   re.compile(r"00001108-(?P<Headset>.+)$"),
   re.compile(r"0000110b-(?P<AudioSink>.+)$"),
]

re_connected = re.compile(r"Connected:(?P<Connected>.+)$")

re_paired = re.compile(r"Paired:(?P<Paired>.+)$")

Item_info_dev = ['Headset', 'AudioSink', 'Connected', 'Paired']

regexps_devinfo = [
   re.compile(r"00001108-(?P<Headset>.+)$"),
   re.compile(r"0000110b-(?P<AudioSink>.+)$"),
   re.compile(r"Connected:(?P<Connected>.+)$"),
   re.compile(r"Paired:(?P<Paired>.+)$"),
]
########################################
#pactl (pulseaudio controller) wrapper
########################################
#for parse_sinks
re_sink = re.compile(r"^Sink #(?P<Ident>.+)$")
re_prop_sink = [
   re.compile(r"State:(?P<State>.+)$"),
   re.compile(r"Description:\s+(?P<Name>.+)$")
]

#for parse_streams
re_stream = re.compile(r"^Sink Input #(?P<Ident>.+)$")
re_prop_stream = [
   re.compile(r"Sink:\s+(?P<Sink>.+)$"),
   re.compile(r"media\.name\s=\s(?P<Name>.+)$")
]
# id_str : ident of the stream, id_sink : ident of the sink
def audiosink_set(id_str, id_sink):
    print("audiosink_set ")
    #print("id_str : %d", id_str)
    #print("id_sink : %d", id_sink)
    cmd = ["/usr/bin/pactl", "move-sink-input",  id_str, id_sink]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    res = proc.stdout.read().decode('utf-8')
    return res

def scan_streams():
    cmd = ["/usr/bin/pactl", "list",  "sink-inputs"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    res = proc.stdout.read().decode('utf-8')
    return res

def parse_streams(streams):
    streams_lines = streams.split('\n')
    l_streams = []
    for line in streams_lines:
       line = line.strip()
       elt = re_stream.search(line)
       if elt is not None:
           l_streams.append(elt.groupdict())
           continue
       for reg in re_prop_stream:
           res = reg.search(line)
           if res is not None:
              l_streams[-1].update(res.groupdict())
    return l_streams


def scan_sinks():
    cmd = ["/usr/bin/pactl", "list", "sinks"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    res = proc.stdout.read().decode('utf-8')
    return res

def parse_sinks(sinks):
    sinks_lines=sinks.split('\n')
    l_sinks =[]
    for line in sinks_lines:
       line = line.strip()
       elt = re_sink.search(line)
       if elt is not None:
           l_sinks.append(elt.groupdict())
           continue
       for reg in re_prop_sink:
           res = reg.search(line)
           if res is not None:
              l_sinks[-1].update(res.groupdict())
    return l_sinks



def status_playback(self):
    sink_ident = []
    stream_ident = None

    list_sinks = scan_sinks()
    sinks = parse_sinks(list_sinks)
    #print("refresh label_audio\n")
    #print(sinks)
    mess_bt = ""
    if sinks != []:
       for sk in sinks:
          for bt_dev_conn in self.list_dev_connect:
              if sk['Name'] == bt_dev_conn['name']:
                  if mess_bt != "":
                      mess_bt = mess_bt + "\n"
                  mess_bt = mess_bt + "The audio BT device " + sk['Name'] + " is connected"
                  sink_ident.append({'name': sk['Name'], 'ident': sk['Ident']})

    if mess_bt == "":
        mess_bt = "Device not connected"

    self.label_audio.set_markup("<span font='20' color='#000000'>%s</span>" % mess_bt)
    self.label_audio.set_justify(Gtk.Justification.LEFT)
    self.label_audio.set_line_wrap(True)
    return [stream_ident, sink_ident]


def get_device_info(bl, macadr):
    #print("get_device_info")
    info_dev = bl.blctl_info(macadr)
    dict_info = {}
    for elt in Item_info_dev:
        dict_info[elt] = ''
    for reelt in regexps_devinfo:
        for elt in info_dev:
            result = reelt.search(elt)
            if result is not None:
                dict_info.update(result.groupdict())
                break
    return(dict_info)


def list_devices(self, paired = False):
    #print("list_devices")
    if self.locked_devices == False:
       self.locked_devices = True
       self.bluetooth_liststore.clear()
       self.current_devs=[]
       i=0
       if paired == True:
          devs = self.bl.blctl_paired_devices()
       else:
          devs = self.bl.blctl_devices()
       for elt in devs:
          elt_info = get_device_info(self.bl, elt['mac_address'])
          #print("name===" , elt['name'].encode('utf-8').strip())
          if elt['name'] == "RSSI is nil":
              continue
          if elt['name'] == "TxPower is nil":
              continue
          #do not list device without real name
          if elt['mac_address'].replace(':','') != elt['name'].replace('-',''):
              i=i+1
              self.current_devs.append(elt['mac_address'])
              #print(elt_info)
              l_elt = []
              l_elt.append(i)
              l_elt.append(elt['name'])
              l_elt.append(elt_info['Connected'])
              if elt_info['Headset'] != '' or elt_info['AudioSink'] != '':
                  l_elt.append('yes')
              else:
                  l_elt.append('no')

              if elt_info['Connected'] == " yes":
                  if elt not in self.list_dev_connect:
                     self.list_dev_connect.insert(0,elt)
                     self.bl.set_prompt(elt['name'])
              self.bluetooth_liststore.append(l_elt)

       self.locked_devices = False


def device_connected(bl, macadr):
    info_dev=bl.blctl_info(macadr)
    if info_dev is not None:
        for elt in info_dev:
            result = re_connected.search(elt)
            if result is not None:
               l_info_dev = result.groupdict()
               if l_info_dev["Connected"] == " yes":
                   return True
    return False


def device_paired(bl, macadr):
    info_dev=bl.blctl_info(macadr)
    if info_dev is not None:
        for elt in info_dev:
            result = re_paired.search(elt)
            if result is not None:
               l_info_dev = result.groupdict()
               if l_info_dev["Paired"] == " yes":
                   return True
    return False


def device_audio(bl, macadr):
    info_dev=bl.blctl_info(macadr)
    for reelt in regexps_audio:
        for elt in info_dev:
            result = reelt.search(elt)
            if result is not None:
                return True
    return False

# -------------------------------------------------------------------
# -------------------------------------------------------------------
def gtk_style():
        css = b"""

.widget .grid .label {
    background-color: rgba (31%, 32%, 32%, 0.9);
}
.textview {
    color: gray;
}
.label {
    color: black;
}
.switch {
    min-height: 44px;
}
        """
        style_provider = Gtk.CssProvider()
        style_provider.load_from_data(css)

        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

class BluetoothWindow(Gtk.Dialog):
    def __init__(self, parent):
        Gtk.Dialog.__init__(self, "Wifi", parent, 0)
        self.maximize()
        self.set_decorated(False)

        gtk_style()
        try:
            display = Gdk.Display.get_default()
            monitor = display.get_primary_monitor()
            geometry = monitor.get_geometry()
            scale_factor = monitor.get_scale_factor()
            self.screen_width = scale_factor * geometry.width
            self.screen_height = scale_factor * geometry.height
        except:
            self.screen_width = self.get_screen().get_width()
            self.screen_height = self.get_screen().get_height()
        self.treelist_height = get_treelist_height_from_screen_size(self.screen_width, self.screen_height)
        sizes = get_sizes_from_screen_size(self.screen_width, self.screen_height)
        self.font_size = sizes[SIZES_ID_FONT_SIZE]
        self.button_height = sizes[SIZES_ID_BUTTON_HEIGHT]

        self.connect("button-release-event", self.on_page_press_event)
        mainvbox = self.get_content_area()

        self.dev_selected = {'mac_address':'', 'name':''}
        self.audio_bt_sink = []
        self.list_dev_connect = []
        self.current_devs = []
        self.locked_devices = False
        self.scan_done = False
        self.previous_click_time=0

        self.page_bluetooth = Gtk.VBox()
        self.page_bluetooth.set_border_width(15)

        self.title = Gtk.Label()
        self.title.set_markup("<span font='%d' color='#00000000'>Connect bluetooth headset</span>" % (self.font_size+5))
        self.page_bluetooth.add(self.title)

        self.ButtonBox = Gtk.HBox(homogeneous=True)

        self.lb_button_scan = Gtk.Label()
        self.lb_button_scan.set_markup("<span font='%d'>start scan</span>" % self.font_size)
        self.button_scan = Gtk.Button()
        self.button_scan.set_property("height-request", self.button_height)
        self.button_scan.add(self.lb_button_scan)
        self.button_scan.connect("clicked", self.on_selection_scan_clicked)
        self.ButtonBox.add(self.button_scan)

        self.lb_button_connect = Gtk.Label()
        self.lb_button_connect.set_markup("<span font='%d' color='#88888888'>connect</span>" % self.font_size)
        self.button_connect = Gtk.Button()
        self.button_connect.add(self.lb_button_connect)
        self.button_connect.connect("clicked", self.on_selection_connect_clicked)
        self.ButtonBox.add(self.button_connect)

        self.page_bluetooth.add(self.ButtonBox)

        self.progress_vbox = Gtk.VBox()
        self.scan_progress = Gtk.ProgressBar()
        self.scan_progress.set_fraction(0.0)
        self.progress_vbox.pack_start(self.scan_progress, False, False, 3)
        self.page_bluetooth.add(self.progress_vbox)

        self.tree_list_vbox = Gtk.VBox(homogeneous=True)

        self.bluetooth_liststore = Gtk.ListStore(int, str, str, str)
        self.bluetooth_treeview = Gtk.TreeView(self.bluetooth_liststore)

        l_col = ["n°", "name", "connected", "Audio"]
        for i, column_title in enumerate(l_col):
            renderer = Gtk.CellRendererText()
            renderer.set_property('font', "%d" % self.font_size)
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.bluetooth_treeview.append_column(column)
        self.bluetooth_treeview.get_selection().connect("changed", self.on_changed)

        self.scroll_treelist = Gtk.ScrolledWindow()
        self.scroll_treelist.set_vexpand(False)
        self.scroll_treelist.set_hexpand(False)
        self.scroll_treelist.set_property("min-content-height", self.treelist_height)
        self.scroll_treelist.add(self.bluetooth_treeview)
        self.tree_list_vbox.pack_start(self.scroll_treelist, True, True, 3)

        self.page_bluetooth.add(self.tree_list_vbox)

        self.label_audio = Gtk.Label()
        self.label_audio.set_markup("<span font='%d' color='#FFFFFFFF'> </span>" % self.font_size)
        self.label_audio.set_justify(Gtk.Justification.LEFT)
        self.label_audio.set_line_wrap(True)
        self.page_bluetooth.add(self.label_audio)

        mainvbox.pack_start(self.page_bluetooth, False, True, 3)
        self.show_all()

        # enable bluetooth
        os.system('su -c \"hciconfig hci0 up\"')
        #self.bluetooth_state = os.system('hciconfig hci0 up')
        self.bl = Bluetoothctl()

        list_devices(self, paired=True)
        self.audio_bt_sink = status_playback(self)


    def display_message(self, message):
        dialog = Gtk.Dialog("Error", self, 0, (Gtk.STOCK_OK, Gtk.ResponseType.OK))
        dialog.set_decorated(False)
        width, height = self.get_size()
        dialog.set_default_size(width, height)
        rgba = Gdk.RGBA(0.31, 0.32, 0.31, 0.8)
        dialog.override_background_color(0,rgba)

        label0 = Gtk.Label() #for padding

        label1 = Gtk.Label()
        label1.set_markup(message)
        label1.set_justify(Gtk.Justification.CENTER)
        label1.set_line_wrap(True)

        label2 = Gtk.Label() #for padding

        # Create a centering alignment object
        align = Gtk.Alignment()
        align.set(0.5, 0, 0, 0)

        dialog.vbox.pack_start(label0, True, False, 0)
        dialog.vbox.pack_start(label1, True, True,  0)
        dialog.vbox.pack_start(align,  True, True,  0)
        dialog.vbox.pack_start(label2, True, False, 0)

        dialog.action_area.reparent(align)
        dialog.show_all()

        dialog.run()
        print("INFO dialog closed")

        dialog.destroy()


    def on_page_press_event(self, widget, event):
        self.click_time = time()
        #print(self.click_time - self.previous_click_time)
        # TODO : a fake click is observed, workaround hereafter
        if (self.click_time - self.previous_click_time) < 0.01:
            self.previous_click_time = self.click_time
        elif (self.click_time - self.previous_click_time) < 0.3:
            print ("BluetoothWindow double click : exit")
            self.bl.close()
            self.destroy()
        else:
            #print ("simple click")
            self.previous_click_time = self.click_time


    def delayed_status_playback(self, user_data):
        self.audio_bt_sink = status_playback(self)
        return False

    def progress_timeout(self, user_data):
        new_val=self.scan_progress.get_fraction() + 0.01
        if new_val > 1:
           self.scan_progress.set_fraction(0.0)
           self.bl.blctl_scan_off()
           self.lb_button_scan.set_markup("<span font='%d'>start scan</span>" % self.font_size)
           self.scan_done = True
           self.update_display()
           return False

        self.scan_progress.set_fraction(new_val)
        self.scan_progress.set_text(str(new_val*100) + " % completed")
        return True

    def on_changed(self, selection):
        (model, iter) = selection.get_selected()
        #print("on_changed")
        if iter is not None:
            self.audio_bt_sink = status_playback(self)
            #print(self.audio_bt_sink)
            self.dev_selected.update({'mac_address':self.current_devs[model[iter][0]-1], 'name':model[iter][1]})
            if model[iter][2] == " yes":
                self.lb_button_connect.set_markup("<span font='%d'>disconnect</span>" % self.font_size)
            else:
                if self.label_audio.get_text() == "Device not connected":
                    self.lb_button_connect.set_markup("<span font='%d'>connect</span>" %  self.font_size)
                else:
                    self.lb_button_connect.set_markup("<span font='%d' color='#88888888'>connect</span>" % self.font_size)
        return True

    def connect_process(self, dev):
        if device_connected(self.bl, dev['mac_address']):
            self.lb_button_connect.set_markup("<span font='%d'>disconnect</span>" % self.font_size)
            self.update_display()
        else:
            connect_res=self.bl.blctl_connect(dev['mac_address'])
            if connect_res == True:
                self.lb_button_connect.set_markup("<span font='%d' color='#88888888'>disconnect</span>" % self.font_size)
                self.update_display()
        # refresh status_playback after 2,5s because pulseaudio takes some time to update its status
        timer_update_dev = GLib.timeout_add(2500, self.delayed_status_playback, None)
        #In some cases, 2.5s is still not enough
        timer_update_dev = GLib.timeout_add(4000, self.delayed_status_playback, None)

    def on_selection_connect_clicked(self, widget):
        if self.dev_selected['mac_address'] != '':
            device = self.dev_selected
            if self.lb_button_connect.get_text() == "connect":
                if self.label_audio.get_text() == "Device not connected":
                    self.bl.set_prompt(device['name'])
                    if device_paired(self.bl, device['mac_address']) == False:
                        pairing_res=self.bl.blctl_pair(device['mac_address'])
                        if pairing_res == 0:
                            self.bl.blctl_session.send("no\n")
                        else:
                            if pairing_res == 1:
                                sleep(5)
                                self.connect_process(device)
                    else:
                        self.connect_process(device)
                else:
                    print("[WARNING] A BT device is already connected :\ndisconnect it before connecting a new device\n")
                    self.display_message("<span font='15' color='#000000'>A BT device is already connected :\nPlease disconnect it before connecting a new device\n</span>")
            else:
                connect_res=self.bl.blctl_disconnect(device['mac_address'])
                self.lb_button_connect.set_markup("<span font='%d' color='#88888888'>connect</span>" % self.font_size)
                self.update_display()
        else:
            print("[WARNING] Select the BT device to connect\n")
            self.display_message("<span font='15' color='#000000'>Please select a device in the list\n</span>")

    def on_selection_scan_clicked(self, widget):
        if self.lb_button_scan.get_text() == "start scan":
           self.bl.blctl_scan_on()
           timer_scan = GLib.timeout_add(SCAN_DURATION_IN_S * 10, self.progress_timeout, None)
           self.lb_button_scan.set_markup("<span font='%d'>scan progress</span>"% self.font_size)

    def update_display(self):
        if (self.scan_done == True):
            list_devices(self, False)
        else:
            list_devices(self, True)
        self.dev_selected.update({'mac_address':'', 'name':''})
        self.audio_bt_sink = status_playback(self)

# -------------------------------------------------------------------
# -------------------------------------------------------------------
def create_subdialogwindow(parent):
    _window = BluetoothWindow(parent)
    _window.show_all()
    response = _window.run()
    _window.destroy()


# -------------------------------------------------
# -------------------------------------------------
#               test submodule
class TestUIWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Test Launcher")
        create_subdialogwindow(self)
        self.show_all()

if __name__ == "__main__":
    win = TestUIWindow()
    win.connect("delete-event", Gtk.main_quit)
    win.show_all()

