package cloud.molberg.picarprobe;

import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

/** Read-only shell process that samples the factory SAIC vehicle Binder service. */
public final class CarTelemetryProbe {
    private static final String HUB_TOKEN = "com.saicmotor.sdk.vehiclesettings.IHubService";
    private static final String CHARGING_TOKEN = "com.saicmotor.sdk.vehiclesettings.IVehicleChargingService";
    private static final String CONDITION_TOKEN = "com.saicmotor.sdk.vehiclesettings.IVehicleConditionService";
    private static final AtomicBoolean finished = new AtomicBoolean(false);

    private CarTelemetryProbe() {}

    public static void main(String[] arguments) {
        try {
            Looper.prepareMainLooper();
            Context context = ActivityThread.systemMain().getSystemContext();
            Intent intent = new Intent("com.saicmotor.service.vehicle.VehicleService")
                .setPackage("com.saicmotor.service.vehicle");
            boolean binding = context.bindService(intent, new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder hub) {
                    try {
                        IBinder charging = service(hub, "vehiclecharging");
                        IBinder condition = service(hub, "vehiclecondition");
                        float soc = readFloat(charging, CHARGING_TOKEN, 3);
                        int rangeKm = readInt(charging, CHARGING_TOKEN, 4);
                        float speedKph = readFloat(condition, CONDITION_TOKEN, 3);
                        float batteryVolts = readFloat(charging, CHARGING_TOKEN, 64);
                        float totalConsumptionKwh = readFloat(charging, CHARGING_TOKEN, 75);
                        int chargingStatus = readInt(charging, CHARGING_TOKEN, 9);
                        succeed(soc, rangeKm, speedKph, batteryVolts, totalConsumptionKwh, chargingStatus);
                    } catch (Throwable error) {
                        fail("binder_read_failed");
                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    fail("service_disconnected");
                }
            }, Context.BIND_AUTO_CREATE);
            if (!binding) fail("service_unavailable");

            Thread timeout = new Thread(() -> {
                try { Thread.sleep(5000); } catch (InterruptedException ignored) { return; }
                fail("service_timeout");
            }, "car-telemetry-timeout");
            timeout.setDaemon(true);
            timeout.start();
            Looper.loop();
        } catch (Throwable error) {
            fail("probe_failed");
        }
    }

    private static IBinder service(IBinder hub, String name) throws Exception {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(HUB_TOKEN);
            data.writeString(name);
            hub.transact(1, data, reply, 0);
            reply.readException();
            IBinder binder = reply.readStrongBinder();
            if (binder == null) throw new IllegalStateException("Missing " + name);
            return binder;
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private static float readFloat(IBinder binder, String token, int code) throws Exception {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(token);
            binder.transact(code, data, reply, 0);
            reply.readException();
            return reply.readFloat();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private static int readInt(IBinder binder, String token, int code) throws Exception {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(token);
            binder.transact(code, data, reply, 0);
            reply.readException();
            return reply.readInt();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private static void succeed(float soc, int rangeKm, float speedKph, float batteryVolts, float consumption, int charging) {
        if (!finished.compareAndSet(false, true)) return;
        System.out.printf(Locale.US,
            "{\"status\":\"ok\",\"soc\":%.3f,\"rangeKm\":%d,\"speedKph\":%.3f,\"batteryVolts\":%.3f,\"totalConsumptionKwh\":%.3f,\"chargingStatus\":%d}%n",
            soc, rangeKm, speedKph, batteryVolts, consumption, charging);
        System.exit(0);
    }

    private static void fail(String reason) {
        if (!finished.compareAndSet(false, true)) return;
        System.out.println("{\"status\":\"error\",\"reason\":\"" + reason + "\"}");
        System.exit(0);
    }
}
