#!/bin/bash
# gxde-k9 —— GXDE K9 守护进程（后台服务）
log.warn() { echo -e "[\e[33mWARN\e[0m]:  \e[1m$*\e[0m"; }
log.error()  { echo -e "[\e[31mERROR\e[0m]: \e[1m$*\e[0m"; }
log.info() { echo -e "[\e[96mINFO\e[0m]:  \e[1m$*\e[0m"; }
log.debug()  { echo -e "[\e[32mDEBUG\e[0m]: \e[1m$*\e[0m"; }

VERSION="1.0.1"

# 默认目录
# 判断当前用户是否为 root
if [[ "$EUID" -eq 0 ]]; then
    BASE_DIR_SYSTEM="/usr/share/gxde-k9/system"
else
    BASE_DIR_SYSTEM="/usr/share/gxde-k9"
fi

BASE_DIR_USER="$HOME/.local/share/GXDE/gxde-k9"

PID_DIR="/tmp/GXDE/gxde-k9/$UID/"
LOCKFILE="${PID_DIR}/gxde-k9-daemon.lock"

# 日志目录（用户侧）：按 task 分类存放日志，启动时清理
LOG_DIR_USER="${HOME}/.local/share/GXDE/gxde-k9/logs/"
# 单个任务日志文件大小上限（字节），默认 10MB
LOG_MAX_SIZE=$((1024 * 1024 * 10))
# 是否启用任务日志（chocker 或用户可通过 --no-logs 关闭）
K9_ENABLE_LOGS=1



# 打印帮助信息
print_help() {
    cat << EOF
Usage: $0 [options]

Options:
  --base-dir <path>    Specify the directory to monitor for .slimy .timer .shot scripts.
  --pid-dir <path>     Specify the location for the PID DIR.
  --termux             Use ${HOME}/gxde-k9/ as monitor directory to adapt termux.
  --log-dir <path>     Specify the log directory (default: ~/.local/share/GXDE/gxde-k9/logs/).
  --log-max-size <B>   Per-task log file size limit in bytes (default: 10485760).
  --no-logs            Disable per-task logging.
  -h,--help            Show this help message and exit.
  -v,--version         Show version information and exit.

Description:
  K9 Lick Daemon gen 2 monitors a specified directory for .slimy scripts
  and executes them. It also supports .timer files with crontab-like schedules.
  By default, it monitors $SLIMY_DIR_SYSTEM and $TIMER_DIR_SYSTEM, as well as
  user-specific directories: $SLIMY_DIR_USER and $TIMER_DIR_USER.

Logs:
  Each task's stdout/stderr is captured into LOG_DIR/<category>/<task-name>.log
  under the user home. Logs are truncated on each daemon start.

Timer Example:
* * * * * | <command>
- - - - - -
| | | | | |
| | | | | +--- IMPORTANT: K9 Need an extra '|' to identify commands !!!!!!!!!!
| | | | +----- Day of week (0 - 7) (Sunday=0 or 7)
| | | +------- Month (1 - 12)
| | +--------- Day of month (1 - 31)
| +----------- Hour (0 - 23)
+------------- Minute (0 - 59)

EOF
    exit 0
}

# 任务日志：写入指定分类与名称的日志文件，超过上限则截断
# 用法: k9_log_task <category> <task_name> <line>
k9_log_task() {
    [[ "$K9_ENABLE_LOGS" -eq 0 ]] && return 0
    local category="$1"
    local task_name="$2"
    local line="$3"
    local category_dir="${LOG_DIR_USER%/}/${category}"
    local log_file="${category_dir}/${task_name}.log"
    [[ -d "$category_dir" ]] || mkdir -p "$category_dir" 2>/dev/null || return 0
    # 大小检查与截断
    if [[ -f "$log_file" ]]; then
        local cur_size
        cur_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
        if (( cur_size >= LOG_MAX_SIZE )); then
            # 保留后半部分，丢弃前半部分
            : > "$log_file"
        fi
    fi
    printf '[%s] %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$line" >> "$log_file"
}

# 启动时清理旧日志
k9_clean_logs() {
    [[ "$K9_ENABLE_LOGS" -eq 0 ]] && return 0
    if [[ -d "$LOG_DIR_USER" ]]; then
        rm -rf "${LOG_DIR_USER:?}/"*/  2>/dev/null
        find "$LOG_DIR_USER" -type f -name '*.log' -delete 2>/dev/null
    fi
    mkdir -p "$LOG_DIR_USER" 2>/dev/null
}

# 自动创建多个目录（如果不存在）
create_directory_if_not_exists() {
    for dir_path in "$@"; do
        if [[ ! -d "$dir_path" ]]; then
            log.info "Directory $dir_path does not exist. Creating..."
            mkdir -p "$dir_path"
        fi
    done
}

function check_exist(){
    for arg in "$@";do
        if [[ ! -e "$arg" ]];then
            log.error "File or Directory does not exist: $arg "
        exit 1
        fi
    done
}


# 防止多次运行，带 PID 校验
check_lockfile() {
    if [[ -f "$LOCKFILE" ]]; then
        existing_pid=$(cat "$LOCKFILE")
        if [[ -n "$existing_pid" && -e "/proc/$existing_pid" ]]; then
            log.error "Daemon is already running with PID $existing_pid. Exiting."
            exit 1
        else
            log.info "Stale lockfile detected. Removing lockfile and continuing."
            rm -f "$LOCKFILE"
        fi
    fi
}

# 创建锁文件
create_lockfile() {
    create_directory_if_not_exists "$(dirname "$LOCKFILE")"
    echo $$ > "$LOCKFILE"
}

# 删除锁文件
remove_lockfile() {
    rm -f "$LOCKFILE"
    exit 0
}

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        --base-dir)
            shift
            if [[ -z "$1" ]]; then
                log.error "Error: --base-dir requires a directory path."
                exit 1
            fi
            BASE_DIR="$1"
            ;;
        --pid-dir)
            shift
            if [[ -z "$1" ]]; then
                log.error "Error: --pid-dir requires a directory path."
                exit 1
            fi
            PID_DIR="$1"
            LOCKFILE="$PID_DIR/gxde-k9-daemon.lock"
            ;;
        --termux)
            shift
            BASE_DIR=${HOME}/GXDE/gxde-k9
            PID_DIR="${HOME}/GXDE/gxde-k9/$UID/"
            LOCKFILE="$PID_DIR/gxde-k9-daemon.lock"
            ;;
        --log-dir)
            shift
            if [[ -z "$1" ]]; then
                log.error "Error: --log-dir requires a directory path."
                exit 1
            fi
            LOG_DIR_USER="$1"
            ;;
        --log-max-size)
            shift
            if [[ -z "$1" ]]; then
                log.error "Error: --log-max-size requires a positive integer (bytes)."
                exit 1
            fi
            LOG_MAX_SIZE="$1"
            ;;
        --no-logs)
            K9_ENABLE_LOGS=0
            ;;
        -h|--help)
            print_help
            ;;
        --version|-v)
            echo "gxde-k9 v${VERSION}"
            exit 0
            ;;
        *)
            log.error "未知选项: $1"
            print_help
            ;;
    esac
    shift
done



# 使用用户指定的目录或默认目录
if [[ -n "$BASE_DIR" ]]; then
    BASE_DIR_SYSTEM="${BASE_DIR}"
fi

# 合成变量
    SLIMY_DIR_SYSTEM="${BASE_DIR_SYSTEM}/slimy/"
    TIMER_DIR_SYSTEM="${BASE_DIR_SYSTEM}/timer/"
    SHOT_DIR_SYSTEM="${BASE_DIR_SYSTEM}/shot/"
    EDGING_DIR_SYSTEM="${BASE_DIR_SYSTEM}/edging/"
    SLIMY_DIR="$SLIMY_DIR_SYSTEM"
    TIMER_DIR="$TIMER_DIR_SYSTEM"
    SHOT_DIR="$SHOT_DIR_SYSTEM"
    EDGING_DIR="$EDGING_DIR_SYSTEM"
if [[ -z "$BASE_DIR" ]]; then # 有指定的就不改了
    SLIMY_DIR_USER="${BASE_DIR_USER}/slimy/"
    TIMER_DIR_USER="${BASE_DIR_USER}/timer/"
    SHOT_DIR_USER="${BASE_DIR_USER}/shot/"
    EDGING_DIR_USER="${BASE_DIR_USER}/edging/"
fi

log.debug $BASE_DIR $BASE_DIR_SYSTEM $SLIMY_DIR
export SLIMY_DIR
export TIMER_DIR
export SHOT_DIR
export PID_DIR
export EDGING_DIR
export LOCKFILE
export LOG_DIR_USER
export LOG_MAX_SIZE
export K9_ENABLE_LOGS
export K9_LOG_DIR="$LOG_DIR_USER"

# 启动时清理上次日志
k9_clean_logs

create_directory_if_not_exists ${SLIMY_DIR} ${TIMER_DIR} ${SHOT_DIR} ${SLIMY_DIR_USER} ${TIMER_DIR_USER} ${SHOT_DIR_USER} ${PID_DIR} ${EDGING_DIR_USER} ${EDGING_DIR}


# 检查并创建锁文件
check_lockfile
trap remove_lockfile SIGINT SIGTERM
create_lockfile


# 确保目录存在
if [[ ! -d "$SLIMY_DIR" ]]; then
    log.error "Error: Slimy directory $SLIMY_DIR does not exist!"
    remove_lockfile
fi

if [[ ! -d "$TIMER_DIR" ]]; then
    log.error "Error: Timer directory $TIMER_DIR does not exist!"
    remove_lockfile
fi

if [[ ! -d "$SHOT_DIR" ]]; then
    log.error "Error: Shot directory $SHOT_DIR does not exist!"
    remove_lockfile
fi

if [[ ! -d "$PID_DIR" ]]; then
    log.error "Error: PID directory $PID_DIR does not exist!"
    remove_lockfile
fi

if [[ ! -d "$EDGING_DIR" ]]; then
    log.error "Error: EDGING directory $EDGING_DIR does not exist!"
    remove_lockfile
fi


# 输出启动信息
log.info "--------------------------------------------------------------"
log.info "K9 Lick Daemon gen 2 is ready"
log.info "I am $(whoami)"
log.info "Watching slimy orders at $SLIMY_DIR | $SLIMY_DIR_USER"
log.info "Monitoring timers at $TIMER_DIR | $TIMER_DIR_USER"
log.info "Monitoring shots at $SHOT_DIR | $SHOT_DIR_USER"
log.info "Monitoring edgings at $EDGING_DIR | $EDGING_DIR_USER"
log.info "Lockfile location: $LOCKFILE"
log.info "--------------------------------------------------------------"

# 处理 cron 字段是否匹配
matches_cron_field() {
    local field="$1"
    local current_value="$2"
    if [[ "$field" == "*" ]]; then
        return 0  # 匹配所有值
    elif [[ "$field" == */* ]]; then
        # 如果是 "*/x" 的形式，例如 "*/5"，表示每 x 个单位匹配一次
        local step
        step=$(echo "$field" | cut -d'/' -f2)
        if (( current_value % step == 0 )); then
            return 0
        fi
    elif [[ "$field" == "$current_value" ]]; then
        return 0  # 完全匹配
    elif [[ "$field" == *"-"* ]]; then
        # 如果是范围形式，例如 "1-5" 表示匹配 1 到 5
        local start end
        start=$(echo "$field" | cut -d'-' -f1)
        end=$(echo "$field" | cut -d'-' -f2)
        if (( current_value >= start && current_value <= end )); then
            return 0
        fi
    elif [[ "$field" == *","* ]]; then
        # 如果是逗号分隔的多个值，例如 "1,2,5"
        IFS=',' read -ra values <<< "$field"
        for value in "${values[@]}"; do
            if [[ "$value" == "$current_value" ]]; then
                return 0
            fi
        done
    fi

    return 1  # 不匹配
}
# 解析 crontab-like 格式的定时器时间 (支持完整的 cron 语法)
is_cron_time_matched() {
    local cron_time="$1"
    local minute hour day month weekday

    # 将 cron 表达式分解成时间字段
    IFS=' ' read -r minute hour day month weekday <<< "$cron_time"

    # 获取当前的时间值
# 获取当前的时间值
local current_minute=$(date +'%M' | sed 's/^0//')  # 去掉前导零
local current_hour=$(date +'%H' | sed 's/^0//')    # 去掉前导零
local current_day=$(date +'%d' | sed 's/^0//')      # 去掉前导零
local current_month=$(date +'%m' | sed 's/^0//')    # 去掉前导零
local current_weekday=$(date +'%u')                  # 星期几没有前导零

    # 检查分钟字段
    if ! matches_cron_field "$minute" "$current_minute"; then return 1; fi

    # 检查小时字段
    if ! matches_cron_field "$hour" "$current_hour"; then return 1; fi

    # 检查日期字段
    if ! matches_cron_field "$day" "$current_day"; then return 1; fi

    # 检查月份字段
    if ! matches_cron_field "$month" "$current_month"; then return 1; fi

    # 检查星期几字段
    if ! matches_cron_field "$weekday" "$current_weekday"; then return 1; fi

    # 如果所有字段都匹配，则返回 true
    return 0
}


execute_timers_scripts_in_dir(){
    local dir_path="$1"
        for timer_file in "$dir_path"/*.timer ; do
            [[ -e "$timer_file" ]] || continue
            local timer_name
            timer_name=$(basename "$timer_file" .timer)
            while IFS= read -r line; do
                [[ -z "$line" || "$line" == \#* ]] && continue
                schedule=$(echo "$line" | cut -d'|' -f1)
                command=$(echo "$line" | cut -d'|' -f2-)
                # 如果 crontab 格式匹配当前时间
                if is_cron_time_matched "$schedule"; then
                    log.info "$(date +'%Y-%m-%d %H:%M:%S') - Running scheduled task: $command"
                    k9_log_task "timer" "$timer_name" "RUN: $command"
                    if [[ "$K9_ENABLE_LOGS" -eq 1 ]]; then
                        local out
                        out=$(bash -c "$command" 2>&1)
                        local rc=$?
                        [[ -n "$out" ]] && k9_log_task "timer" "$timer_name" "$out"
                        [[ $rc -ne 0 ]] && k9_log_task "timer" "$timer_name" "EXIT=$rc"
                    else
                        bash -c "$command" &
                    fi
                fi
            done < "$timer_file"
        done
}

# 监控 timer 文件并执行任务
monitor_timers() {
    while true; do
    execute_timers_scripts_in_dir "$TIMER_DIR"
    # 如果用户目录存在，也执行其中的脚本
    if [[ -n "$TIMER_DIR_USER" ]]; then
    execute_timers_scripts_in_dir "$TIMER_DIR_USER"
    fi
        sleep 60
    done
}


# 运行所有 .slimy 脚本
execute_slimy_scripts_in_dir() {
    local dir_path="$1"

    # 查找所有 .slimy 文件并逐一执行
    for slimy_file in "$dir_path"/*.slimy; do
        # 检查文件是否存在，避免在目录为空时出错
        [[ -e "$slimy_file" ]] || continue
        local slimy_name
        slimy_name=$(basename "$slimy_file" .slimy)
        if [[ "$K9_ENABLE_LOGS" -eq 1 ]]; then
            local out
            out=$(bash "$slimy_file" 2>&1)
            local rc=$?
            [[ -n "$out" ]] && k9_log_task "slimy" "$slimy_name" "$out"
            if [[ $rc -ne 0 ]]; then
                log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing slimy script: $slimy_file"
                k9_log_task "slimy" "$slimy_name" "EXIT=$rc"
            fi
        else
            bash "$slimy_file" || log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing slimy script: $slimy_file"
        fi
    done &
}


run_slimy_scripts() {
    # 执行指定目录中的所有 .slimy 脚本
    execute_slimy_scripts_in_dir "$SLIMY_DIR"

    # 如果用户目录存在，也执行其中的脚本
    if [[ -n "$SLIMY_DIR_USER" ]]; then
        execute_slimy_scripts_in_dir "$SLIMY_DIR_USER"
    fi
}

execute_shot_scripts_in_dir() {
    local dir_path="$1"
    for shot_file in "$dir_path"/*.shot ; do
        [[ -e "$shot_file" ]] || continue
        local shot_name
        shot_name=$(basename "$shot_file" .shot)
        log.info "$(date +'%Y-%m-%d %H:%M:%S') - Executing one-shot script: $shot_file"
        if [[ "$K9_ENABLE_LOGS" -eq 1 ]]; then
            local out
            out=$(bash "$shot_file" 2>&1)
            local rc=$?
            [[ -n "$out" ]] && k9_log_task "shot" "$shot_name" "$out"
            if [[ $rc -ne 0 ]]; then
                log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing one-shot script: $shot_file"
                k9_log_task "shot" "$shot_name" "EXIT=$rc"
            fi
        else
            bash "$shot_file" || log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing one-shot script: $shot_file"
        fi
    done
}

run_shot_scripts() {
    # 执行指定目录中的所有 .shot 脚本
    execute_shot_scripts_in_dir "$SHOT_DIR"

    # 如果用户目录存在，也执行其中的脚本
    if [[ -n "$SHOT_DIR_USER" ]]; then
        execute_shot_scripts_in_dir "$SHOT_DIR_USER"
    fi
}

# 启动 slimy 监控 (每5秒执行一次)
watch_slimy_scripts() {
    while true; do
        run_slimy_scripts
        sleep 5
    done
}

edging_scripts_handler(){
local _edging_file="$1"
local _edging_name
_edging_name=$(basename "$_edging_file" .edging)

# 由 k9 提供的 logging helper（未运行于 k9 环境时输出则被丢弃）
k9_log_task() {
    local category="$1" name="$2" line="$3"
    if [[ -n "$K9_LOG_DIR" && -n "$category" && -n "$name" ]]; then
        local d="$K9_LOG_DIR/$category"
        mkdir -p "$d" 2>/dev/null
        printf '[%s] %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$line" >> "$d/$name.log"
    fi
}

source "$_edging_file"

# PID 文件目录（从环境变量 PID_DIR 获取，默认为用户配置目录）
PID_DIR="${PID_DIR:-/tmp/GXDE/gxde-k9/}"

# PROCESS_NAME 名从 .edging 文件名得来
PROCESS_NAME="$_edging_name"

# ============ 脚本逻辑 ============

if [[ -n "$DONT_RUN_IF_SYSTEMD_EXIST" ]] && [ -d /run/systemd/system ]; then
    exit 0  # We don't need K9 to take over systemd work if DONT_RUN_IF_SYSTEMD_EXIST
fi


# 确保 PID 文件目录存在
mkdir -p "$PID_DIR" || {
    k9_log_task "edging" "$PROCESS_NAME" "ERROR: cannot create PID dir $PID_DIR"
    exit 1
}

# 生成 PID 文件路径
PID_FILE="$PID_DIR/$PROCESS_NAME.pid"

# 检查 PID 文件是否存在且有效
if [[ -f "$PID_FILE" ]]; then
    PID=$(cat "$PID_FILE" 2>/dev/null)
    if [[ -n "$PID" && -e "/proc/$PID" ]]; then
            exit 0
    else
        k9_log_task "edging" "$PROCESS_NAME" "stale PID file, removing: $PID_FILE"
        rm -f "$PID_FILE"
    fi
fi

# 如果 PID 文件不存在或无效，启动目标进程
k9_log_task "edging" "$PROCESS_NAME" "starting: $TARGET_PROCESS"
bash -c "$TARGET_PROCESS" </dev/null >/dev/null 2>&1 &
NEW_PID=$!
if [[ -n "$NEW_PID" && -e "/proc/$NEW_PID" ]]; then
    echo "$NEW_PID" > "$PID_FILE"
    k9_log_task "edging" "$PROCESS_NAME" "started PID=$NEW_PID"
else
    k9_log_task "edging" "$PROCESS_NAME" "ERROR: failed to start"
    exit 1
fi
}

execute_edging_scripts_in_dir() {
    local dir_path="$1"

    # 查找所有 .edging 文件并逐一执行
    for edging_file in "$dir_path"/*.edging; do
        # 检查文件是否存在，避免在目录为空时出错
        [[ -e "$edging_file" ]] || continue

        if [[ "$K9_ENABLE_LOGS" -eq 1 ]]; then
            edging_scripts_handler "$edging_file"
            local rc=$?
            if [[ $rc -ne 0 ]]; then
                local en
                en=$(basename "$edging_file" .edging)
                log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing edging script: $edging_file"
                k9_log_task "edging" "$en" "HANDLER_EXIT=$rc"
            fi
        else
            edging_scripts_handler "$edging_file" || log.error "$(date +'%Y-%m-%d %H:%M:%S') - Error executing edging script: $edging_file"
        fi
    done &
}
run_edging_scripts(){
    # 执行指定目录中的所有 .edging 脚本
    execute_edging_scripts_in_dir "$EDGING_DIR"

    # 如果用户目录存在，也执行其中的脚本
    if [[ -n "$SHOT_DIR_USER" ]]; then
        execute_edging_scripts_in_dir "$EDGING_DIR_USER"
    fi

}

watch_edging_scripts(){
    while true; do
        run_edging_scripts
        sleep 5
    done
}

# 只执行一次 shot 脚本
run_shot_scripts &
# 启动 slimy 
watch_slimy_scripts &
watch_edging_scripts &
# 启动定时任务监控
monitor_timers &

wait
