#!/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.
#
# Name:
#       MOXA DIO Control Utility
#
# Authors:
#       2024    Elvis Yao <ElvisCW.Yao@moxa.com>
#       2024    Wilson Huang <WilsonYS.Huang@moxa.com>
#

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

MODEL_NAME=""
DIO_STATE_STR=("LOW" "HIGH")
NUM_OF_DI=""
NUM_OF_DO=""
NUM_OF_DIO_STATE=${#DIO_STATE_STR[@]}
TARGET_DI_PORT=""
TARGET_DO_PORT=""
TARGET_STATE=""
TARGET_OPCODE=0
TARGET_GET_DI_STATE_OPCODE=1
TARGET_GET_DO_STATE_OPCODE=2
TARGET_DI_DO_INVALID_OPCODE=3
TARGET_STATE_INVALID_OPCODE=4
TARGET_DI_STATE_INVALID_OPCODE=5
TARGET_SET_DO_STATE_OPCODE=6
TARGET_DI_DO_STATE_INVALID_OPCODE=7

OS_ID="$(awk -v opt="ID" -F= '$1==opt { print $2 ;}' /etc/os-release | tr -d '"')"

function RKPA110::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73 80 81 82 83)
        DO_IT87_GPIO_TBL=(74 75 76 77 84 85 86 87)
}

function RKPA110::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function RKPA110::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function RKPA110::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function RKPA110::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function RKPC110::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73 80 81 82 83)
        DO_IT87_GPIO_TBL=(74 75 76 77 84 85 86 87)
}

function RKPC110::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function RKPC110::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function RKPC110::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function RKPC110::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function MPC3000::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73)
        DO_IT87_GPIO_TBL=(74 75 76 77)
}

function MPC3000::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function MPC3000::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function MPC3000::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function MPC3000::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function BXPA101::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73)
        DO_IT87_GPIO_TBL=(74 75 76 77)
}

function BXPA101::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function BXPA101::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function BXPA101::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function BXPA101::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function BXPA100::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73)
        DO_IT87_GPIO_TBL=(74 75 76 77)
}

function BXPA100::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function BXPA100::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function BXPA100::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function BXPA100::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function BXPC100::profile() {
        DI_IT87_GPIO_TBL=(70 71 72 73)
        DO_IT87_GPIO_TBL=(74 75 76 77)
}

function BXPC100::init() {
        NUM_OF_DI=${#DI_IT87_GPIO_TBL[@]}
        NUM_OF_DO=${#DO_IT87_GPIO_TBL[@]}
}

function BXPC100::get_di_state() {
        get_dio_state_it87 "di" "$1"
}

function BXPC100::get_do_state() {
        get_dio_state_it87 "do" "$1"
}

function BXPC100::set_do_state() {
        set_do_state_it87 "$1" "$2"
}

function get_dio_state_it87() {
        local direction
        local dio_port

        direction=${1}
        dio_port=${2}

        if [[ "${OS_ID}" == "centos" ]]; then
                get_${direction}_state_it87_via_sysfs $dio_port
        else
                get_${direction}_state_it87_via_libgpiod $dio_port
        fi
}

function set_do_state_it87() {
        local do_port
        local do_state

        do_port=${1}
        do_state=${2}

        if [[ "${OS_ID}" == "centos" ]]; then
                set_do_state_it87_via_sysfs $do_port $do_state
        else
                set_do_state_it87_via_libgpiod $do_port $do_state
        fi
}

function get_di_state_it87_via_sysfs() {
        local di_port
        local di_state
        local sio_gpio_pin
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        di_port=${1}
        sio_gpio_pin=$(gpc_it8786 ${DI_IT87_GPIO_TBL[$di_port]})
        [[ $? -ne 0 ]] && ret=1
        di_state=$(gpio_get_value_sysfs $sio_gpio_pin)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get State Failed"
                exit $ret
        fi

        echo "Current DI status is ${DIO_STATE_STR[$di_state]} status."
}

function get_di_state_it87_via_libgpiod() {
        local di_port
        local di_state
        local sio_gpio_pin
        local gpc_name
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        di_port=${1}
        sio_gpio_pin=$(gpc_it8786_remap ${DI_IT87_GPIO_TBL[$di_port]})
        gpc_name=$(gpio_get_gpiochip_name gpio_it87)
        [[ $? -ne 0 ]] && ret=1
        di_state=$(gpio_get_value_libgpiod $sio_gpio_pin $gpc_name)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get State Failed"
                exit $ret
        fi

        echo "Current DI status is ${DIO_STATE_STR[$di_state]} status."
}

function get_do_state_it87_via_sysfs() {
        local do_port
        local do_state
        local sio_gpio_pin

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        do_port=${1}
        sio_gpio_pin=$(gpc_it8786 ${DO_IT87_GPIO_TBL[$do_port]})
        [[ $? -ne 0 ]] && ret=1
        do_state=$(gpio_get_value_sysfs $sio_gpio_pin)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get State Failed"
                exit $ret
        fi

        echo "Current DO status is ${DIO_STATE_STR[$do_state]} status."
}

function get_do_state_it87_via_libgpiod() {
        local do_port
        local do_state
        local sio_gpio_pin
        local gpc_name
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        do_port=${1}
        sio_gpio_pin=$(gpc_it8786_remap ${DO_IT87_GPIO_TBL[$do_port]})
        gpc_name=$(gpio_get_gpiochip_name gpio_it87)
        [[ $? -ne 0 ]] && ret=1
        do_state=$(gpio_get_value_libgpiod $sio_gpio_pin $gpc_name)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get State Failed"
                exit $ret
        fi

        echo "Current DO status is ${DIO_STATE_STR[$do_state]} status."
}

function set_do_state_it87_via_sysfs() {
        local do_port
        local do_state
        local sio_gpio_pin

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        do_port=${1}
        do_state=${2}
        sio_gpio_pin=$(gpc_it8786 ${DO_IT87_GPIO_TBL[$do_port]})
        [[ $? -ne 0 ]] && ret=1

        gpio_set_value_sysfs $sio_gpio_pin $do_state
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Set State Failed"
                exit $ret
        fi

        echo "Set OK."
        echo "Current DO status is ${DIO_STATE_STR[$do_state]} status."
}

function set_do_state_it87_via_libgpiod() {
        local do_port
        local do_state
        local sio_gpio_pin
        local gpc_name
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        do_port=${1}
        do_state=${2}
        sio_gpio_pin=$(gpc_it8786_remap ${DO_IT87_GPIO_TBL[$do_port]})
        gpc_name=$(gpio_get_gpiochip_name gpio_it87)
        [[ $? -ne 0 ]] && ret=1

        gpio_set_value_libgpiod $sio_gpio_pin $do_state $gpc_name
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Set State Failed"
                exit $ret
        fi

        echo "Set OK."
        echo "Current DO status is ${DIO_STATE_STR[$do_state]} status."
}

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 script_usage() {
        cat <<EOF
USAGE:
	mx-dio-ctl -i <di_port>
        mx-dio-ctl -o <do_port> [-s <state>]

OPTIONS:
	-i <di_port>
                Get DI port state

        -o <do_port>
                Determine DO port. Without option '-s', it will get DO port state

        -s <state>
                Set DO port state
                        0: low
                        1: high

EXAMPLE:
	Get DI port 0 state
	mx-dio-ctl -i 0

	Get DO port 0 state
	mx-dio-ctl -o 0

	Set DO port 1 state to low
	mx-dio-ctl -o 0 -s 0

	Set DO port 1 state to high
	mx-dio-ctl -o 0 -s 1

EOF
}

function script_params() {
        if [[ $# -eq 0 ]]; then
                script_usage
                exit 1
        fi

        while getopts "hi:o:s:" opt; do
                case "${opt}" in
                i)
                        TARGET_DI_PORT="$OPTARG"
                        TARGET_OPCODE=$((TARGET_OPCODE += 1))
                        ;;
                o)
                        TARGET_DO_PORT="$OPTARG"
                        TARGET_OPCODE=$((TARGET_OPCODE += 2))
                        ;;
                s)
                        TARGET_STATE="$OPTARG"
                        TARGET_OPCODE=$((TARGET_OPCODE += 4))
                        ;;

                h)
                        script_usage
                        exit 0
                        ;;
                \?)
                        script_usage
                        exit 22
                        ;;
                :)
                        echo "Option -$OPTARG requires an argument." >&2
                        script_usage
                        exit 22
                        ;;
                esac
        done
}

function script_init() {
        load_model_name

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

        "${MODEL_NAME}"::profile

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

        "${MODEL_NAME}"::init
}

function verify_di_port() {
        if ! check_leading_zero_digit $TARGET_DI_PORT; then
                echo "Invaild DIN port format."
                exit 1
        fi

        if [[ $TARGET_DI_PORT -lt 0 || $TARGET_DI_PORT -ge $NUM_OF_DI ]]; then
                echo "Invalid DIN port."
                exit 1
        fi
}

function verify_do_port() {
        if ! check_leading_zero_digit $TARGET_DO_PORT; then
                echo "Invaild DOUT port format."
                exit 1
        fi

        if [[ $TARGET_DO_PORT -lt 0 || $TARGET_DO_PORT -ge $NUM_OF_DO ]]; then
                echo "Invalid DOUT port."
                exit 1
        fi
}

function verify_state() {
        if ! check_leading_zero_digit $TARGET_STATE; then
                echo "Invalid DOUT state format."
                exit 1
        fi

        if [[ $TARGET_STATE -lt 0 || $TARGET_STATE -ge $NUM_OF_DIO_STATE ]]; then
                echo "Invalid DOUT state."
                exit 1
        fi
}

function main() {
        script_params "$@"
        script_init

        case $TARGET_OPCODE in
        $TARGET_GET_DI_STATE_OPCODE)
                verify_di_port

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

                "${MODEL_NAME}"::get_di_state $TARGET_DI_PORT
                ;;
        $TARGET_GET_DO_STATE_OPCODE)
                verify_do_port

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

                "${MODEL_NAME}"::get_do_state $TARGET_DO_PORT
                ;;
        $TARGET_SET_DO_STATE_OPCODE)
                verify_do_port
                verify_state

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

                "${MODEL_NAME}"::set_do_state $TARGET_DO_PORT $TARGET_STATE
                ;;
        $TARGET_STATE_INVALID_OPCODE)
                echo "Require -o argument for DO port number"
                script_usage
                exit 1
                ;;
        $TARGET_DI_DO_INVALID_OPCODE | $TARGET_DI_STATE_INVALID_OPCODE | $TARGET_DI_DO_STATE_INVALID_OPCODE)
                script_usage
                exit 1
                ;;
        *)
                script_usage
                exit 1
                ;;
        esac
}

main $@
