#!/bin/bash

#https://habr.com/ru/post/520488/

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

# Определяем местоположение скрипта
SCRIPT_PATH=$(dirname "$(readlink -f "$0")")
# Импортируем функции
source $SCRIPT_PATH/super_user_check
source $SCRIPT_PATH/astra-mobile-clean-functions
source $SCRIPT_PATH/astra-mobile-clean-and-lock-config

check_root_rights

#==============================================================================
# Возможные результаты детектирования
#==============================================================================
# Выходное значение детектора: "Детектор инициализирован"
declare -i DETECTOR_INIT=0
# Выходное значение детектора: "Сработал детектор встряски"
declare -i SHAKE_DETECTED=1
# Выходное значение детектора: "Сработал детектор удара"
declare -i HIT_DETECTED=2
# Выходное значение детектора: "Детектор не сработал"
declare -i NOT_DETECTED=3

#==============================================================================
# Глобальные переменные
#==============================================================================
# Путь к устройству акселерометра (Определяется функцией accelGetDevicePath)
declare IIO_ACCEL_DEVICE_PATH=""
# Длительность прерывания обработка (миллисекундах)
declare PROCESS_INTERRUPT_VAL
# Порог чувствительности встряски
declare SHAKE_THRESHOLD
# Порог чувствительности удара
declare HIT_THRESHOLD

# Текущие значения X,Y,Z
declare currentX=0
declare currentY=0
declare currentZ=0

# Предыдущие значения X,Y,Z
declare previousX=0
declare previousY=0
declare previousZ=0

#==============================================================================
# Функции
#==============================================================================
## @abs
## @brief - Функция вернёт значение числа по модулю
## @param $1 - Число со знаком
## @return Вернёт значение числа по модулю
function abs() {
    local VAL=$1
    echo ${VAL#-}
}

## @calc
## @brief - Функция вычислит простое выражение (в том числе и с дробными числами)
## @param $1 - Выражение в формате строки. Например '10 / 3'
## @return Вернёт результат вычисления выражения
function calc() {
    local EXPRESSION="$1"
    echo $( echo "$EXPRESSION" | bc -l )
}

## @isTrue
## @brief - Функция вычислит простое логическое выражение (в том числе и с дробными числами)
## @param $1 - Выражение в формате строки. Например '10 >= 3'
## @return Вернёт признак истинности выражения
function isTrue() {
    local EXPRESSION="$1"
    [ $(calc "$EXPRESSION") -ne 0 ]
}

## @tick
## @brief - Функция вернёт количество миллисекунд от начала эпохи
## @return Вернёт количество миллисекунд от начала эпохи
function tick() {
    echo $(date +%s%N | cut -b1-13)
}

## @tock
## @brief - Функция вернёт разницу между текущим и полученным временем в миллисекундах от начала эпохи
## @param $1 - Количество миллисекунд от начала эпохи (Из функции tick)
## @return Вернёт разницу во времени в миллисекундах
function tock() {
    local tick_time=$1
    tock_time=$(tick)
    echo $(calc "$tock_time-$tick_time")
}

## @getInterrupt
## @brief - Функция вернёт значение продолжительности прерывания обработки
## @return Вернёт значение продолжительности прерывания обработки
function getInterrupt() {
    # Обработка будет примерно 3 раза в секунду (дробную часть обрежем)
    echo $(calc '1000 / 3' | sed -r 's/\..+//')
}

#==============================================================================
# Акселерометр
#==============================================================================
## @accelGetDevicePath
## @brief - Функция вернёт путь к устройству акселерометра
## @return Вернёт путь к устройству акселерометра
function accelGetDevicePath() {
    local IIO_DEVICES=( $(ls /dev | grep 'iio:') )
    local outSensorPath=""

    for Device in ${IIO_DEVICES[@]}
    do
        local IIO_Type="$(udevadm info --query=property --name "/dev/$Device" |  grep 'IIO_SENSOR_PROXY_TYPE=')"
        if [[ $IIO_Type == *"-accel"* ]]; then
            outSensorPath="/sys/$(udevadm info --query=property --name "/dev/$Device" | awk -F '=' '{ if ($1=="DEVPATH") print $2 }' )"
            break
        fi
    done

    echo "$outSensorPath"
}

## @accelGetScale
## @brief - Функция вернёт градацию значений акселерометра
## @return Вернёт градацию значений акселерометра
function accelGetScale() {
    echo $(cat $IIO_ACCEL_DEVICE_PATH/in_accel_scale)
}

## @accelGetOffset
## @brief - Функция вернёт смещение значений акселерометра
## @return Вернёт смещение значений акселерометра
function accelGetOffset() {
    local offset=0
    if [ -f $IIO_ACCEL_DEVICE_PATH/in_accel_offset ]; then offset=$(cat $IIO_ACCEL_DEVICE_PATH/in_accel_offset); fi
    echo $offset
}

## @accelGetRawX
## @brief - Функция вернёт сырые данные акселерометра по X
## @return Вернёт сырые данные по X
function accelGetRawX() {
    echo $(cat $IIO_ACCEL_DEVICE_PATH/in_accel_x_raw)
}

## @accelGetRawY
## @brief - Функция вернёт сырые данные акселерометра по Y
## @return Вернёт сырые данные по Y
function accelGetRawY() {
    echo $(cat $IIO_ACCEL_DEVICE_PATH/in_accel_y_raw)
}

## @accelGetRawZ
## @brief - Функция вернёт сырые данные акселерометра по Z
## @return Вернёт сырые данные по Z
function accelGetRawZ() {
    echo $(cat $IIO_ACCEL_DEVICE_PATH/in_accel_z_raw)
}

## @accelGetX
## @brief - Функция вернёт данные акселерометра по X
## @return Вернёт данные по X
function accelGetX() {
    local raw=$(accelGetRawX)         # int
    local offset=$(accelGetOffset)    # int
    local scale=$(accelGetScale)      # float
    
    echo $(calc "scale=4; $(( raw + offset )) * $scale")
}

## @accelGetY
## @brief - Функция вернёт данные акселерометра по Y
## @return Вернёт данные по Y
function accelGetY() {
    local raw=$(accelGetRawY)         # int
    local offset=$(accelGetOffset)    # int
    local scale=$(accelGetScale)      # float
    
    echo $(calc "scale=4; $(( raw + offset )) * $scale")
}

## @accelGetZ
## @brief - Функция вернёт данные акселерометра по Z
## @return Вернёт данные по Z
function accelGetZ() {
    local raw=$(accelGetRawZ)         # int
    local offset=$(accelGetOffset)    # int
    local scale=$(accelGetScale)      # float
    
    echo $(calc "scale=4; $(( raw + offset )) * $scale")
}

#==============================================================================
# Обработка
#==============================================================================
## @init
## @brief - Функция инициализирует параметры обработки
function init() {
    local try=30
    # При реинициализации нет смысла перезапрашивать путь к устройству. "Никуда оно не денется с подводной лодки" =)
    while [[ -z "$IIO_ACCEL_DEVICE_PATH" && $try -gt 0 ]]
    do
        sleep 1
        IIO_ACCEL_DEVICE_PATH=$(accelGetDevicePath)
        try=$((try-1))
    done

    PROCESS_INTERRUPT_VAL=$(getInterrupt)
    # !! ПОРОГ УДАРА ВСЕГДА ДОЛЖЕН БЫТЬ ВЫШЕ ПОРОГА ВСТРЯСКИ!!
    SHAKE_THRESHOLD=$(iniGetLightModeSensitivity)
    HIT_THRESHOLD=$(calc "$SHAKE_THRESHOLD * 2.5")
}

## @reset
## @brief - Функция сброса
function reset() {
    currentX=0
    currentY=0
    currentZ=0

    previousX=0
    previousY=0
    previousZ=0
}

## @readAccelData
## @brief - Функция считает данные акселерометра
## Реализовано как отдельная функция для ускорения обработке в случаи 
## необходимости повторного доступа к данным на итерации т.к. на некоторых
## устройствах доступ к данным очень затратен по времени.
function readAccelData() {
    currentX=$(accelGetX)
    currentY=$(accelGetY)
    currentZ=$(accelGetZ)
}

## @passDataToTheNext
## @brief - Функция передаст данные для следующей итерации
function passDataToTheNext {
    previousX=$currentX
    previousY=$currentY
    previousZ=$currentZ
}

## @print
## @brief - Функция отладки с выводом данных
function print() {
    echo "X=$currentX; Y=$currentY; Z=$currentZ"
}

## @onDetected
## @brief - Обработчик события детектирования
## @param $1 - Тип события
function onDetected() {
    local EVENT_TYPE=$1

    if [ $EVENT_TYPE -eq $SHAKE_DETECTED ]; then
        echo "[$(date +'%d.%m.%Y %T')] SHAKE DETECTED!"
    elif [ $EVENT_TYPE -eq $HIT_DETECTED ]; then
        echo "[$(date +'%d.%m.%Y %T')] HIT DETECTED!"
    fi
    
    /bin/bash $SCRIPT_PATH/astra-mobile-lock
    sleep 3

    reset
}

## @detectShake
## @brief - Функция детектирования события встряски
## @return Вернёт признак возникновения события встряски
function detectShake() {
    local -n dX=$1
    local -n dY=$2
    local -n dZ=$3
    # 2 из 3 должны превысить заданный порог встряски
    $(isTrue "$dX>=$SHAKE_THRESHOLD") && $(isTrue "$dY>=$SHAKE_THRESHOLD") || $(isTrue "$dX>=$SHAKE_THRESHOLD") && $(isTrue "$dZ>=$SHAKE_THRESHOLD") || $(isTrue "$dY>=$SHAKE_THRESHOLD") && $(isTrue "$dZ>=$SHAKE_THRESHOLD")
}

## @detectHit
## @brief - Функция детектирования события удара
## @return Вернёт признак возникновения события удара
function detectHit() {
    local -n dX=$1
    local -n dY=$2
    local -n dZ=$3
    # Хотя бы 1 должен превысить заданный порог удара (порог всегда должен быть выше порога встряски)
    $(isTrue "$dX>=$HIT_THRESHOLD") || $(isTrue "$dY>=$HIT_THRESHOLD") || $(isTrue "$dZ>=$HIT_THRESHOLD")
}

## @detector
## @brief - Функция детектирования событий акселерометра
## @return Вернёт одно из значений детектора (DETECTOR_INIT|SHAKE_DETECTED|HIT_DETECTED|NOT_DETECTED)
function detector() {
    local outResult=$NOT_DETECTED

    if $(isTrue "$previousX==0") && $(isTrue "$previousY==0") && $(isTrue "$previousZ==0"); then
        outResult=$DETECTOR_INIT
    else
        local deltaX=$( abs $(calc "$previousX - $currentX") )
        local deltaY=$( abs $(calc "$previousY - $currentY") )
        local deltaZ=$( abs $(calc "$previousZ - $currentZ") )

#        echo "dX=$deltaX; dY=$deltaY; dZ=$deltaZ"

        if $(detectShake deltaX deltaY deltaZ); then
            outResult=$SHAKE_DETECTED
        elif $(detectHit deltaX deltaY deltaZ); then
            outResult=$HIT_DETECTED
        fi
    fi

    return $outResult
}

## detectIteration
## @brief - Функция выполнит итерацию детектирования
function detectIteration() {
    # Запускаем детектор на текущей итерации
    detector
    local detectResult=$?
    # Проверяем результат выполнения
    if [[ "$detectResult" != @($NOT_DETECTED|$DETECTOR_INIT) ]]; then
        # Обрабатываем событие детектирования
        onDetected $detectResult
    fi
}

## @loop
## @brief - Функция циклического детектирования
function loop() {
    # Счётчик итераций до реинициализации
    local iterationToReinit=0
    # Предварительная очистка данных
    reset

    while [ 1 ]
    do
        if ! $(iniGetLightModeActive); then
            # Если обработка не активирована в настройках
            # Сбросим счётчик итераций до реинициализации (Реинициализация произойдёт сразу после активации)
            iterationToReinit=0
            # Сбрасываем данные обработки (на случай если деактивация произведена в процессе работы)
            reset
            # Ждём 10 секунд и снова перепроверяем
            sleep 10
        else
            startTime=$(tick)

            if [ $iterationToReinit -eq 0 ]; then
                # Раз в несколько итераций переинициализируем данные на случай изменения настроек
                init
                iterationToReinit=15
            else
                iterationToReinit=$(($iterationToReinit-1))
            fi

            # Читаем данные сенсора
            readAccelData
            # Выполняем итерацию обработки
            detectIteration
            # Передаём данные на следующую итерацию
            passDataToTheNext

            # Вычисляем период прерывания обработки
            timeDifference=$(tock $startTime)
            interruptTime=$(( $PROCESS_INTERRUPT_VAL - $timeDifference ))

            # Отладка
#            print
#            echo "PROCESS_INTERRUPT_VAL=$PROCESS_INTERRUPT_VAL; TimeDiff=$timeDifference; interruptTime=$interruptTime"
#            echo "===================="

            # Прерывание обработки
            if [[ $interruptTime -gt 0 ]]; then sleep 0.$interruptTime; fi
        fi

    done
}

#==============================================================================
# Main
#==============================================================================
# Первичная инициализация
init

if [ -z "$IIO_ACCEL_DEVICE_PATH" ]; then
    echo "Failed to find accelerometer sensor!"
    exit 1
else
    echo "Accel detected: $IIO_ACCEL_DEVICE_PATH"
    # Запускаем цикл обработки
    loop
fi
#==============================================================================
