#!/bin/bash

#================================================================================
# Инициализация
#================================================================================

DBUS_INTROSPECTABLE_INTERFACE='org.freedesktop.DBus.Introspectable'
DBUS_INTROSPECT_METHOD='Introspect'

DBUS_BLUEZ_OBJECT='org.bluez'
DBUS_BLUEZ_PATH=$(echo "/$DBUS_BLUEZ_OBJECT" | tr "." "//")

DBUS_BLUEZ_DEVICE_INTERFACE="$DBUS_BLUEZ_OBJECT.Device1"
DBUS_BLUEZ_DEVICE_CONNECT_METHOD='Connect'
DBUS_BLUEZ_DEVICE_DISCONNECT_METHOD='Disconnect'
DBUS_BLUEZ_DEVICE_CONNECTED_PROPERTY='Connected'
DBUS_BLUEZ_DEVICE_TRUSTED_PROPERTY='Trusted'
DBUS_BLUEZ_DEVICE_BLOCKED_PROPERTY='Blocked'
DBUS_BLUEZ_DEVICE_NAME_PROPERTY='Name'
DBUS_BLUEZ_DEVICE_ADDRESS_PROPERTY='Address'

TEMP_SLEEP_FILE_PATH="/tmp/enabled_bluetooth_devices.list"

#================================================================================
# Функции
#================================================================================

## getBool
## @brief - Функция преобразования вывода DBUS к Bool
function getBool() {
    echo $@ | awk -F ' ' '{$1=""; $2=""; print $0}'
}

## getString
## @brief - Функция преобразования вывода DBUS к String
function getString() {
    echo $@ | awk -F ' ' '{$1=""; print $0}'
}

## dbusCallMethod
## @brief - Функция вызовет метод DBUS
## @param $1 - Имя объект DBUS
## @param $2 - Путь в объекте DBUS
## @param $3 - Вызываемый метод
## @param $4 - Параметры
## @return Вернёт результат вызова метода
function dbusCallMethod() {
    local DEST_OBJECT="$1"
    local DBUS_OBJECT_PATH="$2"
    local METHOD_NAME="$3"
    local VARS="$4"

    if [ -z "$VARS" ]; then
        echo $(dbus-send --system --type=method_call \
            --dest="$DEST_OBJECT" --print-reply \
            "$DBUS_OBJECT_PATH" "$METHOD_NAME"
         )
    else
        echo $(dbus-send --system --type=method_call \
            --dest="$DEST_OBJECT" --print-reply \
            "$DBUS_OBJECT_PATH" "$METHOD_NAME" \
            "$VARS"
         )
    fi
}

## dbusGetProperty
## @brief - Функция запроса свойства DBUS
## @param $1 - Имя объект DBUS
## @param $2 - Путь в объекте DBUS
## @param $3 - Вызываемый интерфейс
## @param $4 - Запрашиваемое свойство
## @return Вернёт свойство в формате DBUS (требуется преобразование)
function dbusGetProperty() {
    local DEST_OBJECT="$1"
    local DBUS_OBJECT_PATH="$2"
    local INTERFACE_NAME="$3"
    local PROPERTY_NAME="$4"

    local Property=$(dbus-send --system \
            --print-reply=literal \
            --dest="$DEST_OBJECT" \
            "$DBUS_OBJECT_PATH" org.freedesktop.DBus.Properties.Get \
            string:"$INTERFACE_NAME" \
            string:"$PROPERTY_NAME"
    )

    echo "$Property"
}

## dbusIntrospect
## @brief - Функция вызовет метод Introspect указанного объекта
## @param $1 - Имя объект DBUS
## @param $2 - Путь в объекте DBUS
## @return Вернёт структуру объекта в формате XML
function dbusIntrospect() {
    local DEST_OBJECT="$1"
    local DBUS_OBJECT_PATH="$2"

    echo $(dbusCallMethod "$DEST_OBJECT" "$DBUS_OBJECT_PATH" "$DBUS_INTROSPECTABLE_INTERFACE.$DBUS_INTROSPECT_METHOD" '')
}

## bluezGetControllersList
## @brief - Функция вернёт список контроллеров bluetooth
## @param $1 - Ссылка на список, в который будут помещены контроллеры
## @return Вернёт список контроллеров bluetooth
function bluezGetControllersList() {
    declare -n OUT_CONTROLLERS=$1

    local xml_introsppect_result=$(dbusIntrospect "$DBUS_BLUEZ_OBJECT" "$DBUS_BLUEZ_PATH")
    local regex='<node name="([[:alnum:]_-]*)"'
    # Ищем все контроллеры, по соответствию шаблону
    while [[ "$xml_introsppect_result" =~ $regex  ]]
    do
        # Добавляем в выходной контейнер первую группу (само имя контроллера)
        OUT_CONTROLLERS[${#OUT_CONTROLLERS[*]}]="${BASH_REMATCH[1]}"
        # Удаляем первое попавшееся вхождение целиком
        xml_introsppect_result=${xml_introsppect_result/"${BASH_REMATCH[0]}"/}
    done
}

## bluezGetControllerDevicesList
## @brief - Функция вернёт список устройств контроллера bluetooth
## @param $1 - Путь к контроллеру
## @param $2 - Ссылка на список, в который будут помещены контроллеры
## @return Вернёт список устройств контроллера bluetooth
function bluezGetControllerDevicesList() {
    local CONTROLLER_PATH="$1"
    declare -n OUT_DEVICES=$2

    local xml_introsppect_result="$(dbusIntrospect $DBUS_BLUEZ_OBJECT $CONTROLLER_PATH)"
    local regex='<node name="([[:alnum:]_-]*)"'
    # Ищем все устройства, по соответствию шаблону
    while [[ "$xml_introsppect_result" =~ $regex  ]]
    do
        # Добавляем в выходной контейнер первую группу (само имя устройства)
        OUT_DEVICES[${#OUT_DEVICES[*]}]="${BASH_REMATCH[1]}"
        # Удаляем первое попавшееся вхождение целиком
        xml_introsppect_result=${xml_introsppect_result/"${BASH_REMATCH[0]}"/}
    done
}

## bluezGetDevicePaths
## @brief - Функция вернёт список путей всех bluetooth устройств на всех контроллерах
## @param $1 - Ссылка на список, в который будут помещены пути устройств
## @return Вернёт список всех bluetooth устройств
function bluezGetDevicePaths() {
    declare -n OUT_DEVICE_PATHS=$1
    local controllers=()

    bluezGetControllersList controllers
    for controller in "${controllers[@]}"
    do
        local devices=()
        bluezGetControllerDevicesList "$DBUS_BLUEZ_PATH/$controller" devices

        for device in "${devices[@]}"
        do
            OUT_DEVICE_PATHS[${#OUT_DEVICE_PATHS[*]}]="$DBUS_BLUEZ_PATH/$controller/$device"
        done
    done
}

## bluezDeviceName
## @brief - Функция вернёт имя bluetooth устройства
## @param $1 - Путь устройства
## @return Вернёт имя bluetooth устройства
function bluezDeviceName() {
    local DEVICE_PATH="$1"
    echo $(getString $(dbusGetProperty "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE" "$DBUS_BLUEZ_DEVICE_NAME_PROPERTY"))
}

## bluezDeviceAddres
## @brief - Функция вернёт адрес bluetooth устройства
## @param $1 - Путь устройства
## @return Вернёт адрес bluetooth устройства
function bluezDeviceAddres() {
    local DEVICE_PATH="$1"
    echo $(getString $(dbusGetProperty "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE" "$DBUS_BLUEZ_DEVICE_ADDRESS_PROPERTY"))
}

## bluezDeviceIsConneced
## @brief - Функция проверит подключено ли устройство на данный момент
## @param $1 - Путь устройства
## @return Вернёт истину если подключено
function bluezDeviceIsConneced() {
    local DEVICE_PATH="$1"
    $(getBool $(dbusGetProperty "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE" "$DBUS_BLUEZ_DEVICE_CONNECTED_PROPERTY")) == 'true'
}

## bluezDeviceIsTrusted
## @brief - Функция проверит доверенное ли устройство
## @param $1 - Путь устройства
## @return Вернёт истину если доверенное
function bluezDeviceIsTrusted() {
    local DEVICE_PATH="$1"
    $(getBool $(dbusGetProperty "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE" "$DBUS_BLUEZ_DEVICE_TRUSTED_PROPERTY")) == 'true'
}

## bluezDeviceIsBlocked
## @brief - Функция проверит заблокировано ли устройство
## @param $1 - Путь устройства
## @return Вернёт истину если заблокировано
function bluezDeviceIsBlocked() {
    local DEVICE_PATH="$1"
    $(getBool $(dbusGetProperty "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE" "$DBUS_BLUEZ_DEVICE_BLOCKED_PROPERTY")) == 'true'
}

## bluezConnectDevice
## @brief - Функция выполнит подключение bluetooth устройства
## @param $1 - Путь устройства
function bluezConnectDevice() {
    local DEVICE_PATH="$1"
    if ! $(bluezDeviceIsConneced "$DEVICE_PATH"); then
        dbusCallMethod "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE.$DBUS_BLUEZ_DEVICE_CONNECT_METHOD" ''
    fi
}

## bluezDisconnectDevice
## @brief - Функция выполнит отключение bluetooth устройства
## @param $1 - Путь устройства
function bluezDisconnectDevice() {
    local DEVICE_PATH="$1"
    if $(bluezDeviceIsConneced "$DEVICE_PATH"); then
        dbusCallMethod "$DBUS_BLUEZ_OBJECT" "$DEVICE_PATH" "$DBUS_BLUEZ_DEVICE_INTERFACE.$DBUS_BLUEZ_DEVICE_DISCONNECT_METHOD" ''
    fi
}

function waitBluetoothInit() {
    local result=1
    local try=10
    # Если bluetooth не активирован, активируем его
    if [[ "$(bluetooth)" =~ "= off" ]]; then bluetooth on; fi

    while [ "$try" -gt 0 ]
    do
        local device_paths=()
        bluezGetDevicePaths device_paths

        if [ -z "${device_paths[@]}" ]; then
            # Устройства не получены, попытка завершилась неудачей
            try=$((try-1))
            sleep 1
        else
            # Устройства успешно получены, bluetooth инициализирован
            result=0
            break
        fi
    done

    return $result
}

#================================================================================
# main
#================================================================================
case "$1" in
    pre)
        if [ -f "$TEMP_SLEEP_FILE_PATH" ]; then rm "$TEMP_SLEEP_FILE_PATH"; fi
        touch "$TEMP_SLEEP_FILE_PATH"
        # Получаем список bluetooth устройств
        paths=()
        bluezGetDevicePaths paths

        for path in "${paths[@]}"
        do
            # Подключённые на данный момент устройства запишем во временный файл
            if $(bluezDeviceIsConneced "$path"); then
                echo $(bluezDeviceAddres "$path") >> "$TEMP_SLEEP_FILE_PATH"
            fi
        done
        ;;
    post)
        # Отсутствующий или пустой файл игнорируем
        if [ ! -f "$TEMP_SLEEP_FILE_PATH" ] || [ ! -s "$TEMP_SLEEP_FILE_PATH" ]; then exit 0; fi
        # Получаем список ранее активных устройств
        device_address_list="$(cat "$TEMP_SLEEP_FILE_PATH" | tr "\n" "|")"

        # Ожидаем инициализации bluetooth
        waitBluetoothInit
        # Если инициализация не прошла, выходим
        if [ $? -gt 0 ]; then exit 0; fi
        # Получаем список всех устройств bluetooth
        paths=()
        bluezGetDevicePaths paths

        for path in "${paths[@]}"
        do
            # Для отключённых доверенных не заблокированных устройств
            if ! $(bluezDeviceIsConneced "$path") && ! $(bluezDeviceIsBlocked "$path") && $(bluezDeviceIsTrusted "$path"); then
                address="$(bluezDeviceAddres "$path")"
                # Входящих в список из файла
                if [[ "$address" =~ ^($device_address_list)$ ]]; then
                    try=30
                    while ! $(bluezDeviceIsConneced "$path") && [ "$try" -gt 0 ]; do
                        if [[ "$(bluetooth)" =~ "= off" ]]; then bluetooth on; fi
                        # Выполним подключение для восстановления соединения
                        bluezConnectDevice "$path"
                        try=$((try-1))
                        sleep 1
                    done
                fi
            fi
        done

        rm "$TEMP_SLEEP_FILE_PATH"
        ;;
esac

exit 0
#================================================================================
