#!/bin/sh

# Copyright (c) 2016, prpl Foundation
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without
# fee is hereby granted, provided that the above copyright notice and this permission notice appear
# in all copies.
# 
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
# FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Author: Nils Koenig <openwrt@newk.it>

. /lib/functions.sh

SCRIPT=$0
LOCKFILE=/tmp/wifi_schedule.lock
LOGFILE=/tmp/log/wifi_schedule.log
PACKAGE=wifi_schedule
GLOBAL=${PACKAGE}.@global[0]
# remember, if radio state was changed
radio_state_changed=0
# variable to remember if wifi should be enabled disabled (needed by check and apply function)
wifi_disabled=1
# variable to remember if scheduler is enabled
schedule_enabled=0
# variable to remember if stop should be forced
forcewifidown=0

_log()
{
    if [ ${LOGGING} -eq 1 ]; then
        local ts=$(date)
        echo "$ts $@" >> ${LOGFILE}
    fi
}

_exit()
{
    local rc=$1
    lock -u ${LOCKFILE}
    exit ${rc}
}

_cron_restart()
{
    /etc/init.d/cron restart > /dev/null
}

_add_cron_script()
{
    (crontab -l ; echo "$1") | sort -u | crontab -
}

_rm_cron_script()
{
    crontab -l | grep -v "$1" | sort -u | crontab -
}

_rm_and_add_cron_script()
{
    (crontab -l | grep -v "$1"; [ -n "$2" ] && echo "$2") | sort -u | crontab -
}

_get_uci_value_raw()
{
    local value
    value=$(uci get $1 2> /dev/null)
    local rc=$?
    echo ${value}
    return ${rc}
}

_get_uci_value()
{
    local value
    value=$(_get_uci_value_raw $1)
    local rc=$?
    if [ ${rc} -ne 0 ]; then
        _log "Could not determine UCI value $1"
        return 1
    fi
    echo ${value}
}

_format_dow_list()
{
    local dow=$1
    local flist=""
    local day
    for day in ${dow}
    do
        if [ ! -z ${flist} ]; then
            flist="${flist},"
        fi
        flist="${flist}${day:0:3}"
    done
    echo ${flist}
}

_shift_dow_list_by_one_day()
{
    local dow=$1
    local flist=""
    local day
    for day in $1
    do
	if [ ! -z ${flist} ]; then
            flist="${flist},"
        fi
        case $day in
            Mon*)
                flist="${flist}Tue"
            ;;
            Tue*)
                flist="${flist}Wed"
            ;;
            Wed*)
                flist="${flist}Thu"
            ;;
            Thu*)
                flist="${flist}Fri"
            ;;
            Fri*)
                flist="${flist}Sat"
            ;;
            Sat*)
                flist="${flist}Sun"
            ;;
            Sun*)
                flist="${flist}Mon"
            ;;
        esac
    done

    echo "$flist"
}

_enable_wifi_schedule()
{
    local entry=$1
    local enabled starttime stoptime dow

    config_get enabled $entry enabled 0
    [ "$enabled" -eq 0 ] && return 0
    config_get starttime $entry starttime
    [ -z "$starttime" ] && return 1
    config_get stoptime $entry stoptime
    [ -z "$stoptime" ] && return 1
    config_get dow $entry daysofweek
    [ -z "$dow" ] && return 1

    local fdow=$(_format_dow_list "$dow")
    local stopmode="stop"
    if [ $forcewifidown -eq 1 ]; then
        stopmode="forcestop"
    fi

    # If it shall stop on the same day at 00:00, 24:00 is the transition to the following day, if this is the case,
    # the days of the weeks have to be incremented by one
    if [ "${stoptime}" == "24:00" ]; then
        fdow_stop=$(_shift_dow_list_by_one_day "$dow")
        stoptime="0:00"
    else
        fdow_stop=${fdow}
    fi

    local stop_cron_entry="$(echo ${stoptime} | awk -F':' '{print $2, $1}') * * ${fdow_stop} ${SCRIPT} check" # ${entry}"
    echo "${stop_cron_entry}"

    if [[ $starttime != $stoptime ]]                             
    then                                                         
        local start_cron_entry="$(echo ${starttime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} check" # ${entry}"
        echo "${start_cron_entry}"
    fi

    return 0
}

_get_wireless_interfaces()
{
    local n=$(cat /proc/net/wireless | wc -l)
    cat /proc/net/wireless | tail -n $(($n - 2))|awk -F':' '{print $1}'| sed  's/ //' 
}


get_module_list()
{
    local mod_list
    local _if
    for _if in $(_get_wireless_interfaces)
    do
        local mod=$(basename $(readlink -f /sys/class/net/${_if}/device/driver))
        local mod_dep=$(modinfo ${mod} | awk '{if ($1 ~ /depends/) print $2}')
        mod_list=$(echo -e "${mod_list}\n${mod},${mod_dep}" | sort | uniq)
    done
    echo $mod_list | tr ',' ' '
}

save_module_list_uci()
{
    local list=$(get_module_list)
    uci set ${GLOBAL}.modules="${list}"
    uci commit ${PACKAGE}
}

_unload_modules()
{
    local list=$(_get_uci_value ${GLOBAL}.modules) 
    local retries
    retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1
    _log "unload_modules ${list} (retries: ${retries})"
    local i=0
    while [[ ${i} -lt ${retries}  &&  "${list}" != "" ]]
    do  
        i=$(($i+1))
        local mod
        local first=0
        for mod in ${list}
        do
            if [ $first -eq 0 ]; then
                list=""
                first=1
            fi
            rmmod ${mod} > /dev/null 2>&1
            if [ $? -ne 0 ]; then
                list="$list $mod"
            fi
        done
    done
}


_load_modules()
{
    local list=$(_get_uci_value ${GLOBAL}.modules)
    local retries
    retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1
    _log "load_modules ${list} (retries: ${retries})"
    local i=0
    while [[ ${i} -lt ${retries}  &&  "${list}" != "" ]]
    do  
        i=$(($i+1))
        local mod
        local first=0
        for mod in ${list}
        do
            if [ $first -eq 0 ]; then
                list=""
                first=1
            fi
            modprobe ${mod} > /dev/null 2>&1
            rc=$? 
            if [ $rc -ne 255 ]; then
                list="$list $mod"
            fi
        done
    done
}

_create_cron_entries()
{
    config_foreach _enable_wifi_schedule entry
}

check_cron_status()
{
    config_load wifi_schedule
    config_foreach handle_global global
    if [ ${schedule_enabled} -eq 1 ]; then
        _rm_and_add_cron_script "${SCRIPT}" "$(_create_cron_entries)"
    else
        _rm_cron_script "${SCRIPT}"
    fi
    _cron_restart
}

set_radio_state() {
    local cfg="$1"
    local wifi_disabled="$2"
    local sched_disabled="$3"

    config_get cur_state "$cfg" disabled 0

    if [ "$wifi_disabled" = "1" ]; then
        if [ "$wifi_disabled" != "$cur_state" ]; then
            uci_set wireless "$cfg" disabled $wifi_disabled
            radio_state_changed=1
        fi
    else
        local value
        config_get value $1 cfg_disabled '0'     # default value if unset is 0 (on)

        uci_set wireless $1 disabled $value

	if [ "$sched_disabled" = "1" ]; then
		[ "$value" != "$curstate" ] && radio_state_changed=1
        else
		[ "$value" = "0" ] && [ "$value" != "$curstate" ] && radio_state_changed=1
	fi
    fi
}
disable_wifi()
{
    config_load wireless
    _rm_cron_script "${SCRIPT} recheck"
    _cron_restart

    config_foreach set_radio_state wifi-device 1

    [ "$radio_state_changed" -eq 1 ] && touch /tmp/cs_omit_wireless
    [ "$radio_state_changed" -eq 1 ] && { 
        uci_commit wireless
        reload_config # use procd to check for config changes
    }

    local unload_modules
    unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1
    if [[ "${unload_modules}" == "1" ]]; then
        _unload_modules
    fi    
}

soft_disable_wifi()
{
    local _disable_wifi=1
    local iwinfo=/usr/bin/iwinfo
    if [ ! -e ${iwinfo} ]; then
        _log "${iwinfo} not available, skipping"
        return 1
    fi

    # check if no stations are associated
    local _if
    for _if in $(_get_wireless_interfaces)
    do
	[ "${_if:0:3}" == "ath" ] || continue
        output=$(${iwinfo} ${_if} assoclist)
        if [[ "$output" != "No station connected" ]]
        then
            _disable_wifi=0
            local stations=$(echo ${output}| grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}' | tr '\n' ' ')
            _log "Station(s) ${stations}associated on ${_if}"
        fi
    done

    if [ ${_disable_wifi} -eq 1 ]; then
        _log "No stations associated, disable wifi."
        disable_wifi
    else
        _log "Could not disable wifi due to associated stations, retrying..."
        local recheck_interval=$(_get_uci_value ${GLOBAL}.recheck_interval)
        _add_cron_script "*/${recheck_interval} * * * * ${SCRIPT} recheck"
        _cron_restart
    fi
}

enable_wifi()
{
    config_load wireless
    _rm_cron_script "${SCRIPT} recheck"
    _cron_restart

    . /lib/delos-functions.sh
    dvl_check_and_set_wifi_cfg_disabled
    #reload config . maybe someting has been changed in the function above
    config_load wireless
    config_foreach set_radio_state wifi-device 0

    local unload_modules
    unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1
    if [[ "${unload_modules}" == "1" ]]; then
        _load_modules
    fi
    [ "$radio_state_changed" -eq 1 ] && touch /tmp/cs_omit_wireless
    [ "$radio_state_changed" -eq 1 ] && { 
        uci_commit wireless
        reload_config # use procd to check for config changes
    }
}

disable_sched()
{
    config_load wireless
    config_foreach set_radio_state wifi-device 0 1

    [ "$radio_state_changed" -eq 1 ] && {
        uci_commit wireless
        reload_config # use procd to check for config changes
    }
}

handle_global() {
    local cfg="$1"
    config_get_bool schedule_enabled "$cfg" enabled 0
    config_get_bool forcewifidown "$cfg" forcewifidown 0
}

handle_entry() {
    local cfg="$1"
    local entry_enabled=
    local starttime=
    local stoptime=
    local dow=
    local curTime=$(date "+%k%M")
    local weekday=$(date "+%A")

    config_get_bool entry_enabled "$cfg" enabled 0
    config_get dow "$cfg" daysofweek
    config_get starttime "$cfg" starttime
    config_get stoptime "$cfg" stoptime

    # Check if current entry is enabled
    [ "$entry_enabled" -eq 0 ] && return 0

    # Check if current entry is relevant for today
    echo "$dow" | grep "$weekday" > /dev/null || return 0

    #remove :, so that we can make easy integer comparison
    starttime=${starttime//:}
    stoptime=${stoptime//:}

    [ $curTime -ge $starttime ] && [ $curTime -lt $stoptime ] && wifi_disabled=0
}

# Check if the radio should be turned on/off accordingly to the wifi scheduler settings, returns "off" or "on"
check_and_apply_wifischeduler_state() {
    config_load wifi_schedule
    config_foreach handle_global global

    if [ "$schedule_enabled" -eq 0 ]; then
	disable_sched
	return 0
    fi

    config_foreach handle_entry entry

    if [ "$wifi_disabled" -eq 0 ]; then
        enable_wifi
    else
        if [ "$forcewifidown" -eq 1 ]; then
            disable_wifi
        else
            soft_disable_wifi
        fi
    fi
}

usage()
{
    echo ""
    echo "$0 cron|start|stop|forcestop|recheck|getmodules|savemodules|help"
    echo ""
    echo "    UCI Config File: /etc/config/${PACKAGE}"
    echo ""
    echo "    cron: Create cronjob entries."
    echo "    start: Start wifi."
    echo "    stop: Stop wifi gracefully, i.e. check if there are stations associated and if so keep retrying."
    echo "    forcestop: Stop wifi immediately."
    echo "    recheck: Recheck if wifi can be disabled now."
    echo "    getmodules: Returns a list of modules used by the wireless driver(s)"
    echo "    savemodules: Saves a list of automatic determined modules to UCI"
    echo "    check: Check if should be turned on or not"
    echo "    help: This description."
    echo ""
}

###############################################################################
# MAIN
###############################################################################
LOGGING=$(_get_uci_value ${GLOBAL}.logging) || LOGGING=0
_log ${SCRIPT} $1 $2
lock ${LOCKFILE}

config_load wireless

case "$1" in
    cron) check_cron_status ;;
    start) enable_wifi ;;
    forcestop) disable_wifi ;;
    stop) soft_disable_wifi ;;
    recheck) soft_disable_wifi ;;
    getmodules) get_module_list ;;
    savemodules) save_module_list_uci ;;
    check) check_and_apply_wifischeduler_state;;
    help|--help|-h|*) usage ;;
esac

_exit 0