import re
import subprocess
import traceback
from time import sleep
from typing import List

import notify2
from dbus import DBusException
from libqtile import widget
from notify2 import Notification, URGENCY_NORMAL
from libqtile.log_utils import logger
from libqtile import qtile

BUTTON_LEFT = 1
BUTTON_MIDDLE = 2
BUTTON_RIGHT = 3
BUTTON_UP = 4
BUTTON_DOWN = 5
BUTTON_MUTE = 1
BUTTON_SCROLL_UP = 4
BUTTON_SCROLL_DOWN = 5


def is_running(process):
    s = subprocess.Popen(["ps", "axuw"], stdout=subprocess.PIPE)
    if isinstance(process, list):
        process = "".join(process)
    for x in s.stdout:
        if re.search(process, x.decode('utf-8')):
            return True
    return False


def execute(process):
    try:
        if isinstance(process, list):
            return subprocess.Popen(process)
        elif isinstance(process, str):
            return subprocess.Popen(process.split())
        else:
            logger.info(f"Failed to execute_once")
    except FileNotFoundError as e:
        logger.error(f"Could not execute {process}, FileNotFoundError - {e}")


def execute_once(process):
    logger.info(f"Attempting to execute_once: {process}")
    if not is_running(process):
        return execute(process)
    logger.info(f"Process was already running: {process}")


def start_in_group(theme, qtile, group: str, command: List[str], floating: bool = False,
                   intrusive: bool = False, dont_break: bool = False):
    try:
        proc = subprocess.Popen(command)
        match_args = {"net_wm_pid": proc.pid}
        rule_args = {
            "float": floating,
            "intrusive": intrusive,
            "group": group,
            "break_on_match": not dont_break,
        }
        rule_id = qtile.add_rule(match_args, rule_args)
        theme.autostart_app_rules[proc.pid] = rule_id
        return proc
    except FileNotFoundError as e:
        logger.error(f"Could not execute {process}, FileNotFoundError - {e}")


def start_in_group_once(theme, qtile, group: str, command: List[str], floating: bool = False,
                        intrusive: bool = False, dont_break: bool = False):
    logger.info(f"Attempting to start_in_group_once: {command}")
    if not is_running(command):
        return start_in_group(theme=theme, qtile=qtile, group=group, command=command,
                              floating=floating, intrusive=intrusive, dont_break=dont_break)
    logger.info(f"Process was already running: {command}")


def call_process(command, **kwargs):
    """
    Run the given command and return the string from stdout.
    """
    return subprocess.check_output(command, **kwargs).decode()


def get_screen_count():
    try:
        if qtile.core.name == "x11":
            logger.info("Using xrandr to detect screen count")
            output = subprocess.check_output("xrandr -q".split()).decode('utf-8')
            output = [x for x in output.split("\n") if " connected" in x]
        else:
            logger.info("Using lsmon (wallutils) to detect screen count")
            output = subprocess.check_output(["lsmon"]).decode('utf-8')
            output = output.split("\n")
    except subprocess.CalledProcessError:
        return 1

    if output:
        return len(output)
    else:
        return 1


def bar_separator(config):
    return widget.Sep(foreground=config.get('colour_spacer_background', '#777777'),
                      linewidth=config.get('width_spacer', 1),
                      padding=config.get('padding_spacer', 4),
                      )

def init_notify(qtile):
    if qtile and qtile.theme_instance and qtile.theme_instance.startup_completed:
        try:
            if not notify2.is_initted():
                logger.warning("Initializing Notify2")
                notify2.init("QTileWM")
        except DBusException:
            logger.error(f"Failed to initialize Notify2 (DBus error), retrying later.")
        except Exception:
            logger.error(f"Failed to initialize Notify2 (Generic error), retrying later.")
    else:
        logger.warning(f"Not initializing Notify2 yet, QTile startup not completed.")


def notify(qtile, title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None):
    if image is not None:
        notification = Notification(
            summary=title, message=content,
            icon=image
        )
    else:
        notification = Notification(
            summary=title, message=content
        )
    notification.set_timeout(timeout)
    notification.set_urgency(urgency)

    init_notify(qtile)

    try:
        try:
            return notification.show()
        except notify2.UninittedError:
            logger.warning("Notify2 is not initialized")
    except Exception as e:
        logger.warning("Showing notification failed: {}".format(e))
        logger.warning(traceback.format_exc())


def spawn_popup(qtile, x, y, text):
    """
    :param qtile: The main qtile instance
    :type qtile: Qtile
    :param x: x-coordinate
    :type x: int
    :param y: y-coordinate
    :type y: int
    :param text: String to display
    :type text: str
    :return: The popup instance
    :rtype: Internal
    """
    if qtile.core.name == "x11":
        from libqtile.backend.x11.window import Internal
    else:
        from libqtile.backend.wayland.window import Internal
    popup = Internal.create(
        qtile, x, y, 100, 100, opacity=1
    )

    # Create textwidget for in window
    popup.bordercolor = "#000000"
    popup.borderwidth = 1

    popup.focus(False)

    #popup.

    return popup


def despawn_popup(popup):
    """
    :type popup: Internal
    :param popup: The popup to despawn
    """
    popup.kill()


def test_popups(qtile):
    popup = spawn_popup(qtile, 10, 10, "Hello World!")
    sleep(3)
    despawn_popup(popup)


def display_wm_class(qtile):
    window = qtile.currentWindow if qtile else None

    if window:
        wm_class = window.get_wm_class() or None
        name = window.name

        if wm_class:
            notify(qtile=qtile, title="WM_Class of {}".format(name),
                   content="{}".format(wm_class),
                   urgency=notify2.URGENCY_CRITICAL)


def bluetooth_audio_sink():
    try:
        output = subprocess.check_output("pamixer --list-sinks".split()).decode("utf-8")
        output = [x for x in output.split('\n') if "blue" in x.lower()]
    except (subprocess.CalledProcessError, FileNotFoundError):
        return -1

    sink = -1
    try:
        sink = int(output[0].split()[0])
    except IndexError:
        pass
    except AttributeError:
        pass
    except ValueError:
        pass

    return sink


def bluetooth_audio_connected():
    return bluetooth_audio_sink() != -1