package com.qsjnic.mcu.service;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.Keep;
import androidx.annotation.Nullable;

import com.qsjnic.mcu.service.feature.DomainBus;

/**
 * Receives every NORMAL/NOTIFY frame from the native RX thread and dispatches
 * it onto a dedicated Looper thread for parsing/logging (B3) or fan-out to
 * registered Listeners (D phase).
 *
 * <p>The native bridge calls {@link #onMcuFrame(int, int, int, byte[])}
 * directly via cached {@code jmethodID}, so this method must NOT be renamed
 * or have its signature changed without updating jni_bridge.cpp.
 */
public final class FrameRouter {

    private static final String TAG = "FrameRouter";

    // Frame type constants (must match protocol.h FRAME_TYPE_* values).
    private static final int FRAME_TYPE_NORMAL = 0x06;
    private static final int FRAME_TYPE_NOTIFY = 0x07;

    // MsgType constants.
    private static final int MSG_TYPE_PUBLIC = 0x01;
    private static final int MSG_TYPE_KEY    = 0x02;
    private static final int MSG_TYPE_RADIO  = 0x05;
    private static final int MSG_TYPE_UPDATE = 0x07;

    private static final HandlerThread WORKER;
    private static final Handler        HANDLER;

    static {
        WORKER = new HandlerThread("FrameRouter");
        WORKER.start();
        HANDLER = new Handler(WORKER.getLooper());
    }

    private static DomainBus bus;

    public static void setBus(DomainBus b) { bus = b; }

    private FrameRouter() {}

    /** Called from native RX thread. Keep signature stable. */
    @Keep
    public static void onMcuFrame(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
        // Copy payload bytes ASAP because the native buffer is only valid
        // during the call. We're invoked synchronously on the RX thread,
        // and we marshall to a worker handler for parsing.
        final byte[] copy = (payload == null) ? null : payload.clone();
        HANDLER.post(() -> dispatch(frameType, msgType, cmd, copy));
    }

    private static void dispatch(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
        final int len = payload == null ? 0 : payload.length;
        QsMcuSystemService.broadcastRawFrame(frameType, msgType, cmd, payload);
        if (bus != null) {
            bus.onFrame(frameType, msgType, cmd, payload);
        }
        switch (msgType) {
            case MSG_TYPE_PUBLIC:
                handlePublic(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_KEY:
                handleKey(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_RADIO:
                handleRadio(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_UPDATE:
                handleUpdate(frameType, cmd, payload, len);
                break;
            default:
                Log.w(TAG, String.format("unknown msgType=0x%02X cmd=0x%02X len=%d",
                        msgType, cmd, len));
        }
    }

    // ------------------------------------------------------------ Public

    private static void handlePublic(int frameType, int cmd, byte[] p, int len) {
        switch (cmd) {
            case 0x01: // LINE_DETECT
                if (len >= 1) {
                    int b = p[0] & 0xFF;
                    Log.i(TAG, String.format(
                            "LINE_DETECT IG=%d ACC=%d IIL=%d Back=%d Aux=%d Brake=%d LTurn=%d RTurn=%d",
                            (b      ) & 1, (b >> 1) & 1, (b >> 2) & 1, (b >> 3) & 1,
                            (b >> 4) & 1, (b >> 5) & 1, (b >> 6) & 1, (b >> 7) & 1));
                }
                break;
            case 0x02: // MCU_VERSION (string)
                Log.i(TAG, "MCU Version: " + safeUtf8(p, 0, len));
                break;
            case 0x03: // TIME_INFO
                if (len >= 7) {
                    Log.i(TAG, String.format("TIME_INFO set=%d 20%02d-%02d-%02d %02d:%02d:%02d",
                            p[0] & 0xFF, p[1] & 0xFF, p[2] & 0xFF, p[3] & 0xFF,
                            p[4] & 0xFF, p[5] & 0xFF, p[6] & 0xFF));
                }
                break;
            case 0x05: // SYS_STATUS
                if (len >= 1) Log.i(TAG, "SYS_STATUS firstPwrOn=" + (p[0] & 0xFF));
                break;
            case 0x08: // TFT_DUTY
                if (len >= 1) Log.i(TAG, "TFT_DUTY " + (p[0] & 0xFF) + "%");
                break;
            case 0x0D: // VOLTAGE
                if (len >= 2) {
                    int mv = ((p[0] & 0xFF) << 8) | (p[1] & 0xFF);
                    Log.i(TAG, "VOLTAGE " + mv + " mV");
                }
                break;
            case 0x13: // VERSION_STR (1B type + str)
                if (len >= 2) {
                    Log.i(TAG, String.format("VERSION_STR type=0x%02X \"%s\"",
                            p[0] & 0xFF, safeUtf8(p, 1, len - 1)));
                }
                break;
            default:
                Log.i(TAG, String.format("PUBLIC cmd=0x%02X type=0x%02X len=%d",
                        cmd, frameType, len));
        }
    }

    // ------------------------------------------------------------ Key

    private static void handleKey(int frameType, int cmd, byte[] p, int len) {
        if (cmd == 0x01 && len >= 2) {
            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",
                    p[0] & 0xFF, p[1] & 0xFF));
            return;
        }
        Log.i(TAG, String.format("KEY cmd=0x%02X type=0x%02X len=%d",
                cmd, frameType, len));
    }

    // ------------------------------------------------------------ Radio

    private static void handleRadio(int frameType, int cmd, byte[] p, int len) {
        switch (cmd) {
            case 0x01: // FREQ
                if (len >= 7) {
                    int freq = ((p[0] & 0xFF) << 8) | (p[1] & 0xFF);
                    int band = p[2] & 0x0F;
                    int signal = p[6] & 0xFF;
                    Log.i(TAG, String.format("RADIO_FREQ %s %d%s signal=%d",
                            band == 0 ? "FM" : "AM",
                            freq,
                            band == 0 ? "(*100KHz)" : "KHz",
                            signal));
                }
                break;
            case 0x04: // TA_PTY_TIPS
                if (p != null && len >= 1) {
                    int mode  = (p[0] >> 4) & 0x0F;
                    int value =  p[0]       & 0x0F;
                    Log.i(TAG, String.format("RADIO_TA_PTY_TIPS mode=%d value=%d", mode, value));
                }
                break;
            default:
                Log.i(TAG, String.format("RADIO cmd=0x%02X type=0x%02X len=%d",
                        cmd, frameType, len));
        }
    }

    // ------------------------------------------------------------ Update

    private static void handleUpdate(int frameType, int cmd, byte[] p, int len) {
        Log.i(TAG, String.format("UPDATE cmd=0x%02X type=0x%02X len=%d",
                cmd, frameType, len));
    }

    private static String safeUtf8(byte[] p, int offset, int len) {
        if (p == null || len <= 0) return "";
        // Trim trailing nulls/0xFFs that some MCU strings carry.
        int actual = 0;
        for (int i = 0; i < len; i++) {
            byte b = p[offset + i];
            if (b == 0 || b == (byte) 0xFF) break;
            actual++;
        }
        return new String(p, offset, actual);
    }
}
