#!/system/bin/sh
# FlipperDroid Bridge Daemon
# Manages bidirectional communication between Android and Flipper Zero
# Protocol: binary framed messages over USB CDC (ttyACM) or BT rfcomm

CONFIG_DIR="/data/adb/flipperdroid"
CONFIG_FILE="$CONFIG_DIR/config.sh"
LOG_FILE="$CONFIG_DIR/logs/flipperdroid.log"
FIFO_TX="$CONFIG_DIR/bridge_tx"
FIFO_RX="$CONFIG_DIR/bridge_rx"
CMD_FIFO="$CONFIG_DIR/cmd_fifo"
RESP_DIR="$CONFIG_DIR/responses"

MAGIC_HI=$(printf '\xFD')
MAGIC_LO=$(printf '\x01')

log() {
  local level="$1"; shift
  local min_level=$(cat "$CONFIG_DIR/log_level" 2>/dev/null || echo 2)
  [ "$level" -le "$min_level" ] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [bridge] $*" >> "$LOG_FILE"
}

source "$CONFIG_FILE" 2>/dev/null

mkdir -p "$RESP_DIR"

# CRC8 lookup (Dallas/Maxim polynomial 0x31)
crc8() {
  local data="$1"
  local crc=0
  local i j byte
  for i in $(echo "$data" | sed 's/../& /g'); do
    byte=$((16#$i))
    crc=$((crc ^ byte))
    for j in $(seq 1 8); do
      if [ $((crc & 0x80)) -ne 0 ]; then
        crc=$(( ((crc << 1) ^ 0x31) & 0xFF ))
      else
        crc=$(( (crc << 1) & 0xFF ))
      fi
    done
  done
  printf '%02x' $crc
}

# Build a protocol frame: magic(2) + len(2) + cmd(1) + payload(N) + crc8(1)
build_frame() {
  local cmd_hex="$1"
  local payload_hex="$2"
  local payload_len=$((${#payload_hex} / 2))
  local total_len=$((payload_len + 1))  # cmd + payload
  local len_hi=$(printf '%02x' $((total_len >> 8)))
  local len_lo=$(printf '%02x' $((total_len & 0xFF)))
  local frame_data="${len_hi}${len_lo}${cmd_hex}${payload_hex}"
  local crc=$(crc8 "$frame_data")
  echo "FD01${frame_data}${crc}"
}

# Send raw hex frame to Flipper
send_raw() {
  local hex="$1"
  local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
  [ -z "$dev" ] || [ ! -c "$dev" ] && return 1
  echo "$hex" | xxd -r -p > "$dev"
}

# Send command and wait for response
send_cmd() {
  local cmd="$1"
  local payload="${2:-}"
  local timeout="${3:-5}"
  local frame=$(build_frame "$cmd" "$payload")
  local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)

  [ -z "$dev" ] || [ ! -c "$dev" ] && echo "ERR:no_device" && return 1

  log 3 "TX: cmd=0x${cmd} payload=${payload:-<none>}"

  # Send frame
  echo "$frame" | xxd -r -p > "$dev" 2>/dev/null
  if [ $? -ne 0 ]; then
    log 1 "TX failed — device gone?"
    echo "disconnected" > "$CONFIG_DIR/status"
    echo "ERR:tx_failed"
    return 1
  fi

  # Read response with timeout
  local response=""
  response=$(timeout "$timeout" dd if="$dev" bs=1 count=256 2>/dev/null | xxd -p | tr -d '\n')

  if [ -z "$response" ]; then
    log 1 "RX timeout for cmd 0x${cmd}"
    echo "ERR:timeout"
    return 1
  fi

  log 3 "RX: $response"

  # Parse response — extract after magic bytes
  local resp_cmd=$(echo "$response" | cut -c9-10)
  local resp_payload=$(echo "$response" | cut -c11-)

  if [ "$resp_cmd" = "fe" ]; then
    echo "OK:${resp_payload}"
  elif [ "$resp_cmd" = "ff" ]; then
    echo "ERR:${resp_payload}"
  else
    echo "RAW:${response}"
  fi
}

#############################
# Handshake with Flipper
#############################

handshake() {
  log 2 "Initiating handshake with Flipper Zero..."

  # Send PING
  local resp=$(send_cmd "01" "" 3)
  if ! echo "$resp" | grep -q "^OK"; then
    log 1 "Handshake PING failed: $resp"
    return 1
  fi
  log 2 "PING OK"

  # Get version
  resp=$(send_cmd "02" "" 3)
  if echo "$resp" | grep -q "^OK:"; then
    local version_hex=$(echo "$resp" | sed 's/OK://')
    echo "$version_hex" | xxd -r -p > "$CONFIG_DIR/flipper_version" 2>/dev/null
    log 2 "Flipper version: $(cat "$CONFIG_DIR/flipper_version")"
  fi

  # Get capabilities
  resp=$(send_cmd "03" "" 3)
  if echo "$resp" | grep -q "^OK:"; then
    echo "$resp" | sed 's/OK://' > "$CONFIG_DIR/flipper_caps"
    log 2 "Capabilities: $(cat "$CONFIG_DIR/flipper_caps")"
  fi

  echo "connected" > "$CONFIG_DIR/status"
  log 2 "Handshake complete — bridge active"
  return 0
}

#############################
# Command FIFO handler
# WebUI and other tools write commands here
#############################

process_api_command() {
  local cmd_line="$1"
  local req_id=$(echo "$cmd_line" | cut -d'|' -f1)
  local subsystem=$(echo "$cmd_line" | cut -d'|' -f2)
  local action=$(echo "$cmd_line" | cut -d'|' -f3)
  local params=$(echo "$cmd_line" | cut -d'|' -f4-)

  log 3 "API cmd: $req_id $subsystem $action $params"

  local result=""

  case "$subsystem" in
    system)
      case "$action" in
        ping)     result=$(send_cmd "01") ;;
        version)  result=$(send_cmd "02") ;;
        caps)     result=$(send_cmd "03") ;;
        status)   result=$(send_cmd "04") ;;
      esac
      ;;

    gpio)
      case "$action" in
        init)
          local pin=$(echo "$params" | cut -d',' -f1)
          local mode=$(echo "$params" | cut -d',' -f2)
          local pin_hex=$(printf '%02x' "$pin")
          local mode_hex=$(printf '%02x' "$mode")
          result=$(send_cmd "10" "${pin_hex}${mode_hex}")
          ;;
        write)
          local pin=$(echo "$params" | cut -d',' -f1)
          local val=$(echo "$params" | cut -d',' -f2)
          result=$(send_cmd "11" "$(printf '%02x%02x' "$pin" "$val")")
          ;;
        read)
          local pin=$(echo "$params" | cut -d',' -f1)
          result=$(send_cmd "12" "$(printf '%02x' "$pin")")
          ;;
        pwm)
          local pin=$(echo "$params" | cut -d',' -f1)
          local freq=$(echo "$params" | cut -d',' -f2)
          local duty=$(echo "$params" | cut -d',' -f3)
          local pin_hex=$(printf '%02x' "$pin")
          local freq_hex=$(printf '%08x' "$freq")
          local duty_hex=$(printf '%02x' "$duty")
          result=$(send_cmd "13" "${pin_hex}${freq_hex}${duty_hex}")
          ;;
        adc)
          local pin=$(echo "$params" | cut -d',' -f1)
          result=$(send_cmd "14" "$(printf '%02x' "$pin")")
          ;;
      esac
      ;;

    subghz)
      case "$action" in
        set_freq)
          local freq=$(echo "$params" | cut -d',' -f1)
          result=$(send_cmd "20" "$(printf '%08x' "$freq")")
          ;;
        tx)
          local data_hex="$params"
          result=$(send_cmd "21" "$data_hex" 10)
          ;;
        rx_start)  result=$(send_cmd "22") ;;
        rx_stop)   result=$(send_cmd "23") ;;
        get_rssi)  result=$(send_cmd "24") ;;
        set_mod)
          local mod=$(echo "$params" | cut -d',' -f1)
          local bw=$(echo "$params" | cut -d',' -f2)
          result=$(send_cmd "25" "$(printf '%02x%02x' "$mod" "$bw")")
          ;;
        replay)
          local slot=$(echo "$params" | cut -d',' -f1)
          result=$(send_cmd "26" "$(printf '%02x' "$slot")")
          ;;
      esac
      ;;

    rfid)
      case "$action" in
        read)     result=$(send_cmd "30" "" 10) ;;
        emulate)  result=$(send_cmd "31" "$params") ;;
        write)    result=$(send_cmd "32" "$params") ;;
      esac
      ;;

    nfc)
      case "$action" in
        poll)          result=$(send_cmd "40" "" 10) ;;
        read_full)     result=$(send_cmd "41" "" 15) ;;
        emulate)       result=$(send_cmd "42" "$params") ;;
        relay_start)   result=$(send_cmd "43") ;;
        relay_stop)    result=$(send_cmd "44") ;;
        raw_exchange)  result=$(send_cmd "45" "$params" 10) ;;
      esac
      ;;

    ir)
      case "$action" in
        tx)
          local proto=$(echo "$params" | cut -d',' -f1)
          local addr=$(echo "$params" | cut -d',' -f2)
          local cmd=$(echo "$params" | cut -d',' -f3)
          result=$(send_cmd "50" "$(printf '%02x%08x%08x' "$proto" "$addr" "$cmd")")
          ;;
        tx_raw)    result=$(send_cmd "51" "$params" 10) ;;
        rx_start)  result=$(send_cmd "52") ;;
        rx_stop)   result=$(send_cmd "53") ;;
        replay)    result=$(send_cmd "54" "$(printf '%02x' "$params")") ;;
      esac
      ;;

    ibutton)
      case "$action" in
        read)     result=$(send_cmd "60" "" 10) ;;
        emulate)  result=$(send_cmd "61" "$params") ;;
        write)    result=$(send_cmd "62" "$params") ;;
      esac
      ;;

    badusb)
      case "$action" in
        start)  result=$(send_cmd "70") ;;
        exec)   result=$(send_cmd "71" "$(echo -n "$params" | xxd -p)") ;;
        stop)   result=$(send_cmd "72") ;;
      esac
      ;;

    file)
      case "$action" in
        list)    result=$(send_cmd "90" "$(echo -n "$params" | xxd -p)" 10) ;;
        read)    result=$(send_cmd "91" "$(echo -n "$params" | xxd -p)" 10) ;;
        write)   result=$(send_cmd "92" "$params" 10) ;;
        delete)  result=$(send_cmd "93" "$(echo -n "$params" | xxd -p)") ;;
      esac
      ;;

    *)
      result="ERR:unknown_subsystem"
      ;;
  esac

  # Write result to response file
  echo "$result" > "$RESP_DIR/${req_id}"
  log 3 "Result for $req_id: $result"
}

#############################
# Async event listener
# Reads unsolicited events from Flipper
#############################

event_listener() {
  local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
  [ -z "$dev" ] || [ ! -c "$dev" ] && return

  log 2 "Event listener started on $dev"

  while true; do
    # Read from device, look for event frames (cmd >= 0xA0)
    local raw=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)

    # Check for magic bytes
    if [ "$raw" = "fd01" ]; then
      # Read length
      local len_hex=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)
      local len=$((16#$len_hex))

      # Read cmd + payload + crc
      local data=$(dd if="$dev" bs=1 count=$((len + 1)) 2>/dev/null | xxd -p)
      local event_cmd=$(echo "$data" | cut -c1-2)
      local event_payload=$(echo "$data" | cut -c3-$((${#data} - 2)))

      case "$event_cmd" in
        a0)  # GPIO interrupt
          local pin=$((16#$(echo "$event_payload" | cut -c1-2)))
          local val=$((16#$(echo "$event_payload" | cut -c3-4)))
          log 3 "GPIO IRQ: pin=$pin val=$val"
          echo "gpio_irq|$pin|$val|$(date +%s)" >> "$CONFIG_DIR/events"
          ;;
        a1)  # SubGHz RX data
          log 3 "SubGHz RX: $event_payload"
          echo "subghz_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
          ;;
        a2)  # IR RX
          log 3 "IR RX: $event_payload"
          echo "ir_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
          ;;
        a3)  # NFC field detected
          log 3 "NFC field event: $event_payload"
          echo "nfc_field|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
          ;;
        a4)  # Button press on Flipper
          local btn=$((16#$(echo "$event_payload" | cut -c1-2)))
          local state=$((16#$(echo "$event_payload" | cut -c3-4)))
          log 3 "Button: btn=$btn state=$state"
          echo "button|$btn|$state|$(date +%s)" >> "$CONFIG_DIR/events"
          ;;
        a5)  # CPU offload request from Flipper
          local task_id=$(echo "$event_payload" | cut -c1-8)
          local workload=$(echo "$event_payload" | cut -c9-)
          log 2 "CPU offload request: task=$task_id"
          handle_cpu_offload "$task_id" "$workload" &
          ;;
      esac
    fi
  done
}

#############################
# CPU sharing — run Flipper's workload on phone
#############################

handle_cpu_offload() {
  local task_id="$1"
  local workload_hex="$2"

  log 2 "Processing CPU offload task $task_id"

  # Decode workload — it's a simple bytecode or shell command
  local workload=$(echo "$workload_hex" | xxd -r -p)
  local result=""

  # Execute in sandboxed context
  result=$(echo "$workload" | timeout 30 sh 2>&1)
  local exit_code=$?

  # Send result back to Flipper
  local status_hex=$(printf '%02x' $exit_code)
  local result_hex=$(echo -n "$result" | xxd -p | tr -d '\n')
  send_cmd "81" "${task_id}${status_hex}${result_hex}" 5

  log 2 "CPU task $task_id complete (exit=$exit_code)"
}

#############################
# Connection monitor
#############################

connection_monitor() {
  while true; do
    sleep 10
    local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
    local status=$(cat "$CONFIG_DIR/status" 2>/dev/null)

    if [ "$status" = "connected" ]; then
      # Check if device still exists
      if [ ! -c "$dev" ]; then
        log 1 "Flipper device $dev disappeared — disconnected"
        echo "disconnected" > "$CONFIG_DIR/status"

        # Try to rediscover
        sleep 5
        local new_dev=""
        for tty in /dev/ttyACM*; do
          [ -c "$tty" ] && new_dev="$tty" && break
        done

        if [ -n "$new_dev" ]; then
          log 2 "Flipper reconnected on $new_dev"
          echo "$new_dev" > "$CONFIG_DIR/flipper_dev"
          stty -F "$new_dev" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
          handshake && echo "connected" > "$CONFIG_DIR/status"
        fi
      else
        # Heartbeat ping
        local resp=$(send_cmd "01" "" 2)
        if ! echo "$resp" | grep -q "^OK"; then
          log 1 "Heartbeat failed — marking disconnected"
          echo "disconnected" > "$CONFIG_DIR/status"
        fi
      fi
    elif [ "$status" = "disconnected" ]; then
      # Try to find Flipper
      for tty in /dev/ttyACM*; do
        if [ -c "$tty" ]; then
          log 2 "Found new device $tty, attempting handshake"
          echo "$tty" > "$CONFIG_DIR/flipper_dev"
          stty -F "$tty" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
          if handshake; then
            echo "connected" > "$CONFIG_DIR/status"
          fi
          break
        fi
      done
    fi
  done
}

#############################
# Main loop
#############################

log 2 "FlipperDroid bridge daemon starting"

# Create command FIFO
rm -f "$CMD_FIFO"
mkfifo "$CMD_FIFO" 2>/dev/null
chmod 660 "$CMD_FIFO"

# Initial handshake
if [ "$(cat "$CONFIG_DIR/status" 2>/dev/null)" = "connected" ]; then
  handshake
fi

# Start connection monitor in background
connection_monitor &
MONITOR_PID=$!

# Start event listener in background
event_listener &
LISTENER_PID=$!

# Store PIDs for cleanup
echo "$MONITOR_PID" > "$CONFIG_DIR/monitor.pid"
echo "$LISTENER_PID" > "$CONFIG_DIR/listener.pid"

# Main command processing loop — reads from FIFO
log 2 "Bridge daemon ready, listening on $CMD_FIFO"

while true; do
  if read -r cmd_line < "$CMD_FIFO" 2>/dev/null; then
    [ -n "$cmd_line" ] && process_api_command "$cmd_line" &
  else
    sleep 1
  fi
done
