#!/bin/bash

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

# Определяем местоположение скрипта
SCRIPT_PATH=$(dirname "$(readlink -f "$0")")
# Импортируем общие функции
source $SCRIPT_PATH/simcontrol-constans
source $SCRIPT_PATH/simcontrol-config

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

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

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

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

## getArray
## @brief - Функция преобразования вывода DBUS к массиву
function getArray() {
    local ARRAY=( $(echo $@ | sed 's/.*\[ //;s/\ ].*//;') )
    echo ${ARRAY[@]}
}

#==============================================================================
# DBus
#==============================================================================
## 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
}

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

    Result=$(dbus-send --system \
            --dest=$DEST_OBJECT \
            --type=method_call \
            --print-reply \
            $INTERFACE_PATH \
            $METHOD_NAME \
            $VARS
    )

    echo $Result
}

#==============================================================================
# Modem
#==============================================================================
## modemGetList
## @brief - Функция вернёт список интерфейсов модемов
## @param $1 - Ссылка на заполняемый список
## @return Вернёт список интерфейсов модемов
function modemGetList() {
    declare -n OUT_MODEMS=$1
    OUT_MODEMS=( $(mmcli --list-modems | awk '{ print $1 }') )
}

## modemGetBySimICCID
## @brief - Функция вернёт модем по ICCID его сим-карты
## @param $1 - ICCID искомой сим-карты
## @return Вернёт интерфейс модема сим-карты или пустую строку
function modemGetBySimICCID() {
    local ICCID=$1
    local OutModemInterface=""
    # Получаем список модемов
    local ModemList=()
    modemGetList ModemList
    # Ищем модем, управляющий сим картой с указанным ICCID
    for ModemInterface in ${ModemList[@]}
    do
        # Получаем интерфейс сим-карты модема
        local SimInterface=$(modemGetSimInterface "$ModemInterface")
        if [ -z "$SimInterface" ]; then continue; fi

        # Получаем ICCID сим-карты
        local SimICCID="$(simGetICCID $SimInterface)"
        # Если ICCID сошлись то это и есть наш модем
        if [ "$SimICCID" == "$ICCID" ]; then
            OutModemInterface="$ModemInterface"
            break;
        fi
    done
    # Вернём интерфейс модема
    echo "$OutModemInterface"
}

## modemGetProperty
## @brief - Функция запросит свойство модема
## @param $1 - Путь к объекту модема
## @param $2 - Имя свойства
## @return Вернёт свойство в формате DBUS (требуется преобразование)
function modemGetProperty() {
    echo $(dbusGetProperty $DBUS_MODEMMANAGER_OBJECT $1 $DBUS_MODEM_INTERFACE $2)
}

## modemGetSimInterface
## @brief - Функция запросит путь интерфейса sim-карты модема
## @param $1 - Путь к объекту модема
## @return Вернёт свойство в формате DBUS (требуется преобразование)
function modemGetSimInterface() {
    # Получаем данные
    local val=$(getString $(modemGetProperty $1 $DBUS_MODEM_SIM_PROPERTY))
    # Обрезаем начальные и конечные проблелы (trim)
    val="$(echo "${val}" | sed -e 's/^[[:space:]]*//')"
    # Обрабатываем пустой путь
    if [ "$val" == '/' ]; then val=''; fi

    echo "$val"
}

## modemGetDevice
## @brief - Функция запросит путь устройства модема
## @param $1 - Путь к объекту модема
## @return Вернёт свойство в формате DBUS (требуется преобразование)
function modemGetDevice() {
    # Получаем данные
    local val=$(getString $(modemGetProperty $1 $DBUS_MODEM_DEVICE_PROPERTY))
    # Обрезаем начальные и конечные проблелы (trim)
    val="$(echo "${val}" | sed -e 's/^[[:space:]]*//')"
    # Обрабатываем пустой путь
    if [ "$val" == '/' ]; then val=''; fi

    echo "$val"
}

## modemCallMethod
## @brief - Функция вызовет метод модема
## @param $1 - Путь в объекте DBUS
## @param $2 - Вызываемый метод
## @param $3 - Параметры
## @return Вернёт результат вызова метода
function modemCallMethod() {
    echo $(dbusCallMethod $DBUS_MODEMMANAGER_OBJECT $1 $2 "$3")
}

## modemSetEnable
## @brief - Функция установит режим активности модема
## @param $1 - Интерфейс модема
## @param $2 - Значение активности
## @return Вернёт результат вызова метода
function modemSetEnable() {
    local MODEM_INTERFACE=$1
    local VALUE=$2
    echo $(modemCallMethod $MODEM_INTERFACE $DBUS_MODEM_INTERFACE.Enable "boolean:$VALUE")
}

#==============================================================================
# Sim
#==============================================================================
## simGetProperty
## @brief - Функция запросит свойство sim-карты
## @param $1 - Путь к объекту sim-карты
## @param $2 - Имя свойства
## @return Вернёт свойство в формате DBUS (требуется преобразование)
function simGetProperty() {
    echo $(dbusGetProperty $DBUS_MODEMMANAGER_OBJECT $1 $DBUS_SIM_INTERFACE $2)
}

## simGetICCID
## @brief - Функция запросит ICCID (идентификатор) sim-карты
## @param $1 - Путь к объекту sim-карты
## @return Вернёт ICCID в формате строки
function simGetICCID() {
    echo $(getString $(simGetProperty $1 $DBUS_SIM_ICCID_PROPERTY))
}

#==============================================================================
# Device
#==============================================================================
## deviceGetVendorID
## @brief - Функция запросит идентификатор вендора устройства
## @param $1 - Путь к устройству
## @return Вернёт идентификатор вендора
function deviceGetVendorID() {
    local VENDOR_PATH="$1/idVendor"
    local Vendor=""

    if [ -f $VENDOR_PATH ]; then Vendor=$(cat "$VENDOR_PATH"); fi
    echo "$Vendor"
}

## deviceGetProductID
## @brief - Функция запросит идентификатор продукта устройства
## @param $1 - Путь к устройству
## @return Вернёт идентификатор продукта
function deviceGetProductID() {
    local PRODUCT_PATH="$1/idProduct"
    local Product=""

    if [ -f $PRODUCT_PATH ]; then Product=$(cat "$PRODUCT_PATH"); fi
    echo "$Product"
}

#==============================================================================
# Functions
#==============================================================================
## waitModemManager
## @brief - Функция ожидает инициализации ModemManager
function waitModemManager() {
    local try=30
    local ModemList=()

    while [ $try -gt 0 ]
    do
        modemGetList ModemList
# echo "ModemList=${ModemList[@]}"
        if [[ -z ${ModemList[@]} || "${ModemList[@]}" == 'No' ]]; then
            try=$((try-1))
            sleep 1
        else
            break
        fi
    done
    # Если так и не получили список
    if [[ -z ${ModemList[@]} || "${ModemList[@]}" == 'No' ]]; then
        return 101;
    else
        return 0
    fi
}

## reloadUdev
## @brief - Функция перезагрузит udev
function reloadUdev() {
    udevadm control --reload
    udevadm trigger
    # TODO: Реализовать более надёжный механизм ожидания перезагрузки udev
    sleep 3
}

## mmApplay
## @brief - Функция применит изменения для ModemManager
function mmApplay() {
    reloadUdev
    systemctl restart ModemManager.service
}

## mmApplay
## @brief - Функция применит изменения для ModemManager и дождётся его инициализации
function mmApplayAndWait() {
    mmApplay
    waitModemManager
    return $?
}

## removeDeviceFilter
## @brief - Функция удалит фильтр устройств
function removeDeviceFilter() {
    local result=0

    if [ -f "$SIM_UDEV_FILTER_PATH" ]; then
        # Если файл фильтра существует - сносим его
        rm "$SIM_UDEV_FILTER_PATH"
        if [ -f "$SIM_UDEV_FILTER_PATH" ]; then result=1; fi
    fi

    return $result
}

## creatDeviceFilter
## @brief - Функция создаст фильтр устройств
## @param $1 - Список устройств
function creatDeviceFilter() {
    declare -n DEVICE_LIST=$1
    local result=0

    if [ ${#DEVICE_LIST[@]} -gt 0 ]; then
        # Добавляем проверку действия:
        echo 'ACTION!="add|change|move", GOTO="mm_blacklist_internal_modem_end"' > "$SIM_UDEV_FILTER_PATH"

        for DevicePath in ${DEVICE_LIST[@]}
        do
            local DeviceVendorID=$(deviceGetVendorID "$DevicePath")
            local DeviceProductID=$(deviceGetProductID "$DevicePath")
            # Запрещаем обработку этого устройства
            echo "ATTRS{idVendor}==\"$DeviceVendorID\", ATTRS{idProduct}==\"$DeviceProductID\", ENV{ID_MM_DEVICE_IGNORE}=\"1\"" >> "$SIM_UDEV_FILTER_PATH"
        done

        # Добавляем метку выхода
        echo 'LABEL="mm_blacklist_internal_modem_end"' >> "$SIM_UDEV_FILTER_PATH"

        # Применим изменения
        mmApplay

        if ! [ -f "$SIM_UDEV_FILTER_PATH" ]; then result=1; fi
    fi

    return $result
}

## setSimBlackList
## @brief - Функция задаст чёрный список модемов сим-карт
## @param $1 - Перечень идентификаторов сим-карт, модемы которых нужно блокировать
function setSimBlackList() {
    declare -n ICCIDS=$1
    local result=0
    # Удалим существующий фильтр
    removeDeviceFilter
    # Ожидаем применение изменений
    mmApplayAndWait
    result=$?

    # Если не возникло ошибок продолжаем обработку
    if [ $result -eq 0 ]; then
        # Если список ICCID сим-карт не пуст
        if ! [[ -z "${ICCIDS[@]}" ]]; then
            local DeviceList=()

            # Перебираем список идентификаторов симкарт
            for ICCID in ${ICCIDS[@]}
            do
                # Получаем интерфейс модема, управляющего сим-картой с данным ICCID
                local ModemInterface=$(modemGetBySimICCID "$ICCID")
                if [ -z "$ModemInterface" ]; then continue; fi
                # Получаем устройство модема в udev
                local ModemDevice=$(modemGetDevice "$ModemInterface")
                if ! [ -d "$ModemDevice" ]; then continue; fi
                # Отключаем модем
                modemSetEnable "$ModemInterface" 'false'
                # Добавляем устройства в список на блокировку
                DeviceList[${#DeviceList[*]}]="$ModemDevice"
            done

            # Создаём новый фильтр
            creatDeviceFilter DeviceList
            result=$?
        fi
    fi # [ $result -eq 0 ]

    return $result
}

## setSimWhiteList
## @brief - Функция задаст белый список модемов сим-карт
## @param $1 - Перечень идентификаторов сим-карт, модемы которых не нужно блокировать
function setSimWhiteList() {
    declare -n ICCIDS=$1
    local result=0
    # Удалим существующий фильтр
    removeDeviceFilter
    # Ожидаем применение изменений
    mmApplayAndWait
    result=$?

    # Если не возникло ошибок продолжаем обработку
    if [ $result -eq 0 ]; then
        local DeviceList=()
        local ModemList=()
        modemGetList ModemList

        # Перебираем список доступных модемов
        for ModemInterface in ${ModemList[@]}
        do
            # Получаем интерфейс сим-карты модема
            local SimInterface=$(modemGetSimInterface "$ModemInterface")
            if [ -z "$SimInterface" ]; then continue; fi

            # Получаем ICCID сим-карты
            local SimICCID=$(simGetICCID "$SimInterface")
            # Проверяем что ICCID текущей сим-карты в списке
            local Contain=false
            for ICCID in ${ICCIDS[@]}
            do
                if [ "$SimICCID" == "$ICCID" ]; then
                    Contain=true
                    break;
                fi
            done

            # Если сим-карта модема не содержится в белом списке
            if [ $Contain == false ]; then
                # Получаем устройство модема в udev
                local ModemDevice=$(modemGetDevice "$ModemInterface")
                if ! [ -d "$ModemDevice" ]; then continue; fi
                # Отключаем модем
                modemSetEnable "$ModemInterface" 'false'
                # Добавляем устройства в список на блокировку
                DeviceList[${#DeviceList[*]}]="$ModemDevice"
            fi
        done

        # Создаём новый фильтр
        creatDeviceFilter DeviceList
        result=$?
        # Применим изменения
        mmApplay
    fi # [ $result -eq 0 ]

    return $result
}

## applySimBlackList
## @brief - Функция применит чёрный список модемов сим-карт
applySimBlackList() {
    local ICCID_LIST=( $(echo $(iniGetSimList) | sed "s/ /\n/g" | awk -F ':' '{print $2}') )
    setSimBlackList ICCID_LIST
    return $?
}

## applySimWhiteList
## @brief - Функция применит белый список модемов сим-карт
applySimWhiteList() {
    local ICCID_LIST=( $(echo $(iniGetSimList) | sed "s/ /\n/g" | awk -F ':' '{print $2}') )
    setSimWhiteList ICCID_LIST
    return $?
}
