#!/bin/bash
#
# Copyright (C) 2024 MOXA Inc. All rights reserved.
# This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
# See the file LICENSE for details.
#
# Authors:
#       2024    Elvis Yao <ElvisCW.Yao@moxa.com>
# Description:
#       Disk hotswap daemon

source "/usr/lib/mx-gpio-lib"
source "/usr/lib/mx-common-lib"

MODEL_NAME=""

function V2406D::profile() {
        NUM_OF_DISK=2
        GPIO_BTN_PIN=(59 61)
        GPIO_LED_PIN=(58 60)
        GPIO_CHIP="$(gpio_get_gpiochip_name gpio_it87)"
        DISK_DEVPATH=(
                "/devices/pci0000:00/0000:00:17.0/ata1/host0/target0:0:0/0:0:0:0/block/"
                "/devices/pci0000:00/0000:00:17.0/ata2/host1/target1:0:0/1:0:0:0/block/"
        )
        BTN_ACTIVE_LOW=true
}

function V2406D::init() {
        BUTTON_ACTION=("" "")

        for ((i = 0; i < $NUM_OF_DISK; i++)); do
                scan_disk $i
        done
}

function V2406D::button_action_handler() {
        local btn_idx="$1"
        local edge_event="$2"
        local t_ms="$3"
        local remove_time_ms=3000

        case "$edge_event" in
        "release")
                if [[ "${BUTTON_ACTION[$btn_idx]}" == "scan" ]]; then
                        scan_disk $btn_idx
                elif [[ "${BUTTON_ACTION[$btn_idx]}" == "remove" ]]; then
                        umount_partition $btn_idx
                        remove_disk $btn_idx
                fi
                BUTTON_ACTION[$btn_idx]=""
                ;;
        "press")
                if [[ "${BUTTON_ACTION[$btn_idx]}" == "" ]]; then
                        BUTTON_ACTION[$btn_idx]="scan"
                fi
                ;;
        "holding")
                if [[ "$t_ms" -ge "$remove_time_ms" ]]; then
                        BUTTON_ACTION[$btn_idx]="remove"
                fi
                ;;
        esac
}

function V2406D::mount() {
        local disk_idx="$1"
        local disk_dev="$2"
        local mount_point
        local syspath
        local is_mounted
        local p_name
        local p_num

        is_mounted=false

        syspath="/sys/class/block/${disk_dev##*/}"

        for part_dir in "${syspath}"/*; do
                if [ -f "${part_dir}/partition" ]; then
                        p_name="${part_dir##*/}"
                        p_num=$(cat "${part_dir}/partition")
                        mount_point="/media/disk${disk_idx}p${p_num}"

                        if findmnt "$mount_point"; then
                                echo "Partition ${mount_point} is already mounted"
                                is_mounted=true
                                continue
                        fi

                        mkdir -p "$mount_point"

                        if mount "/dev/${p_name}" "$mount_point"; then
                                echo "Partition ${mount_point} is mounted successfully"
                                # If a partition mounts successfully, it is considered a success.
                                is_mounted=true
                        else
                                rmdir "$mount_point"
                                echo "Partition ${mount_point} mount failed"
                        fi
                fi
        done

        if [[ ${is_mounted} = true ]]; then
                V2406D::set_led_state $disk_idx 1
        else
                V2406D::set_led_state $disk_idx 0
        fi
}

function V2406D::umount() {
        local disk_idx="$1"
        local mount_points

        readarray -t mount_points < <(findmnt -l -n -o TARGET | grep "^/media/disk${disk_idx}")

        blink_led $disk_idx 3

        if ((${#mount_points[@]} > 0)); then
                V2406D::set_led_state $disk_idx 1
        else
                V2406D::set_led_state $disk_idx 0
        fi
}

function V2406D::set_led_state() {
        local idx
        local state
        idx=${1}
        state=${2}

        gpio_set_value_libgpiod ${GPIO_LED_PIN[$idx]} $state $GPIO_CHIP
}

function V3400::profile() {
        NUM_OF_DISK=2
        GPIO_BTN_PIN=(2 4)
        GPIO_LED_PIN=(3 5)
        GPIO_CHIP="$(gpio_get_gpiochip_name cp2112_gpio)"
        DISK_DEVPATH=(
                "/devices/pci0000:00/0000:00:17.0/ata1/host0/target0:0:0/0:0:0:0/block/"
                "/devices/pci0000:00/0000:00:17.0/ata2/host1/target1:0:0/1:0:0:0/block/"
        )
        BTN_ACTIVE_LOW=true
}

function V3400::init() {
        BUTTON_ACTION=("" "")

        for ((i = 0; i < $NUM_OF_DISK; i++)); do
                scan_disk $i
        done
}

function V3400::button_action_handler() {
        local btn_idx="$1"
        local edge_event="$2"
        local t_ms="$3"
        local remove_time_ms=3000

        case "$edge_event" in
        "release")
                if [[ "${BUTTON_ACTION[$btn_idx]}" == "scan" ]]; then
                        scan_disk $btn_idx
                elif [[ "${BUTTON_ACTION[$btn_idx]}" == "remove" ]]; then
                        umount_partition $btn_idx
                        remove_disk $btn_idx
                fi
                BUTTON_ACTION[$btn_idx]=""
                ;;
        "press")
                if [[ "${BUTTON_ACTION[$btn_idx]}" == "" ]]; then
                        BUTTON_ACTION[$btn_idx]="scan"
                fi
                ;;
        "holding")
                if [[ "$t_ms" -ge "$remove_time_ms" ]]; then
                        BUTTON_ACTION[$btn_idx]="remove"
                fi
                ;;
        esac
}

function V3400::mount() {
        local disk_idx="$1"
        local disk_dev="$2"
        local mount_point
        local syspath
        local is_mounted
        local p_name
        local p_num

        is_mounted=false

        syspath="/sys/class/block/${disk_dev##*/}"

        for part_dir in "${syspath}"/*; do
                if [ -f "${part_dir}/partition" ]; then
                        p_name="${part_dir##*/}"
                        p_num=$(cat "${part_dir}/partition")
                        mount_point="/media/disk${disk_idx}p${p_num}"

                        if findmnt "$mount_point"; then
                                echo "Partition ${mount_point} is already mounted"
                                is_mounted=true
                                continue
                        fi

                        mkdir -p "$mount_point"

                        if mount "/dev/${p_name}" "$mount_point"; then
                                echo "Partition ${mount_point} is mounted successfully"
                                # If a partition mounts successfully, it is considered a success.
                                is_mounted=true
                        else
                                rmdir "$mount_point"
                                echo "Partition ${mount_point} mount failed"
                        fi
                fi
        done

        if [[ ${is_mounted} = true ]]; then
                V3400::set_led_state $disk_idx 1
        else
                V3400::set_led_state $disk_idx 0
        fi
}

function V3400::umount() {
        local disk_idx="$1"
        local mount_points

        readarray -t mount_points < <(findmnt -l -n -o TARGET | grep "^/media/disk${disk_idx}")

        blink_led $disk_idx 3

        if ((${#mount_points[@]} > 0)); then
                V3400::set_led_state $disk_idx 1
        else
                V3400::set_led_state $disk_idx 0
        fi
}

function V3400::set_led_state() {
        local port
        local state
        port=${1}
        state=${2}

        set_cp2112_led_state_libgpiod "$port" "$state"
}

function set_cp2112_led_state_libgpiod() {
        local idx=$1
        local state=$2
        local check_state
        local ret

        gpio_set_value_libgpiod ${GPIO_LED_PIN[$idx]} $state $GPIO_CHIP
        [[ $? -ne 0 ]] && ret=1

        check_state=$(gpio_get_value_libgpiod ${GPIO_LED_PIN[$idx]} $GPIO_CHIP)
        [[ $? -ne 0 ]] && ret=1
        if [[ $ret -ne 0 || "$state" -ne "$check_state" ]]; then
                echo "Set State Failed"
                exit $ret
        fi
}

function button_monitor() {
        local is_press=()
        local press_start=()
        local curr_stat=()
        local prev_stat=()

        prev_stat=($(gpio_get_values_libgpiod $GPIO_CHIP ${GPIO_BTN_PIN[@]}))

        while true; do
                curr_stat=($(gpio_get_values_libgpiod $GPIO_CHIP ${GPIO_BTN_PIN[@]}))

                for ((i = 0; i < $NUM_OF_DISK; i++)); do
                        curr=${curr_stat[$i]}
                        prev=${prev_stat[$i]}

                        if [[ "$curr" != "$prev" ]]; then
                                t_ms=$(date +%s%3N)

                                if [[ $BTN_ACTIVE_LOW = false && "$prev" == "0" && "$curr" == "1" ]] ||
                                        [[ $BTN_ACTIVE_LOW = true && "$prev" == "1" && "$curr" == "0" ]]; then
                                        "${MODEL_NAME}"::button_action_handler "$i" "press"
                                        is_press[$i]=true
                                        press_start[$i]=$t_ms
                                elif [[ $BTN_ACTIVE_LOW = false && "$prev" == "1" && "$curr" == "0" ]] ||
                                        [[ $BTN_ACTIVE_LOW = true && "$prev" == "0" && "$curr" == "1" ]]; then
                                        "${MODEL_NAME}"::button_action_handler "$i" "release"
                                        is_press[$i]=false
                                        press_start[$i]=0
                                fi
                                prev_stat[$i]="${curr_stat[$i]}"
                        else
                                if [[ ${is_press[$i]} = true ]]; then
                                        now=$(date +%s%3N)
                                        "${MODEL_NAME}"::button_action_handler "$i" "holding" $((now - press_start[$i]))
                                fi
                        fi
                done

                sleep 0.1
        done
}

function disk_uevent_monitor() {
        local action
        local devpath
        local devname
        local devtype
        local disk_idx

        udevadm monitor --kernel --property --subsystem-match=block |
                while IFS= read -r line; do
                        case "$line" in
                        ACTION=*) action="${line#ACTION=}" ;;
                        DEVPATH=*) devpath="${line#DEVPATH=}" ;;
                        DEVNAME=*) devname="${line#DEVNAME=}" ;;
                        DEVTYPE=*) devtype="${line#DEVTYPE=}" ;;
                        "")
                                if [[ "$devtype" != "disk" ]]; then
                                        continue
                                fi

                                disk_idx=""
                                for ((i = 0; i < $NUM_OF_DISK; i++)); do
                                        if [[ "$devpath" == *"${DISK_DEVPATH[$i]}"* ]]; then
                                                disk_idx=$i
                                                break
                                        fi
                                done

                                if [[ -z "$disk_idx" ]]; then
                                        continue
                                fi

                                case "$action" in
                                "add" | "change")
                                        "${MODEL_NAME}"::mount "$disk_idx" "$devname"
                                        ;;
                                "remove")
                                        "${MODEL_NAME}"::umount "$disk_idx" "$devname"
                                        ;;
                                esac

                                action=""
                                devpath=""
                                devname=""
                                devtype=""
                                disk_idx=""
                                ;;
                        esac
                done
}

function blink_led() {
        local led_idx=$1
        local count=$2
        local interval=$3

        interval=${interval:-1} # default 1 second

        for ((i = 1; i <= count; i++)); do
                "${MODEL_NAME}"::set_led_state $led_idx 1
                sleep "$interval"
                "${MODEL_NAME}"::set_led_state $led_idx 0
                sleep "$interval"
        done
}

function umount_partition() {
        local disk_idx=$1
        local mount_points

        readarray -t mount_points < <(findmnt -l -n -o TARGET | grep "^/media/disk${disk_idx}")

        if ((${#mount_points[@]} == 0)); then
                echo "No mount points found for Disk $((disk_idx + 1))"
                return
        fi

        sync # flush data to disk

        for mp in "${mount_points[@]}"; do
                if umount "$mp"; then
                        echo "Unmount $mp successfully"
                        rmdir "$mp"
                else
                        echo "Error: Failed to unmount $mp. Attempting lazy unmount..."
                        umount -l "$mp"
                        rmdir "$mp"
                        echo "Lazy unmount initiated for $mp"
                fi
        done
}

function scan_disk() {
        local disk_index=${1}
        local host="host${disk_index}"
        local target="target${disk_index}:0:0"

        # rescan bus
        dev_host="/sys/class/scsi_host/${host}"
        if [ -d "$dev_host" ]; then
                echo "- - -" >${dev_host}/scan
                udevadm trigger --action=add --property-match="DEVPATH=${DISK_DEVPATH[$disk_index]}*"
        else
                echo "Disk host $host is not found"
                return 1
        fi
}

function remove_disk() {
        local disk_index=${1}
        local host="host${disk_index}"
        local target="target${disk_index}:0:0"
        local path="${disk_index}:0:0:0"
        local field="\$1"

        # remove disks
        if [[ -f /sys/class/scsi_host/${host}/device/${target}/${path}/delete ]]; then
                echo 1 >/sys/class/scsi_host/${host}/device/${target}/${path}/delete
                echo "Remove disk $((disk_index + 1)) successfully"
        else
                echo "WARNING! can't remove disk $((disk_index + 1)). It may has been removed before"
                return 1
        fi

        return 0
}

function load_model_name() {
        for name in $(get_model_name_from_dmi_type12); do
                if [[ "$(type -t "${name}::profile")" = 'function' ]]; then
                        MODEL_NAME="${name}"
                        break
                fi
        done

        if [[ -z "${MODEL_NAME}" ]]; then
                for name in $(get_model_name_from_dmi_type1); do
                        if [[ "$(type -t "${name}::profile")" = 'function' ]]; then
                                MODEL_NAME="${name}"
                                break
                        fi
                done
        fi

        if [[ -z "${MODEL_NAME}" ]]; then
                echo "Unsupported model"
                exit 38
        fi
}

function main() {
        pids=()
        load_model_name

        echo "The model ${MODEL_NAME} is loaded successfully"

        if [[ ! $(type -t "${MODEL_NAME}"::profile) == function ]]; then
                echo "${MODEL_NAME} profile function is not define"
                exit 1
        fi

        "${MODEL_NAME}"::profile

        thread_start disk_uevent_monitor
        pids+=($!)

        if [[ ! $(type -t "${MODEL_NAME}"::init) == function ]]; then
                echo "${MODEL_NAME} init function is not define"
                exit 1
        fi

        "${MODEL_NAME}"::init

        thread_start button_monitor
        pids+=($!)

        thread_join "${pids[@]}"
}

main "$@"
